[
  {
    "path": ".editorconfig",
    "content": "[*.{py,sh,md,cfg,sample}]\nindent_style = tab\nindent_size = 4"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: aristocratos\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG]\"\nlabels: bug\nassignees: aristocratos\n\n---\n\n**Describe the bug**\n\n[A clear and concise description of what the bug is.]\n\n**To Reproduce**\n\n[Steps to reproduce the behavior:]\n\n**Expected behavior**\n\n[A clear and concise description of what you expected to happen.]\n\n**Screenshots**\n\n[If applicable, add screenshots to help explain your problem.]\n\n**Info (please complete the following information):**\n - bpytop version: `bpytop -v`\n - psutil version: `bpytop -v` (version 5.7.0 or above is required)\n - (Linux) Linux distribution and version:\n - (OSX/FreeBSD) Os release version:\n - Terminal used:\n - Font used:\n - Python version, `python3 --version` (version 3.6 or above is required):\n\n**Additional context**\n\ncontents of `~/.config/bpytop/error.log`\n\n(try running bpytop with `--debug` flag if error.log is empty)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[REQUEST]\"\nlabels: enhancement\nassignees: aristocratos\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/testing.yml",
    "content": "name: Testing\n\non: [pull_request, push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    if: \"!contains(github.event.head_commit.message, '[skip tests]')\"\n    strategy:\n      max-parallel: 3\n      matrix:\n        python-version: [3.7, 3.8, 3.9]\n\n    steps:\n    - uses: actions/checkout@v1\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v2\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install tox tox-gh-actions poetry\n    - name: Test with tox\n      run: tox"
  },
  {
    "path": ".gitignore",
    "content": "syntax: glob\n*.al\n*.bak\n*.egg-info\n*.la\n*.lo\n*.o\n*.orig\n*.pyc\n*.pyd\n*.rej\n*.so\n*.swp\n.failed-tests.txt\n.cache/\n.idea/\n.tox/\nbuild/\ndist/\n__pycache__\n.mypy_cache\n.vscode"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\npython:\n  - \"3.6\"\n  - \"3.7\"\n  - \"3.8\"\njobs:\n  include:\n    - name: \"mypy\"\n      python: 3.8\n      env:\n        - TOXENV=mypy\n    - name: \"pylint\"\n      python: 3.8\n      env:\n        - TOXENV=pylint\nbefore_install: pip install poetry\ninstall: pip install tox-travis\nscript: tox\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## v1.0.68\n\n* Fixed: typos discovered by codespell, by @cclauss\n* Added: search processes using vim keybinds, by @jedi2610\n* Fixed: Removed a simple consider-using-in pitfall case, by @NaelsonDouglas\n* Added: New theme gruvbox_dark_v2, by @pietryszak\n* Fixed: Implement strtobool over distutils strtobool, by @RCristiano\n\n## v1.0.67\n\n* Fixed: Removed not needed escape character replacements\n* Fixed: Themes missing when installing with pip3\n* Fixed: Color in-range check, by @GerbenWelter\n\n## v1.0.66\n\n* Fixed: Program not stalling when system time is changed and regular update of current timezone\n* Fixed: NetBox not redrawing when network interface is removed, by @UmarJ\n* Fixed: Some typos\n\n## v1.0.65\n\n* Fixed: Removed degrees symbol from Kelvin scale, by @jrbergen\n* Fixed: Mouse buttons not working in netbox when changing interface\n* Fixed: q key not working when terminal size warning is showed\n* Fixed: Cleanup of unused libraries and other small fixes\n\n## v1.0.64\n\n* Changed: Init screen not shown by default\n* Fixed: Broken cleanup in ProcBox class\n* Fixed: cpu frequency type change in psutil 5.8.1\n* Added: Option to toggle CPU frequency\n* Fixed: Check for config in /usr/local/etc instead of /etc on BSD\n\n## v1.0.63\n\n* Added: Options for choosing temperature scale and re-added support for negative celsius temps\n* Changed: Cpu values above 0 will always register on the graphs\n\n## v1.0.62\n\n* Fixed: Support cpus with non-sequential core ids, patch by @ErwinJunge\n* Added: New theme Adapta, by @olokelo\n* Changed: Net graphs will now round up any value above 0 to register on graph\n\n## v1.0.61\n\n* Added: Vim keys (h, j, k, l) for browsing and moved help to shift+h\n* Changed: Size constraints now adapts to currently shown boxes\n\n## v1.0.60\n\n* Added: Ignore input unicode decode errors\n* Fixed: Wrong letter in \"io\" highlighted\n* Fixed: Crash on missing psutil.disk_usage\n* Added: Toggle for IO graphs in regular disk usage mode\n* Added: Toggle for uptime and uptime added as a option for the clock formatting\n* Added: Ability choose cpu graph attributes and split up upper and lower part\n* Added: Ability to toggle one big CPU graph instead of two combined graphs\n* Added: IP address to net box\n\n## v1.0.59\n\n* Fixed: Crash on missing disks\n* Fixed: IO stats text transparency\n\n## v1.0.58\n\n* Added: Disks io stat graphs and a dedicated io mode for disks box\n* Fixed: Better detection for disk io stats including multiple disks for OsX\n* Changed: Terminate, Kill, Interrupt shortcuts now only uses uppercase T, K, I\n* Changed: Process filtering changed to non case-sensitive, patch by @UmarJ\n\t* Case-sensitive proc filtering using uppercase F\n* Changed: Get CPU load average from psutil module instead of os module, patch by @araczkowski\n* Fixed: Misc bugs\n\n## v1.0.57\n\n* Fixed: proc_sorting option counter not updating in menu, by @UmarJ\n* Added: Support for non truecolor terminals through 24-bit to 256-color conversion\n\t* Activate by setting \"truecolor\" variable to False or starting with \"-lc/--low-color\" argument\n\n## v1.0.56\n\n* Fixed: units_to_bytes returning 0 if input value <10 and in bits\n* Added: Testing for some functions and classes\n* Added: net_iface variable to set startup network interface, by @raTmole\n* Added: use_fstab variable to get the disk list from /etc/fstab, by @BrHal\n* Added: Categories in Options menu and only_physical option for disks\n\n## v1.0.55\n\n* Fixed: Disks usage and free meters not updating unless resized\n* Changed: All boxes are now toggeable with key 1-4, start argument -b/--boxes and config variable shown_boxes.\n* Changed: Moved testing from Travis CI to Github workflow\n\n## v1.0.54\n\n* Fixed: Added nullfs filesystem to auto exclude from disks list\n* Fixed: Process box not updating on window resize\n\n## v1.0.53\n\n* Added: Process update multiplier (only update processes every X times) to reduce cpu usage (set to 2 by default)\n* Changed: Patch for faster loading of config file, by @rohithill\n* Added: Network interface list now updates automatically, by @UmarJ\n* Notice: Bumped minimum python version to 3.7 because of unicode issues in 3.6\n* Added: pylint disable=unsubscriptable-object because of python 3.9 issue\n* Changed: Default theme now has a black background\n* Fixed: Crash if bpytop.conf exists but don't have update_ms variable set\n\n## v1.0.52\n\n* Fixed: Removed \"/sys/class/power_supply\" check for FreeBSD and OsX\n\n## v1.0.51\n\n* Fixed: Text argument in subprocess not working on python 3.6\n* Changed: Disks filtering now uses full mountpoint path for better accuracy\n* Fixed: Disable battery detection if /sys/class/power_supply is missing to avoid exception is psutil\n* Fixed: Catch faulty temperature readings instead of crashing\n* Changed: psutil update to 5.8.0 in pypi package (fixes errors on apple silicon cpus)\n\n## v1.0.50\n\n* Fixed: Correction for missing coretemp values\n* Fixed: Cpu temp calculation from cores if missing and better multi cpu temp support\n* Added: New theme dusklight, by @drazil100\n\n## v1.0.49\n\n* Fixed: Missing default values for cpu temp high and crit\n\n## v1.0.48\n\n* Added: Sync clock to timer if timer = 1000ms\n* Fixed: Wrong coretemp mapping when missing package id 0\n* Fixed: Sizing when coretemp is hidden\n* Added: Link to Terminess Powerline with included braille symbols in README.md\n\n## v1.0.47\n\n* Added: Testing, by @ErwinJunge\n* Added: Theme matcha-dark-sea, by @TheCynicalLiger\n* Fixed: New type errors for mypy v 0.790\n* Added: pylint and mypy test with tox, by @ErwinJunge\n\n## v1.0.46\n\n* Changed: psutil update to 5.7.3 in pypi package\n* Fixed: Better sensor and temperature detection\n\n## v1.0.45\n\n* Fixed: Missing temps if high or crit is None, by @TheComputerGuy96\n* Changed: Some refactoring by @dpshelio\n* Added: Proper mapping for correct coretemp display and added toggle for coretemp\n* Fixed: Cleanup of escaped characters in process argument string\n\n## v1.0.44\n\n* Added: Spread CPUs across columns evenly if possible, by @ErwinJunge\n* Added: Additional crash fixes for graph and swap toggles\n\n## v1.0.43\n\n* Fixed: Battery meter not clearing properly when disabled\n* Fixed: Correction for broken cpu high and cpu critical temps\n* Fixed: get_cpu_name() function for some Xeon cpus\n* Fixed: Additional error handling to prevent crashes from graph and swap toggles\n\n## v1.0.42\n\n* Fixed: Battery status not using same sensors as psutil\n* Added: Stripping of .local from /host clock format\n* Fixed: Battery clear if removed\n\n## v1.0.41\n\n* Skipped due to pypi - github versioning error\n\n## v1.0.40\n\n* Fixed: Title leading whitespace\n* Fixed: Battery meter crash on non Linux systems\n\n## v1.0.39\n\n* Fixed: Manual sensor selection screen refresh\n* Fixed: Rare swap toggle crash\n* Fixed: Clock and battery placement and sizing\n\n## v1.0.38\n\n* Fixed: Cpu sensor check when changing from manual sensor to Auto\n* Fixed: Menu collection timeout and menu background update stall\n* Added: Custom options for clock formatting: hostname and username\n\n## v1.0.37\n\n* Fixed: Swap toggle rare crash\n* Fixed: Cpu sensor option to trigger temp toggle if check temp is true\n\n## v1.0.36\n\n* Added: Rounding for floating_humanizer() short option\n* Fixed: Cpu temp not showing when manually selected and not auto detected\n* Fixed Crash during theme change\n\n## v1.0.35\n\n* Fixed: Decimal placement in floating_humanizer() function\n\n## v1.0.34\n\n* Changed: Improvement on cpu name detection\n* Added: Option to choose cpu temperature sensor\n* Fixed: Battery meter adaptation\n\n## v1.0.33\n\n* Changed: Improvement on osx cpu temperature collection with coretemp\n* Fixed: Battery stats crash and better battery status detection\n* README: coretemp install instructions by @hacker1024\n* README: Added notice about font problems and possible solutions\n\n## v1.0.32\n\n* Added: Symbol for battery inactive\n* Fixed: Cpu model name exception for certain xeon cpus\n* Fixed: Exception when sending signal using uppercase T, K, I\n* Fixed: Battery meter placement calculation correction\n* Added: Support for OSX cpu core temperatures via coretemp program\n\n## v1.0.31\n\n* Fixed: Battery meter redraw after terminal resize\n* Fixed: Battery meter additional fixes\n* Fixed: Cpu temp color wrong on small sizes\n\n## v1.0.30\n\n* Changed: Argument parsing using argparse\n* Fixed: Hide battery time when not known\n\n## v1.0.29\n\n* Fixed: Battery percent converted to integer and battery time hidden at 100% level\n\n## v1.0.28\n\n* Fixed: Battery meter causing crash when connecting/disconnecting battery\n* README: Added more repositories\n\n## v1.0.27\n\n* Added: kyli0x theme by @kyli0x\n* Added: Battery meter and stats\n* Added: Option to change the tree view auto collapse depth\n\n## v1.0.26\n\n* Fixed: Cpu temp color index crash\n* Fixed: Start from virtualenv crash\n\n## v1.0.25\n\n* Added: More sizing adaptation for processes\n* Fixed: Clock centering\n\n## v1.0.24\n\n* Fixed: \"view_mode\" option entry format\n* Fixed: Help menu entries\n\n## v1.0.23\n\n* Added: View mode toggle with 3 presets, \"full\", \"proc\" and \"stat\"\n* Added: Rescaling of net stat box width on smaller terminal sizes\n* Changed: Net box height slight increase, mem/disks box height slight decrease\n* Fixed: Some element placement fixes by @RedBearAK\n* Fixed: \"delete\" and \"filter\" mouse click area misaligned\n* Added: Option to sync network scaling between download and upload\n\n## v1.0.22\n\n* Some refactoring and cleanup\n* README: Info for debian package\n* Added: Theme search path for snap install\n* README: Updated snap install info\n\n\n## v1.0.21\n\n* Fixed: Clean excess whitespace from CPU model name, by @RedBearAK\n* Changed: README.md absolute paths to work on PyPi\n\n## v1.0.20\n\n* Release bump to fix pypi and source version mismatch\n\n## v1.0.19\n\n* Changed: net_auto variable now default to True\n* Fixed: Sorting out negative cpu temperature values from bad sensors\n\n## v1.0.18\n\n* Fixed: Init screen and error log level when starting from pip installation\n\n## v1.0.17\n\n* Added: Option to toggle theme background color\n* Added: Dracula theme by @AethanFoot\n* Added: PyPi theme install and path detection\n* Added: PyPi packaging with poetry by @cjolowicz\n* Added: Error checking for net_download and net_upload config values\n* Added: psutil outdated warning message\n* Changed: Expanded cpu name detection\n\n## v1.0.16\n\n* Fixed: net_upload variable not working\n* Added: Ability to expand/collapse processes in the tree view\n\n## v1.0.15\n\n* Added: Network graph color gradient bandwidth option by @drazil100\n* Added: cpu_thermal sensor detection for raspberri pi cpu temp\n* Fixed: Single color graphs crash\n\n## v1.0.14\n\n* Added: New theme values \"graph_text\", \"meter_bg\", \"process_start\", \"process_mid\" and \"process_end\", see default_black.theme for reference.\n* Updated: default_black.theme with new values\n* Updated: monokai.theme and gruvbox_dark.theme with \"graph_text\" value.\n\n## v1.0.13\n\n* Fixed: Cpu usage bug when showing tree and memory in percent\n* Fixed: Check for minimum terminal size at start when init screen is enabled\n\n## v1.0.12\n\n* Fixed: Cpu high and cpu crit for osx and raspberry pi\n\n## v1.0.11\n\n* Fixed: getsensors detection of vcgencmd\n* Fixed: Load AVG being drawn outside box on small sizes\n* Fixed: Slowdown when showing memory in percent instead of bytes\n* Fixed: Cpu temperature colors not converted to percent of cpu critical temp\n* Fixed: Crash on sorting change when lacking permissions\n\n## v1.0.10\n\n* Fixed: Raspberry pi cpu temps, actually fixed this time...\n\n## v1.0.9\n\n* Fixed: Raspberry pi cpu temp, again.\n\n## v1.0.8\n\n* Added: Set terminal title at start\n* Added: Update checker, can be toggled off in options menu\n* Added: Option to show memory in bytes for processes, enabled by default\n* Added: Options to set custom network graphs minimum scaling values and a \"auto\" button to toggle manual and default values.\n* Fixed: Failure to detect cpu temp on raspberry pi\n* Changed: Layout changes to cpu box\n\n## v1.0.7\n\n* Changed: Info box now restores last selection on close\n* Fixed: Crash when starting with show_disks=False\n\n## v1.0.6\n\n* Fixed: Cpu temps index error on uneven temp collection\n* Fixed: No cpu percent in info box when filtering\n\n## v1.0.5\n\n* Fixed: Attribute typo in detailed process collection\n\n## v1.0.4\n\n* Fixed: Crash when filtering and showing info box\n* Added: Improved cpu temperature detection\n* Fixed: Broken cpu box layout on high core count and change to default layout\n* Changed: Selection now returns to last selection when pressing down from info box\n\n## v1.0.3\n\n* Fixed: Crash on detailed info when showing tree\n* Fixed: Incorrect sorting for memory\n* FIxed: Removed unsupported osx psutil values\n* Changed: Removed shift modifiers for some keys and removed redundant toggles\n\n## v1.0.2\n\n* Added: IndexError catch for cpu temperature collection\n* Fixed: net_io_counters() not iterating over itself\n* Fixed: Clear mouse queue to avoid accidental character interpretation\n* Added: \"/etc/bpytop.conf\" as default seed for config file creation if it exists.\n* Added: Error handling for exception in psutil.cpu_freq()\n\n## v1.0.1\n\n* Fixed: Bad assumption of cpu model name string contents.\n* Added: Exception catch for psutil io_counters error caused by psutil < 5.7.0 and Linux kernel >= 5\n* Added: Error handling for psutil.net_io_counters() errors.\n\n## v1.0.0\n\n* First release\n* Missing update checker\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at admin@qvantnet.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\n## When submitting pull requests\n\n* Explain your thinking in why a change or addition is needed.\n  * Is it a requested change or feature?\n  * If not, open a feature request to get feedback before making a pull request.\n\n* Split up multiple unrelated changes in multiple pull requests.\n\n* If it's a fix for a unreported bug, make a bug report and link the pull request.\n\n* Purely cosmetic changes won't be accepted without a very good explanation of its value.\n  * (Some design choices are for better configurability of syntax highlighting.)\n\n## Formatting\n\n### Follow the current syntax design\n\n* Indent type: Tabs\n\n* Tab size: 4\n\n## Optimization\n\n* Avoid writing to disk if possible.\n\n* Make sure variables/arrays are cleaned up if not reused.\n\n* Compare cpu and memory usage with and without your code and look for alternatives if they cause a noticeable negative impact.\n\nFor questions contact Aristocratos at admin@qvantnet.com\n\nFor proposing changes to this document create a [new issue](https://github.com/aristocratos/bashtop/issues/new/choose).\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "PREFIX ?= /usr/local\nDOCDIR ?= $(PREFIX)/share/bpytop/doc\n\nall:\n\t@echo Run \\'make install\\' to install bpytop.\n\ninstall:\n\t@mkdir -p $(DESTDIR)$(PREFIX)/bin\n\t@cp -p bpytop.py $(DESTDIR)$(PREFIX)/bin/bpytop\n\t@mkdir -p $(DESTDIR)$(DOCDIR)\n\t@cp -p README.md $(DESTDIR)$(DOCDIR)\n\t@cp -pr themes $(DESTDIR)$(PREFIX)/share/bpytop\n\t@chmod 755 $(DESTDIR)$(PREFIX)/bin/bpytop\n\nuninstall:\n\t@rm -rf $(DESTDIR)$(PREFIX)/bin/bpytop\n\t@rm -rf $(DESTDIR)$(DOCDIR)\n\t@rm -rf $(DESTDIR)$(PREFIX)/share/bpytop\n"
  },
  {
    "path": "README.md",
    "content": "# ![bpytop](https://github.com/aristocratos/bpytop/raw/master/Imgs/logo.png)\n\n<a href=\"https://repology.org/project/bpytop/versions\">\n    <img src=\"https://repology.org/badge/vertical-allrepos/bpytop.svg\" alt=\"Packaging status\" align=\"right\">\n</a>\n\n![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux)\n![OSX](https://img.shields.io/badge/-OSX-black?logo=apple)\n![FreeBSD](https://img.shields.io/badge/-FreeBSD-red?logo=freebsd)\n![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow)\n![Python](https://img.shields.io/badge/Python-v3.7%5E-green?logo=python)\n![bpytop_version](https://img.shields.io/github/v/tag/aristocratos/bpytop?label=version)\n[![pypi_version](https://img.shields.io/pypi/v/bpytop?label=pypi)](https://pypi.org/project/bpytop)\n[![Test Status](https://img.shields.io/github/workflow/status/aristocratos/bpytop/Testing?label=tests)](https://github.com/aristocratos/bpytop/actions?query=workflow%3Atesting)\n[![Donate](https://img.shields.io/badge/-Donate-yellow?logo=paypal)](https://paypal.me/aristocratos)\n[![Sponsor](https://img.shields.io/badge/-Sponsor-red?logo=github)](https://github.com/sponsors/aristocratos)\n[![Coffee](https://img.shields.io/badge/-Buy%20me%20a%20Coffee-grey?logo=Ko-fi)](https://ko-fi.com/aristocratos)\n\n[![bpytop](https://img.shields.io/badge/-snapcraft.io-black)](https://snapcraft.io/bpytop)[![bpytop](https://snapcraft.io//bpytop/badge.svg)](https://snapcraft.io/bpytop)\n\n## Index\n\n* [News](#news)\n* [Documents](#documents)\n* [Description](#description)\n* [Features](#features)\n* [Themes](#themes)\n* [Support and funding](#support-and-funding)\n* [Prerequisites](#prerequisites) (Read this if you are having issues!)\n* [Dependencies](#dependencies)\n* [Screenshots](#screenshots)\n* [Installation](#installation)\n* [Configurability](#configurability)\n* [License](#license)\n\n## News\n\n### C++ Version\n\n##### 18 September 2021\n\n![btop++](https://raw.githubusercontent.com/aristocratos/btop/main/Img/logo.png)\n\nThe Linux version of btop++ is complete. Released as version 1.0.0\n\nGet it at https://github.com/aristocratos/btop\n\nThe development plan right now:\n\n* 1.1.0 Mac OsX support\n* 1.2.0 FreeBSD support\n* 1.3.0 Support for GPU monitoring\n* 1.X.0 Other platforms and features...\n\n##### 2 May 2021\n\nI've started work on the third iteration of bashtop->bpytop.\nIt's being written in C++ and will simply be called `btop`.\n\nI'm aiming at releasing a beta version around August this year and will publish the repo when I've got the core functionality and structure ready for anybody that wanna help out.\n\nThis project is gonna take some time until it has complete feature parity with bpytop, since all system information gathering will likely have to be written from scratch without any external libraries.\nAnd will need some help in the form of code contributions to get complete support for BSD and OSX.\n\nIf you got suggestions of C++ libraries that are multi-platform and are as extensive as [psutil](https://github.com/giampaolo/psutil) are for python, feel free to open up a new thread in Discussions, it could help speed up the development a lot.\n\nWill post any updates about this project here until the repo is made available.\n\n## Documents\n\n#### [CHANGELOG.md](https://github.com/aristocratos/bpytop/blob/master/CHANGELOG.md)\n\n#### [CONTRIBUTING.md](https://github.com/aristocratos/bpytop/blob/master/CONTRIBUTING.md)\n\n#### [CODE_OF_CONDUCT.md](https://github.com/aristocratos/bpytop/blob/master/CODE_OF_CONDUCT.md)\n\n## Description\n\nResource monitor that shows usage and stats for processor, memory, disks, network and processes.\n\nPython port and continuation of [bashtop](https://github.com/aristocratos/bashtop).\n\n## Features\n\n* Easy to use, with a game inspired menu system.\n* Full mouse support, all buttons with a highlighted key is clickable and mouse scroll works in process list and menu boxes.\n* Fast and responsive UI with UP, DOWN keys process selection.\n* Function for showing detailed stats for selected process.\n* Ability to filter processes, multiple filters can be entered.\n* Easy switching between sorting options.\n* Send SIGTERM, SIGKILL, SIGINT to selected process.\n* UI menu for changing all config file options.\n* Auto scaling graph for network usage.\n* Shows message in menu if new version is available\n* Shows current read and write speeds for disks\n\n## Themes\n\nBpytop uses the same theme files as bashtop so any theme made for bashtop will work.\n\nSee [themes](https://github.com/aristocratos/bpytop/tree/master/themes) folder for available themes.\n\nThe `make install` command places the default themes in `/usr/local/share/bpytop/themes`.\nIf installed with `pip3` the themes will be located in a folder called `bpytop-themes` in the python3 site-packages folder.\nUser created themes should be placed in `$HOME/.config/bpytop/themes`.\n\nLet me know if you want to contribute with new themes.\n\n## Support and funding\n\nYou can sponsor this project through github, see [my sponsors page](https://github.com/sponsors/aristocratos) for options.\n\nOr donate through [paypal](https://paypal.me/aristocratos) or [ko-fi](https://ko-fi.com/aristocratos).\n\nAny support is greatly appreciated!\n\n## Prerequisites\n\n#### Mac Os X\n\nWill not display correctly in the standard terminal (unless truecolor is set to False)!\nRecommended alternative [iTerm2](https://www.iterm2.com/)\n\nWill also need to be run as superuser to display stats for processes not owned by user.\n\nOsX on Apple Silicon (arm) requires psutil version 5.8.0 to work and currently has no temperature monitoring.\nUpgrade psutil with `sudo pip3 install psutil --upgrade`\n\n#### Linux, Mac Os X and FreeBSD\n\nFor correct display, a terminal with support for:\n\n* 24-bit truecolor ([See list of terminals with truecolor support](https://gist.github.com/XVilka/8346728))\n* 256-color terminals are supported through 24-bit to 256-color conversion when setting \"truecolor\" to False in the options or with \"-lc/--low-color\" argument.\n* Wide characters (Are sometimes problematic in web-based terminals)\n\nAlso needs a UTF8 locale and a font that covers:\n\n* Unicode Block “Braille Patterns” U+2800 - U+28FF\n* Unicode Block “Geometric Shapes” U+25A0 - U+25FF\n* Unicode Block \"Box Drawing\" and \"Block Elements\" U+2500 - U+259F\n\n#### Notice (Text rendering issues)\n\nIf you are having problems with the characters in the graphs not looking like they do in the screenshots,\nit's likely a problem with your systems configured fallback font not having support for braille characters.\n\nSee [Terminess Powerline](https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/Terminus/terminus-ttf-4.40.1) for an example of a font that includes the braille symbols.\n\nSee comments by @sgleizes [link](https://github.com/aristocratos/bpytop/issues/100#issuecomment-684036827) and @XenHat [link](https://github.com/aristocratos/bpytop/issues/100#issuecomment-691585587) in issue #100 for possible solutions.\n\nIf text are misaligned and you are using Konsole or Yakuake, turning off \"Bi-Directional text rendering\" is a possible fix.\n\nCharacters clipping in to each other or text/border misalignments is not bugs caused by bpytop, but most likely a fontconfig or terminal problem where the braille characters making up the graphs aren't rendered correctly.\nLook to the creators of the terminal emulator you use to fix these issues if the previous mentioned fixes don't work for you.\n\n#### Notice (SSH)\n\nDropbear seems to not be able to set correct locale. So if accessing bpytop over ssh, OpenSSH is recommended.\n\n## Dependencies\n\n**[Python3](https://www.python.org/downloads/)** (v3.7 or later)\n\n**[psutil module](https://github.com/giampaolo/psutil)** (v5.7.0 or later)\n\n## Optionals for additional stats\n\n(Optional OSX) **[coretemp](https://github.com/hacker1024/coretemp)** (recommended), or **[osx-cpu-temp](https://github.com/lavoiesl/osx-cpu-temp)** (less accurate) needed to show CPU temperatures.\n\n## Screenshots\n\nMain UI showing details for a selected process.\n![Screenshot 1](https://github.com/aristocratos/bpytop/raw/master/Imgs/main.png)\n\nMain UI in mini mode.\n![Screenshot 2](https://github.com/aristocratos/bpytop/raw/master/Imgs/mini.png)\n\nMain menu.\n![Screenshot 3](https://github.com/aristocratos/bpytop/raw/master/Imgs/menu.png)\n\nOptions menu.\n![Screenshot 4](https://github.com/aristocratos/bpytop/raw/master/Imgs/options.png)\n\n## Installation\n\nI only maintain the PyPi package, so will not take responsibility for issues caused by any other install method!\n\n### PyPi (will always have latest version)\n\n> Install or update to latest version\n``` bash\npip3 install bpytop --upgrade\n```\n\n### Mac OsX\n\n>Install with Homebrew\n```bash\nbrew install bpytop\n```\n\n>Optional coretemp (Shows temperatures for cpu cores)\n```bash\nbrew install hacker1024/hacker1024/coretemp\n```\n\n>Alternatively install with MacPorts\n```bash\nport install bpytop\n```\n\nOsX on Apple Silicon (arm) requires psutil version 5.8.0 to work and currently has no temperature monitoring.\nUpgrade psutil with `sudo pip3 install psutil --upgrade`\n\n### Arch Linux\n\nAvailable in the Arch Linux [community] repository as `bpytop`\n\n>Installation\n\n```bash\nsudo pacman -S bpytop\n```\n\n### Debian based\n\nAvailable in [official Debian repository](https://tracker.debian.org/pkg/bpytop) since Debian 11\n\n>Installation\n\n```bash\nsudo apt install bpytop\n```\n\nAvailable for debian/ubuntu from [Azlux's repository](http://packages.azlux.fr/)\n\n### FreeBSD package\n\nAvailable in [FreeBSD ports](https://www.freshports.org/sysutils/bpytop/)\n\n>Install pre-built package\n\n``` bash\nsudo pkg install bpytop\n```\n\n### Fedora/CentOS 8 package\n\n[Available](https://src.fedoraproject.org/rpms/bpytop) in the Fedora and [EPEL-8 repository](https://fedoraproject.org/wiki/EPEL).\n\n>Installation\n\n``` bash\nsudo dnf install bpytop\n```\n\n### Gentoo / Calculate Linux\n\nAvailable from [adrien-overlay](https://github.com/aaaaadrien/adrien-overlay)\n\n>Installation\n\n``` bash\nsudo emerge -av sys-process/bpytop\n```\n\n### Mageia Cauldron (Mageia 8)\n\nAvailable in Mageia Cauldron and then Mageia 8 when it is released.\n\n>Installation\n\n``` bash\nsudo urpmi bpytop\nsudo dnf install bpytop\n```\n\n### MX Linux\n\nAvailable in the MX Test Repo as `bpytop`\nPlease use MX Package Installer MX Test Repo tab to install.\n\nhttp://mxrepo.com/mx/testrepo/pool/test/b/bpytop/\n\n### Void Linux\n\nAvailable in void repo and void-packages ports tree\n\n>Installation\n\n``` bash\nsudo xbps-install bpytop\n```\n\n### Snap package\n\n(Note! There is some issues caused by the snap sandboxing)\n\nby @kz6fittycent\n\nhttps://github.com/kz6fittycent/bpytop-snap\n\n>Install the package\n``` bash\nsudo snap install bpytop\n```\n\nThe config folder will be located in `~/snap/bpytop/current/.config/bpytop`\n\n## Manual installation\n\n#### Dependencies installation Linux\n\n>Install python3 and git with a package manager of you choice\n\n>Install psutil python module (sudo might be required)\n\n``` bash\npython3 -m pip install psutil\n```\n\n#### Dependencies installation OSX\n\n>Install homebrew if not already installed\n\n``` bash\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)\"\n```\n\n>Install python3 if not already installed\n\n``` bash\nbrew install python3 git\n```\n\n>Install psutil python module\n\n``` bash\npython3 -m pip install psutil\n```\n\n>Install optional dependency coretemp (recommended), or osx-cpu-temp (less accurate)\n\n``` bash\nbrew install hacker1024/hacker1024/coretemp\n```\n\n``` bash\nbrew install osx-cpu-temp\n```\n\n#### Dependencies installation FreeBSD\n\n>Install with pkg and pip\n\n``` bash\nsudo pkg install git python3 py37-psutil\n```\n\n#### Manual installation Linux, OSX and FreeBSD\n\n>Clone and install\n\n``` bash\ngit clone https://github.com/aristocratos/bpytop.git\ncd bpytop\nsudo make install\n```\n\n>to uninstall it\n\n``` bash\nsudo make uninstall\n```\n\n## Configurability\n\nAll options changeable from within UI.\nConfig files stored in \"$HOME/.config/bpytop\" folder\n\n#### bpytop.cfg: (auto generated if not found)\n\n\"/etc/bpytop.conf\" will be used as default seed for config file creation if it exists. (\"/usr/local/etc/bpytop.conf\" on BSD)\n\n```bash\n#? Config file for bpytop v. 1.0.64\n\n#* Color theme, looks for a .theme file in \"/usr/[local/]share/bpytop/themes\" and \"~/.config/bpytop/themes\", \"Default\" for builtin default theme.\n#* Prefix name by a plus sign (+) for a theme located in user themes folder, i.e. color_theme=\"+monokai\"\ncolor_theme=\"monokai\"\n\n#* If the theme set background should be shown, set to False if you want terminal background transparency\ntheme_background=False\n\n#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.\ntruecolor=True\n\n#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace.\nshown_boxes=\"cpu mem net proc\"\n\n#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs.\nupdate_ms=2000\n\n#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)\nproc_update_mult=2\n\n#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu responsive\",\n#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly.\nproc_sorting=\"cpu lazy\"\n\n#* Reverse sorting order, True or False.\nproc_reversed=False\n\n#* Show processes as a tree\nproc_tree=False\n\n#* Which depth the tree view should auto collapse processes at\ntree_depth=3\n\n#* Use the cpu graph colors in the process list.\nproc_colors=True\n\n#* Use a darkening gradient in the process list.\nproc_gradient=True\n\n#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.\nproc_per_core=False\n\n#* Show process memory as bytes instead of percent\nproc_mem_bytes=True\n\n#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available, see:\n#* https://psutil.readthedocs.io/en/latest/#psutil.cpu_times for attributes available on specific platforms.\n#* Select from a list of detected attributes from the options menu\ncpu_graph_upper=\"total\"\n\n#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available, see:\n#* https://psutil.readthedocs.io/en/latest/#psutil.cpu_times for attributes available on specific platforms.\n#* Select from a list of detected attributes from the options menu\ncpu_graph_lower=\"total\"\n\n#* Toggles if the lower CPU graph should be inverted.\ncpu_invert_lower=True\n\n#* Set to True to completely disable the lower CPU graph.\ncpu_single_graph=False\n\n#* Shows the system uptime in the CPU box.\nshow_uptime=True\n\n#* Check cpu temperature, needs \"osx-cpu-temp\" on MacOS X.\ncheck_temp=True\n\n#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.\ncpu_sensor=Auto\n\n#* Show temperatures for cpu cores also if check_temp is True and sensors has been found\nshow_coretemp=True\n\n#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"\ntemp_scale=\"celsius\"\n\n#* Show CPU frequency, can cause slowdowns on certain systems with some versions of psutil\nshow_cpu_freq=True\n\n#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.\ndraw_clock=\"%H:%M\"\n\n#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.\nbackground_update=True\n\n#* Custom cpu model name, empty string to disable.\ncustom_cpu_name=\"\"\n\n#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with a comma \",\".\n#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot, /home/user\"\ndisks_filter=\"exclude=/boot\"\n\n#* Show graphs instead of meters for memory values.\nmem_graphs=True\n\n#* If swap memory should be shown in memory box.\nshow_swap=True\n\n#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.\nswap_disk=True\n\n#* If mem box should be split to also show disks info.\nshow_disks=True\n\n#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.\nonly_physical=True\n\n#* Read disks list from /etc/fstab. This also disables only_physical.\nuse_fstab=True\n\n#* Toggles if io stats should be shown in regular disk usage view\nshow_io_stat=True\n\n#* Toggles io mode for disks, showing only big graphs for disk read/write speeds.\nio_mode=False\n\n#* Set to True to show combined read/write io graphs in io mode.\nio_graph_combined=False\n\n#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"device:speed\" separate disks with a comma \",\".\n#* Example: \"/dev/sda:100, /dev/sdb:20\"\nio_graph_speeds=\"\"\n\n#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"\nnet_download=\"100Mbit\"\nnet_upload=\"100Mbit\"\n\n#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.\nnet_auto=True\n\n#* Sync the scaling for download and upload to whichever currently has the highest scale\nnet_sync=False\n\n#* If the network graphs color gradient should scale to bandwidth usage or auto scale, bandwidth usage is based on \"net_download\" and \"net_upload\" values\nnet_color_fixed=False\n\n#* Starts with the Network Interface specified here.\nnet_iface=\"br0\"\n\n#* Show battery stats in top right if battery is present\nshow_battery=True\n\n#* Show init screen at startup, the init screen is purely cosmetical\nshow_init=False\n\n#* Enable check for new version from github.com/aristocratos/bpytop at start.\nupdate_check=True\n\n#* Set loglevel for \"~/.config/bpytop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info.\nlog_level=DEBUG\n\n```\n\n#### Command line options:\n\n``` text\nusage: bpytop.py [-h] [-b BOXES] [-lc] [-v] [--debug]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -b BOXES, --boxes BOXES\n                        which boxes to show at start, example: -b \"cpu mem net proc\"\n  -lc, --low-color      disable truecolor, converts 24-bit colors to 256-color\n  -v, --version         show version info and exit\n  --debug               start with loglevel set to DEBUG overriding value set in config\n```\n\n## LICENSE\n\n[Apache License 2.0](https://github.com/aristocratos/bpytop/blob/master/LICENSE)\n"
  },
  {
    "path": "bpytop.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=not-callable, no-member, unsubscriptable-object\n# indent = tab\n# tab-size = 4\n\n# Copyright 2021 Aristocratos (jakob@qvantnet.com)\n\n#    Licensed under the Apache License, Version 2.0 (the \"License\");\n#    you may not use this file except in compliance with the License.\n#    You may obtain a copy of the License at\n\n#        http://www.apache.org/licenses/LICENSE-2.0\n\n#    Unless required by applicable law or agreed to in writing, software\n#    distributed under the License is distributed on an \"AS IS\" BASIS,\n#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#    See the License for the specific language governing permissions and\n#    limitations under the License.\n\nimport os, sys, io, threading, signal, re, subprocess, logging, logging.handlers, argparse\nimport urllib.request\nfrom time import time, sleep, strftime, tzset\nfrom datetime import timedelta\nfrom _thread import interrupt_main\nfrom collections import defaultdict\nfrom select import select\nfrom string import Template\nfrom math import ceil, floor\nfrom random import randint\nfrom shutil import which\nfrom typing import List, Dict, Tuple, Union, Any, Iterable\n\nerrors: List[str] = []\ntry: import fcntl, termios, tty, pwd\nexcept Exception as e: errors.append(f'{e}')\n\ntry: import psutil # type: ignore\nexcept Exception as e: errors.append(f'{e}')\n\nSELF_START = time()\n\nSYSTEM: str\nif \"linux\" in sys.platform: SYSTEM = \"Linux\"\nelif \"bsd\" in sys.platform: SYSTEM = \"BSD\"\nelif \"darwin\" in sys.platform: SYSTEM = \"MacOS\"\nelse: SYSTEM = \"Other\"\n\nif errors:\n\tprint(\"ERROR!\")\n\tprint(\"\\n\".join(errors))\n\tif SYSTEM == \"Other\":\n\t\tprint(\"\\nUnsupported platform!\\n\")\n\telse:\n\t\tprint(\"\\nInstall required modules!\\n\")\n\traise SystemExit(1)\n\nVERSION: str = \"1.0.68\"\n\n#? Argument parser ------------------------------------------------------------------------------->\nargs = argparse.ArgumentParser()\nargs.add_argument(\"-b\", \"--boxes\",\t\taction=\"store\",\tdest=\"boxes\", \thelp = \"which boxes to show at start, example: -b \\\"cpu mem net proc\\\"\")\nargs.add_argument(\"-lc\", \"--low-color\", action=\"store_true\", \t\t\thelp = \"disable truecolor, converts 24-bit colors to 256-color\")\nargs.add_argument(\"-v\", \"--version\",\taction=\"store_true\", \t\t\thelp = \"show version info and exit\")\nargs.add_argument(\"--debug\",\t\t\taction=\"store_true\", \t\t\thelp = \"start with loglevel set to DEBUG overriding value set in config\")\nstdargs = args.parse_args()\n\nif stdargs.version:\n\tprint(f'bpytop version: {VERSION}\\n'\n\t\tf'psutil version: {\".\".join(str(x) for x in psutil.version_info)}')\n\traise SystemExit(0)\n\nARG_BOXES: str = stdargs.boxes\nLOW_COLOR: bool = stdargs.low_color\nDEBUG: bool = stdargs.debug\n\n#? Variables ------------------------------------------------------------------------------------->\n\nBANNER_SRC: List[Tuple[str, str, str]] = [\n\t(\"#ffa50a\", \"#0fd7ff\", \"██████╗ ██████╗ ██╗   ██╗████████╗ ██████╗ ██████╗\"),\n\t(\"#f09800\", \"#00bfe6\", \"██╔══██╗██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔═══██╗██╔══██╗\"),\n\t(\"#db8b00\", \"#00a6c7\", \"██████╔╝██████╔╝ ╚████╔╝    ██║   ██║   ██║██████╔╝\"),\n\t(\"#c27b00\", \"#008ca8\", \"██╔══██╗██╔═══╝   ╚██╔╝     ██║   ██║   ██║██╔═══╝ \"),\n\t(\"#a86b00\", \"#006e85\", \"██████╔╝██║        ██║      ██║   ╚██████╔╝██║\"),\n\t(\"#000000\", \"#000000\", \"╚═════╝ ╚═╝        ╚═╝      ╚═╝    ╚═════╝ ╚═╝\"),\n]\n\n#*?This is the template used to create the config file\nDEFAULT_CONF: Template = Template(f'#? Config file for bpytop v. {VERSION}' + '''\n\n#* Color theme, looks for a .theme file in \"/usr/[local/]share/bpytop/themes\" and \"~/.config/bpytop/themes\", \"Default\" for builtin default theme.\n#* Prefix name by a plus sign (+) for a theme located in user themes folder, i.e. color_theme=\"+monokai\"\ncolor_theme=\"$color_theme\"\n\n#* If the theme set background should be shown, set to False if you want terminal background transparency\ntheme_background=$theme_background\n\n#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.\ntruecolor=$truecolor\n\n#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace.\nshown_boxes=\"$shown_boxes\"\n\n#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs.\nupdate_ms=$update_ms\n\n#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)\nproc_update_mult=$proc_update_mult\n\n#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu responsive\",\n#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly.\nproc_sorting=\"$proc_sorting\"\n\n#* Reverse sorting order, True or False.\nproc_reversed=$proc_reversed\n\n#* Show processes as a tree\nproc_tree=$proc_tree\n\n#* Which depth the tree view should auto collapse processes at\ntree_depth=$tree_depth\n\n#* Use the cpu graph colors in the process list.\nproc_colors=$proc_colors\n\n#* Use a darkening gradient in the process list.\nproc_gradient=$proc_gradient\n\n#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.\nproc_per_core=$proc_per_core\n\n#* Show process memory as bytes instead of percent\nproc_mem_bytes=$proc_mem_bytes\n\n#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available, see:\n#* https://psutil.readthedocs.io/en/latest/#psutil.cpu_times for attributes available on specific platforms.\n#* Select from a list of detected attributes from the options menu\ncpu_graph_upper=\"$cpu_graph_upper\"\n\n#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available, see:\n#* https://psutil.readthedocs.io/en/latest/#psutil.cpu_times for attributes available on specific platforms.\n#* Select from a list of detected attributes from the options menu\ncpu_graph_lower=\"$cpu_graph_lower\"\n\n#* Toggles if the lower CPU graph should be inverted.\ncpu_invert_lower=$cpu_invert_lower\n\n#* Set to True to completely disable the lower CPU graph.\ncpu_single_graph=$cpu_single_graph\n\n#* Shows the system uptime in the CPU box.\nshow_uptime=$show_uptime\n\n#* Check cpu temperature, needs \"osx-cpu-temp\" on MacOS X.\ncheck_temp=$check_temp\n\n#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.\ncpu_sensor=$cpu_sensor\n\n#* Show temperatures for cpu cores also if check_temp is True and sensors has been found\nshow_coretemp=$show_coretemp\n\n#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"\ntemp_scale=\"$temp_scale\"\n\n#* Show CPU frequency, can cause slowdowns on certain systems with some versions of psutil\nshow_cpu_freq=$show_cpu_freq\n\n#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.\ndraw_clock=\"$draw_clock\"\n\n#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.\nbackground_update=$background_update\n\n#* Custom cpu model name, empty string to disable.\ncustom_cpu_name=\"$custom_cpu_name\"\n\n#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with a comma \",\".\n#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot, /home/user\"\ndisks_filter=\"$disks_filter\"\n\n#* Show graphs instead of meters for memory values.\nmem_graphs=$mem_graphs\n\n#* If swap memory should be shown in memory box.\nshow_swap=$show_swap\n\n#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.\nswap_disk=$swap_disk\n\n#* If mem box should be split to also show disks info.\nshow_disks=$show_disks\n\n#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.\nonly_physical=$only_physical\n\n#* Read disks list from /etc/fstab. This also disables only_physical.\nuse_fstab=$use_fstab\n\n#* Toggles if io stats should be shown in regular disk usage view\nshow_io_stat=$show_io_stat\n\n#* Toggles io mode for disks, showing only big graphs for disk read/write speeds.\nio_mode=$io_mode\n\n#* Set to True to show combined read/write io graphs in io mode.\nio_graph_combined=$io_graph_combined\n\n#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"device:speed\" separate disks with a comma \",\".\n#* Example: \"/dev/sda:100, /dev/sdb:20\"\nio_graph_speeds=\"$io_graph_speeds\"\n\n#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"\nnet_download=\"$net_download\"\nnet_upload=\"$net_upload\"\n\n#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.\nnet_auto=$net_auto\n\n#* Sync the scaling for download and upload to whichever currently has the highest scale\nnet_sync=$net_sync\n\n#* If the network graphs color gradient should scale to bandwidth usage or auto scale, bandwidth usage is based on \"net_download\" and \"net_upload\" values\nnet_color_fixed=$net_color_fixed\n\n#* Starts with the Network Interface specified here.\nnet_iface=\"$net_iface\"\n\n#* Show battery stats in top right if battery is present\nshow_battery=$show_battery\n\n#* Show init screen at startup, the init screen is purely cosmetical\nshow_init=$show_init\n\n#* Enable check for new version from github.com/aristocratos/bpytop at start.\nupdate_check=$update_check\n\n#* Set loglevel for \"~/.config/bpytop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info.\nlog_level=$log_level\n''')\n\nCONFIG_DIR: str = f'{os.path.expanduser(\"~\")}/.config/bpytop'\nif not os.path.isdir(CONFIG_DIR):\n\ttry:\n\t\tos.makedirs(CONFIG_DIR)\n\t\tos.mkdir(f'{CONFIG_DIR}/themes')\n\texcept PermissionError:\n\t\tprint(f'ERROR!\\nNo permission to write to \"{CONFIG_DIR}\" directory!')\n\t\traise SystemExit(1)\nCONFIG_FILE: str = f'{CONFIG_DIR}/bpytop.conf'\nTHEME_DIR: str = \"\"\n\nif os.path.isdir(f'{os.path.dirname(__file__)}/bpytop-themes'):\n\tTHEME_DIR = f'{os.path.dirname(__file__)}/bpytop-themes'\nelif os.path.isdir(f'{os.path.dirname(__file__)}/themes'):\n\tTHEME_DIR = f'{os.path.dirname(__file__)}/themes'\nelse:\n\tfor td in [\"/usr/local/\", \"/usr/\", \"/snap/bpytop/current/usr/\"]:\n\t\tif os.path.isdir(f'{td}share/bpytop/themes'):\n\t\t\tTHEME_DIR = f'{td}share/bpytop/themes'\n\t\t\tbreak\nUSER_THEME_DIR: str = f'{CONFIG_DIR}/themes'\n\nCORES: int = psutil.cpu_count(logical=False) or 1\nTHREADS: int = psutil.cpu_count(logical=True) or 1\n\nTHREAD_ERROR: int = 0\n\nDEFAULT_THEME: Dict[str, str] = {\n\t\"main_bg\" : \"#00\",\n\t\"main_fg\" : \"#cc\",\n\t\"title\" : \"#ee\",\n\t\"hi_fg\" : \"#969696\",\n\t\"selected_bg\" : \"#7e2626\",\n\t\"selected_fg\" : \"#ee\",\n\t\"inactive_fg\" : \"#40\",\n\t\"graph_text\" : \"#60\",\n\t\"meter_bg\" : \"#40\",\n\t\"proc_misc\" : \"#0de756\",\n\t\"cpu_box\" : \"#3d7b46\",\n\t\"mem_box\" : \"#8a882e\",\n\t\"net_box\" : \"#423ba5\",\n\t\"proc_box\" : \"#923535\",\n\t\"div_line\" : \"#30\",\n\t\"temp_start\" : \"#4897d4\",\n\t\"temp_mid\" : \"#5474e8\",\n\t\"temp_end\" : \"#ff40b6\",\n\t\"cpu_start\" : \"#50f095\",\n\t\"cpu_mid\" : \"#f2e266\",\n\t\"cpu_end\" : \"#fa1e1e\",\n\t\"free_start\" : \"#223014\",\n\t\"free_mid\" : \"#b5e685\",\n\t\"free_end\" : \"#dcff85\",\n\t\"cached_start\" : \"#0b1a29\",\n\t\"cached_mid\" : \"#74e6fc\",\n\t\"cached_end\" : \"#26c5ff\",\n\t\"available_start\" : \"#292107\",\n\t\"available_mid\" : \"#ffd77a\",\n\t\"available_end\" : \"#ffb814\",\n\t\"used_start\" : \"#3b1f1c\",\n\t\"used_mid\" : \"#d9626d\",\n\t\"used_end\" : \"#ff4769\",\n\t\"download_start\" : \"#231a63\",\n\t\"download_mid\" : \"#4f43a3\",\n\t\"download_end\" : \"#b0a9de\",\n\t\"upload_start\" : \"#510554\",\n\t\"upload_mid\" : \"#7d4180\",\n\t\"upload_end\" : \"#dcafde\",\n\t\"process_start\" : \"#80d0a3\",\n\t\"process_mid\" : \"#dcd179\",\n\t\"process_end\" : \"#d45454\",\n}\n\nMENUS: Dict[str, Dict[str, Tuple[str, ...]]] = {\n\t\"options\" : {\n\t\t\"normal\" : (\n\t\t\t\"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐\",\n\t\t\t\"│ │├─┘ │ ││ ││││└─┐\",\n\t\t\t\"└─┘┴   ┴ ┴└─┘┘└┘└─┘\"),\n\t\t\"selected\" : (\n\t\t\t\"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗\",\n\t\t\t\"║ ║╠═╝ ║ ║║ ║║║║╚═╗\",\n\t\t\t\"╚═╝╩   ╩ ╩╚═╝╝╚╝╚═╝\") },\n\t\"help\" : {\n\t\t\"normal\" : (\n\t\t\t\"┬ ┬┌─┐┬  ┌─┐\",\n\t\t\t\"├─┤├┤ │  ├─┘\",\n\t\t\t\"┴ ┴└─┘┴─┘┴  \"),\n\t\t\"selected\" : (\n\t\t\t\"╦ ╦╔═╗╦  ╔═╗\",\n\t\t\t\"╠═╣║╣ ║  ╠═╝\",\n\t\t\t\"╩ ╩╚═╝╩═╝╩  \") },\n\t\"quit\" : {\n\t\t\"normal\" : (\n\t\t\t\"┌─┐ ┬ ┬ ┬┌┬┐\",\n\t\t\t\"│─┼┐│ │ │ │ \",\n\t\t\t\"└─┘└└─┘ ┴ ┴ \"),\n\t\t\"selected\" : (\n\t\t\t\"╔═╗ ╦ ╦ ╦╔╦╗ \",\n\t\t\t\"║═╬╗║ ║ ║ ║  \",\n\t\t\t\"╚═╝╚╚═╝ ╩ ╩  \") }\n}\n\nMENU_COLORS: Dict[str, Tuple[str, ...]] = {\n\t\"normal\" : (\"#0fd7ff\", \"#00bfe6\", \"#00a6c7\", \"#008ca8\"),\n\t\"selected\" : (\"#ffa50a\", \"#f09800\", \"#db8b00\", \"#c27b00\")\n}\n\n#? Units for floating_humanizer function\nUNITS: Dict[str, Tuple[str, ...]] = {\n\t\"bit\" : (\"bit\", \"Kib\", \"Mib\", \"Gib\", \"Tib\", \"Pib\", \"Eib\", \"Zib\", \"Yib\", \"Bib\", \"GEb\"),\n\t\"byte\" : (\"Byte\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\", \"ZiB\", \"YiB\", \"BiB\", \"GEB\")\n}\n\nSUBSCRIPT: Tuple[str, ...] = (\"₀\", \"₁\", \"₂\", \"₃\", \"₄\", \"₅\", \"₆\", \"₇\", \"₈\", \"₉\")\nSUPERSCRIPT: Tuple[str, ...] = (\"⁰\", \"¹\", \"²\", \"³\", \"⁴\", \"⁵\", \"⁶\", \"⁷\", \"⁸\", \"⁹\")\n\n#? Setup error logger ---------------------------------------------------------------->\n\ntry:\n\terrlog = logging.getLogger(\"ErrorLogger\")\n\terrlog.setLevel(logging.DEBUG)\n\teh = logging.handlers.RotatingFileHandler(f'{CONFIG_DIR}/error.log', maxBytes=1048576, backupCount=4)\n\teh.setLevel(logging.DEBUG)\n\teh.setFormatter(logging.Formatter(\"%(asctime)s | %(levelname)s: %(message)s\", datefmt=\"%d/%m/%y (%X)\"))\n\terrlog.addHandler(eh)\nexcept PermissionError:\n\tprint(f'ERROR!\\nNo permission to write to \"{CONFIG_DIR}\" directory!')\n\traise SystemExit(1)\n\n#? Timers for testing and debugging -------------------------------------------------------------->\n\nclass TimeIt:\n\ttimers: Dict[str, float] = {}\n\tpaused: Dict[str, float] = {}\n\n\t@classmethod\n\tdef start(cls, name):\n\t\tcls.timers[name] = time()\n\n\t@classmethod\n\tdef pause(cls, name):\n\t\tif name in cls.timers:\n\t\t\tcls.paused[name] = time() - cls.timers[name]\n\t\t\tdel cls.timers[name]\n\n\t@classmethod\n\tdef stop(cls, name):\n\t\tif name in cls.timers:\n\t\t\ttotal: float = time() - cls.timers[name]\n\t\t\tdel cls.timers[name]\n\t\t\tif name in cls.paused:\n\t\t\t\ttotal += cls.paused[name]\n\t\t\t\tdel cls.paused[name]\n\t\t\terrlog.debug(f'{name} completed in {total:.6f} seconds')\n\ndef timeit_decorator(func):\n\tdef timed(*args, **kw):\n\t\tts = time()\n\t\tout = func(*args, **kw)\n\t\terrlog.debug(f'{func.__name__} completed in {time() - ts:.6f} seconds')\n\t\treturn out\n\treturn timed\n\n\n#? Issue #364 ----------------------------------------------------------->\n\ndef strtobool(val: str) -> bool:\n\t\"\"\"Convert a string representation of truth to true (1) or false (0).\n\n\tTrue values are 'y', 'yes', 't', 'true', 'on', and '1'; false values\n\tare 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if\n\t'val' is anything else.\n\t\"\"\"\n\ttry:\n\t\tval = val.lower()\n\texcept AttributeError:\n\t\traise ValueError(f\"invalid type {type(val)} for truth value {val}\")\n\tif val in ('y', 'yes', 't', 'true', 'on', '1'):\n\t\treturn True\n\telif val in ('n', 'no', 'f', 'false', 'off', '0'):\n\t\treturn False\n\telse:\n\t\traise ValueError(f\"invalid truth value {val}\")\n\n#? Set up config class and load config ----------------------------------------------------------->\n\nclass Config:\n\t'''Holds all config variables and functions for loading from and saving to disk'''\n\tkeys: List[str] = [\"color_theme\", \"update_ms\", \"proc_sorting\", \"proc_reversed\", \"proc_tree\", \"check_temp\", \"draw_clock\", \"background_update\", \"custom_cpu_name\",\n\t\t\t\t\t\t\"proc_colors\", \"proc_gradient\", \"proc_per_core\", \"proc_mem_bytes\", \"disks_filter\", \"update_check\", \"log_level\", \"mem_graphs\", \"show_swap\",\n\t\t\t\t\t\t\"swap_disk\", \"show_disks\", \"use_fstab\", \"net_download\", \"net_upload\", \"net_auto\", \"net_color_fixed\", \"show_init\", \"theme_background\",\n\t\t\t\t\t\t\"net_sync\", \"show_battery\", \"tree_depth\", \"cpu_sensor\", \"show_coretemp\", \"proc_update_mult\", \"shown_boxes\", \"net_iface\", \"only_physical\",\n\t\t\t\t\t\t\"truecolor\", \"io_mode\", \"io_graph_combined\", \"io_graph_speeds\", \"show_io_stat\", \"cpu_graph_upper\", \"cpu_graph_lower\", \"cpu_invert_lower\",\n\t\t\t\t\t\t\"cpu_single_graph\", \"show_uptime\", \"temp_scale\", \"show_cpu_freq\"]\n\tconf_dict: Dict[str, Union[str, int, bool]] = {}\n\tcolor_theme: str = \"Default\"\n\ttheme_background: bool = True\n\ttruecolor: bool = True\n\tshown_boxes: str = \"cpu mem net proc\"\n\tupdate_ms: int = 2000\n\tproc_update_mult: int = 2\n\tproc_sorting: str = \"cpu lazy\"\n\tproc_reversed: bool = False\n\tproc_tree: bool = False\n\ttree_depth: int = 3\n\tproc_colors: bool = True\n\tproc_gradient: bool = True\n\tproc_per_core: bool = False\n\tproc_mem_bytes: bool = True\n\tcpu_graph_upper: str = \"total\"\n\tcpu_graph_lower: str = \"total\"\n\tcpu_invert_lower: bool = True\n\tcpu_single_graph: bool = False\n\tshow_uptime: bool = True\n\tcheck_temp: bool = True\n\tcpu_sensor: str = \"Auto\"\n\tshow_coretemp: bool = True\n\ttemp_scale: str = \"celsius\"\n\tshow_cpu_freq: bool = True\n\tdraw_clock: str = \"%X\"\n\tbackground_update: bool = True\n\tcustom_cpu_name: str = \"\"\n\tdisks_filter: str = \"\"\n\tupdate_check: bool = True\n\tmem_graphs: bool = True\n\tshow_swap: bool = True\n\tswap_disk: bool = True\n\tshow_disks: bool = True\n\tonly_physical: bool = True\n\tuse_fstab: bool = False\n\tshow_io_stat: bool = True\n\tio_mode: bool = False\n\tio_graph_combined: bool = False\n\tio_graph_speeds: str = \"\"\n\tnet_download: str = \"10M\"\n\tnet_upload: str = \"10M\"\n\tnet_color_fixed: bool = False\n\tnet_auto: bool = True\n\tnet_sync: bool = False\n\tnet_iface: str = \"\"\n\tshow_battery: bool = True\n\tshow_init: bool = False\n\tlog_level: str = \"WARNING\"\n\n\twarnings: List[str] = []\n\tinfo: List[str] = []\n\n\tsorting_options: List[str] = [\"pid\", \"program\", \"arguments\", \"threads\", \"user\", \"memory\", \"cpu lazy\", \"cpu responsive\"]\n\tlog_levels: List[str] = [\"ERROR\", \"WARNING\", \"INFO\", \"DEBUG\"]\n\tcpu_percent_fields: List = [\"total\"]\n\tcpu_percent_fields.extend(getattr(psutil.cpu_times_percent(), \"_fields\", []))\n\ttemp_scales: List[str] = [\"celsius\", \"fahrenheit\", \"kelvin\", \"rankine\"]\n\n\tcpu_sensors: List[str] = [ \"Auto\" ]\n\n\tif hasattr(psutil, \"sensors_temperatures\"):\n\t\ttry:\n\t\t\t_temps = psutil.sensors_temperatures()\n\t\t\tif _temps:\n\t\t\t\tfor _name, _entries in _temps.items():\n\t\t\t\t\tfor _num, _entry in enumerate(_entries, 1):\n\t\t\t\t\t\tif hasattr(_entry, \"current\"):\n\t\t\t\t\t\t\tcpu_sensors.append(f'{_name}:{_num if _entry.label == \"\" else _entry.label}')\n\t\texcept:\n\t\t\tpass\n\n\tchanged: bool = False\n\trecreate: bool = False\n\tconfig_file: str = \"\"\n\n\t_initialized: bool = False\n\n\tdef __init__(self, path: str):\n\t\tself.config_file = path\n\t\tconf: Dict[str, Union[str, int, bool]] = self.load_config()\n\t\tif not \"version\" in conf.keys():\n\t\t\tself.recreate = True\n\t\t\tself.info.append(f'Config file malformatted or missing, will be recreated on exit!')\n\t\telif conf[\"version\"] != VERSION:\n\t\t\tself.recreate = True\n\t\t\tself.info.append(f'Config file version and bpytop version mismatch, will be recreated on exit!')\n\t\tfor key in self.keys:\n\t\t\tif key in conf.keys() and conf[key] != \"_error_\":\n\t\t\t\tsetattr(self, key, conf[key])\n\t\t\telse:\n\t\t\t\tself.recreate = True\n\t\t\t\tself.conf_dict[key] = getattr(self, key)\n\t\tself._initialized = True\n\n\tdef __setattr__(self, name, value):\n\t\tif self._initialized:\n\t\t\tobject.__setattr__(self, \"changed\", True)\n\t\tobject.__setattr__(self, name, value)\n\t\tif name not in [\"_initialized\", \"recreate\", \"changed\"]:\n\t\t\tself.conf_dict[name] = value\n\n\tdef load_config(self) -> Dict[str, Union[str, int, bool]]:\n\t\t'''Load config from file, set correct types for values and return a dict'''\n\t\tnew_config: Dict[str,Union[str, int, bool]] = {}\n\t\tconf_file: str = \"\"\n\t\tif os.path.isfile(self.config_file):\n\t\t\tconf_file = self.config_file\n\t\telif SYSTEM == \"BSD\" and os.path.isfile(\"/usr/local/etc/bpytop.conf\"):\n\t\t\tconf_file = \"/usr/local/etc/bpytop.conf\"\n\t\telif SYSTEM != \"BSD\" and os.path.isfile(\"/etc/bpytop.conf\"):\n\t\t\tconf_file = \"/etc/bpytop.conf\"\n\t\telse:\n\t\t\treturn new_config\n\t\ttry:\n\t\t\twith open(conf_file, \"r\") as f:\n\t\t\t\tfor line in f:\n\t\t\t\t\tline = line.strip()\n\t\t\t\t\tif line.startswith(\"#? Config\"):\n\t\t\t\t\t\tnew_config[\"version\"] = line[line.find(\"v. \") + 3:]\n\t\t\t\t\t\tcontinue\n\t\t\t\t\tif not '=' in line:\n\t\t\t\t\t\tcontinue\n\t\t\t\t\tkey, line = line.split('=', maxsplit=1)\n\t\t\t\t\tif not key in self.keys:\n\t\t\t\t\t\tcontinue\n\t\t\t\t\tline = line.strip('\"')\n\t\t\t\t\tif type(getattr(self, key)) == int:\n\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\tnew_config[key] = int(line)\n\t\t\t\t\t\texcept ValueError:\n\t\t\t\t\t\t\tself.warnings.append(f'Config key \"{key}\" should be an integer!')\n\t\t\t\t\tif type(getattr(self, key)) == bool:\n\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\tnew_config[key] = bool(strtobool(line))\n\t\t\t\t\t\texcept ValueError:\n\t\t\t\t\t\t\tself.warnings.append(f'Config key \"{key}\" can only be True or False!')\n\t\t\t\t\tif type(getattr(self, key)) == str:\n\t\t\t\t\t\tnew_config[key] = str(line)\n\t\texcept Exception as e:\n\t\t\terrlog.exception(str(e))\n\t\tif \"proc_sorting\" in new_config and not new_config[\"proc_sorting\"] in self.sorting_options:\n\t\t\tnew_config[\"proc_sorting\"] = \"_error_\"\n\t\t\tself.warnings.append(f'Config key \"proc_sorted\" didn\\'t get an acceptable value!')\n\t\tif \"log_level\" in new_config and not new_config[\"log_level\"] in self.log_levels:\n\t\t\tnew_config[\"log_level\"] = \"_error_\"\n\t\t\tself.warnings.append(f'Config key \"log_level\" didn\\'t get an acceptable value!')\n\t\tif \"update_ms\" in new_config and int(new_config[\"update_ms\"]) < 100:\n\t\t\tnew_config[\"update_ms\"] = 100\n\t\t\tself.warnings.append(f'Config key \"update_ms\" can\\'t be lower than 100!')\n\t\tfor net_name in [\"net_download\", \"net_upload\"]:\n\t\t\tif net_name in new_config and not new_config[net_name][0].isdigit(): # type: ignore\n\t\t\t\tnew_config[net_name] = \"_error_\"\n\t\tif \"cpu_sensor\" in new_config and not new_config[\"cpu_sensor\"] in self.cpu_sensors:\n\t\t\tnew_config[\"cpu_sensor\"] = \"_error_\"\n\t\t\tself.warnings.append(f'Config key \"cpu_sensor\" does not contain an available sensor!')\n\t\tif \"shown_boxes\" in new_config and not new_config[\"shown_boxes\"] == \"\":\n\t\t\tfor box in new_config[\"shown_boxes\"].split(): #type: ignore\n\t\t\t\tif not box in [\"cpu\", \"mem\", \"net\", \"proc\"]:\n\t\t\t\t\tnew_config[\"shown_boxes\"] = \"_error_\"\n\t\t\t\t\tself.warnings.append(f'Config key \"shown_boxes\" contains invalid box names!')\n\t\t\t\t\tbreak\n\t\tfor cpu_graph in [\"cpu_graph_upper\", \"cpu_graph_lower\"]:\n\t\t\tif cpu_graph in new_config and not new_config[cpu_graph] in self.cpu_percent_fields:\n\t\t\t\tnew_config[cpu_graph] = \"_error_\"\n\t\t\t\tself.warnings.append(f'Config key \"{cpu_graph}\" does not contain an available cpu stat attribute!')\n\t\tif \"temp_scale\" in new_config and not new_config[\"temp_scale\"] in self.temp_scales:\n\t\t\tnew_config[\"temp_scale\"] = \"_error_\"\n\t\t\tself.warnings.append(f'Config key \"temp_scale\" does not contain a recognized temperature scale!')\n\t\treturn new_config\n\n\tdef save_config(self):\n\t\t'''Save current config to config file if difference in values or version, creates a new file if not found'''\n\t\tif not self.changed and not self.recreate: return\n\t\ttry:\n\t\t\twith open(self.config_file, \"w\" if os.path.isfile(self.config_file) else \"x\") as f:\n\t\t\t\tf.write(DEFAULT_CONF.substitute(self.conf_dict))\n\t\texcept Exception as e:\n\t\t\terrlog.exception(str(e))\n\ntry:\n\tCONFIG: Config = Config(CONFIG_FILE)\n\tif DEBUG:\n\t\terrlog.setLevel(logging.DEBUG)\n\telse:\n\t\terrlog.setLevel(getattr(logging, CONFIG.log_level))\n\t\tDEBUG = CONFIG.log_level == \"DEBUG\"\n\terrlog.info(f'New instance of bpytop version {VERSION} started with pid {os.getpid()}')\n\terrlog.info(f'Loglevel set to {\"DEBUG\" if DEBUG else CONFIG.log_level}')\n\terrlog.debug(f'Using psutil version {\".\".join(str(x) for x in psutil.version_info)}')\n\terrlog.debug(f'CMD: {\" \".join(sys.argv)}')\n\tif CONFIG.info:\n\t\tfor info in CONFIG.info:\n\t\t\terrlog.info(info)\n\t\tCONFIG.info = []\n\tif CONFIG.warnings:\n\t\tfor warning in CONFIG.warnings:\n\t\t\terrlog.warning(warning)\n\t\tCONFIG.warnings = []\nexcept Exception as e:\n\terrlog.exception(f'{e}')\n\traise SystemExit(1)\n\nif ARG_BOXES:\n\t_new_boxes: List = []\n\tfor _box in ARG_BOXES.split():\n\t\tif _box in [\"cpu\", \"mem\", \"net\", \"proc\"]:\n\t\t\t_new_boxes.append(_box)\n\tCONFIG.shown_boxes = \" \".join(_new_boxes)\n\tdel _box, _new_boxes\n\nif SYSTEM == \"Linux\" and not os.path.isdir(\"/sys/class/power_supply\"):\n\tCONFIG.show_battery = False\n\nif psutil.version_info[0] < 5 or (psutil.version_info[0] == 5 and psutil.version_info[1] < 7):\n\twarn = f'psutil version {\".\".join(str(x) for x in psutil.version_info)} detected, version 5.7.0 or later required for full functionality!'\n\tprint(\"WARNING!\", warn)\n\terrlog.warning(warn)\n\n\n#? Classes --------------------------------------------------------------------------------------->\n\nclass Term:\n\t\"\"\"Terminal info and commands\"\"\"\n\twidth: int = 0\n\theight: int = 0\n\tresized: bool = False\n\t_w : int = 0\n\t_h : int = 0\n\tfg: str = \"\" \t\t\t\t\t\t\t\t\t\t\t\t#* Default foreground color\n\tbg: str = \"\" \t\t\t\t\t\t\t\t\t\t\t\t#* Default background color\n\thide_cursor \t\t= \"\\033[?25l\"\t\t\t\t\t\t\t#* Hide terminal cursor\n\tshow_cursor \t\t= \"\\033[?25h\"\t\t\t\t\t\t\t#* Show terminal cursor\n\talt_screen \t\t\t= \"\\033[?1049h\"\t\t\t\t\t\t\t#* Switch to alternate screen\n\tnormal_screen \t\t= \"\\033[?1049l\"\t\t\t\t\t\t\t#* Switch to normal screen\n\tclear\t\t\t\t= \"\\033[2J\\033[0;0f\"\t\t\t\t\t#* Clear screen and set cursor to position 0,0\n\tmouse_on\t\t\t= \"\\033[?1002h\\033[?1015h\\033[?1006h\" \t#* Enable reporting of mouse position on click and release\n\tmouse_off\t\t\t= \"\\033[?1002l\" \t\t\t\t\t\t#* Disable mouse reporting\n\tmouse_direct_on\t\t= \"\\033[?1003h\"\t\t\t\t\t\t\t#* Enable reporting of mouse position at any movement\n\tmouse_direct_off\t= \"\\033[?1003l\"\t\t\t\t\t\t\t#* Disable direct mouse reporting\n\twinch = threading.Event()\n\told_boxes: List = []\n\tmin_width: int = 0\n\tmin_height: int = 0\n\n\t@classmethod\n\tdef refresh(cls, *args, force: bool = False):\n\t\t\"\"\"Update width, height and set resized flag if terminal has been resized\"\"\"\n\t\tif Init.running: cls.resized = False; return\n\t\tif cls.resized: cls.winch.set(); return\n\t\tcls._w, cls._h = os.get_terminal_size()\n\t\tif (cls._w, cls._h) == (cls.width, cls.height) and cls.old_boxes == Box.boxes and not force: return\n\t\tif force: Collector.collect_interrupt = True\n\t\tif cls.old_boxes != Box.boxes:\n\t\t\tw_p = h_p = 0\n\t\t\tcls.min_width = cls.min_height = 0\n\t\t\tcls.old_boxes = Box.boxes.copy()\n\t\t\tfor box_class in Box.__subclasses__():\n\t\t\t\tfor box_name in Box.boxes:\n\t\t\t\t\tif box_name in str(box_class).capitalize():\n\t\t\t\t\t\tif not (box_name == \"cpu\" and \"proc\" in Box.boxes) and not (box_name == \"net\" and \"mem\" in Box.boxes) and w_p + box_class.width_p <= 100:\n\t\t\t\t\t\t\tw_p += box_class.width_p\n\t\t\t\t\t\t\tcls.min_width += getattr(box_class, \"min_w\", 0)\n\t\t\t\t\t\tif not (box_name in [\"mem\", \"net\"] and \"proc\" in Box.boxes) and h_p + box_class.height_p <= 100:\n\t\t\t\t\t\t\th_p += box_class.height_p\n\t\t\t\t\t\t\tcls.min_height += getattr(box_class, \"min_h\", 0)\n\t\twhile (cls._w, cls._h) != (cls.width, cls.height) or (cls._w < cls.min_width or cls._h < cls.min_height):\n\t\t\tif Init.running: Init.resized = True\n\t\t\tCpuBox.clock_block = True\n\t\t\tcls.resized = True\n\t\t\tCollector.collect_interrupt = True\n\t\t\tcls.width, cls.height = cls._w, cls._h\n\t\t\tDraw.now(Term.clear)\n\t\t\tbox_width = min(50, cls._w - 2)\n\t\t\tDraw.now(f'{create_box(cls._w // 2 - box_width // 2, cls._h // 2 - 2, 50, 3, \"resizing\", line_color=Colors.green, title_color=Colors.white)}',\n\t\t\t\tf'{Mv.r(box_width // 4)}{Colors.default}{Colors.black_bg}{Fx.b}Width : {cls._w}   Height: {cls._h}{Fx.ub}{Term.bg}{Term.fg}')\n\t\t\tif cls._w < 80 or cls._h < 24:\n\t\t\t\twhile cls._w < cls.min_width or cls._h < cls.min_height:\n\t\t\t\t\tDraw.now(Term.clear)\n\t\t\t\t\tbox_width = min(50, cls._w - 2)\n\t\t\t\t\tDraw.now(f'{create_box(cls._w // 2 - box_width // 2, cls._h // 2 - 2, box_width, 4, \"warning\", line_color=Colors.red, title_color=Colors.white)}',\n\t\t\t\t\t\tf'{Mv.r(box_width // 4)}{Colors.default}{Colors.black_bg}{Fx.b}Width: {Colors.red if cls._w < cls.min_width else Colors.green}{cls._w}   ',\n\t\t\t\t\t\tf'{Colors.default}Height: {Colors.red if cls._h < cls.min_height else Colors.green}{cls._h}{Term.bg}{Term.fg}',\n\t\t\t\t\t\tf'{Mv.d(1)}{Mv.l(25)}{Colors.default}{Colors.black_bg}Current config need: {cls.min_width} x {cls.min_height}{Fx.ub}{Term.bg}{Term.fg}')\n\t\t\t\t\tcls.winch.wait(0.3)\n\t\t\t\t\twhile Key.has_key():\n\t\t\t\t\t\tif Key.last() == \"q\": clean_quit()\n\t\t\t\t\tcls.winch.clear()\n\t\t\t\t\tcls._w, cls._h = os.get_terminal_size()\n\t\t\telse:\n\t\t\t\tcls.winch.wait(0.3)\n\t\t\t\tcls.winch.clear()\n\t\t\tcls._w, cls._h = os.get_terminal_size()\n\n\t\tKey.mouse = {}\n\t\tBox.calc_sizes()\n\t\tCollector.proc_counter = 1\n\t\tif Menu.active: Menu.resized = True\n\t\tBox.draw_bg(now=False)\n\t\tcls.resized = False\n\t\tTimer.finish()\n\n\t@staticmethod\n\tdef echo(on: bool):\n\t\t\"\"\"Toggle input echo\"\"\"\n\t\t(iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(sys.stdin.fileno())\n\t\tif on:\n\t\t\tlflag |= termios.ECHO # type: ignore\n\t\telse:\n\t\t\tlflag &= ~termios.ECHO # type: ignore\n\t\tnew_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]\n\t\ttermios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, new_attr)\n\n\t@staticmethod\n\tdef title(text: str = \"\") -> str:\n\t\tout: str = f'{os.environ.get(\"TERMINAL_TITLE\", \"\")}'\n\t\tif out and text: out += \" \"\n\t\tif text: out += f'{text}'\n\t\treturn f'\\033]0;{out}\\a'\n\nclass Fx:\n\t\"\"\"Text effects\n\t* trans(string: str): Replace whitespace with escape move right to not overwrite background behind whitespace.\n\t* uncolor(string: str) : Removes all 24-bit color and returns string .\"\"\"\n\tstart\t\t\t\t\t= \"\\033[\"\t\t\t#* Escape sequence start\n\tsep\t\t\t\t\t\t= \";\"\t\t\t\t#* Escape sequence separator\n\tend\t\t\t\t\t\t= \"m\"\t\t\t\t#* Escape sequence end\n\treset = rs\t\t\t\t= \"\\033[0m\"\t\t\t#* Reset foreground/background color and text effects\n\tbold = b\t\t\t\t= \"\\033[1m\"\t\t\t#* Bold on\n\tunbold = ub\t\t\t\t= \"\\033[22m\"\t\t#* Bold off\n\tdark = d\t\t\t\t= \"\\033[2m\"\t\t\t#* Dark on\n\tundark = ud\t\t\t\t= \"\\033[22m\"\t\t#* Dark off\n\titalic = i\t\t\t\t= \"\\033[3m\"\t\t\t#* Italic on\n\tunitalic = ui\t\t\t= \"\\033[23m\"\t\t#* Italic off\n\tunderline = u\t\t\t= \"\\033[4m\"\t\t\t#* Underline on\n\tununderline = uu\t\t= \"\\033[24m\"\t\t#* Underline off\n\tblink = bl \t\t\t\t= \"\\033[5m\"\t\t\t#* Blink on\n\tunblink = ubl\t\t\t= \"\\033[25m\"\t\t#* Blink off\n\tstrike = s \t\t\t\t= \"\\033[9m\"\t\t\t#* Strike / crossed-out on\n\tunstrike = us\t\t\t= \"\\033[29m\"\t\t#* Strike / crossed-out off\n\n\t#* Precompiled regex for finding a 24-bit color escape sequence in a string\n\tcolor_re = re.compile(r\"\\033\\[\\d+;\\d?;?\\d*;?\\d*;?\\d*m\")\n\n\t@staticmethod\n\tdef trans(string: str):\n\t\treturn string.replace(\" \", \"\\033[1C\")\n\n\t@classmethod\n\tdef uncolor(cls, string: str) -> str:\n\t\treturn f'{cls.color_re.sub(\"\", string)}'\n\nclass Raw(object):\n\t\"\"\"Set raw input mode for device\"\"\"\n\tdef __init__(self, stream):\n\t\tself.stream = stream\n\t\tself.fd = self.stream.fileno()\n\tdef __enter__(self):\n\t\tself.original_stty = termios.tcgetattr(self.stream)\n\t\ttty.setcbreak(self.stream)\n\tdef __exit__(self, type, value, traceback):\n\t\ttermios.tcsetattr(self.stream, termios.TCSANOW, self.original_stty)\n\nclass Nonblocking(object):\n\t\"\"\"Set nonblocking mode for device\"\"\"\n\tdef __init__(self, stream):\n\t\tself.stream = stream\n\t\tself.fd = self.stream.fileno()\n\tdef __enter__(self):\n\t\tself.orig_fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)\n\t\tfcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl | os.O_NONBLOCK)\n\tdef __exit__(self, *args):\n\t\tfcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl)\n\nclass Mv:\n\t\"\"\"Class with collection of cursor movement functions: .t[o](line, column) | .r[ight](columns) | .l[eft](columns) | .u[p](lines) | .d[own](lines) | .save() | .restore()\"\"\"\n\t@staticmethod\n\tdef to(line: int, col: int) -> str:\n\t\treturn f'\\033[{line};{col}f'\t#* Move cursor to line, column\n\t@staticmethod\n\tdef right(x: int) -> str:\t\t\t#* Move cursor right x columns\n\t\treturn f'\\033[{x}C'\n\t@staticmethod\n\tdef left(x: int) -> str:\t\t\t#* Move cursor left x columns\n\t\treturn f'\\033[{x}D'\n\t@staticmethod\n\tdef up(x: int) -> str:\t\t\t\t#* Move cursor up x lines\n\t\treturn f'\\033[{x}A'\n\t@staticmethod\n\tdef down(x: int) -> str:\t\t\t#* Move cursor down x lines\n\t\treturn f'\\033[{x}B'\n\n\tsave: str = \"\\033[s\" \t\t\t\t#* Save cursor position\n\trestore: str = \"\\033[u\" \t\t\t#* Restore saved cursor position\n\tt = to\n\tr = right\n\tl = left\n\tu = up\n\td = down\n\nclass Key:\n\t\"\"\"Handles the threaded input reader for keypresses and mouse events\"\"\"\n\tlist: List[str] = []\n\tmouse: Dict[str, List[List[int]]] = {}\n\tmouse_pos: Tuple[int, int] = (0, 0)\n\tescape: Dict[Union[str, Tuple[str, str]], str] = {\n\t\t\"\\n\" :\t\t\t\t\t\"enter\",\n\t\t(\"\\x7f\", \"\\x08\") :\t\t\"backspace\",\n\t\t(\"[A\", \"OA\") :\t\t\t\"up\",\n\t\t(\"[B\", \"OB\") :\t\t\t\"down\",\n\t\t(\"[D\", \"OD\") :\t\t\t\"left\",\n\t\t(\"[C\", \"OC\") :\t\t\t\"right\",\n\t\t\"[2~\" :\t\t\t\t\t\"insert\",\n\t\t\"[3~\" :\t\t\t\t\t\"delete\",\n\t\t\"[H\" :\t\t\t\t\t\"home\",\n\t\t\"[F\" :\t\t\t\t\t\"end\",\n\t\t\"[5~\" :\t\t\t\t\t\"page_up\",\n\t\t\"[6~\" :\t\t\t\t\t\"page_down\",\n\t\t\"\\t\" :\t\t\t\t\t\"tab\",\n\t\t\"[Z\" :\t\t\t\t\t\"shift_tab\",\n\t\t\"OP\" :\t\t\t\t\t\"f1\",\n\t\t\"OQ\" :\t\t\t\t\t\"f2\",\n\t\t\"OR\" :\t\t\t\t\t\"f3\",\n\t\t\"OS\" :\t\t\t\t\t\"f4\",\n\t\t\"[15\" :\t\t\t\t\t\"f5\",\n\t\t\"[17\" :\t\t\t\t\t\"f6\",\n\t\t\"[18\" :\t\t\t\t\t\"f7\",\n\t\t\"[19\" :\t\t\t\t\t\"f8\",\n\t\t\"[20\" :\t\t\t\t\t\"f9\",\n\t\t\"[21\" :\t\t\t\t\t\"f10\",\n\t\t\"[23\" :\t\t\t\t\t\"f11\",\n\t\t\"[24\" :\t\t\t\t\t\"f12\"\n\t\t}\n\tnew = threading.Event()\n\tidle = threading.Event()\n\tmouse_move = threading.Event()\n\tmouse_report: bool = False\n\tidle.set()\n\tstopping: bool = False\n\tstarted: bool = False\n\treader: threading.Thread\n\t@classmethod\n\tdef start(cls):\n\t\tcls.stopping = False\n\t\tcls.reader = threading.Thread(target=cls._get_key)\n\t\tcls.reader.start()\n\t\tcls.started = True\n\n\t@classmethod\n\tdef stop(cls):\n\t\tif cls.started and cls.reader.is_alive():\n\t\t\tcls.stopping = True\n\t\t\ttry:\n\t\t\t\tcls.reader.join()\n\t\t\texcept:\n\t\t\t\tpass\n\n\t@classmethod\n\tdef last(cls) -> str:\n\t\tif cls.list: return cls.list.pop()\n\t\telse: return \"\"\n\n\t@classmethod\n\tdef get(cls) -> str:\n\t\tif cls.list: return cls.list.pop(0)\n\t\telse: return \"\"\n\n\t@classmethod\n\tdef get_mouse(cls) -> Tuple[int, int]:\n\t\tif cls.new.is_set():\n\t\t\tcls.new.clear()\n\t\treturn cls.mouse_pos\n\n\t@classmethod\n\tdef mouse_moved(cls) -> bool:\n\t\tif cls.mouse_move.is_set():\n\t\t\tcls.mouse_move.clear()\n\t\t\treturn True\n\t\telse:\n\t\t\treturn False\n\n\t@classmethod\n\tdef has_key(cls) -> bool:\n\t\treturn bool(cls.list)\n\n\t@classmethod\n\tdef clear(cls):\n\t\tcls.list = []\n\n\t@classmethod\n\tdef input_wait(cls, sec: float = 0.0, mouse: bool = False) -> bool:\n\t\t'''Returns True if key is detected else waits out timer and returns False'''\n\t\tif cls.list: return True\n\t\tif mouse: Draw.now(Term.mouse_direct_on)\n\t\tcls.new.wait(sec if sec > 0 else 0.0)\n\t\tif mouse: Draw.now(Term.mouse_direct_off, Term.mouse_on)\n\n\t\tif cls.new.is_set():\n\t\t\tcls.new.clear()\n\t\t\treturn True\n\t\telse:\n\t\t\treturn False\n\n\t@classmethod\n\tdef break_wait(cls):\n\t\tcls.list.append(\"_null\")\n\t\tcls.new.set()\n\t\tsleep(0.01)\n\t\tcls.new.clear()\n\n\t@classmethod\n\tdef _get_key(cls):\n\t\t\"\"\"Get a key or escape sequence from stdin, convert to readable format and save to keys list. Meant to be run in it's own thread.\"\"\"\n\t\tinput_key: str = \"\"\n\t\tclean_key: str = \"\"\n\t\ttry:\n\t\t\twhile not cls.stopping:\n\t\t\t\twith Raw(sys.stdin):\n\t\t\t\t\tif not select([sys.stdin], [], [], 0.1)[0]:\t\t\t#* Wait 100ms for input on stdin then restart loop to check for stop flag\n\t\t\t\t\t\tcontinue\n\t\t\t\t\tinput_key += sys.stdin.read(1)\t\t\t\t\t\t#* Read 1 key safely with blocking on\n\t\t\t\t\tif input_key == \"\\033\":\t\t\t\t\t\t\t\t#* If first character is a escape sequence keep reading\n\t\t\t\t\t\tcls.idle.clear()\t\t\t\t\t\t\t\t#* Report IO block in progress to prevent Draw functions from getting a IO Block error\n\t\t\t\t\t\tDraw.idle.wait()\t\t\t\t\t\t\t\t#* Wait for Draw function to finish if busy\n\t\t\t\t\t\twith Nonblocking(sys.stdin): \t\t\t\t\t#* Set non blocking to prevent read stall\n\t\t\t\t\t\t\tinput_key += sys.stdin.read(20)\n\t\t\t\t\t\t\tif input_key.startswith(\"\\033[<\"):\n\t\t\t\t\t\t\t\t_ = sys.stdin.read(1000)\n\t\t\t\t\t\tcls.idle.set()\t\t\t\t\t\t\t\t\t#* Report IO blocking done\n\t\t\t\t\t#errlog.debug(f'{repr(input_key)}')\n\t\t\t\t\tif input_key == \"\\033\":\tclean_key = \"escape\"\t\t#* Key is \"escape\" key if only containing \\033\n\t\t\t\t\telif input_key.startswith((\"\\033[<0;\", \"\\033[<35;\", \"\\033[<64;\", \"\\033[<65;\")): #* Detected mouse event\n\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\tcls.mouse_pos = (int(input_key.split(\";\")[1]), int(input_key.split(\";\")[2].rstrip(\"mM\")))\n\t\t\t\t\t\texcept:\n\t\t\t\t\t\t\tpass\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tif input_key.startswith(\"\\033[<35;\"):\t\t#* Detected mouse move in mouse direct mode\n\t\t\t\t\t\t\t\tcls.mouse_move.set()\n\t\t\t\t\t\t\t\tcls.new.set()\n\t\t\t\t\t\t\telif input_key.startswith(\"\\033[<64;\"):\t\t#* Detected mouse scroll up\n\t\t\t\t\t\t\t\tclean_key = \"mouse_scroll_up\"\n\t\t\t\t\t\t\telif input_key.startswith(\"\\033[<65;\"):\t\t#* Detected mouse scroll down\n\t\t\t\t\t\t\t\tclean_key = \"mouse_scroll_down\"\n\t\t\t\t\t\t\telif input_key.startswith(\"\\033[<0;\") and input_key.endswith(\"m\"): #* Detected mouse click release\n\t\t\t\t\t\t\t\tif Menu.active:\n\t\t\t\t\t\t\t\t\tclean_key = \"mouse_click\"\n\t\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\t\tfor key_name, positions in cls.mouse.items(): #* Check if mouse position is clickable\n\t\t\t\t\t\t\t\t\t\tif list(cls.mouse_pos) in positions:\n\t\t\t\t\t\t\t\t\t\t\tclean_key = key_name\n\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\t\t\tclean_key = \"mouse_click\"\n\t\t\t\t\telif input_key == \"\\\\\": clean_key = \"\\\\\"\t\t\t#* Clean up \"\\\" to not return escaped\n\t\t\t\t\telse:\n\t\t\t\t\t\tfor code in cls.escape.keys():\t\t\t\t\t#* Go through dict of escape codes to get the cleaned key name\n\t\t\t\t\t\t\tif input_key.lstrip(\"\\033\").startswith(code):\n\t\t\t\t\t\t\t\tclean_key = cls.escape[code]\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\telse:\t\t\t\t\t\t\t\t\t\t\t#* If not found in escape dict and length of key is 1, assume regular character\n\t\t\t\t\t\t\tif len(input_key) == 1:\n\t\t\t\t\t\t\t\tclean_key = input_key\n\t\t\t\t\tif clean_key:\n\t\t\t\t\t\tcls.list.append(clean_key)\t\t\t\t\t\t#* Store up to 10 keys in input queue for later processing\n\t\t\t\t\t\tif len(cls.list) > 10: del cls.list[0]\n\t\t\t\t\t\tclean_key = \"\"\n\t\t\t\t\t\tcls.new.set()\t\t\t\t\t\t\t\t\t#* Set threading event to interrupt main thread sleep\n\t\t\t\t\tinput_key = \"\"\n\n\n\t\texcept Exception as e:\n\t\t\terrlog.exception(f'Input thread failed with exception: {e}')\n\t\t\tcls.idle.set()\n\t\t\tcls.list.clear()\n\t\t\tclean_quit(1, thread=True)\n\nclass Draw:\n\t'''Holds the draw buffer and manages IO blocking queue\n\t* .buffer([+]name[!], *args, append=False, now=False, z=100) : Add *args to buffer\n\t* - Adding \"+\" prefix to name sets append to True and appends to name's current string\n\t* - Adding \"!\" suffix to name sets now to True and print name's current string\n\t* .out(clear=False) : Print all strings in buffer, clear=True clear all buffers after\n\t* .now(*args) : Prints all arguments as a string\n\t* .clear(*names) : Clear named buffers, all if no argument\n\t* .last_screen() : Prints all saved buffers\n\t'''\n\tstrings: Dict[str, str] = {}\n\tz_order: Dict[str, int] = {}\n\tsaved: Dict[str, str] = {}\n\tsave: Dict[str, bool] = {}\n\tonce: Dict[str, bool] = {}\n\tidle = threading.Event()\n\tidle.set()\n\n\t@classmethod\n\tdef now(cls, *args):\n\t\t'''Wait for input reader and self to be idle then print to screen'''\n\t\tKey.idle.wait()\n\t\tcls.idle.wait()\n\t\tcls.idle.clear()\n\t\ttry:\n\t\t\tprint(*args, sep=\"\", end=\"\", flush=True)\n\t\texcept BlockingIOError:\n\t\t\tpass\n\t\t\tKey.idle.wait()\n\t\t\tprint(*args, sep=\"\", end=\"\", flush=True)\n\t\tcls.idle.set()\n\n\t@classmethod\n\tdef buffer(cls, name: str, *args: str, append: bool = False, now: bool = False, z: int = 100, only_save: bool = False, no_save: bool = False, once: bool = False):\n\t\tstring: str = \"\"\n\t\tif name.startswith(\"+\"):\n\t\t\tname = name.lstrip(\"+\")\n\t\t\tappend = True\n\t\tif name.endswith(\"!\"):\n\t\t\tname = name.rstrip(\"!\")\n\t\t\tnow = True\n\t\tcls.save[name] = not no_save\n\t\tcls.once[name] = once\n\t\tif not name in cls.z_order or z != 100: cls.z_order[name] = z\n\t\tif args: string = \"\".join(args)\n\t\tif only_save:\n\t\t\tif name not in cls.saved or not append: cls.saved[name] = \"\"\n\t\t\tcls.saved[name] += string\n\t\telse:\n\t\t\tif name not in cls.strings or not append: cls.strings[name] = \"\"\n\t\t\tcls.strings[name] += string\n\t\t\tif now:\n\t\t\t\tcls.out(name)\n\n\t@classmethod\n\tdef out(cls, *names: str, clear = False):\n\t\tout: str = \"\"\n\t\tif not cls.strings: return\n\t\tif names:\n\t\t\tfor name in sorted(cls.z_order, key=cls.z_order.get, reverse=True): #type: ignore\n\t\t\t\tif name in names and name in cls.strings:\n\t\t\t\t\tout += cls.strings[name]\n\t\t\t\t\tif cls.save[name]:\n\t\t\t\t\t\tcls.saved[name] = cls.strings[name]\n\t\t\t\t\tif clear or cls.once[name]:\n\t\t\t\t\t\tcls.clear(name)\n\t\t\tcls.now(out)\n\t\telse:\n\t\t\tfor name in sorted(cls.z_order, key=cls.z_order.get, reverse=True): #type: ignore\n\t\t\t\tif name in cls.strings:\n\t\t\t\t\tout += cls.strings[name]\n\t\t\t\t\tif cls.save[name]:\n\t\t\t\t\t\tcls.saved[name] = cls.strings[name]\n\t\t\t\t\tif cls.once[name] and not clear:\n\t\t\t\t\t\tcls.clear(name)\n\t\t\tif clear:\n\t\t\t\tcls.clear()\n\t\t\tcls.now(out)\n\n\t@classmethod\n\tdef saved_buffer(cls) -> str:\n\t\tout: str = \"\"\n\t\tfor name in sorted(cls.z_order, key=cls.z_order.get, reverse=True): #type: ignore\n\t\t\tif name in cls.saved:\n\t\t\t\tout += cls.saved[name]\n\t\treturn out\n\n\n\t@classmethod\n\tdef clear(cls, *names, saved: bool = False):\n\t\tif names:\n\t\t\tfor name in names:\n\t\t\t\tif name in cls.strings:\n\t\t\t\t\tdel cls.strings[name]\n\t\t\t\tif name in cls.save:\n\t\t\t\t\tdel cls.save[name]\n\t\t\t\tif name in cls.once:\n\t\t\t\t\tdel cls.once[name]\n\t\t\t\tif saved:\n\t\t\t\t\tif name in cls.saved:\n\t\t\t\t\t\tdel cls.saved[name]\n\t\t\t\t\tif name in cls.z_order:\n\t\t\t\t\t\tdel cls.z_order[name]\n\t\telse:\n\t\t\tcls.strings = {}\n\t\t\tcls.save = {}\n\t\t\tcls.once = {}\n\t\t\tif saved:\n\t\t\t\tcls.saved = {}\n\t\t\t\tcls.z_order = {}\n\nclass Color:\n\t'''Holds representations for a 24-bit color value\n\t__init__(color, depth=\"fg\", default=False)\n\t-- color accepts 6 digit hexadecimal: string \"#RRGGBB\", 2 digit hexadecimal: string \"#FF\" or decimal RGB \"255 255 255\" as a string.\n\t-- depth accepts \"fg\" or \"bg\"\n\t__call__(*args) joins str arguments to a string and apply color\n\t__str__ returns escape sequence to set color\n\t__iter__ returns iteration over red, green and blue in integer values of 0-255.\n\t* Values:  .hexa: str  |  .dec: Tuple[int, int, int]  |  .red: int  |  .green: int  |  .blue: int  |  .depth: str  |  .escape: str\n\t'''\n\thexa: str; dec: Tuple[int, int, int]; red: int; green: int; blue: int; depth: str; escape: str; default: bool\n\n\tdef __init__(self, color: str, depth: str = \"fg\", default: bool = False):\n\t\tself.depth = depth\n\t\tself.default = default\n\t\ttry:\n\t\t\tif not color:\n\t\t\t\tself.dec = (-1, -1, -1)\n\t\t\t\tself.hexa = \"\"\n\t\t\t\tself.red = self.green = self.blue = -1\n\t\t\t\tself.escape = \"\\033[49m\" if depth == \"bg\" and default else \"\"\n\t\t\t\treturn\n\n\t\t\telif color.startswith(\"#\"):\n\t\t\t\tself.hexa = color\n\t\t\t\tif len(self.hexa) == 3:\n\t\t\t\t\tself.hexa += self.hexa[1:3] + self.hexa[1:3]\n\t\t\t\t\tc = int(self.hexa[1:3], base=16)\n\t\t\t\t\tself.dec = (c, c, c)\n\t\t\t\telif len(self.hexa) == 7:\n\t\t\t\t\tself.dec = (int(self.hexa[1:3], base=16), int(self.hexa[3:5], base=16), int(self.hexa[5:7], base=16))\n\t\t\t\telse:\n\t\t\t\t\traise ValueError(f'Incorrectly formatted hexadecimal rgb string: {self.hexa}')\n\n\t\t\telse:\n\t\t\t\tc_t = tuple(map(int, color.split(\" \")))\n\t\t\t\tif len(c_t) == 3:\n\t\t\t\t\tself.dec = c_t #type: ignore\n\t\t\t\telse:\n\t\t\t\t\traise ValueError(f'RGB dec should be \"0-255 0-255 0-255\"')\n\n\t\t\tif not all(0 <= c <= 255 for c in self.dec):\n\t\t\t\traise ValueError(f'One or more RGB values are out of range: {color}')\n\n\t\texcept Exception as e:\n\t\t\terrlog.exception(str(e))\n\t\t\tself.escape = \"\"\n\t\t\treturn\n\n\t\tif self.dec and not self.hexa: self.hexa = f'{hex(self.dec[0]).lstrip(\"0x\").zfill(2)}{hex(self.dec[1]).lstrip(\"0x\").zfill(2)}{hex(self.dec[2]).lstrip(\"0x\").zfill(2)}'\n\n\t\tif self.dec and self.hexa:\n\t\t\tself.red, self.green, self.blue = self.dec\n\t\t\tself.escape = f'\\033[{38 if self.depth == \"fg\" else 48};2;{\";\".join(str(c) for c in self.dec)}m'\n\n\t\tif not CONFIG.truecolor or LOW_COLOR:\n\t\t\tself.escape = f'{self.truecolor_to_256(rgb=self.dec, depth=self.depth)}'\n\n\tdef __str__(self) -> str:\n\t\treturn self.escape\n\n\tdef __repr__(self) -> str:\n\t\treturn repr(self.escape)\n\n\tdef __iter__(self) -> Iterable:\n\t\tfor c in self.dec: yield c\n\n\tdef __call__(self, *args: str) -> str:\n\t\tif len(args) < 1: return \"\"\n\t\treturn f'{self.escape}{\"\".join(args)}{getattr(Term, self.depth)}'\n\n\t@staticmethod\n\tdef truecolor_to_256(rgb: Tuple[int, int, int], depth: str=\"fg\") -> str:\n\t\tout: str = \"\"\n\t\tpre: str = f'\\033[{\"38\" if depth == \"fg\" else \"48\"};5;'\n\n\t\tgreyscale: Tuple[int, int, int] = ( rgb[0] // 11, rgb[1] // 11, rgb[2] // 11 )\n\t\tif greyscale[0] == greyscale[1] == greyscale[2]:\n\t\t\tout = f'{pre}{232 + greyscale[0]}m'\n\t\telse:\n\t\t\tout = f'{pre}{round(rgb[0] / 51) * 36 + round(rgb[1] / 51) * 6 + round(rgb[2] / 51) + 16}m'\n\n\t\treturn out\n\n\t@staticmethod\n\tdef escape_color(hexa: str = \"\", r: int = 0, g: int = 0, b: int = 0, depth: str = \"fg\") -> str:\n\t\t\"\"\"Returns escape sequence to set color\n\t\t* accepts either 6 digit hexadecimal hexa=\"#RRGGBB\", 2 digit hexadecimal: hexa=\"#FF\"\n\t\t* or decimal RGB: r=0-255, g=0-255, b=0-255\n\t\t* depth=\"fg\" or \"bg\"\n\t\t\"\"\"\n\t\tdint: int = 38 if depth == \"fg\" else 48\n\t\tcolor: str = \"\"\n\t\tif hexa:\n\t\t\ttry:\n\t\t\t\tif len(hexa) == 3:\n\t\t\t\t\tc = int(hexa[1:], base=16)\n\t\t\t\t\tif CONFIG.truecolor and not LOW_COLOR:\n\t\t\t\t\t\tcolor = f'\\033[{dint};2;{c};{c};{c}m'\n\t\t\t\t\telse:\n\t\t\t\t\t\tcolor = f'{Color.truecolor_to_256(rgb=(c, c, c), depth=depth)}'\n\t\t\t\telif len(hexa) == 7:\n\t\t\t\t\tif CONFIG.truecolor and not LOW_COLOR:\n\t\t\t\t\t\tcolor = f'\\033[{dint};2;{int(hexa[1:3], base=16)};{int(hexa[3:5], base=16)};{int(hexa[5:7], base=16)}m'\n\t\t\t\t\telse:\n\t\t\t\t\t\tcolor = f'{Color.truecolor_to_256(rgb=(int(hexa[1:3], base=16), int(hexa[3:5], base=16), int(hexa[5:7], base=16)), depth=depth)}'\n\t\t\texcept ValueError as e:\n\t\t\t\terrlog.exception(f'{e}')\n\t\telse:\n\t\t\tif CONFIG.truecolor and not LOW_COLOR:\n\t\t\t\tcolor = f'\\033[{dint};2;{r};{g};{b}m'\n\t\t\telse:\n\t\t\t\tcolor = f'{Color.truecolor_to_256(rgb=(r, g, b), depth=depth)}'\n\t\treturn color\n\n\t@classmethod\n\tdef fg(cls, *args) -> str:\n\t\tif len(args) > 2: return cls.escape_color(r=args[0], g=args[1], b=args[2], depth=\"fg\")\n\t\telse: return cls.escape_color(hexa=args[0], depth=\"fg\")\n\n\t@classmethod\n\tdef bg(cls, *args) -> str:\n\t\tif len(args) > 2: return cls.escape_color(r=args[0], g=args[1], b=args[2], depth=\"bg\")\n\t\telse: return cls.escape_color(hexa=args[0], depth=\"bg\")\n\nclass Colors:\n\t'''Standard colors for menus and dialogs'''\n\tdefault = Color(\"#cc\")\n\twhite = Color(\"#ff\")\n\tred = Color(\"#bf3636\")\n\tgreen = Color(\"#68bf36\")\n\tblue = Color(\"#0fd7ff\")\n\tyellow = Color(\"#db8b00\")\n\tblack_bg = Color(\"#00\", depth=\"bg\")\n\tnull = Color(\"\")\n\nclass Theme:\n\t'''__init__ accepts a dict containing { \"color_element\" : \"color\" }'''\n\n\tthemes: Dict[str, str] = {}\n\tcached: Dict[str, Dict[str, str]] = { \"Default\" : DEFAULT_THEME }\n\tcurrent: str = \"\"\n\n\tmain_bg = main_fg = title = hi_fg = selected_bg = selected_fg = inactive_fg = proc_misc = cpu_box = mem_box = net_box = proc_box = div_line = temp_start = temp_mid = temp_end = cpu_start = cpu_mid = cpu_end = free_start = free_mid = free_end = cached_start = cached_mid = cached_end = available_start = available_mid = available_end = used_start = used_mid = used_end = download_start = download_mid = download_end = upload_start = upload_mid = upload_end = graph_text = meter_bg = process_start = process_mid = process_end = Colors.default\n\n\tgradient: Dict[str, List[str]] = {\n\t\t\"temp\" : [],\n\t\t\"cpu\" : [],\n\t\t\"free\" : [],\n\t\t\"cached\" : [],\n\t\t\"available\" : [],\n\t\t\"used\" : [],\n\t\t\"download\" : [],\n\t\t\"upload\" : [],\n\t\t\"proc\" : [],\n\t\t\"proc_color\" : [],\n\t\t\"process\" : [],\n\t}\n\tdef __init__(self, theme: str):\n\t\tself.refresh()\n\t\tself._load_theme(theme)\n\n\tdef __call__(self, theme: str):\n\t\tfor k in self.gradient.keys(): self.gradient[k] = []\n\t\tself._load_theme(theme)\n\n\tdef _load_theme(self, theme: str):\n\t\ttdict: Dict[str, str]\n\t\tif theme in self.cached:\n\t\t\ttdict = self.cached[theme]\n\t\telif theme in self.themes:\n\t\t\ttdict = self._load_file(self.themes[theme])\n\t\t\tself.cached[theme] = tdict\n\t\telse:\n\t\t\terrlog.warning(f'No theme named \"{theme}\" found!')\n\t\t\ttheme = \"Default\"\n\t\t\tCONFIG.color_theme = theme\n\t\t\ttdict = DEFAULT_THEME\n\t\tself.current = theme\n\t\t#if CONFIG.color_theme != theme: CONFIG.color_theme = theme\n\t\tif not \"graph_text\" in tdict and \"inactive_fg\" in tdict:\n\t\t\ttdict[\"graph_text\"] = tdict[\"inactive_fg\"]\n\t\tif not \"meter_bg\" in tdict and \"inactive_fg\" in tdict:\n\t\t\ttdict[\"meter_bg\"] = tdict[\"inactive_fg\"]\n\t\tif not \"process_start\" in tdict and \"cpu_start\" in tdict:\n\t\t\ttdict[\"process_start\"] = tdict[\"cpu_start\"]\n\t\t\ttdict[\"process_mid\"] = tdict.get(\"cpu_mid\", \"\")\n\t\t\ttdict[\"process_end\"] = tdict.get(\"cpu_end\", \"\")\n\n\n\t\t#* Get key names from DEFAULT_THEME dict to not leave any color unset if missing from theme dict\n\t\tfor item, value in DEFAULT_THEME.items():\n\t\t\tdefault = item in [\"main_fg\", \"main_bg\"]\n\t\t\tdepth = \"bg\" if item in [\"main_bg\", \"selected_bg\"] else \"fg\"\n\t\t\tif item in tdict:\n\t\t\t\tsetattr(self, item, Color(tdict[item], depth=depth, default=default))\n\t\t\telse:\n\t\t\t\tsetattr(self, item, Color(value, depth=depth, default=default))\n\n\t\t#* Create color gradients from one, two or three colors, 101 values indexed 0-100\n\t\tself.proc_start, self.proc_mid, self.proc_end = self.main_fg, Colors.null, self.inactive_fg\n\t\tself.proc_color_start, self.proc_color_mid, self.proc_color_end = self.inactive_fg, Colors.null, self.process_start\n\n\t\trgb: Dict[str, Tuple[int, int, int]]\n\t\tcolors: List[List[int]] = []\n\t\tfor name in self.gradient:\n\t\t\trgb = { \"start\" : getattr(self, f'{name}_start').dec, \"mid\" : getattr(self, f'{name}_mid').dec, \"end\" : getattr(self, f'{name}_end').dec }\n\t\t\tcolors = [ list(getattr(self, f'{name}_start')) ]\n\t\t\tif rgb[\"end\"][0] >= 0:\n\t\t\t\tr = 50 if rgb[\"mid\"][0] >= 0 else 100\n\t\t\t\tfor first, second in [\"start\", \"mid\" if r == 50 else \"end\"], [\"mid\", \"end\"]:\n\t\t\t\t\tfor i in range(r):\n\t\t\t\t\t\tcolors += [[rgb[first][n] + i * (rgb[second][n] - rgb[first][n]) // r for n in range(3)]]\n\t\t\t\t\tif r == 100:\n\t\t\t\t\t\tbreak\n\t\t\t\tself.gradient[name] += [ Color.fg(*color) for color in colors ]\n\n\t\t\telse:\n\t\t\t\tc = Color.fg(*rgb[\"start\"])\n\t\t\t\tself.gradient[name] += [c] * 101\n\t\t#* Set terminal colors\n\t\tTerm.fg = f'{self.main_fg}'\n\t\tTerm.bg = f'{self.main_bg}' if CONFIG.theme_background else \"\\033[49m\"\n\t\tDraw.now(self.main_fg, self.main_bg)\n\n\t@classmethod\n\tdef refresh(cls):\n\t\t'''Sets themes dict with names and paths to all found themes'''\n\t\tcls.themes = { \"Default\" : \"Default\" }\n\t\ttry:\n\t\t\tfor d in (THEME_DIR, USER_THEME_DIR):\n\t\t\t\tif not d: continue\n\t\t\t\tfor f in os.listdir(d):\n\t\t\t\t\tif f.endswith(\".theme\"):\n\t\t\t\t\t\tcls.themes[f'{\"\" if d == THEME_DIR else \"+\"}{f[:-6]}'] = f'{d}/{f}'\n\t\texcept Exception as e:\n\t\t\terrlog.exception(str(e))\n\n\t@staticmethod\n\tdef _load_file(path: str) -> Dict[str, str]:\n\t\t'''Load a bashtop formatted theme file and return a dict'''\n\t\tnew_theme: Dict[str, str] = {}\n\t\ttry:\n\t\t\twith open(path, \"r\") as f:\n\t\t\t\tfor line in f:\n\t\t\t\t\tif not line.startswith(\"theme[\"): continue\n\t\t\t\t\tkey = line[6:line.find(\"]\")]\n\t\t\t\t\ts = line.find('\"')\n\t\t\t\t\tvalue = line[s + 1:line.find('\"', s + 1)]\n\t\t\t\t\tnew_theme[key] = value\n\t\texcept Exception as e:\n\t\t\terrlog.exception(str(e))\n\n\t\treturn new_theme\n\nclass Banner:\n\t'''Holds the bpytop banner, .draw(line, [col=0], [center=False], [now=False])'''\n\tout: List[str] = []\n\tc_color: str = \"\"\n\tlength: int = 0\n\tif not out:\n\t\tfor num, (color, color2, line) in enumerate(BANNER_SRC):\n\t\t\tif len(line) > length: length = len(line)\n\t\t\tout_var = \"\"\n\t\t\tline_color = Color.fg(color)\n\t\t\tline_color2 = Color.fg(color2)\n\t\t\tline_dark = Color.fg(f'#{80 - num * 6}')\n\t\t\tfor n, letter in enumerate(line):\n\t\t\t\tif letter == \"█\" and c_color != line_color:\n\t\t\t\t\tif 5 < n < 25: c_color = line_color2\n\t\t\t\t\telse: c_color = line_color\n\t\t\t\t\tout_var += c_color\n\t\t\t\telif letter == \" \":\n\t\t\t\t\tletter = f'{Mv.r(1)}'\n\t\t\t\t\tc_color = \"\"\n\t\t\t\telif letter != \"█\" and c_color != line_dark:\n\t\t\t\t\tc_color = line_dark\n\t\t\t\t\tout_var += line_dark\n\t\t\t\tout_var += letter\n\t\t\tout.append(out_var)\n\n\t@classmethod\n\tdef draw(cls, line: int, col: int = 0, center: bool = False, now: bool = False):\n\t\tout: str = \"\"\n\t\tif center: col = Term.width // 2 - cls.length // 2\n\t\tfor n, o in enumerate(cls.out):\n\t\t\tout += f'{Mv.to(line + n, col)}{o}'\n\t\tout += f'{Term.fg}'\n\t\tif now: Draw.out(out)\n\t\telse: return out\n\nclass Symbol:\n\th_line: str\t\t\t= \"─\"\n\tv_line: str\t\t\t= \"│\"\n\tleft_up: str\t\t= \"┌\"\n\tright_up: str\t\t= \"┐\"\n\tleft_down: str\t\t= \"└\"\n\tright_down: str\t\t= \"┘\"\n\ttitle_left: str\t\t= \"┤\"\n\ttitle_right: str\t= \"├\"\n\tdiv_up: str\t\t\t= \"┬\"\n\tdiv_down: str\t\t= \"┴\"\n\tgraph_up: Dict[float, str] = {\n\t0.0 : \" \", 0.1 : \"⢀\", 0.2 : \"⢠\", 0.3 : \"⢰\", 0.4 : \"⢸\",\n\t1.0 : \"⡀\", 1.1 : \"⣀\", 1.2 : \"⣠\", 1.3 : \"⣰\", 1.4 : \"⣸\",\n\t2.0 : \"⡄\", 2.1 : \"⣄\", 2.2 : \"⣤\", 2.3 : \"⣴\", 2.4 : \"⣼\",\n\t3.0 : \"⡆\", 3.1 : \"⣆\", 3.2 : \"⣦\", 3.3 : \"⣶\", 3.4 : \"⣾\",\n\t4.0 : \"⡇\", 4.1 : \"⣇\", 4.2 : \"⣧\", 4.3 : \"⣷\", 4.4 : \"⣿\"\n\t}\n\tgraph_up_small = graph_up.copy()\n\tgraph_up_small[0.0] = \"\\033[1C\"\n\n\tgraph_down: Dict[float, str] = {\n\t0.0 : \" \", 0.1 : \"⠈\", 0.2 : \"⠘\", 0.3 : \"⠸\", 0.4 : \"⢸\",\n\t1.0 : \"⠁\", 1.1 : \"⠉\", 1.2 : \"⠙\", 1.3 : \"⠹\", 1.4 : \"⢹\",\n\t2.0 : \"⠃\", 2.1 : \"⠋\", 2.2 : \"⠛\", 2.3 : \"⠻\", 2.4 : \"⢻\",\n\t3.0 : \"⠇\", 3.1 : \"⠏\", 3.2 : \"⠟\", 3.3 : \"⠿\", 3.4 : \"⢿\",\n\t4.0 : \"⡇\", 4.1 : \"⡏\", 4.2 : \"⡟\", 4.3 : \"⡿\", 4.4 : \"⣿\"\n\t}\n\tgraph_down_small = graph_down.copy()\n\tgraph_down_small[0.0] = \"\\033[1C\"\n\tmeter: str = \"■\"\n\tup: str = \"↑\"\n\tdown: str = \"↓\"\n\tleft: str = \"←\"\n\tright: str = \"→\"\n\tenter: str = \"↲\"\n\tok: str = f'{Color.fg(\"#30ff50\")}√{Color.fg(\"#cc\")}'\n\tfail: str = f'{Color.fg(\"#ff3050\")}!{Color.fg(\"#cc\")}'\n\nclass Graph:\n\t'''Class for creating and adding to graphs\n\t* __str__ : returns graph as a string\n\t* add(value: int) : adds a value to graph and returns it as a string\n\t* __call__ : same as add\n\t'''\n\tout: str\n\twidth: int\n\theight: int\n\tgraphs: Dict[bool, List[str]]\n\tcolors: List[str]\n\tinvert: bool\n\tmax_value: int\n\tcolor_max_value: int\n\toffset: int\n\tno_zero: bool\n\tround_up_low: bool\n\tcurrent: bool\n\tlast: int\n\tlowest: int = 0\n\tsymbol: Dict[float, str]\n\n\tdef __init__(self, width: int, height: int, color: Union[List[str], Color, None], data: List[int], invert: bool = False, max_value: int = 0, offset: int = 0, color_max_value: Union[int, None] = None, no_zero: bool = False, round_up_low: bool = False):\n\t\tself.graphs: Dict[bool, List[str]] = {False : [], True : []}\n\t\tself.current: bool = True\n\t\tself.width = width\n\t\tself.height = height\n\t\tself.invert = invert\n\t\tself.offset = offset\n\t\tself.round_up_low = round_up_low\n\t\tself.no_zero = no_zero or round_up_low\n\t\tif not data: data = [0]\n\t\tif max_value:\n\t\t\tself.lowest = 1 if self.round_up_low else 0\n\t\t\tself.max_value = max_value\n\t\t\tdata = [ min_max((v + offset) * 100 // (max_value + offset), min_max(v + offset, 0, self.lowest), 100) for v in data ] #* Convert values to percentage values of max_value with max_value as ceiling\n\t\telse:\n\t\t\tself.max_value = 0\n\t\tif color_max_value:\n\t\t\tself.color_max_value = color_max_value\n\t\telse:\n\t\t\tself.color_max_value = self.max_value\n\t\tif self.color_max_value and self.max_value:\n\t\t\tcolor_scale = int(100.0 * self.max_value / self.color_max_value)\n\t\telse:\n\t\t\tcolor_scale = 100\n\t\tself.colors: List[str] = []\n\t\tif isinstance(color, list) and height > 1:\n\t\t\tfor i in range(1, height + 1): self.colors.insert(0, color[min(100, i * color_scale // height)]) #* Calculate colors of graph\n\t\t\tif invert: self.colors.reverse()\n\t\telif isinstance(color, Color) and height > 1:\n\t\t\tself.colors = [ f'{color}' for _ in range(height) ]\n\t\telse:\n\t\t\tif isinstance(color, list): self.colors = color\n\t\t\telif isinstance(color, Color): self.colors = [ f'{color}' for _ in range(101) ]\n\t\tif self.height == 1:\n\t\t\tself.symbol = Symbol.graph_down_small if invert else Symbol.graph_up_small\n\t\telse:\n\t\t\tself.symbol = Symbol.graph_down if invert else Symbol.graph_up\n\t\tvalue_width: int = ceil(len(data) / 2)\n\t\tfiller: str = \"\"\n\t\tif value_width > width: #* If the size of given data set is bigger then width of graph, shrink data set\n\t\t\tdata = data[-(width*2):]\n\t\t\tvalue_width = ceil(len(data) / 2)\n\t\telif value_width < width: #* If the size of given data set is smaller then width of graph, fill graph with whitespace\n\t\t\tfiller = self.symbol[0.0] * (width - value_width)\n\t\tif len(data) % 2: data.insert(0, 0)\n\t\tfor _ in range(height):\n\t\t\tfor b in [True, False]:\n\t\t\t\tself.graphs[b].append(filler)\n\t\tself._create(data, new=True)\n\n\tdef _create(self, data: List[int], new: bool = False):\n\t\th_high: int\n\t\th_low: int\n\t\tvalue: Dict[str, int] = { \"left\" : 0, \"right\" : 0 }\n\t\tval: int\n\t\tside: str\n\n\t\t#* Create the graph\n\t\tfor h in range(self.height):\n\t\t\th_high = round(100 * (self.height - h) / self.height) if self.height > 1 else 100\n\t\t\th_low = round(100 * (self.height - (h + 1)) / self.height) if self.height > 1 else 0\n\t\t\tfor v in range(len(data)):\n\t\t\t\tif new: self.current = bool(v % 2) #* Switch between True and False graphs\n\t\t\t\tif new and v == 0: self.last = 0\n\t\t\t\tfor val, side in [self.last, \"left\"], [data[v], \"right\"]: # type: ignore\n\t\t\t\t\tif val >= h_high:\n\t\t\t\t\t\tvalue[side] = 4\n\t\t\t\t\telif val <= h_low:\n\t\t\t\t\t\tvalue[side] = 0\n\t\t\t\t\telse:\n\t\t\t\t\t\tif self.height == 1: value[side] = round(val * 4 / 100 + 0.5)\n\t\t\t\t\t\telse: value[side] = round((val - h_low) * 4 / (h_high - h_low) + 0.1)\n\t\t\t\t\tif self.no_zero and not (new and v == 0 and side == \"left\") and h == self.height - 1 and value[side] < 1 and not (self.round_up_low and val == 0): value[side] = 1\n\t\t\t\tif new: self.last = data[v]\n\t\t\t\tself.graphs[self.current][h] += self.symbol[float(value[\"left\"] + value[\"right\"] / 10)]\n\t\tif data: self.last = data[-1]\n\t\tself.out = \"\"\n\n\t\tif self.height == 1:\n\t\t\tself.out += f'{\"\" if not self.colors else (THEME.inactive_fg if self.last < 5 else self.colors[self.last])}{self.graphs[self.current][0]}'\n\t\telif self.height > 1:\n\t\t\tfor h in range(self.height):\n\t\t\t\tif h > 0: self.out += f'{Mv.d(1)}{Mv.l(self.width)}'\n\t\t\t\tself.out += f'{\"\" if not self.colors else self.colors[h]}{self.graphs[self.current][h if not self.invert else (self.height - 1) - h]}'\n\t\tif self.colors: self.out += f'{Term.fg}'\n\n\tdef __call__(self, value: Union[int, None] = None) -> str:\n\t\tif not isinstance(value, int): return self.out\n\t\tself.current = not self.current\n\t\tif self.height == 1:\n\t\t\tif self.graphs[self.current][0].startswith(self.symbol[0.0]):\n\t\t\t\tself.graphs[self.current][0] = self.graphs[self.current][0].replace(self.symbol[0.0], \"\", 1)\n\t\t\telse:\n\t\t\t\tself.graphs[self.current][0] = self.graphs[self.current][0][1:]\n\t\telse:\n\t\t\tfor n in range(self.height):\n\t\t\t\tself.graphs[self.current][n] = self.graphs[self.current][n][1:]\n\t\tif self.max_value: value = min_max((value + self.offset) * 100 // (self.max_value + self.offset), min_max(value + self.offset, 0, self.lowest), 100)\n\t\tself._create([value])\n\t\treturn self.out\n\n\tdef add(self, value: Union[int, None] = None) -> str:\n\t\treturn self.__call__(value)\n\n\tdef __str__(self):\n\t\treturn self.out\n\n\tdef __repr__(self):\n\t\treturn repr(self.out)\n\n\nclass Graphs:\n\t'''Holds all graphs and lists of graphs for dynamically created graphs'''\n\tcpu: Dict[str, Graph] = {}\n\tcores: List[Graph] = [NotImplemented] * THREADS\n\ttemps: List[Graph] = [NotImplemented] * (THREADS + 1)\n\tnet: Dict[str, Graph] = {}\n\tdetailed_cpu: Graph = NotImplemented\n\tdetailed_mem: Graph = NotImplemented\n\tpid_cpu: Dict[int, Graph] = {}\n\tdisk_io: Dict[str, Dict[str, Graph]] = {}\n\nclass Meter:\n\t'''Creates a percentage meter\n\t__init__(value, width, theme, gradient_name) to create new meter\n\t__call__(value) to set value and return meter as a string\n\t__str__ returns last set meter as a string\n\t'''\n\tout: str\n\tcolor_gradient: List[str]\n\tcolor_inactive: Color\n\tgradient_name: str\n\twidth: int\n\tinvert: bool\n\tsaved: Dict[int, str]\n\n\tdef __init__(self, value: int, width: int, gradient_name: str, invert: bool = False):\n\t\tself.gradient_name = gradient_name\n\t\tself.color_gradient = THEME.gradient[gradient_name]\n\t\tself.color_inactive = THEME.meter_bg\n\t\tself.width = width\n\t\tself.saved = {}\n\t\tself.invert = invert\n\t\tself.out = self._create(value)\n\n\tdef __call__(self, value: Union[int, None]) -> str:\n\t\tif not isinstance(value, int): return self.out\n\t\tif value > 100: value = 100\n\t\telif value < 0: value = 100\n\t\tif value in self.saved:\n\t\t\tself.out = self.saved[value]\n\t\telse:\n\t\t\tself.out = self._create(value)\n\t\treturn self.out\n\n\tdef __str__(self) -> str:\n\t\treturn self.out\n\n\tdef __repr__(self):\n\t\treturn repr(self.out)\n\n\tdef _create(self, value: int) -> str:\n\t\tif value > 100: value = 100\n\t\telif value < 0: value = 100\n\t\tout: str = \"\"\n\t\tfor i in range(1, self.width + 1):\n\t\t\tif value >= round(i * 100 / self.width):\n\t\t\t\tout += f'{self.color_gradient[round(i * 100 / self.width) if not self.invert else round(100 - (i * 100 / self.width))]}{Symbol.meter}'\n\t\t\telse:\n\t\t\t\tout += self.color_inactive(Symbol.meter * (self.width + 1 - i))\n\t\t\t\tbreak\n\t\telse:\n\t\t\tout += f'{Term.fg}'\n\t\tif not value in self.saved:\n\t\t\tself.saved[value] = out\n\t\treturn out\n\nclass Meters:\n\tcpu: Meter\n\tbattery: Meter\n\tmem: Dict[str, Union[Meter, Graph]] = {}\n\tswap: Dict[str, Union[Meter, Graph]] = {}\n\tdisks_used: Dict[str, Meter] = {}\n\tdisks_free: Dict[str, Meter] = {}\n\nclass Box:\n\t'''Box class with all needed attributes for create_box() function'''\n\tname: str\n\tnum: int = 0\n\tboxes: List = []\n\tview_modes: Dict[str, List] = {\"full\" : [\"cpu\", \"mem\", \"net\", \"proc\"], \"stat\" : [\"cpu\", \"mem\", \"net\"], \"proc\" : [\"cpu\", \"proc\"]}\n\tview_mode: str\n\tfor view_mode in view_modes:\n\t\tif sorted(CONFIG.shown_boxes.split(), key=str.lower) == view_modes[view_mode]:\n\t\t\tbreak\n\telse:\n\t\tview_mode = \"user\"\n\t\tview_modes[\"user\"] = CONFIG.shown_boxes.split()\n\theight_p: int\n\twidth_p: int\n\tx: int\n\ty: int\n\twidth: int\n\theight: int\n\tout: str\n\tbg: str\n\t_b_cpu_h: int\n\t_b_mem_h: int\n\tredraw_all: bool\n\tbuffers: List[str] = []\n\tc_counter: int = 0\n\tclock_on: bool = False\n\tclock: str = \"\"\n\tclock_len: int = 0\n\tresized: bool = False\n\tclock_custom_format: Dict[str, Any] = {\n\t\t\"/host\" : os.uname()[1],\n\t\t\"/user\" : os.environ.get(\"USER\") or pwd.getpwuid(os.getuid())[0],\n\t\t\"/uptime\" : \"\",\n\t\t}\n\tif clock_custom_format[\"/host\"].endswith(\".local\"):\n\t\tclock_custom_format[\"/host\"] = clock_custom_format[\"/host\"].replace(\".local\", \"\")\n\n\t@classmethod\n\tdef calc_sizes(cls):\n\t\t'''Calculate sizes of boxes'''\n\t\tcls.boxes = CONFIG.shown_boxes.split()\n\t\tfor sub in cls.__subclasses__():\n\t\t\tsub._calc_size() # type: ignore\n\t\t\tsub.resized = True # type: ignore\n\n\t@classmethod\n\tdef draw_update_ms(cls, now: bool = True):\n\t\tif not \"cpu\" in cls.boxes: return\n\t\tupdate_string: str = f'{CONFIG.update_ms}ms'\n\t\txpos: int = CpuBox.x + CpuBox.width - len(update_string) - 15\n\t\tif not \"+\" in Key.mouse:\n\t\t\tKey.mouse[\"+\"] = [[xpos + 7 + i, CpuBox.y] for i in range(3)]\n\t\t\tKey.mouse[\"-\"] = [[CpuBox.x + CpuBox.width - 4 + i, CpuBox.y] for i in range(3)]\n\t\tDraw.buffer(\"update_ms!\" if now and not Menu.active else \"update_ms\",\n\t\t\tf'{Mv.to(CpuBox.y, xpos)}{THEME.cpu_box(Symbol.h_line * 7, Symbol.title_left)}{Fx.b}{THEME.hi_fg(\"+\")} ',\n\t\t\tf'{THEME.title(update_string)} {THEME.hi_fg(\"-\")}{Fx.ub}{THEME.cpu_box(Symbol.title_right)}', only_save=Menu.active, once=True)\n\t\tif now and not Menu.active:\n\t\t\tDraw.clear(\"update_ms\")\n\t\t\tif CONFIG.show_battery and hasattr(psutil, \"sensors_battery\") and psutil.sensors_battery():\n\t\t\t\tDraw.out(\"battery\")\n\n\t@classmethod\n\tdef draw_clock(cls, force: bool = False):\n\t\tif not \"cpu\" in cls.boxes or not cls.clock_on: return\n\t\tcls.c_counter += 1\n\t\tif cls.c_counter > 3600 / (Config.update_ms / 1000):\n\t\t\ttzset()\n\t\t\tcls.c_counter = 0\n\t\tout: str = \"\"\n\t\tif force: pass\n\t\telif Term.resized or strftime(CONFIG.draw_clock) == cls.clock: return\n\t\tclock_string = cls.clock = strftime(CONFIG.draw_clock)\n\t\tfor custom in cls.clock_custom_format:\n\t\t\tif custom in clock_string:\n\t\t\t\tif custom == \"/uptime\": cls.clock_custom_format[\"/uptime\"] = CpuCollector.uptime\n\t\t\t\tclock_string = clock_string.replace(custom, cls.clock_custom_format[custom])\n\t\tclock_len = len(clock_string[:(CpuBox.width-56)])\n\t\tif cls.clock_len != clock_len and not CpuBox.resized:\n\t\t\tout = f'{Mv.to(CpuBox.y, ((CpuBox.width)//2)-(cls.clock_len//2))}{Fx.ub}{THEME.cpu_box}{Symbol.h_line * cls.clock_len}'\n\t\tcls.clock_len = clock_len\n\t\tnow: bool = False if Menu.active else not force\n\t\tout += (f'{Mv.to(CpuBox.y, ((CpuBox.width)//2)-(clock_len//2))}{Fx.ub}{THEME.cpu_box}'\n\t\t\tf'{Symbol.title_left}{Fx.b}{THEME.title(clock_string[:clock_len])}{Fx.ub}{THEME.cpu_box}{Symbol.title_right}{Term.fg}')\n\t\tDraw.buffer(\"clock\", out, z=1, now=now, once=not force, only_save=Menu.active)\n\t\tif now and not Menu.active:\n\t\t\tif CONFIG.show_battery and hasattr(psutil, \"sensors_battery\") and psutil.sensors_battery():\n\t\t\t\tDraw.out(\"battery\")\n\n\t@classmethod\n\tdef empty_bg(cls) -> str:\n\t\treturn (f'{Term.clear}' +\n\t\t\t\t(f'{Banner.draw(Term.height // 2 - 10, center=True)}'\n\t\t\t\tf'{Mv.d(1)}{Mv.l(46)}{Colors.black_bg}{Colors.default}{Fx.b}[esc] Menu'\n\t\t\t\tf'{Mv.r(25)}{Fx.i}Version: {VERSION}{Fx.ui}' if Term.height > 22 else \"\") +\n\t\t\t\tf'{Mv.d(1)}{Mv.l(34)}{Fx.b}All boxes hidden!'\n\t\t\t\tf'{Mv.d(1)}{Mv.l(17)}{Fx.b}[1] {Fx.ub}Toggle CPU box'\n\t\t\t\tf'{Mv.d(1)}{Mv.l(18)}{Fx.b}[2] {Fx.ub}Toggle MEM box'\n\t\t\t\tf'{Mv.d(1)}{Mv.l(18)}{Fx.b}[3] {Fx.ub}Toggle NET box'\n\t\t\t\tf'{Mv.d(1)}{Mv.l(18)}{Fx.b}[4] {Fx.ub}Toggle PROC box'\n\t\t\t\tf'{Mv.d(1)}{Mv.l(19)}{Fx.b}[m] {Fx.ub}Cycle presets'\n\t\t\t\tf'{Mv.d(1)}{Mv.l(17)}{Fx.b}[q] Quit {Fx.ub}{Term.bg}{Term.fg}')\n\n\t@classmethod\n\tdef draw_bg(cls, now: bool = True):\n\t\t'''Draw all boxes outlines and titles'''\n\t\tout: str = \"\"\n\t\tif not cls.boxes:\n\t\t\tout = cls.empty_bg()\n\t\telse:\n\t\t\tout = \"\".join(sub._draw_bg() for sub in cls.__subclasses__()) # type: ignore\n\t\tDraw.buffer(\"bg\", out, now=now, z=1000, only_save=Menu.active, once=True)\n\t\tcls.draw_update_ms(now=now)\n\t\tif CONFIG.draw_clock: cls.draw_clock(force=True)\n\nclass SubBox:\n\tbox_x: int = 0\n\tbox_y: int = 0\n\tbox_width: int = 0\n\tbox_height: int = 0\n\tbox_columns: int = 0\n\tcolumn_size: int = 0\n\nclass CpuBox(Box, SubBox):\n\tname = \"cpu\"\n\tnum = 1\n\tx = 1\n\ty = 1\n\theight_p = 32\n\twidth_p = 100\n\tmin_w: int = 60\n\tmin_h: int = 8\n\tresized: bool = True\n\tredraw: bool = False\n\tbuffer: str = \"cpu\"\n\tbattery_percent: int = 1000\n\tbattery_secs: int = 0\n\tbattery_status: str = \"Unknown\"\n\told_battery_pos = 0\n\told_battery_len = 0\n\tbattery_path: Union[str, None] = \"\"\n\tbattery_clear: bool = False\n\tbattery_symbols: Dict[str, str] = {\"Charging\": \"▲\",\n\t\t\t\t\t\t\t\t\t\"Discharging\": \"▼\",\n\t\t\t\t\t\t\t\t\t\"Full\": \"■\",\n\t\t\t\t\t\t\t\t\t\"Not charging\": \"■\"}\n\tclock_block: bool = True\n\tBox.buffers.append(buffer)\n\n\t@classmethod\n\tdef _calc_size(cls):\n\t\tif not \"cpu\" in cls.boxes:\n\t\t\tBox._b_cpu_h = 0\n\t\t\tcls.width = Term.width\n\t\t\treturn\n\t\tcpu = CpuCollector\n\t\theight_p: int\n\t\tif cls.boxes == [\"cpu\"]:\n\t\t\theight_p = 100\n\t\telse:\n\t\t\theight_p = cls.height_p\n\t\tcls.width = round(Term.width * cls.width_p / 100)\n\t\tcls.height = round(Term.height * height_p / 100)\n\t\tif cls.height < 8: cls.height = 8\n\t\tBox._b_cpu_h = cls.height\n\t\t#THREADS = 64\n\t\tcls.box_columns = ceil((THREADS + 1) / (cls.height - 5))\n\t\tif cls.box_columns * (20 + 13 if cpu.got_sensors else 21) < cls.width - (cls.width // 3):\n\t\t\tcls.column_size = 2\n\t\t\tcls.box_width = (20 + 13 if cpu.got_sensors else 21) * cls.box_columns - ((cls.box_columns - 1) * 1)\n\t\telif cls.box_columns * (15 + 6 if cpu.got_sensors else 15) < cls.width - (cls.width // 3):\n\t\t\tcls.column_size = 1\n\t\t\tcls.box_width = (15 + 6 if cpu.got_sensors else 15) * cls.box_columns - ((cls.box_columns - 1) * 1)\n\t\telif cls.box_columns * (8 + 6 if cpu.got_sensors else 8) < cls.width - (cls.width // 3):\n\t\t\tcls.column_size = 0\n\t\telse:\n\t\t\tcls.box_columns = (cls.width - cls.width // 3) // (8 + 6 if cpu.got_sensors else 8); cls.column_size = 0\n\n\t\tif cls.column_size == 0: cls.box_width = (8 + 6 if cpu.got_sensors else 8) * cls.box_columns + 1\n\n\t\tcls.box_height = ceil(THREADS / cls.box_columns) + 4\n\n\t\tif cls.box_height > cls.height - 2: cls.box_height = cls.height - 2\n\t\tcls.box_x = (cls.width - 1) - cls.box_width\n\t\tcls.box_y = cls.y + ceil((cls.height - 2) / 2) - ceil(cls.box_height / 2) + 1\n\n\t@classmethod\n\tdef _draw_bg(cls) -> str:\n\t\tif not \"cpu\" in cls.boxes: return \"\"\n\t\tif not \"M\" in Key.mouse:\n\t\t\tKey.mouse[\"M\"] = [[cls.x + 10 + i, cls.y] for i in range(6)]\n\t\treturn (f'{create_box(box=cls, line_color=THEME.cpu_box)}'\n\t\tf'{Mv.to(cls.y, cls.x + 10)}{THEME.cpu_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(\"M\")}{THEME.title(\"enu\")}{Fx.ub}{THEME.cpu_box(Symbol.title_right)}'\n\t\tf'{create_box(x=cls.box_x, y=cls.box_y, width=cls.box_width, height=cls.box_height, line_color=THEME.div_line, fill=False, title=CPU_NAME[:cls.box_width - 14] if not CONFIG.custom_cpu_name else CONFIG.custom_cpu_name[:cls.box_width - 14])}')\n\n\t@classmethod\n\tdef battery_activity(cls) -> bool:\n\t\tif not hasattr(psutil, \"sensors_battery\") or psutil.sensors_battery() == None:\n\t\t\tif cls.battery_percent != 1000:\n\t\t\t\tcls.battery_clear = True\n\t\t\treturn False\n\n\t\tif cls.battery_path == \"\":\n\t\t\tcls.battery_path = None\n\t\t\tif os.path.isdir(\"/sys/class/power_supply\"):\n\t\t\t\tfor directory in sorted(os.listdir(\"/sys/class/power_supply\")):\n\t\t\t\t\tif directory.startswith('BAT') or 'battery' in directory.lower():\n\t\t\t\t\t\tcls.battery_path = f'/sys/class/power_supply/{directory}/'\n\t\t\t\t\t\tbreak\n\n\t\treturn_true: bool = False\n\t\tpercent: int = ceil(getattr(psutil.sensors_battery(), \"percent\", 0))\n\t\tif percent != cls.battery_percent:\n\t\t\tcls.battery_percent = percent\n\t\t\treturn_true = True\n\n\t\tseconds: int = getattr(psutil.sensors_battery(), \"secsleft\", 0)\n\t\tif seconds != cls.battery_secs:\n\t\t\tcls.battery_secs = seconds\n\t\t\treturn_true = True\n\n\t\tstatus: str = \"not_set\"\n\t\tif cls.battery_path:\n\t\t\tstatus = readfile(cls.battery_path + \"status\", default=\"not_set\")\n\t\tif status == \"not_set\" and getattr(psutil.sensors_battery(), \"power_plugged\", None) == True:\n\t\t\tstatus = \"Charging\" if cls.battery_percent < 100 else \"Full\"\n\t\telif status == \"not_set\" and getattr(psutil.sensors_battery(), \"power_plugged\", None) == False:\n\t\t\tstatus = \"Discharging\"\n\t\telif status == \"not_set\":\n\t\t\tstatus = \"Unknown\"\n\t\tif status != cls.battery_status:\n\t\t\tcls.battery_status = status\n\t\t\treturn_true = True\n\n\t\treturn return_true or cls.resized or cls.redraw or Menu.active\n\n\t@classmethod\n\tdef _draw_fg(cls):\n\t\tif not \"cpu\" in cls.boxes: return\n\t\tcpu = CpuCollector\n\t\tif cpu.redraw: cls.redraw = True\n\t\tout: str = \"\"\n\t\tout_misc: str = \"\"\n\t\tlavg: str = \"\"\n\t\tx, y, w, h = cls.x + 1, cls.y + 1, cls.width - 2, cls.height - 2\n\t\tbx, by, bw, bh = cls.box_x + 1, cls.box_y + 1, cls.box_width - 2, cls.box_height - 2\n\t\thh: int = ceil(h / 2)\n\t\thh2: int = h - hh\n\t\tmid_line: bool = False\n\t\ttemp: int = 0\n\t\tunit: str = \"\"\n\t\tif not CONFIG.cpu_single_graph and CONFIG.cpu_graph_upper != CONFIG.cpu_graph_lower:\n\t\t\tmid_line = True\n\t\t\tif h % 2: hh = floor(h / 2)\n\t\t\telse: hh2 -= 1\n\n\t\thide_cores: bool = (cpu.cpu_temp_only or not CONFIG.show_coretemp) and cpu.got_sensors\n\t\tct_width: int = (max(6, 6 * cls.column_size)) * hide_cores\n\n\t\tif cls.resized or cls.redraw:\n\t\t\tif not \"m\" in Key.mouse:\n\t\t\t\tKey.mouse[\"m\"] = [[cls.x + 16 + i, cls.y] for i in range(12)]\n\t\t\tout_misc += f'{Mv.to(cls.y, cls.x + 16)}{THEME.cpu_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(\"m\")}{THEME.title}ode:{Box.view_mode}{Fx.ub}{THEME.cpu_box(Symbol.title_right)}'\n\t\t\tGraphs.cpu[\"up\"] = Graph(w - bw - 3, (h if CONFIG.cpu_single_graph else hh), THEME.gradient[\"cpu\"], cpu.cpu_upper, round_up_low=True)\n\t\t\tif not CONFIG.cpu_single_graph:\n\t\t\t\tGraphs.cpu[\"down\"] = Graph(w - bw - 3, hh2, THEME.gradient[\"cpu\"], cpu.cpu_lower, invert=CONFIG.cpu_invert_lower, round_up_low=True)\n\t\t\tMeters.cpu = Meter(cpu.cpu_usage[0][-1], bw - (21 if cpu.got_sensors else 9), \"cpu\")\n\t\t\tif cls.column_size > 0 or ct_width > 0:\n\t\t\t\tfor n in range(THREADS):\n\t\t\t\t\tGraphs.cores[n] = Graph(5 * cls.column_size + ct_width, 1, None, cpu.cpu_usage[n + 1])\n\t\t\tif cpu.got_sensors:\n\t\t\t\tGraphs.temps[0] = Graph(5, 1, None, cpu.cpu_temp[0], max_value=cpu.cpu_temp_crit, offset=-23)\n\t\t\t\tif cls.column_size > 1:\n\t\t\t\t\tfor n in range(1, THREADS + 1):\n\t\t\t\t\t\tif not cpu.cpu_temp[n]:\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\tGraphs.temps[n] = Graph(5, 1, None, cpu.cpu_temp[n], max_value=cpu.cpu_temp_crit, offset=-23)\n\t\t\tDraw.buffer(\"cpu_misc\", out_misc, only_save=True)\n\n\t\tif CONFIG.show_battery and cls.battery_activity():\n\t\t\tbat_out: str = \"\"\n\t\t\tif cls.battery_secs > 0:\n\t\t\t\tbattery_time: str = f' {cls.battery_secs // 3600:02}:{(cls.battery_secs % 3600) // 60:02}'\n\t\t\telse:\n\t\t\t\tbattery_time = \"\"\n\t\t\tif not hasattr(Meters, \"battery\") or cls.resized:\n\t\t\t\tMeters.battery = Meter(cls.battery_percent, 10, \"cpu\", invert=True)\n\t\t\tbattery_symbol: str = cls.battery_symbols.get(cls.battery_status, \"○\")\n\t\t\tbattery_len: int = len(f'{CONFIG.update_ms}') + (11 if cls.width >= 100 else 0) + len(battery_time) + len(f'{cls.battery_percent}')\n\t\t\tbattery_pos = cls.width - battery_len - 17\n\t\t\tif (battery_pos != cls.old_battery_pos or battery_len != cls.old_battery_len) and cls.old_battery_pos > 0 and not cls.resized:\n\t\t\t\tbat_out += f'{Mv.to(y-1, cls.old_battery_pos)}{THEME.cpu_box(Symbol.h_line*(cls.old_battery_len+4))}'\n\t\t\tcls.old_battery_pos, cls.old_battery_len = battery_pos, battery_len\n\t\t\tbat_out += (f'{Mv.to(y-1, battery_pos)}{THEME.cpu_box(Symbol.title_left)}{Fx.b}{THEME.title}BAT{battery_symbol} {cls.battery_percent}%'+\n\t\t\t\t(\"\" if cls.width < 100 else f' {Fx.ub}{Meters.battery(cls.battery_percent)}{Fx.b}') +\n\t\t\t\tf'{THEME.title}{battery_time}{Fx.ub}{THEME.cpu_box(Symbol.title_right)}')\n\t\t\tDraw.buffer(\"battery\", f'{bat_out}{Term.fg}', only_save=Menu.active)\n\t\telif cls.battery_clear:\n\t\t\tout += f'{Mv.to(y-1, cls.old_battery_pos)}{THEME.cpu_box(Symbol.h_line*(cls.old_battery_len+4))}'\n\t\t\tcls.battery_clear = False\n\t\t\tcls.battery_percent = 1000\n\t\t\tcls.battery_secs = 0\n\t\t\tcls.battery_status = \"Unknown\"\n\t\t\tcls.old_battery_pos = 0\n\t\t\tcls.old_battery_len = 0\n\t\t\tcls.battery_path = \"\"\n\t\t\tDraw.clear(\"battery\", saved=True)\n\n\t\tcx = cy = cc = 0\n\t\tccw = (bw + 1) // cls.box_columns\n\t\tif cpu.cpu_freq:\n\t\t\tfreq: str = f'{cpu.cpu_freq} Mhz' if cpu.cpu_freq < 1000 else f'{float(cpu.cpu_freq / 1000):.1f} GHz'\n\t\t\tout += f'{Mv.to(by - 1, bx + bw - 9)}{THEME.div_line(Symbol.title_left)}{Fx.b}{THEME.title(freq)}{Fx.ub}{THEME.div_line(Symbol.title_right)}'\n\t\tout += f'{Mv.to(y, x)}{Graphs.cpu[\"up\"](None if cls.resized else cpu.cpu_upper[-1])}'\n\t\tif mid_line:\n\t\t\tout += (f'{Mv.to(y+hh, x-1)}{THEME.cpu_box(Symbol.title_right)}{THEME.div_line}{Symbol.h_line * (w - bw - 3)}{THEME.div_line(Symbol.title_left)}'\n\t\t\t\t\tf'{Mv.to(y+hh, x+((w-bw)//2)-((len(CONFIG.cpu_graph_upper)+len(CONFIG.cpu_graph_lower))//2)-4)}{THEME.main_fg}{CONFIG.cpu_graph_upper}{Mv.r(1)}▲▼{Mv.r(1)}{CONFIG.cpu_graph_lower}')\n\t\tif not CONFIG.cpu_single_graph and Graphs.cpu.get(\"down\"):\n\t\t\tout += f'{Mv.to(y + hh + (1 * mid_line), x)}{Graphs.cpu[\"down\"](None if cls.resized else cpu.cpu_lower[-1])}'\n\t\tout += (f'{THEME.main_fg}{Mv.to(by + cy, bx + cx)}{Fx.b}{\"CPU \"}{Fx.ub}{Meters.cpu(cpu.cpu_usage[0][-1])}'\n\t\t\t\tf'{THEME.gradient[\"cpu\"][cpu.cpu_usage[0][-1]]}{cpu.cpu_usage[0][-1]:>4}{THEME.main_fg}%')\n\t\tif cpu.got_sensors:\n\t\t\ttry:\n\t\t\t\ttemp, unit = temperature(cpu.cpu_temp[0][-1], CONFIG.temp_scale)\n\t\t\t\tout += (f'{THEME.inactive_fg} ⡀⡀⡀⡀⡀{Mv.l(5)}{THEME.gradient[\"temp\"][min_max(cpu.cpu_temp[0][-1], 0, cpu.cpu_temp_crit) * 100 // cpu.cpu_temp_crit]}{Graphs.temps[0](None if cls.resized else cpu.cpu_temp[0][-1])}'\n\t\t\t\t\t\tf'{temp:>4}{THEME.main_fg}{unit}')\n\t\t\texcept:\n\t\t\t\tcpu.got_sensors = False\n\n\t\tcy += 1\n\t\tfor n in range(1, THREADS + 1):\n\t\t\tout += f'{THEME.main_fg}{Mv.to(by + cy, bx + cx)}{Fx.b + \"C\" + Fx.ub if THREADS < 100 else \"\"}{str(n):<{2 if cls.column_size == 0 else 3}}'\n\t\t\tif cls.column_size > 0 or ct_width > 0:\n\t\t\t\tout += f'{THEME.inactive_fg}{\"⡀\" * (5 * cls.column_size + ct_width)}{Mv.l(5 * cls.column_size + ct_width)}{THEME.gradient[\"cpu\"][cpu.cpu_usage[n][-1]]}{Graphs.cores[n-1](None if cls.resized else cpu.cpu_usage[n][-1])}'\n\t\t\telse:\n\t\t\t\tout += f'{THEME.gradient[\"cpu\"][cpu.cpu_usage[n][-1]]}'\n\t\t\tout += f'{cpu.cpu_usage[n][-1]:>{3 if cls.column_size < 2 else 4}}{THEME.main_fg}%'\n\t\t\tif cpu.got_sensors and cpu.cpu_temp[n] and not hide_cores:\n\t\t\t\ttry:\n\t\t\t\t\ttemp, unit = temperature(cpu.cpu_temp[n][-1], CONFIG.temp_scale)\n\t\t\t\t\tif cls.column_size > 1:\n\t\t\t\t\t\tout += f'{THEME.inactive_fg} ⡀⡀⡀⡀⡀{Mv.l(5)}{THEME.gradient[\"temp\"][min_max(cpu.cpu_temp[n][-1], 0, cpu.cpu_temp_crit) * 100 // cpu.cpu_temp_crit]}{Graphs.temps[n](None if cls.resized else cpu.cpu_temp[n][-1])}'\n\t\t\t\t\telse:\n\t\t\t\t\t\tout += f'{THEME.gradient[\"temp\"][min_max(temp, 0, cpu.cpu_temp_crit) * 100 // cpu.cpu_temp_crit]}'\n\t\t\t\t\tout += f'{temp:>4}{THEME.main_fg}{unit}'\n\t\t\t\texcept:\n\t\t\t\t\tcpu.got_sensors = False\n\t\t\telif cpu.got_sensors and not hide_cores:\n\t\t\t\tout += f'{Mv.r(max(6, 6 * cls.column_size))}'\n\t\t\tout += f'{THEME.div_line(Symbol.v_line)}'\n\t\t\tcy += 1\n\t\t\tif cy > ceil(THREADS/cls.box_columns) and n != THREADS:\n\t\t\t\tcc += 1; cy = 1; cx = ccw * cc\n\t\t\t\tif cc == cls.box_columns: break\n\n\t\tif cy < bh - 1: cy = bh - 1\n\n\t\tif cy < bh and cc < cls.box_columns:\n\t\t\tif cls.column_size == 2 and cpu.got_sensors:\n\t\t\t\tlavg = f' Load AVG:  {\"   \".join(str(l) for l in cpu.load_avg):^19.19}'\n\t\t\telif cls.column_size == 2 or (cls.column_size == 1 and cpu.got_sensors):\n\t\t\t\tlavg = f'LAV: {\" \".join(str(l) for l in cpu.load_avg):^14.14}'\n\t\t\telif cls.column_size == 1 or (cls.column_size == 0 and cpu.got_sensors):\n\t\t\t\tlavg = f'L {\" \".join(str(round(l, 1)) for l in cpu.load_avg):^11.11}'\n\t\t\telse:\n\t\t\t\tlavg = f'{\" \".join(str(round(l, 1)) for l in cpu.load_avg[:2]):^7.7}'\n\t\t\tout += f'{Mv.to(by + cy, bx + cx)}{THEME.main_fg}{lavg}{THEME.div_line(Symbol.v_line)}'\n\n\t\tif CONFIG.show_uptime:\n\t\t\tout += f'{Mv.to(y + (0 if not CONFIG.cpu_invert_lower or CONFIG.cpu_single_graph else h - 1), x + 1)}{THEME.graph_text}{Fx.trans(\"up \" + cpu.uptime)}'\n\n\n\t\tDraw.buffer(cls.buffer, f'{out_misc}{out}{Term.fg}', only_save=Menu.active)\n\t\tcls.resized = cls.redraw = cls.clock_block = False\n\nclass MemBox(Box):\n\tname = \"mem\"\n\tnum = 2\n\theight_p = 38\n\twidth_p = 45\n\tmin_w: int = 36\n\tmin_h: int = 10\n\tx = 1\n\ty = 1\n\tmem_meter: int = 0\n\tmem_size: int = 0\n\tdisk_meter: int = 0\n\tdivider: int = 0\n\tmem_width: int = 0\n\tdisks_width: int = 0\n\tdisks_io_h: int = 0\n\tdisks_io_order: List[str] = []\n\tgraph_speeds: Dict[str, int] = {}\n\tgraph_height: int\n\tresized: bool = True\n\tredraw: bool = False\n\tbuffer: str = \"mem\"\n\tswap_on: bool = CONFIG.show_swap\n\tBox.buffers.append(buffer)\n\tmem_names: List[str] = [\"used\", \"available\", \"cached\", \"free\"]\n\tswap_names: List[str] = [\"used\", \"free\"]\n\n\t@classmethod\n\tdef _calc_size(cls):\n\t\tif not \"mem\" in cls.boxes:\n\t\t\tBox._b_mem_h = 0\n\t\t\tcls.width = Term.width\n\t\t\treturn\n\t\twidth_p: int; height_p: int\n\t\tif not \"proc\" in cls.boxes:\n\t\t\twidth_p = 100\n\t\telse:\n\t\t\twidth_p = cls.width_p\n\n\t\tif not \"cpu\" in cls.boxes:\n\t\t\theight_p = 60 if \"net\" in cls.boxes else 98\n\t\telif not \"net\" in cls.boxes:\n\t\t\theight_p = 98 - CpuBox.height_p\n\t\telse:\n\t\t\theight_p = cls.height_p\n\n\t\tcls.width = round(Term.width * width_p / 100)\n\t\tcls.height = round(Term.height * height_p / 100) + 1\n\t\tif cls.height + Box._b_cpu_h > Term.height: cls.height = Term.height - Box._b_cpu_h\n\t\tBox._b_mem_h = cls.height\n\t\tcls.y = Box._b_cpu_h + 1\n\t\tif CONFIG.show_disks:\n\t\t\tcls.mem_width = ceil((cls.width - 3) / 2)\n\t\t\tcls.disks_width = cls.width - cls.mem_width - 3\n\t\t\tif cls.mem_width + cls.disks_width < cls.width - 2: cls.mem_width += 1\n\t\t\tcls.divider = cls.x + cls.mem_width\n\t\telse:\n\t\t\tcls.mem_width = cls.width - 1\n\n\t\titem_height: int = 6 if cls.swap_on and not CONFIG.swap_disk else 4\n\t\tif cls.height - (3 if cls.swap_on and not CONFIG.swap_disk else 2) > 2 * item_height: cls.mem_size = 3\n\t\telif cls.mem_width > 25: cls.mem_size = 2\n\t\telse: cls.mem_size = 1\n\n\t\tcls.mem_meter = cls.width - (cls.disks_width if CONFIG.show_disks else 0) - (9 if cls.mem_size > 2 else 20)\n\t\tif cls.mem_size == 1: cls.mem_meter += 6\n\t\tif cls.mem_meter < 1: cls.mem_meter = 0\n\n\t\tif CONFIG.mem_graphs:\n\t\t\tcls.graph_height = round(((cls.height - (2 if cls.swap_on and not CONFIG.swap_disk else 1)) - (2 if cls.mem_size == 3 else 1) * item_height) / item_height)\n\t\t\tif cls.graph_height == 0: cls.graph_height = 1\n\t\t\tif cls.graph_height > 1: cls.mem_meter += 6\n\t\telse:\n\t\t\tcls.graph_height = 0\n\n\t\tif CONFIG.show_disks:\n\t\t\tcls.disk_meter = cls.width - cls.mem_width - 23\n\t\t\tif cls.disks_width < 25:\n\t\t\t\tcls.disk_meter += 10\n\t\t\tif cls.disk_meter < 1: cls.disk_meter = 0\n\n\t@classmethod\n\tdef _draw_bg(cls) -> str:\n\t\tif not \"mem\" in cls.boxes: return \"\"\n\t\tout: str = \"\"\n\t\tout += f'{create_box(box=cls, line_color=THEME.mem_box)}'\n\t\tif CONFIG.show_disks:\n\t\t\tout += (f'{Mv.to(cls.y, cls.divider + 2)}{THEME.mem_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(\"d\")}{THEME.title(\"isks\")}{Fx.ub}{THEME.mem_box(Symbol.title_right)}'\n\t\t\t\t\tf'{Mv.to(cls.y, cls.divider)}{THEME.mem_box(Symbol.div_up)}'\n\t\t\t\t\tf'{Mv.to(cls.y + cls.height - 1, cls.divider)}{THEME.mem_box(Symbol.div_down)}{THEME.div_line}'\n\t\t\t\t\tf'{\"\".join(f\"{Mv.to(cls.y + i, cls.divider)}{Symbol.v_line}\" for i in range(1, cls.height - 1))}')\n\t\t\tKey.mouse[\"d\"] = [[cls.divider + 3 + i, cls.y] for i in range(5)]\n\t\telse:\n\t\t\tout += f'{Mv.to(cls.y, cls.x + cls.width - 9)}{THEME.mem_box(Symbol.title_left)}{THEME.hi_fg(\"d\")}{THEME.title(\"isks\")}{THEME.mem_box(Symbol.title_right)}'\n\t\t\tKey.mouse[\"d\"] = [[cls.x + cls.width - 8 + i, cls.y] for i in range(5)]\n\t\treturn out\n\n\t@classmethod\n\tdef _draw_fg(cls):\n\t\tif not \"mem\" in cls.boxes: return\n\t\tmem = MemCollector\n\t\tif mem.redraw: cls.redraw = True\n\t\tout: str = \"\"\n\t\tout_misc: str = \"\"\n\t\tgbg: str = \"\"\n\t\tgmv: str = \"\"\n\t\tgli: str = \"\"\n\t\tx, y, w, h = cls.x + 1, cls.y + 1, cls.width - 2, cls.height - 2\n\t\tif cls.resized or cls.redraw:\n\t\t\tcls.redraw = True\n\t\t\tcls._calc_size()\n\t\t\tout_misc += cls._draw_bg()\n\t\t\tMeters.mem = {}\n\t\t\tMeters.swap = {}\n\t\t\tMeters.disks_used = {}\n\t\t\tMeters.disks_free = {}\n\t\t\tif cls.mem_meter > 0:\n\t\t\t\tfor name in cls.mem_names:\n\t\t\t\t\tif CONFIG.mem_graphs:\n\t\t\t\t\t\tMeters.mem[name] = Graph(cls.mem_meter, cls.graph_height, THEME.gradient[name], mem.vlist[name])\n\t\t\t\t\telse:\n\t\t\t\t\t\tMeters.mem[name] = Meter(mem.percent[name], cls.mem_meter, name)\n\t\t\t\tif cls.swap_on:\n\t\t\t\t\tfor name in cls.swap_names:\n\t\t\t\t\t\tif CONFIG.swap_disk and CONFIG.show_disks:\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\telif CONFIG.mem_graphs and not CONFIG.swap_disk:\n\t\t\t\t\t\t\tMeters.swap[name] = Graph(cls.mem_meter, cls.graph_height, THEME.gradient[name], mem.swap_vlist[name])\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tMeters.swap[name] = Meter(mem.swap_percent[name], cls.mem_meter, name)\n\n\t\t\tif CONFIG.show_disks and mem.disks:\n\t\t\t\tif CONFIG.show_io_stat or CONFIG.io_mode:\n\t\t\t\t\td_graph: List[str] = []\n\t\t\t\t\td_no_graph: List[str] = []\n\t\t\t\t\tl_vals: List[Tuple[str, int, str, bool]] = []\n\t\t\t\t\tif CONFIG.io_mode:\n\t\t\t\t\t\tcls.disks_io_h = (cls.height - 2 - len(mem.disks)) // max(1, len(mem.disks_io_dict))\n\t\t\t\t\t\tif cls.disks_io_h < 2: cls.disks_io_h = 1 if CONFIG.io_graph_combined else 2\n\t\t\t\t\telse:\n\t\t\t\t\t\tcls.disks_io_h = 1\n\n\t\t\t\t\tif CONFIG.io_graph_speeds and not cls.graph_speeds:\n\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\tcls.graph_speeds = { spds.split(\":\")[0] : int(spds.split(\":\")[1]) for spds in list(i.strip() for i in CONFIG.io_graph_speeds.split(\",\"))}\n\t\t\t\t\t\texcept (KeyError, ValueError):\n\t\t\t\t\t\t\terrlog.error(\"Wrong formatting in io_graph_speeds variable. Using defaults.\")\n\t\t\t\t\tfor name in mem.disks.keys():\n\t\t\t\t\t\tif name in mem.disks_io_dict:\n\t\t\t\t\t\t\td_graph.append(name)\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\td_no_graph.append(name)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\tif CONFIG.io_graph_combined or not CONFIG.io_mode:\n\t\t\t\t\t\t\tl_vals = [(\"rw\", cls.disks_io_h, \"available\", False)]\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tl_vals = [(\"read\", cls.disks_io_h // 2, \"free\", False), (\"write\", cls.disks_io_h // 2, \"used\", True)]\n\n\t\t\t\t\t\tGraphs.disk_io[name] = {_name : Graph(width=cls.disks_width - (6 if not CONFIG.io_mode else 0), height=_height, color=THEME.gradient[_gradient],\n\t\t\t\t\t\t\t\t\t\t\t\tdata=mem.disks_io_dict[name][_name], invert=_invert, max_value=cls.graph_speeds.get(name, 10), no_zero=True)\n\t\t\t\t\t\t\t\t\t\t\t\tfor _name, _height, _gradient, _invert in l_vals}\n\t\t\t\t\tcls.disks_io_order = d_graph + d_no_graph\n\n\t\t\t\tif cls.disk_meter > 0:\n\t\t\t\t\tfor n, name in enumerate(mem.disks.keys()):\n\t\t\t\t\t\tif n * 2 > h: break\n\t\t\t\t\t\tMeters.disks_used[name] = Meter(mem.disks[name][\"used_percent\"], cls.disk_meter, \"used\")\n\t\t\t\t\t\tif len(mem.disks) * 3 <= h + 1:\n\t\t\t\t\t\t\tMeters.disks_free[name] = Meter(mem.disks[name][\"free_percent\"], cls.disk_meter, \"free\")\n\t\t\tif not \"g\" in Key.mouse:\n\t\t\t\tKey.mouse[\"g\"] = [[x + 8 + i, y-1] for i in range(5)]\n\t\t\tout_misc += (f'{Mv.to(y-1, x + 7)}{THEME.mem_box(Symbol.title_left)}{Fx.b if CONFIG.mem_graphs else \"\"}'\n\t\t\t\tf'{THEME.hi_fg(\"g\")}{THEME.title(\"raph\")}{Fx.ub}{THEME.mem_box(Symbol.title_right)}')\n\t\t\tif CONFIG.show_disks:\n\t\t\t\tif not \"s\" in Key.mouse:\n\t\t\t\t\tKey.mouse[\"s\"] = [[x + w - 6 + i, y-1] for i in range(4)]\n\t\t\t\tout_misc += (f'{Mv.to(y-1, x + w - 7)}{THEME.mem_box(Symbol.title_left)}{Fx.b if CONFIG.swap_disk else \"\"}'\n\t\t\t\tf'{THEME.hi_fg(\"s\")}{THEME.title(\"wap\")}{Fx.ub}{THEME.mem_box(Symbol.title_right)}')\n\t\t\t\tif not \"i\" in Key.mouse:\n\t\t\t\t\tKey.mouse[\"i\"] = [[x + w - 10 + i, y-1] for i in range(2)]\n\t\t\t\tout_misc += (f'{Mv.to(y-1, x + w - 11)}{THEME.mem_box(Symbol.title_left)}{Fx.b if CONFIG.io_mode else \"\"}'\n\t\t\t\tf'{THEME.hi_fg(\"i\")}{THEME.title(\"o\")}{Fx.ub}{THEME.mem_box(Symbol.title_right)}')\n\n\t\t\tif Collector.collect_interrupt: return\n\t\t\tDraw.buffer(\"mem_misc\", out_misc, only_save=True)\n\t\ttry:\n\t\t\t#* Mem\n\t\t\tcx = 1; cy = 1\n\n\t\t\tout += f'{Mv.to(y, x+1)}{THEME.title}{Fx.b}Total:{mem.string[\"total\"]:>{cls.mem_width - 9}}{Fx.ub}{THEME.main_fg}'\n\t\t\tif cls.graph_height > 0:\n\t\t\t\tgli = f'{Mv.l(2)}{THEME.mem_box(Symbol.title_right)}{THEME.div_line}{Symbol.h_line * (cls.mem_width - 1)}{\"\" if CONFIG.show_disks else THEME.mem_box}{Symbol.title_left}{Mv.l(cls.mem_width - 1)}{THEME.title}'\n\t\t\tif cls.graph_height >= 2:\n\t\t\t\tgbg = f'{Mv.l(1)}'\n\t\t\t\tgmv = f'{Mv.l(cls.mem_width - 2)}{Mv.u(cls.graph_height - 1)}'\n\n\t\t\tbig_mem: bool = cls.mem_width > 21\n\t\t\tfor name in cls.mem_names:\n\t\t\t\tif cy > h - 1: break\n\t\t\t\tif Collector.collect_interrupt: return\n\t\t\t\tif cls.mem_size > 2:\n\t\t\t\t\tout += (f'{Mv.to(y+cy, x+cx)}{gli}{name.capitalize()[:None if big_mem else 5]+\":\":<{1 if big_mem else 6.6}}{Mv.to(y+cy, x+cx + cls.mem_width - 3 - (len(mem.string[name])))}{Fx.trans(mem.string[name])}'\n\t\t\t\t\t\t\tf'{Mv.to(y+cy+1, x+cx)}{gbg}{Meters.mem[name](None if cls.resized else mem.percent[name])}{gmv}{str(mem.percent[name])+\"%\":>4}')\n\t\t\t\t\tcy += 2 if not cls.graph_height else cls.graph_height + 1\n\t\t\t\telse:\n\t\t\t\t\tout += f'{Mv.to(y+cy, x+cx)}{name.capitalize():{5.5 if cls.mem_size > 1 else 1.1}} {gbg}{Meters.mem[name](None if cls.resized else mem.percent[name])}{mem.string[name][:None if cls.mem_size > 1 else -2]:>{9 if cls.mem_size > 1 else 7}}'\n\t\t\t\t\tcy += 1 if not cls.graph_height else cls.graph_height\n\t\t\t#* Swap\n\t\t\tif cls.swap_on and CONFIG.show_swap and not CONFIG.swap_disk and mem.swap_string:\n\t\t\t\tif h - cy > 5:\n\t\t\t\t\tif cls.graph_height > 0: out += f'{Mv.to(y+cy, x+cx)}{gli}'\n\t\t\t\t\tcy += 1\n\n\t\t\t\tout += f'{Mv.to(y+cy, x+cx)}{THEME.title}{Fx.b}Swap:{mem.swap_string[\"total\"]:>{cls.mem_width - 8}}{Fx.ub}{THEME.main_fg}'\n\t\t\t\tcy += 1\n\t\t\t\tfor name in cls.swap_names:\n\t\t\t\t\tif cy > h - 1: break\n\t\t\t\t\tif Collector.collect_interrupt: return\n\t\t\t\t\tif cls.mem_size > 2:\n\t\t\t\t\t\tout += (f'{Mv.to(y+cy, x+cx)}{gli}{name.capitalize()[:None if big_mem else 5]+\":\":<{1 if big_mem else 6.6}}{Mv.to(y+cy, x+cx + cls.mem_width - 3 - (len(mem.swap_string[name])))}{Fx.trans(mem.swap_string[name])}'\n\t\t\t\t\t\t\t\tf'{Mv.to(y+cy+1, x+cx)}{gbg}{Meters.swap[name](None if cls.resized else mem.swap_percent[name])}{gmv}{str(mem.swap_percent[name])+\"%\":>4}')\n\t\t\t\t\t\tcy += 2 if not cls.graph_height else cls.graph_height + 1\n\t\t\t\t\telse:\n\t\t\t\t\t\tout += f'{Mv.to(y+cy, x+cx)}{name.capitalize():{5.5 if cls.mem_size > 1 else 1.1}} {gbg}{Meters.swap[name](None if cls.resized else mem.swap_percent[name])}{mem.swap_string[name][:None if cls.mem_size > 1 else -2]:>{9 if cls.mem_size > 1 else 7}}'; cy += 1 if not cls.graph_height else cls.graph_height\n\n\t\t\tif cls.graph_height > 0 and not cy == h: out += f'{Mv.to(y+cy, x+cx)}{gli}'\n\n\t\t\t#* Disks\n\t\t\tif CONFIG.show_disks and mem.disks:\n\t\t\t\tcx = x + cls.mem_width - 1; cy = 0\n\t\t\t\tbig_disk: bool = cls.disks_width >= 25\n\t\t\t\tgli = f'{Mv.l(2)}{THEME.div_line}{Symbol.title_right}{Symbol.h_line * cls.disks_width}{THEME.mem_box}{Symbol.title_left}{Mv.l(cls.disks_width - 1)}'\n\t\t\t\tif CONFIG.io_mode:\n\t\t\t\t\tfor name in cls.disks_io_order:\n\t\t\t\t\t\titem = mem.disks[name]\n\t\t\t\t\t\tio_item = mem.disks_io_dict.get(name, {})\n\t\t\t\t\t\tif Collector.collect_interrupt: return\n\t\t\t\t\t\tif cy > h - 1: break\n\t\t\t\t\t\tout += Fx.trans(f'{Mv.to(y+cy, x+cx)}{gli}{THEME.title}{Fx.b}{item[\"name\"]:{cls.disks_width - 2}.12}{Mv.to(y+cy, x + cx + cls.disks_width - 11)}{item[\"total\"][:None if big_disk else -2]:>9}')\n\t\t\t\t\t\tif big_disk:\n\t\t\t\t\t\t\tout += Fx.trans(f'{Mv.to(y+cy, x + cx + (cls.disks_width // 2) - (len(str(item[\"used_percent\"])) // 2) - 2)}{Fx.ub}{THEME.main_fg}{item[\"used_percent\"]}%')\n\t\t\t\t\t\tcy += 1\n\n\t\t\t\t\t\tif io_item:\n\t\t\t\t\t\t\tif cy > h - 1: break\n\t\t\t\t\t\t\tif CONFIG.io_graph_combined:\n\t\t\t\t\t\t\t\tif cls.disks_io_h <= 1:\n\t\t\t\t\t\t\t\t\tout += f'{Mv.to(y+cy, x+cx-1)}{\" \" * 5}'\n\t\t\t\t\t\t\t\tout += (f'{Mv.to(y+cy, x+cx-1)}{Fx.ub}{Graphs.disk_io[name][\"rw\"](None if cls.redraw else mem.disks_io_dict[name][\"rw\"][-1])}'\n\t\t\t\t\t\t\t\t\t\tf'{Mv.to(y+cy, x+cx-1)}{THEME.main_fg}{item[\"io\"] or \"RW\"}')\n\t\t\t\t\t\t\t\tcy += cls.disks_io_h\n\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\tif cls.disks_io_h <= 3:\n\t\t\t\t\t\t\t\t\tout += f'{Mv.to(y+cy, x+cx-1)}{\" \" * 5}{Mv.to(y+cy+1, x+cx-1)}{\" \" * 5}'\n\t\t\t\t\t\t\t\tout += (f'{Mv.to(y+cy, x+cx-1)}{Fx.ub}{Graphs.disk_io[name][\"read\"](None if cls.redraw else mem.disks_io_dict[name][\"read\"][-1])}'\n\t\t\t\t\t\t\t\t\t\tf'{Mv.to(y+cy, x+cx-1)}{THEME.main_fg}{item[\"io_r\"] or \"R\"}')\n\t\t\t\t\t\t\t\tcy += cls.disks_io_h // 2\n\t\t\t\t\t\t\t\tout += f'{Mv.to(y+cy, x+cx-1)}{Graphs.disk_io[name][\"write\"](None if cls.redraw else mem.disks_io_dict[name][\"write\"][-1])}'\n\t\t\t\t\t\t\t\tcy += cls.disks_io_h // 2\n\t\t\t\t\t\t\t\tout += f'{Mv.to(y+cy-1, x+cx-1)}{THEME.main_fg}{item[\"io_w\"] or \"W\"}'\n\t\t\t\telse:\n\t\t\t\t\tfor name, item in mem.disks.items():\n\t\t\t\t\t\tif Collector.collect_interrupt: return\n\t\t\t\t\t\tif not name in Meters.disks_used:\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\tif cy > h - 1: break\n\t\t\t\t\t\tout += Fx.trans(f'{Mv.to(y+cy, x+cx)}{gli}{THEME.title}{Fx.b}{item[\"name\"]:{cls.disks_width - 2}.12}{Mv.to(y+cy, x + cx + cls.disks_width - 11)}{item[\"total\"][:None if big_disk else -2]:>9}')\n\t\t\t\t\t\tif big_disk:\n\t\t\t\t\t\t\tout += f'{Mv.to(y+cy, x + cx + (cls.disks_width // 2) - (len(item[\"io\"]) // 2) - 2)}{Fx.ub}{THEME.main_fg}{Fx.trans(item[\"io\"])}'\n\t\t\t\t\t\tcy += 1\n\t\t\t\t\t\tif cy > h - 1: break\n\t\t\t\t\t\tif CONFIG.show_io_stat and name in Graphs.disk_io:\n\t\t\t\t\t\t\tout += f'{Mv.to(y+cy, x+cx-1)}{THEME.main_fg}{Fx.ub}{\" IO: \" if big_disk else \" IO   \" + Mv.l(2)}{Fx.ub}{Graphs.disk_io[name][\"rw\"](None if cls.redraw else mem.disks_io_dict[name][\"rw\"][-1])}'\n\t\t\t\t\t\t\tif not big_disk and item[\"io\"]:\n\t\t\t\t\t\t\t\tout += f'{Mv.to(y+cy, x+cx-1)}{Fx.ub}{THEME.main_fg}{item[\"io\"]}'\n\t\t\t\t\t\t\tcy += 1\n\t\t\t\t\t\t\tif cy > h - 1: break\n\t\t\t\t\t\tout += Mv.to(y+cy, x+cx) + (f'Used:{str(item[\"used_percent\"]) + \"%\":>4} ' if big_disk else \"U \")\n\t\t\t\t\t\tout += f'{Meters.disks_used[name](None if cls.resized else mem.disks[name][\"used_percent\"])}{item[\"used\"][:None if big_disk else -2]:>{9 if big_disk else 7}}'\n\t\t\t\t\t\tcy += 1\n\n\t\t\t\t\t\tif len(mem.disks) * 3 + (len(mem.disks_io_dict) if CONFIG.show_io_stat else 0) <= h + 1:\n\t\t\t\t\t\t\tif cy > h - 1: break\n\t\t\t\t\t\t\tout += Mv.to(y+cy, x+cx)\n\t\t\t\t\t\t\tout += f'Free:{str(item[\"free_percent\"]) + \"%\":>4} ' if big_disk else f'{\"F \"}'\n\t\t\t\t\t\t\tout += f'{Meters.disks_free[name](None if cls.resized else mem.disks[name][\"free_percent\"])}{item[\"free\"][:None if big_disk else -2]:>{9 if big_disk else 7}}'\n\t\t\t\t\t\t\tcy += 1\n\t\t\t\t\t\t\tif len(mem.disks) * 4 + (len(mem.disks_io_dict) if CONFIG.show_io_stat else 0) <= h + 1: cy += 1\n\t\texcept (KeyError, TypeError):\n\t\t\treturn\n\t\tDraw.buffer(cls.buffer, f'{out_misc}{out}{Term.fg}', only_save=Menu.active)\n\t\tcls.resized = cls.redraw = False\n\nclass NetBox(Box, SubBox):\n\tname = \"net\"\n\tnum = 3\n\theight_p = 30\n\twidth_p = 45\n\tmin_w: int = 36\n\tmin_h: int = 6\n\tx = 1\n\ty = 1\n\tresized: bool = True\n\tredraw: bool = True\n\tgraph_height: Dict[str, int] = {}\n\tsymbols: Dict[str, str] = {\"download\" : \"▼\", \"upload\" : \"▲\"}\n\tbuffer: str = \"net\"\n\n\tBox.buffers.append(buffer)\n\n\t@classmethod\n\tdef _calc_size(cls):\n\t\tif not \"net\" in cls.boxes:\n\t\t\tcls.width = Term.width\n\t\t\treturn\n\t\tif not \"proc\" in cls.boxes:\n\t\t\twidth_p = 100\n\t\telse:\n\t\t\twidth_p = cls.width_p\n\n\t\tcls.width = round(Term.width * width_p / 100)\n\t\tcls.height = Term.height - Box._b_cpu_h - Box._b_mem_h\n\t\tcls.y = Term.height - cls.height + 1\n\t\tcls.box_width = 27 if cls.width > 45 else 19\n\t\tcls.box_height = 9 if cls.height > 10 else cls.height - 2\n\t\tcls.box_x = cls.width - cls.box_width - 1\n\t\tcls.box_y = cls.y + ((cls.height - 2) // 2) - cls.box_height // 2 + 1\n\t\tcls.graph_height[\"download\"] = round((cls.height - 2) / 2)\n\t\tcls.graph_height[\"upload\"] = cls.height - 2 - cls.graph_height[\"download\"]\n\t\tcls.redraw = True\n\n\t@classmethod\n\tdef _draw_bg(cls) -> str:\n\t\tif not \"net\" in cls.boxes: return \"\"\n\t\treturn f'{create_box(box=cls, line_color=THEME.net_box)}\\\n\t\t{create_box(x=cls.box_x, y=cls.box_y, width=cls.box_width, height=cls.box_height, line_color=THEME.div_line, fill=False, title=\"Download\", title2=\"Upload\")}'\n\n\t@classmethod\n\tdef _draw_fg(cls):\n\t\tif not \"net\" in cls.boxes: return\n\t\tnet = NetCollector\n\t\tif net.redraw: cls.redraw = True\n\t\tif not net.nic: return\n\t\tout: str = \"\"\n\t\tout_misc: str = \"\"\n\t\tx, y, w, h = cls.x + 1, cls.y + 1, cls.width - 2, cls.height - 2\n\t\tbx, by, bw, bh = cls.box_x + 1, cls.box_y + 1, cls.box_width - 2, cls.box_height - 2\n\t\treset: bool = bool(net.stats[net.nic][\"download\"][\"offset\"])\n\n\t\tif cls.resized or cls.redraw:\n\t\t\tout_misc += cls._draw_bg()\n\t\t\tKey.mouse[\"b\"] = [[x+w - len(net.nic[:10]) - 9 + i, y-1] for i in range(4)]\n\t\t\tKey.mouse[\"n\"] = [[x+w - 5 + i, y-1] for i in range(4)]\n\t\t\tKey.mouse[\"z\"] = [[x+w - len(net.nic[:10]) - 14 + i, y-1] for i in range(4)]\n\n\n\t\t\tout_misc += (f'{Mv.to(y-1, x+w - 25)}{THEME.net_box}{Symbol.h_line * (10 - len(net.nic[:10]))}{Symbol.title_left}{Fx.b if reset else \"\"}{THEME.hi_fg(\"z\")}{THEME.title(\"ero\")}'\n\t\t\t\tf'{Fx.ub}{THEME.net_box(Symbol.title_right)}{Term.fg}'\n\t\t\t\tf'{THEME.net_box}{Symbol.title_left}{Fx.b}{THEME.hi_fg(\"<b\")} {THEME.title(net.nic[:10])} {THEME.hi_fg(\"n>\")}{Fx.ub}{THEME.net_box(Symbol.title_right)}{Term.fg}')\n\t\t\tif w - len(net.nic[:10]) - 20 > 6:\n\t\t\t\tKey.mouse[\"a\"] = [[x+w - 20 - len(net.nic[:10]) + i, y-1] for i in range(4)]\n\t\t\t\tout_misc += (f'{Mv.to(y-1, x+w - 21 - len(net.nic[:10]))}{THEME.net_box(Symbol.title_left)}{Fx.b if net.auto_min else \"\"}{THEME.hi_fg(\"a\")}{THEME.title(\"uto\")}'\n\t\t\t\tf'{Fx.ub}{THEME.net_box(Symbol.title_right)}{Term.fg}')\n\t\t\tif w - len(net.nic[:10]) - 20 > 13:\n\t\t\t\tKey.mouse[\"y\"] = [[x+w - 26 - len(net.nic[:10]) + i, y-1] for i in range(4)]\n\t\t\t\tout_misc += (f'{Mv.to(y-1, x+w - 27 - len(net.nic[:10]))}{THEME.net_box(Symbol.title_left)}{Fx.b if CONFIG.net_sync else \"\"}{THEME.title(\"s\")}{THEME.hi_fg(\"y\")}{THEME.title(\"nc\")}'\n\t\t\t\tf'{Fx.ub}{THEME.net_box(Symbol.title_right)}{Term.fg}')\n\t\t\tif net.address and w - len(net.nic[:10]) - len(net.address) - 20 > 15:\n\t\t\t\tout_misc += (f'{Mv.to(y-1, x+7)}{THEME.net_box(Symbol.title_left)}{Fx.b}{THEME.title(net.address)}{Fx.ub}{THEME.net_box(Symbol.title_right)}{Term.fg}')\n\t\t\tDraw.buffer(\"net_misc\", out_misc, only_save=True)\n\n\t\tcy = 0\n\t\tfor direction in [\"download\", \"upload\"]:\n\t\t\tstrings = net.strings[net.nic][direction]\n\t\t\tstats = net.stats[net.nic][direction]\n\t\t\tif cls.redraw: stats[\"redraw\"] = True\n\t\t\tif stats[\"redraw\"] or cls.resized:\n\t\t\t\tGraphs.net[direction] = Graph(w - bw - 3, cls.graph_height[direction], THEME.gradient[direction], stats[\"speed\"], max_value=net.sync_top if CONFIG.net_sync else stats[\"graph_top\"],\n\t\t\t\t\tinvert=direction != \"download\", color_max_value=net.net_min.get(direction) if CONFIG.net_color_fixed else None, round_up_low=True)\n\t\t\tout += f'{Mv.to(y if direction == \"download\" else y + cls.graph_height[\"download\"], x)}{Graphs.net[direction](None if stats[\"redraw\"] else stats[\"speed\"][-1])}'\n\n\t\t\tout += (f'{Mv.to(by+cy, bx)}{THEME.main_fg}{cls.symbols[direction]} {strings[\"byte_ps\"]:<10.10}' +\n\t\t\t\t\t(\"\" if bw < 20 else f'{Mv.to(by+cy, bx+bw - 12)}{\"(\" + strings[\"bit_ps\"] + \")\":>12.12}'))\n\t\t\tcy += 1 if bh != 3 else 2\n\t\t\tif bh >= 6:\n\t\t\t\tout += f'{Mv.to(by+cy, bx)}{cls.symbols[direction]} {\"Top:\"}{Mv.to(by+cy, bx+bw - 12)}{\"(\" + strings[\"top\"] + \")\":>12.12}'\n\t\t\t\tcy += 1\n\t\t\tif bh >= 4:\n\t\t\t\tout += f'{Mv.to(by+cy, bx)}{cls.symbols[direction]} {\"Total:\"}{Mv.to(by+cy, bx+bw - 10)}{strings[\"total\"]:>10.10}'\n\t\t\t\tif bh > 2 and bh % 2: cy += 2\n\t\t\t\telse: cy += 1\n\t\t\tstats[\"redraw\"] = False\n\n\t\tout += (f'{Mv.to(y, x)}{THEME.graph_text(net.sync_string if CONFIG.net_sync else net.strings[net.nic][\"download\"][\"graph_top\"])}'\n\t\t\t\tf'{Mv.to(y+h-1, x)}{THEME.graph_text(net.sync_string if CONFIG.net_sync else net.strings[net.nic][\"upload\"][\"graph_top\"])}')\n\n\t\tDraw.buffer(cls.buffer, f'{out_misc}{out}{Term.fg}', only_save=Menu.active)\n\t\tcls.redraw = cls.resized = False\n\nclass ProcBox(Box):\n\tname = \"proc\"\n\tnum = 4\n\theight_p = 68\n\twidth_p = 55\n\tmin_w: int = 44\n\tmin_h: int = 16\n\tx = 1\n\ty = 1\n\tcurrent_y: int = 0\n\tcurrent_h: int = 0\n\tselect_max: int = 0\n\tselected: int = 0\n\tselected_pid: int = 0\n\tlast_selection: int = 0\n\tfiltering: bool = False\n\tmoved: bool = False\n\tstart: int = 1\n\tcount: int = 0\n\ts_len: int = 0\n\tdetailed: bool = False\n\tdetailed_x: int = 0\n\tdetailed_y: int = 0\n\tdetailed_width: int = 0\n\tdetailed_height: int = 8\n\tresized: bool = True\n\tredraw: bool = True\n\tbuffer: str = \"proc\"\n\tpid_counter: Dict[int, int] = {}\n\tBox.buffers.append(buffer)\n\n\t@classmethod\n\tdef _calc_size(cls):\n\t\tif not \"proc\" in cls.boxes:\n\t\t\tcls.width = Term.width\n\t\t\treturn\n\t\twidth_p: int; height_p: int\n\t\tif not \"net\" in cls.boxes and not \"mem\" in cls.boxes:\n\t\t\twidth_p = 100\n\t\telse:\n\t\t\twidth_p = cls.width_p\n\n\t\tif not \"cpu\" in cls.boxes:\n\t\t\theight_p = 100\n\t\telse:\n\t\t\theight_p = cls.height_p\n\n\t\tcls.width = round(Term.width * width_p / 100)\n\t\tcls.height = round(Term.height * height_p / 100)\n\t\tif cls.height + Box._b_cpu_h > Term.height: cls.height = Term.height - Box._b_cpu_h\n\t\tcls.x = Term.width - cls.width + 1\n\t\tcls.y = Box._b_cpu_h + 1\n\t\tcls.current_y = cls.y\n\t\tcls.current_h = cls.height\n\t\tcls.select_max = cls.height - 3\n\t\tcls.redraw = True\n\t\tcls.resized = True\n\n\t@classmethod\n\tdef _draw_bg(cls) -> str:\n\t\tif not \"proc\" in cls.boxes: return \"\"\n\t\treturn create_box(box=cls, line_color=THEME.proc_box)\n\n\t@classmethod\n\tdef selector(cls, key: str, mouse_pos: Tuple[int, int] = (0, 0)):\n\t\told: Tuple[int, int] = (cls.start, cls.selected)\n\t\tnew_sel: int\n\t\tif key in [\"up\", \"k\"]:\n\t\t\tif cls.selected == 1 and cls.start > 1:\n\t\t\t\tcls.start -= 1\n\t\t\telif cls.selected == 1:\n\t\t\t\tcls.selected = 0\n\t\t\telif cls.selected > 1:\n\t\t\t\tcls.selected -= 1\n\t\telif key in [\"down\", \"j\"]:\n\t\t\tif cls.selected == 0 and ProcCollector.detailed and cls.last_selection:\n\t\t\t\tcls.selected = cls.last_selection\n\t\t\t\tcls.last_selection = 0\n\t\t\tif cls.selected == cls.select_max and cls.start < ProcCollector.num_procs - cls.select_max + 1:\n\t\t\t\tcls.start += 1\n\t\t\telif cls.selected < cls.select_max:\n\t\t\t\tcls.selected += 1\n\t\telif key == \"mouse_scroll_up\" and cls.start > 1:\n\t\t\tcls.start -= 5\n\t\telif key == \"mouse_scroll_down\" and cls.start < ProcCollector.num_procs - cls.select_max + 1:\n\t\t\tcls.start += 5\n\t\telif key == \"page_up\" and cls.start > 1:\n\t\t\tcls.start -= cls.select_max\n\t\telif key == \"page_down\" and cls.start < ProcCollector.num_procs - cls.select_max + 1:\n\t\t\tcls.start += cls.select_max\n\t\telif key == \"home\":\n\t\t\tif cls.start > 1: cls.start = 1\n\t\t\telif cls.selected > 0: cls.selected = 0\n\t\telif key == \"end\":\n\t\t\tif cls.start < ProcCollector.num_procs - cls.select_max + 1: cls.start = ProcCollector.num_procs - cls.select_max + 1\n\t\t\telif cls.selected < cls.select_max: cls.selected = cls.select_max\n\t\telif key == \"mouse_click\":\n\t\t\tif mouse_pos[0] > cls.x + cls.width - 4 and cls.current_y + 1 < mouse_pos[1] < cls.current_y + 1 + cls.select_max + 1:\n\t\t\t\tif mouse_pos[1] == cls.current_y + 2:\n\t\t\t\t\tcls.start = 1\n\t\t\t\telif mouse_pos[1] == cls.current_y + 1 + cls.select_max:\n\t\t\t\t\tcls.start = ProcCollector.num_procs - cls.select_max + 1\n\t\t\t\telse:\n\t\t\t\t\tcls.start = round((mouse_pos[1] - cls.current_y) * ((ProcCollector.num_procs - cls.select_max - 2) / (cls.select_max - 2)))\n\t\t\telse:\n\t\t\t\tnew_sel = mouse_pos[1] - cls.current_y - 1 if mouse_pos[1] >= cls.current_y - 1 else 0\n\t\t\t\tif new_sel > 0 and new_sel == cls.selected:\n\t\t\t\t\tKey.list.insert(0, \"enter\")\n\t\t\t\t\treturn\n\t\t\t\telif new_sel > 0 and new_sel != cls.selected:\n\t\t\t\t\tif cls.last_selection: cls.last_selection = 0\n\t\t\t\t\tcls.selected = new_sel\n\t\telif key == \"mouse_unselect\":\n\t\t\tcls.selected = 0\n\n\t\tif cls.start > ProcCollector.num_procs - cls.select_max + 1 and ProcCollector.num_procs > cls.select_max: cls.start = ProcCollector.num_procs - cls.select_max + 1\n\t\telif cls.start > ProcCollector.num_procs: cls.start = ProcCollector.num_procs\n\t\tif cls.start < 1: cls.start = 1\n\t\tif cls.selected > ProcCollector.num_procs and ProcCollector.num_procs < cls.select_max: cls.selected = ProcCollector.num_procs\n\t\telif cls.selected > cls.select_max: cls.selected = cls.select_max\n\t\tif cls.selected < 0: cls.selected = 0\n\n\t\tif old != (cls.start, cls.selected):\n\t\t\tcls.moved = True\n\t\t\tCollector.collect(ProcCollector, proc_interrupt=True, redraw=True, only_draw=True)\n\n\n\t@classmethod\n\tdef _draw_fg(cls):\n\t\tif not \"proc\" in cls.boxes: return\n\t\tproc = ProcCollector\n\t\tif proc.proc_interrupt: return\n\t\tif proc.redraw: cls.redraw = True\n\t\tout: str = \"\"\n\t\tout_misc: str = \"\"\n\t\tn: int = 0\n\t\tx, y, w, h = cls.x + 1, cls.current_y + 1, cls.width - 2, cls.current_h - 2\n\t\tprog_len: int; arg_len: int; val: int; c_color: str; m_color: str; t_color: str; sort_pos: int; tree_len: int; is_selected: bool; calc: int\n\t\tdgx: int; dgw: int; dx: int; dw: int; dy: int\n\t\tl_count: int = 0\n\t\tscroll_pos: int = 0\n\t\tkilled: bool = True\n\t\tindent: str = \"\"\n\t\toffset: int = 0\n\t\ttr_show: bool = True\n\t\tusr_show: bool = True\n\t\tvals: List[str]\n\t\tg_color: str = \"\"\n\t\ts_len: int = 0\n\t\tif proc.search_filter: s_len = len(proc.search_filter[:10])\n\t\tloc_string: str = f'{cls.start + cls.selected - 1}/{proc.num_procs}'\n\t\tend: str = \"\"\n\n\t\tif proc.detailed:\n\t\t\tdgx, dgw = x, w // 3\n\t\t\tdw = w - dgw - 1\n\t\t\tif dw > 120:\n\t\t\t\tdw = 120\n\t\t\t\tdgw = w - 121\n\t\t\tdx = x + dgw + 2\n\t\t\tdy = cls.y + 1\n\n\t\tif w > 67:\n\t\t\targ_len = w - 53 - (1 if proc.num_procs > cls.select_max else 0)\n\t\t\tprog_len = 15\n\t\telse:\n\t\t\targ_len = 0\n\t\t\tprog_len = w - 38 - (1 if proc.num_procs > cls.select_max else 0)\n\t\t\tif prog_len < 15:\n\t\t\t\ttr_show = False\n\t\t\t\tprog_len += 5\n\t\t\tif prog_len < 12:\n\t\t\t\tusr_show = False\n\t\t\t\tprog_len += 9\n\n\t\tif CONFIG.proc_tree:\n\t\t\ttree_len = arg_len + prog_len + 6\n\t\t\targ_len = 0\n\n\t\t#* Buttons and titles only redrawn if needed\n\t\tif cls.resized or cls.redraw:\n\t\t\ts_len += len(CONFIG.proc_sorting)\n\t\t\tif cls.resized or s_len != cls.s_len or proc.detailed:\n\t\t\t\tcls.s_len = s_len\n\t\t\t\tfor k in [\"e\", \"r\", \"c\", \"T\", \"K\", \"I\", \"enter\", \"left\", \" \", \"f\", \"delete\"]:\n\t\t\t\t\tif k in Key.mouse: del Key.mouse[k]\n\t\t\tif proc.detailed:\n\t\t\t\tkilled = proc.details.get(\"killed\", False)\n\t\t\t\tmain = THEME.main_fg if cls.selected == 0 and not killed else THEME.inactive_fg\n\t\t\t\thi = THEME.hi_fg if cls.selected == 0 and not killed else THEME.inactive_fg\n\t\t\t\ttitle = THEME.title if cls.selected == 0 and not killed else THEME.inactive_fg\n\t\t\t\tif cls.current_y != cls.y + 8 or cls.resized or Graphs.detailed_cpu is NotImplemented:\n\t\t\t\t\tcls.current_y = cls.y + 8\n\t\t\t\t\tcls.current_h = cls.height - 8\n\t\t\t\t\tfor i in range(7): out_misc += f'{Mv.to(dy+i, x)}{\" \" * w}'\n\t\t\t\t\tout_misc += (f'{Mv.to(dy+7, x-1)}{THEME.proc_box}{Symbol.title_right}{Symbol.h_line*w}{Symbol.title_left}'\n\t\t\t\t\tf'{Mv.to(dy+7, x+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(SUPERSCRIPT[cls.num])}{THEME.title(cls.name)}{Fx.ub}{THEME.proc_box(Symbol.title_right)}{THEME.div_line}')\n\t\t\t\t\tfor i in range(7):\n\t\t\t\t\t\tout_misc += f'{Mv.to(dy + i, dgx + dgw + 1)}{Symbol.v_line}'\n\n\t\t\t\tout_misc += (f'{Mv.to(dy-1, x-1)}{THEME.proc_box}{Symbol.left_up}{Symbol.h_line*w}{Symbol.right_up}'\n\t\t\t\t\tf'{Mv.to(dy-1, dgx + dgw + 1)}{Symbol.div_up}'\n\t\t\t\t\tf'{Mv.to(dy-1, x+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.title(str(proc.details[\"pid\"]))}{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\t\t\t\t\tf'{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.title(proc.details[\"name\"][:(dgw - 11)])}{Fx.ub}{THEME.proc_box(Symbol.title_right)}')\n\n\t\t\t\tif cls.selected == 0:\n\t\t\t\t\tKey.mouse[\"enter\"] = [[dx+dw-10 + i, dy-1] for i in range(7)]\n\t\t\t\tif cls.selected == 0 and not killed:\n\t\t\t\t\tKey.mouse[\"T\"] = [[dx+2 + i, dy-1] for i in range(9)]\n\n\t\t\t\tout_misc += (f'{Mv.to(dy-1, dx+dw - 11)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{title if cls.selected > 0 else THEME.title}close{Fx.ub} {main if cls.selected > 0 else THEME.main_fg}{Symbol.enter}{THEME.proc_box(Symbol.title_right)}'\n\t\t\t\t\tf'{Mv.to(dy-1, dx+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{hi}T{title}erminate{Fx.ub}{THEME.proc_box(Symbol.title_right)}')\n\t\t\t\tif dw > 28:\n\t\t\t\t\tif cls.selected == 0 and not killed and not \"K\" in Key.mouse: Key.mouse[\"K\"] = [[dx + 13 + i, dy-1] for i in range(4)]\n\t\t\t\t\tout_misc += f'{THEME.proc_box(Symbol.title_left)}{Fx.b}{hi}K{title}ill{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\t\t\t\tif dw > 39:\n\t\t\t\t\tif cls.selected == 0 and not killed and not \"I\" in Key.mouse: Key.mouse[\"I\"] = [[dx + 19 + i, dy-1] for i in range(9)]\n\t\t\t\t\tout_misc += f'{THEME.proc_box(Symbol.title_left)}{Fx.b}{hi}I{title}nterrupt{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\n\t\t\t\tif Graphs.detailed_cpu is NotImplemented or cls.resized:\n\t\t\t\t\tGraphs.detailed_cpu = Graph(dgw+1, 7, THEME.gradient[\"cpu\"], proc.details_cpu)\n\t\t\t\t\tGraphs.detailed_mem = Graph(dw // 3, 1, None, proc.details_mem)\n\n\t\t\t\tcls.select_max = cls.height - 11\n\t\t\t\ty = cls.y + 9\n\t\t\t\th = cls.height - 10\n\n\t\t\telse:\n\t\t\t\tif cls.current_y != cls.y or cls.resized:\n\t\t\t\t\tcls.current_y = cls.y\n\t\t\t\t\tcls.current_h = cls.height\n\t\t\t\t\ty, h = cls.y + 1, cls.height - 2\n\t\t\t\t\tout_misc += (f'{Mv.to(y-1, x-1)}{THEME.proc_box}{Symbol.left_up}{Symbol.h_line*w}{Symbol.right_up}'\n\t\t\t\t\t\tf'{Mv.to(y-1, x+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(SUPERSCRIPT[cls.num])}{THEME.title(cls.name)}{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\t\t\t\t\t\tf'{Mv.to(y+7, x-1)}{THEME.proc_box(Symbol.v_line)}{Mv.r(w)}{THEME.proc_box(Symbol.v_line)}')\n\t\t\t\tcls.select_max = cls.height - 3\n\n\n\t\t\tsort_pos = x + w - len(CONFIG.proc_sorting) - 7\n\t\t\tif not \"left\" in Key.mouse:\n\t\t\t\tKey.mouse[\"left\"] = [[sort_pos + i, y-1] for i in range(3)]\n\t\t\t\tKey.mouse[\"right\"] = [[sort_pos + len(CONFIG.proc_sorting) + 3 + i, y-1] for i in range(3)]\n\n\n\t\t\tout_misc += (f'{Mv.to(y-1, x + 8)}{THEME.proc_box(Symbol.h_line * (w - 9))}' +\n\t\t\t\t(\"\" if not proc.detailed else f\"{Mv.to(dy+7, dgx + dgw + 1)}{THEME.proc_box(Symbol.div_down)}\") +\n\t\t\t\tf'{Mv.to(y-1, sort_pos)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(\"<\")} {THEME.title(CONFIG.proc_sorting)} '\n\t\t\t\tf'{THEME.hi_fg(\">\")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}')\n\n\n\t\t\tif w > 29 + s_len:\n\t\t\t\tif not \"e\" in Key.mouse: Key.mouse[\"e\"] = [[sort_pos - 5 + i, y-1] for i in range(4)]\n\t\t\t\tout_misc += (f'{Mv.to(y-1, sort_pos - 6)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_tree else \"\"}'\n\t\t\t\t\tf'{THEME.title(\"tre\")}{THEME.hi_fg(\"e\")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}')\n\t\t\tif w > 37 + s_len:\n\t\t\t\tif not \"r\" in Key.mouse: Key.mouse[\"r\"] = [[sort_pos - 14 + i, y-1] for i in range(7)]\n\t\t\t\tout_misc += (f'{Mv.to(y-1, sort_pos - 15)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_reversed else \"\"}'\n\t\t\t\t\tf'{THEME.hi_fg(\"r\")}{THEME.title(\"everse\")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}')\n\t\t\tif w > 47 + s_len:\n\t\t\t\tif not \"c\" in Key.mouse: Key.mouse[\"c\"] = [[sort_pos - 24 + i, y-1] for i in range(8)]\n\t\t\t\tout_misc += (f'{Mv.to(y-1, sort_pos - 25)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_per_core else \"\"}'\n\t\t\t\t\tf'{THEME.title(\"per-\")}{THEME.hi_fg(\"c\")}{THEME.title(\"ore\")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}')\n\n\t\t\tif not \"f\" in Key.mouse or cls.resized: Key.mouse[\"f\"] = [[x+6 + i, y-1] for i in range(6 if not proc.search_filter else 2 + len(proc.search_filter[-10:]))]\n\t\t\tif proc.search_filter:\n\t\t\t\tif not \"delete\" in Key.mouse: Key.mouse[\"delete\"] = [[x+12 + len(proc.search_filter[-10:]) + i, y-1] for i in range(3)]\n\t\t\telif \"delete\" in Key.mouse:\n\t\t\t\tdel Key.mouse[\"delete\"]\n\t\t\tout_misc += (f'{Mv.to(y-1, x + 8)}{THEME.proc_box(Symbol.title_left)}{Fx.b if cls.filtering or proc.search_filter else \"\"}{THEME.hi_fg(\"F\" if cls.filtering and proc.case_sensitive else \"f\")}{THEME.title}' +\n\t\t\t\t(\"ilter\" if not proc.search_filter and not cls.filtering else f' {proc.search_filter[-(10 if w < 83 else w - 74):]}{(Fx.bl + \"█\" + Fx.ubl) if cls.filtering else THEME.hi_fg(\" del\")}') +\n\t\t\t\tf'{THEME.proc_box(Symbol.title_right)}')\n\n\t\t\tmain = THEME.inactive_fg if cls.selected == 0 else THEME.main_fg\n\t\t\thi = THEME.inactive_fg if cls.selected == 0 else THEME.hi_fg\n\t\t\ttitle = THEME.inactive_fg if cls.selected == 0 else THEME.title\n\t\t\tout_misc += (f'{Mv.to(y+h, x + 1)}{THEME.proc_box}{Symbol.h_line*(w-4)}'\n\t\t\t\t\tf'{Mv.to(y+h, x+1)}{THEME.proc_box(Symbol.title_left)}{main}{Symbol.up} {Fx.b}{THEME.main_fg(\"select\")} {Fx.ub}'\n\t\t\t\t\tf'{THEME.inactive_fg if cls.selected == cls.select_max else THEME.main_fg}{Symbol.down}{THEME.proc_box(Symbol.title_right)}'\n\t\t\t\t\tf'{THEME.proc_box(Symbol.title_left)}{title}{Fx.b}info {Fx.ub}{main}{Symbol.enter}{THEME.proc_box(Symbol.title_right)}')\n\t\t\tif not \"enter\" in Key.mouse: Key.mouse[\"enter\"] = [[x + 14 + i, y+h] for i in range(6)]\n\t\t\tif w - len(loc_string) > 34:\n\t\t\t\tif not \"T\" in Key.mouse: Key.mouse[\"T\"] = [[x + 22 + i, y+h] for i in range(9)]\n\t\t\t\tout_misc += f'{THEME.proc_box(Symbol.title_left)}{Fx.b}{hi}T{title}erminate{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\t\t\tif w - len(loc_string) > 40:\n\t\t\t\tif not \"K\" in Key.mouse: Key.mouse[\"K\"] = [[x + 33 + i, y+h] for i in range(4)]\n\t\t\t\tout_misc += f'{THEME.proc_box(Symbol.title_left)}{Fx.b}{hi}K{title}ill{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\t\t\tif w - len(loc_string) > 51:\n\t\t\t\tif not \"I\" in Key.mouse: Key.mouse[\"I\"] = [[x + 39 + i, y+h] for i in range(9)]\n\t\t\t\tout_misc += f'{THEME.proc_box(Symbol.title_left)}{Fx.b}{hi}I{title}nterrupt{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\t\t\tif CONFIG.proc_tree and w - len(loc_string) > 65:\n\t\t\t\tif not \" \" in Key.mouse: Key.mouse[\" \"] = [[x + 50 + i, y+h] for i in range(12)]\n\t\t\t\tout_misc += f'{THEME.proc_box(Symbol.title_left)}{Fx.b}{hi}spc {title}collapse{Fx.ub}{THEME.proc_box(Symbol.title_right)}'\n\n\t\t\t#* Processes labels\n\t\t\tselected: str = CONFIG.proc_sorting\n\t\t\tlabel: str\n\t\t\tif selected == \"memory\": selected = \"mem\"\n\t\t\tif selected == \"threads\" and not CONFIG.proc_tree and not arg_len: selected = \"tr\"\n\t\t\tif CONFIG.proc_tree:\n\t\t\t\tlabel = (f'{THEME.title}{Fx.b}{Mv.to(y, x)}{\" Tree:\":<{tree_len-2}}' + (f'{\"Threads: \":<9}' if tr_show else \" \"*4) + (f'{\"User:\":<9}' if usr_show else \"\") + f'Mem%{\"Cpu%\":>11}{Fx.ub}{THEME.main_fg} ' +\n\t\t\t\t\t\t(\" \" if proc.num_procs > cls.select_max else \"\"))\n\t\t\t\tif selected in [\"pid\", \"program\", \"arguments\"]: selected = \"tree\"\n\t\t\telse:\n\t\t\t\tlabel = (f'{THEME.title}{Fx.b}{Mv.to(y, x)}{\"Pid:\":>7} {\"Program:\" if prog_len > 8 else \"Prg:\":<{prog_len}}' + (f'{\"Arguments:\":<{arg_len-4}}' if arg_len else \"\") +\n\t\t\t\t\t((f'{\"Threads:\":<9}' if arg_len else f'{\"Tr:\":^5}') if tr_show else \"\") + (f'{\"User:\":<9}' if usr_show else \"\") + f'Mem%{\"Cpu%\":>11}{Fx.ub}{THEME.main_fg} ' +\n\t\t\t\t\t(\" \" if proc.num_procs > cls.select_max else \"\"))\n\t\t\t\tif selected == \"program\" and prog_len <= 8: selected = \"prg\"\n\t\t\tselected = selected.split(\" \")[0].capitalize()\n\t\t\tif CONFIG.proc_mem_bytes: label = label.replace(\"Mem%\", \"MemB\")\n\t\t\tlabel = label.replace(selected, f'{Fx.u}{selected}{Fx.uu}')\n\t\t\tout_misc += label\n\n\t\t\tDraw.buffer(\"proc_misc\", out_misc, only_save=True)\n\n\t\t#* Detailed box draw\n\t\tif proc.detailed:\n\t\t\tif proc.details[\"status\"] == psutil.STATUS_RUNNING: stat_color = Fx.b\n\t\t\telif proc.details[\"status\"] in [psutil.STATUS_DEAD, psutil.STATUS_STOPPED, psutil.STATUS_ZOMBIE]: stat_color = f'{THEME.inactive_fg}'\n\t\t\telse: stat_color = \"\"\n\t\t\texpand = proc.expand\n\t\t\tiw = (dw - 3) // (4 + expand)\n\t\t\tiw2 = iw - 1\n\t\t\tout += (f'{Mv.to(dy, dgx)}{Graphs.detailed_cpu(None if cls.moved or proc.details[\"killed\"] else proc.details_cpu[-1])}'\n\t\t\t\t\tf'{Mv.to(dy, dgx)}{THEME.title}{Fx.b}{0 if proc.details[\"killed\"] else proc.details[\"cpu_percent\"]}%{Mv.r(1)}{\"\" if SYSTEM == \"MacOS\" else ((\"C\" if dgw < 20 else \"Core\") + str(proc.details[\"cpu_num\"]))}')\n\t\t\tfor i, l in enumerate([\"C\", \"P\", \"U\"]):\n\t\t\t\tout += f'{Mv.to(dy+2+i, dgx)}{l}'\n\t\t\tfor i, l in enumerate([\"C\", \"M\", \"D\"]):\n\t\t\t\tout += f'{Mv.to(dy+4+i, dx+1)}{l}'\n\t\t\tout += (f'{Mv.to(dy, dx+1)} {\"Status:\":^{iw}.{iw2}}{\"Elapsed:\":^{iw}.{iw2}}' +\n\t\t\t\t\t(f'{\"Parent:\":^{iw}.{iw2}}' if dw > 28 else \"\") + (f'{\"User:\":^{iw}.{iw2}}' if dw > 38 else \"\") +\n\t\t\t\t\t(f'{\"Threads:\":^{iw}.{iw2}}' if expand > 0 else \"\") + (f'{\"Nice:\":^{iw}.{iw2}}' if expand > 1 else \"\") +\n\t\t\t\t\t(f'{\"IO Read:\":^{iw}.{iw2}}' if expand > 2 else \"\") + (f'{\"IO Write:\":^{iw}.{iw2}}' if expand > 3 else \"\") +\n\t\t\t\t\t(f'{\"TTY:\":^{iw}.{iw2}}' if expand > 4 else \"\") +\n\t\t\t\t\tf'{Mv.to(dy+1, dx+1)}{Fx.ub}{THEME.main_fg}{stat_color}{proc.details[\"status\"]:^{iw}.{iw2}}{Fx.ub}{THEME.main_fg}{proc.details[\"uptime\"]:^{iw}.{iw2}} ' +\n\t\t\t\t\t(f'{proc.details[\"parent_name\"]:^{iw}.{iw2}}' if dw > 28 else \"\") + (f'{proc.details[\"username\"]:^{iw}.{iw2}}' if dw > 38 else \"\") +\n\t\t\t\t\t(f'{proc.details[\"threads\"]:^{iw}.{iw2}}' if expand > 0 else \"\") + (f'{proc.details[\"nice\"]:^{iw}.{iw2}}' if expand > 1 else \"\") +\n\t\t\t\t\t(f'{proc.details[\"io_read\"]:^{iw}.{iw2}}' if expand > 2 else \"\") + (f'{proc.details[\"io_write\"]:^{iw}.{iw2}}' if expand > 3 else \"\") +\n\t\t\t\t\t(f'{proc.details[\"terminal\"][-(iw2):]:^{iw}.{iw2}}' if expand > 4 else \"\") +\n\t\t\t\t\tf'{Mv.to(dy+3, dx)}{THEME.title}{Fx.b}{(\"Memory: \" if dw > 42 else \"M:\") + str(round(proc.details[\"memory_percent\"], 1)) + \"%\":>{dw//3-1}}{Fx.ub} {THEME.inactive_fg}{\"⡀\"*(dw//3)}'\n\t\t\t\t\tf'{Mv.l(dw//3)}{THEME.proc_misc}{Graphs.detailed_mem(None if cls.moved else proc.details_mem[-1])} '\n\t\t\t\t\tf'{THEME.title}{Fx.b}{proc.details[\"memory_bytes\"]:.{dw//3 - 2}}{THEME.main_fg}{Fx.ub}')\n\t\t\tcy = dy + (4 if len(proc.details[\"cmdline\"]) > dw - 5 else 5)\n\t\t\tfor i in range(ceil(len(proc.details[\"cmdline\"]) / (dw - 5))):\n\t\t\t\tout += f'{Mv.to(cy+i, dx + 3)}{proc.details[\"cmdline\"][((dw-5)*i):][:(dw-5)]:{\"^\" if i == 0 else \"<\"}{dw-5}}'\n\t\t\t\tif i == 2: break\n\n\t\t#* Checking for selection out of bounds\n\t\tif cls.start > proc.num_procs - cls.select_max + 1 and proc.num_procs > cls.select_max: cls.start = proc.num_procs - cls.select_max + 1\n\t\telif cls.start > proc.num_procs: cls.start = proc.num_procs\n\t\tif cls.start < 1: cls.start = 1\n\t\tif cls.selected > proc.num_procs and proc.num_procs < cls.select_max: cls.selected = proc.num_procs\n\t\telif cls.selected > cls.select_max: cls.selected = cls.select_max\n\t\tif cls.selected < 0: cls.selected = 0\n\n\t\t#* Start iteration over all processes and info\n\t\tcy = 1\n\t\tfor n, (pid, items) in enumerate(proc.processes.items(), start=1):\n\t\t\tif n < cls.start: continue\n\t\t\tl_count += 1\n\t\t\tif l_count == cls.selected:\n\t\t\t\tis_selected = True\n\t\t\t\tcls.selected_pid = pid\n\t\t\telse: is_selected = False\n\n\t\t\tindent, name, cmd, threads, username, mem, mem_b, cpu = [items.get(v, d) for v, d in [(\"indent\", \"\"), (\"name\", \"\"), (\"cmd\", \"\"), (\"threads\", 0), (\"username\", \"?\"), (\"mem\", 0.0), (\"mem_b\", 0), (\"cpu\", 0.0)]]\n\n\t\t\tif CONFIG.proc_tree:\n\t\t\t\targ_len = 0\n\t\t\t\toffset = tree_len - len(f'{indent}{pid}')\n\t\t\t\tif offset < 1: offset = 0\n\t\t\t\tindent = f'{indent:.{tree_len - len(str(pid))}}'\n\t\t\t\tif offset - len(name) > 12:\n\t\t\t\t\tcmd = cmd.split(\" \")[0].split(\"/\")[-1]\n\t\t\t\t\tif not cmd.startswith(name):\n\t\t\t\t\t\toffset = len(name)\n\t\t\t\t\t\targ_len = tree_len - len(f'{indent}{pid} {name} ') + 2\n\t\t\t\t\t\tcmd = f'({cmd[:(arg_len-4)]})'\n\t\t\telse:\n\t\t\t\toffset = prog_len - 1\n\t\t\tif cpu > 1.0 or pid in Graphs.pid_cpu:\n\t\t\t\tif pid not in Graphs.pid_cpu:\n\t\t\t\t\tGraphs.pid_cpu[pid] = Graph(5, 1, None, [0])\n\t\t\t\t\tcls.pid_counter[pid] = 0\n\t\t\t\telif cpu < 1.0:\n\t\t\t\t\tcls.pid_counter[pid] += 1\n\t\t\t\t\tif cls.pid_counter[pid] > 10:\n\t\t\t\t\t\tdel cls.pid_counter[pid], Graphs.pid_cpu[pid]\n\t\t\t\telse:\n\t\t\t\t\tcls.pid_counter[pid] = 0\n\n\t\t\tend = f'{THEME.main_fg}{Fx.ub}' if CONFIG.proc_colors else Fx.ub\n\t\t\tif cls.selected > cy: calc = cls.selected - cy\n\t\t\telif 0 < cls.selected <= cy: calc = cy - cls.selected\n\t\t\telse: calc = cy\n\t\t\tif CONFIG.proc_colors and not is_selected:\n\t\t\t\tvals = []\n\t\t\t\tfor v in [int(cpu), int(mem), int(threads // 3)]:\n\t\t\t\t\tif CONFIG.proc_gradient:\n\t\t\t\t\t\tval = ((v if v <= 100 else 100) + 100) - calc * 100 // cls.select_max\n\t\t\t\t\t\tvals += [f'{THEME.gradient[\"proc_color\" if val < 100 else \"process\"][val if val < 100 else val - 100]}']\n\t\t\t\t\telse:\n\t\t\t\t\t\tvals += [f'{THEME.gradient[\"process\"][v if v <= 100 else 100]}']\n\t\t\t\tc_color, m_color, t_color = vals\n\t\t\telse:\n\t\t\t\tc_color = m_color = t_color = Fx.b\n\t\t\tif CONFIG.proc_gradient and not is_selected:\n\t\t\t\tg_color = f'{THEME.gradient[\"proc\"][calc * 100 // cls.select_max]}'\n\t\t\tif is_selected:\n\t\t\t\tc_color = m_color = t_color = g_color = end = \"\"\n\t\t\t\tout += f'{THEME.selected_bg}{THEME.selected_fg}{Fx.b}'\n\n\t\t\t#* Creates one line for a process with all gathered information\n\t\t\tout += (f'{Mv.to(y+cy, x)}{g_color}{indent}{pid:>{(1 if CONFIG.proc_tree else 7)}} ' +\n\t\t\t\tf'{c_color}{name:<{offset}.{offset}} {end}' +\n\t\t\t\t(f'{g_color}{cmd:<{arg_len}.{arg_len-1}}' if arg_len else \"\") +\n\t\t\t\t(t_color + (f'{threads:>4} ' if threads < 1000 else \"999> \") + end if tr_show else \"\") +\n\t\t\t\t(g_color + (f'{username:<9.9}' if len(username) < 10 else f'{username[:8]:<8}+') if usr_show else \"\") +\n\t\t\t\tm_color + ((f'{mem:>4.1f}' if mem < 100 else f'{mem:>4.0f} ') if not CONFIG.proc_mem_bytes else f'{floating_humanizer(mem_b, short=True):>4.4}') + end +\n\t\t\t\tf' {THEME.inactive_fg}{\"⡀\"*5}{THEME.main_fg}{g_color}{c_color}' + (f' {cpu:>4.1f} ' if cpu < 100 else f'{cpu:>5.0f} ') + end +\n\t\t\t\t(\" \" if proc.num_procs > cls.select_max else \"\"))\n\n\t\t\t#* Draw small cpu graph for process if cpu usage was above 1% in the last 10 updates\n\t\t\tif pid in Graphs.pid_cpu:\n\t\t\t\tout += f'{Mv.to(y+cy, x + w - (12 if proc.num_procs > cls.select_max else 11))}{c_color if CONFIG.proc_colors else THEME.proc_misc}{Graphs.pid_cpu[pid](None if cls.moved else round(cpu))}{THEME.main_fg}'\n\n\t\t\tif is_selected: out += f'{Fx.ub}{Term.fg}{Term.bg}{Mv.to(y+cy, x + w - 1)}{\" \" if proc.num_procs > cls.select_max else \"\"}'\n\n\t\t\tcy += 1\n\t\t\tif cy == h: break\n\t\tif cy < h:\n\t\t\tfor i in range(h-cy):\n\t\t\t\tout += f'{Mv.to(y+cy+i, x)}{\" \" * w}'\n\n\t\t#* Draw scrollbar if needed\n\t\tif proc.num_procs > cls.select_max:\n\t\t\tif cls.resized:\n\t\t\t\tKey.mouse[\"mouse_scroll_up\"] = [[x+w-2+i, y] for i in range(3)]\n\t\t\t\tKey.mouse[\"mouse_scroll_down\"] = [[x+w-2+i, y+h-1] for i in range(3)]\n\t\t\tscroll_pos = round(cls.start * (cls.select_max - 2) / (proc.num_procs - (cls.select_max - 2)))\n\t\t\tif scroll_pos < 0 or cls.start == 1: scroll_pos = 0\n\t\t\telif scroll_pos > h - 3 or cls.start >= proc.num_procs - cls.select_max: scroll_pos = h - 3\n\t\t\tout += (f'{Mv.to(y, x+w-1)}{Fx.b}{THEME.main_fg}↑{Mv.to(y+h-1, x+w-1)}↓{Fx.ub}'\n\t\t\t\t\tf'{Mv.to(y+1+scroll_pos, x+w-1)}█')\n\t\telif \"scroll_up\" in Key.mouse:\n\t\t\tdel Key.mouse[\"scroll_up\"], Key.mouse[\"scroll_down\"]\n\n\t\t#* Draw current selection and number of processes\n\t\tout += (f'{Mv.to(y+h, x + w - 3 - len(loc_string))}{THEME.proc_box}{Symbol.title_left}{THEME.title}'\n\t\t\t\t\tf'{Fx.b}{loc_string}{Fx.ub}{THEME.proc_box(Symbol.title_right)}')\n\n\t\t#* Clean up dead processes graphs and counters\n\t\tcls.count += 1\n\t\tif cls.count == 100:\n\t\t\tcls.count = 0\n\t\t\tfor p in list(cls.pid_counter):\n\t\t\t\tif not psutil.pid_exists(p):\n\t\t\t\t\tdel cls.pid_counter[p], Graphs.pid_cpu[p]\n\n\t\tDraw.buffer(cls.buffer, f'{out_misc}{out}{Term.fg}', only_save=Menu.active)\n\t\tcls.redraw = cls.resized = cls.moved = False\n\nclass Collector:\n\t'''Data collector master class\n\t* .start(): Starts collector thread\n\t* .stop(): Stops collector thread\n\t* .collect(*collectors: Collector, draw_now: bool = True, interrupt: bool = False): queues up collectors to run'''\n\tstopping: bool = False\n\tstarted: bool = False\n\tdraw_now: bool = False\n\tredraw: bool = False\n\tonly_draw: bool = False\n\tthread: threading.Thread\n\tcollect_run = threading.Event()\n\tcollect_idle = threading.Event()\n\tcollect_idle.set()\n\tcollect_done = threading.Event()\n\tcollect_queue: List = []\n\tcollect_interrupt: bool = False\n\tproc_interrupt: bool = False\n\tuse_draw_list: bool = False\n\tproc_counter: int = 1\n\n\t@classmethod\n\tdef start(cls):\n\t\tcls.stopping = False\n\t\tcls.thread = threading.Thread(target=cls._runner, args=())\n\t\tcls.thread.start()\n\t\tcls.started = True\n\n\t@classmethod\n\tdef stop(cls):\n\t\tif cls.started and cls.thread.is_alive():\n\t\t\tcls.stopping = True\n\t\t\tcls.started = False\n\t\t\tcls.collect_queue = []\n\t\t\tcls.collect_idle.set()\n\t\t\tcls.collect_done.set()\n\t\t\ttry:\n\t\t\t\tcls.thread.join()\n\t\t\texcept:\n\t\t\t\tpass\n\n\t@classmethod\n\tdef _runner(cls):\n\t\t'''This is meant to run in it's own thread, collecting and drawing when collect_run is set'''\n\t\tdraw_buffers: List[str] = []\n\t\tdebugged: bool = False\n\t\ttry:\n\t\t\twhile not cls.stopping:\n\t\t\t\tif CONFIG.draw_clock and CONFIG.update_ms != 1000: Box.draw_clock()\n\t\t\t\tcls.collect_run.wait(0.1)\n\t\t\t\tif not cls.collect_run.is_set():\n\t\t\t\t\tcontinue\n\t\t\t\tdraw_buffers = []\n\t\t\t\tcls.collect_interrupt = False\n\t\t\t\tcls.collect_run.clear()\n\t\t\t\tcls.collect_idle.clear()\n\t\t\t\tcls.collect_done.clear()\n\t\t\t\tif DEBUG and not debugged: TimeIt.start(\"Collect and draw\")\n\t\t\t\twhile cls.collect_queue:\n\t\t\t\t\tcollector = cls.collect_queue.pop()\n\t\t\t\t\tif not cls.only_draw:\n\t\t\t\t\t\tcollector._collect()\n\t\t\t\t\tcollector._draw()\n\t\t\t\t\tif cls.use_draw_list: draw_buffers.append(collector.buffer)\n\t\t\t\t\tif cls.collect_interrupt: break\n\t\t\t\tif DEBUG and not debugged: TimeIt.stop(\"Collect and draw\"); debugged = True\n\t\t\t\tif cls.draw_now and not Menu.active and not cls.collect_interrupt:\n\t\t\t\t\tif cls.use_draw_list: Draw.out(*draw_buffers)\n\t\t\t\t\telse: Draw.out()\n\t\t\t\tif CONFIG.draw_clock and CONFIG.update_ms == 1000: Box.draw_clock()\n\t\t\t\tcls.collect_idle.set()\n\t\t\t\tcls.collect_done.set()\n\t\texcept Exception as e:\n\t\t\terrlog.exception(f'Data collection thread failed with exception: {e}')\n\t\t\tcls.collect_idle.set()\n\t\t\tcls.collect_done.set()\n\t\t\tclean_quit(1, thread=True)\n\n\t@classmethod\n\tdef collect(cls, *collectors, draw_now: bool = True, interrupt: bool = False, proc_interrupt: bool = False, redraw: bool = False, only_draw: bool = False):\n\t\t'''Setup collect queue for _runner'''\n\t\tcls.collect_interrupt = interrupt\n\t\tcls.proc_interrupt = proc_interrupt\n\t\tcls.collect_idle.wait()\n\t\tcls.collect_interrupt = False\n\t\tcls.proc_interrupt = False\n\t\tcls.use_draw_list = False\n\t\tcls.draw_now = draw_now\n\t\tcls.redraw = redraw\n\t\tcls.only_draw = only_draw\n\n\t\tif collectors:\n\t\t\tcls.collect_queue = [*collectors]\n\t\t\tcls.use_draw_list = True\n\t\t\tif ProcCollector in cls.collect_queue:\n\t\t\t\tcls.proc_counter = 1\n\n\t\telse:\n\t\t\tcls.collect_queue = list(cls.__subclasses__())\n\t\t\tif CONFIG.proc_update_mult > 1:\n\t\t\t\tif cls.proc_counter > 1:\n\t\t\t\t\tcls.collect_queue.remove(ProcCollector)\n\t\t\t\tif cls.proc_counter == CONFIG.proc_update_mult:\n\t\t\t\t\tcls.proc_counter = 0\n\t\t\t\tcls.proc_counter += 1\n\n\t\tcls.collect_run.set()\n\n\nclass CpuCollector(Collector):\n\t'''Collects cpu usage for cpu and cores, cpu frequency, load_avg, uptime and cpu temps'''\n\tcpu_usage: List[List[int]] = []\n\tcpu_upper: List[int] = []\n\tcpu_lower: List[int] = []\n\tcpu_temp: List[List[int]] = []\n\tcpu_temp_high: int = 0\n\tcpu_temp_crit: int = 0\n\tfor _ in range(THREADS + 1):\n\t\tcpu_usage.append([])\n\t\tcpu_temp.append([])\n\tfreq_error: bool = False\n\tcpu_freq: int = 0\n\tload_avg: List[float] = []\n\tuptime: str = \"\"\n\tbuffer: str = CpuBox.buffer\n\tsensor_method: str = \"\"\n\tgot_sensors: bool = False\n\tsensor_swap: bool = False\n\tcpu_temp_only: bool = False\n\n\t@classmethod\n\tdef get_sensors(cls):\n\t\t'''Check if we can get cpu temps and return method of getting temps'''\n\t\tcls.sensor_method = \"\"\n\t\tif SYSTEM == \"MacOS\":\n\t\t\ttry:\n\t\t\t\tif which(\"coretemp\") and subprocess.check_output([\"coretemp\", \"-p\"], universal_newlines=True).strip().replace(\"-\", \"\").isdigit():\n\t\t\t\t\tcls.sensor_method = \"coretemp\"\n\t\t\t\telif which(\"osx-cpu-temp\") and subprocess.check_output(\"osx-cpu-temp\", universal_newlines=True).rstrip().endswith(\"°C\"):\n\t\t\t\t\tcls.sensor_method = \"osx-cpu-temp\"\n\t\t\texcept: pass\n\t\telif CONFIG.cpu_sensor != \"Auto\" and CONFIG.cpu_sensor in CONFIG.cpu_sensors:\n\t\t\tcls.sensor_method = \"psutil\"\n\t\telif hasattr(psutil, \"sensors_temperatures\"):\n\t\t\ttry:\n\t\t\t\ttemps = psutil.sensors_temperatures()\n\t\t\t\tif temps:\n\t\t\t\t\tfor name, entries in temps.items():\n\t\t\t\t\t\tif name.lower().startswith(\"cpu\"):\n\t\t\t\t\t\t\tcls.sensor_method = \"psutil\"\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tfor entry in entries:\n\t\t\t\t\t\t\tif entry.label.startswith((\"Package\", \"Core 0\", \"Tdie\", \"CPU\")):\n\t\t\t\t\t\t\t\tcls.sensor_method = \"psutil\"\n\t\t\t\t\t\t\t\tbreak\n\t\t\texcept: pass\n\t\tif not cls.sensor_method and SYSTEM == \"Linux\":\n\t\t\ttry:\n\t\t\t\tif which(\"vcgencmd\") and subprocess.check_output([\"vcgencmd\", \"measure_temp\"], universal_newlines=True).strip().endswith(\"'C\"):\n\t\t\t\t\tcls.sensor_method = \"vcgencmd\"\n\t\t\texcept: pass\n\t\tcls.got_sensors = bool(cls.sensor_method)\n\n\t@classmethod\n\tdef _collect(cls):\n\t\tcls.cpu_usage[0].append(ceil(psutil.cpu_percent(percpu=False)))\n\t\tif len(cls.cpu_usage[0]) > Term.width * 4:\n\t\t\tdel cls.cpu_usage[0][0]\n\n\t\tcpu_times_percent = psutil.cpu_times_percent()\n\t\tfor x in [\"upper\", \"lower\"]:\n\t\t\tif getattr(CONFIG, \"cpu_graph_\" + x) == \"total\":\n\t\t\t\tsetattr(cls, \"cpu_\" + x, cls.cpu_usage[0])\n\t\t\telse:\n\t\t\t\tgetattr(cls, \"cpu_\" + x).append(ceil(getattr(cpu_times_percent, getattr(CONFIG, \"cpu_graph_\" + x))))\n\t\t\tif len(getattr(cls, \"cpu_\" + x)) > Term.width * 4:\n\t\t\t\tdel getattr(cls, \"cpu_\" + x)[0]\n\n\t\tfor n, thread in enumerate(psutil.cpu_percent(percpu=True), start=1):\n\t\t\tcls.cpu_usage[n].append(ceil(thread))\n\t\t\tif len(cls.cpu_usage[n]) > Term.width * 2:\n\t\t\t\tdel cls.cpu_usage[n][0]\n\t\ttry:\n\t\t\tif CONFIG.show_cpu_freq and hasattr(psutil.cpu_freq(), \"current\"):\n\t\t\t\tfreq: float = psutil.cpu_freq().current\n\t\t\t\tcls.cpu_freq = round(freq * (1 if freq > 10 else 1000))\n\t\t\telif cls.cpu_freq > 0:\n\t\t\t\tcls.cpu_freq = 0\n\t\texcept Exception as e:\n\t\t\tif not cls.freq_error:\n\t\t\t\tcls.freq_error = True\n\t\t\t\terrlog.error(\"Exception while getting cpu frequency!\")\n\t\t\t\terrlog.exception(f'{e}')\n\t\t\telse:\n\t\t\t\tpass\n\t\tcls.load_avg = [round(lavg, 2) for lavg in psutil.getloadavg()]\n\t\tcls.uptime = str(timedelta(seconds=round(time()-psutil.boot_time(),0)))[:-3].replace(\" days,\", \"d\").replace(\" day,\", \"d\")\n\n\t\tif CONFIG.check_temp and cls.got_sensors:\n\t\t\tcls._collect_temps()\n\n\t@classmethod\n\tdef _collect_temps(cls):\n\t\ttemp: int = 1000\n\t\tcores: List[int] = []\n\t\tcore_dict: Dict[int, int] = {}\n\t\tentry_int: int = 0\n\t\tcpu_type: str = \"\"\n\t\tc_max: int = 0\n\t\ts_name: str = \"_-_\"\n\t\ts_label: str = \"_-_\"\n\t\tif cls.sensor_method == \"psutil\":\n\t\t\ttry:\n\t\t\t\tif CONFIG.cpu_sensor != \"Auto\":\n\t\t\t\t\ts_name, s_label = CONFIG.cpu_sensor.split(\":\", 1)\n\t\t\t\tfor name, entries in psutil.sensors_temperatures().items():\n\t\t\t\t\tfor num, entry in enumerate(entries, 1):\n\t\t\t\t\t\tif name == s_name and (entry.label == s_label or str(num) == s_label):\n\t\t\t\t\t\t\tif entry.label.startswith(\"Package\"):\n\t\t\t\t\t\t\t\tcpu_type = \"intel\"\n\t\t\t\t\t\t\telif entry.label.startswith(\"Tdie\"):\n\t\t\t\t\t\t\t\tcpu_type = \"ryzen\"\n\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\tcpu_type = \"other\"\n\t\t\t\t\t\t\tif getattr(entry, \"high\", None) != None and entry.high > 1: cls.cpu_temp_high = round(entry.high)\n\t\t\t\t\t\t\telse: cls.cpu_temp_high = 80\n\t\t\t\t\t\t\tif getattr(entry, \"critical\", None) != None and entry.critical > 1: cls.cpu_temp_crit = round(entry.critical)\n\t\t\t\t\t\t\telse: cls.cpu_temp_crit = 95\n\t\t\t\t\t\t\ttemp = round(entry.current)\n\t\t\t\t\t\telif entry.label.startswith((\"Package\", \"Tdie\")) and cpu_type in [\"\", \"other\"] and s_name == \"_-_\" and hasattr(entry, \"current\"):\n\t\t\t\t\t\t\tif not cls.cpu_temp_high or cls.sensor_swap or cpu_type == \"other\":\n\t\t\t\t\t\t\t\tcls.sensor_swap = False\n\t\t\t\t\t\t\t\tif getattr(entry, \"high\", None) != None and entry.high > 1: cls.cpu_temp_high = round(entry.high)\n\t\t\t\t\t\t\t\telse: cls.cpu_temp_high = 80\n\t\t\t\t\t\t\t\tif getattr(entry, \"critical\", None) != None and entry.critical > 1: cls.cpu_temp_crit = round(entry.critical)\n\t\t\t\t\t\t\t\telse: cls.cpu_temp_crit = 95\n\t\t\t\t\t\t\tcpu_type = \"intel\" if entry.label.startswith(\"Package\") else \"ryzen\"\n\t\t\t\t\t\t\ttemp = round(entry.current)\n\t\t\t\t\t\telif (entry.label.startswith((\"Core\", \"Tccd\", \"CPU\")) or (name.lower().startswith(\"cpu\") and not entry.label)) and hasattr(entry, \"current\"):\n\t\t\t\t\t\t\tif entry.label.startswith((\"Core\", \"Tccd\")):\n\t\t\t\t\t\t\t\tentry_int = int(entry.label.replace(\"Core\", \"\").replace(\"Tccd\", \"\"))\n\t\t\t\t\t\t\t\tif entry_int in core_dict and cpu_type != \"ryzen\":\n\t\t\t\t\t\t\t\t\tif c_max == 0:\n\t\t\t\t\t\t\t\t\t\tc_max = max(core_dict) + 1\n\t\t\t\t\t\t\t\t\tif c_max < THREADS // 2 and (entry_int + c_max) not in core_dict:\n\t\t\t\t\t\t\t\t\t\tcore_dict[(entry_int + c_max)] = round(entry.current)\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\telif entry_int in core_dict:\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\tcore_dict[entry_int] = round(entry.current)\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\telif cpu_type in [\"intel\", \"ryzen\"]:\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\tif not cpu_type:\n\t\t\t\t\t\t\t\tcpu_type = \"other\"\n\t\t\t\t\t\t\t\tif not cls.cpu_temp_high or cls.sensor_swap:\n\t\t\t\t\t\t\t\t\tcls.sensor_swap = False\n\t\t\t\t\t\t\t\t\tif getattr(entry, \"high\", None) != None and entry.high > 1: cls.cpu_temp_high = round(entry.high)\n\t\t\t\t\t\t\t\t\telse: cls.cpu_temp_high = 60 if name == \"cpu_thermal\" else 80\n\t\t\t\t\t\t\t\t\tif getattr(entry, \"critical\", None) != None and entry.critical > 1: cls.cpu_temp_crit = round(entry.critical)\n\t\t\t\t\t\t\t\t\telse: cls.cpu_temp_crit = 80 if name == \"cpu_thermal\" else 95\n\t\t\t\t\t\t\t\ttemp = round(entry.current)\n\t\t\t\t\t\t\tcores.append(round(entry.current))\n\t\t\t\tif core_dict:\n\t\t\t\t\tif not temp or temp == 1000:\n\t\t\t\t\t\ttemp = sum(core_dict.values()) // len(core_dict)\n\t\t\t\t\tif not cls.cpu_temp_high or not cls.cpu_temp_crit:\n\t\t\t\t\t\tcls.cpu_temp_high, cls.cpu_temp_crit = 80, 95\n\t\t\t\t\tcls.cpu_temp[0].append(temp)\n\t\t\t\t\tif cpu_type == \"ryzen\":\n\t\t\t\t\t\tccds: int = len(core_dict)\n\t\t\t\t\t\tcores_per_ccd: int = CORES // ccds\n\t\t\t\t\t\tz: int = 1\n\t\t\t\t\t\tfor x in range(THREADS):\n\t\t\t\t\t\t\tif x == CORES:\n\t\t\t\t\t\t\t\tz = 1\n\t\t\t\t\t\t\tif CORE_MAP[x] + 1 > cores_per_ccd * z:\n\t\t\t\t\t\t\t\tz += 1\n\t\t\t\t\t\t\tif z in core_dict:\n\t\t\t\t\t\t\t\tcls.cpu_temp[x+1].append(core_dict[z])\n\t\t\t\t\telse:\n\t\t\t\t\t\tfor x in range(THREADS):\n\t\t\t\t\t\t\tif CORE_MAP[x] in core_dict:\n\t\t\t\t\t\t\t\tcls.cpu_temp[x+1].append(core_dict[CORE_MAP[x]])\n\n\t\t\t\telif len(cores) == THREADS / 2:\n\t\t\t\t\tcls.cpu_temp[0].append(temp)\n\t\t\t\t\tfor n, t in enumerate(cores, start=1):\n\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\tcls.cpu_temp[n].append(t)\n\t\t\t\t\t\t\tcls.cpu_temp[THREADS // 2 + n].append(t)\n\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\tbreak\n\n\t\t\t\telse:\n\t\t\t\t\tcls.cpu_temp[0].append(temp)\n\t\t\t\t\tif len(cores) > 1:\n\t\t\t\t\t\tfor n, t in enumerate(cores, start=1):\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tcls.cpu_temp[n].append(t)\n\t\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\t\tbreak\n\t\t\texcept Exception as e:\n\t\t\t\terrlog.exception(f'{e}')\n\t\t\t\tcls.got_sensors = False\n\t\t\t\tCpuBox._calc_size()\n\n\t\telse:\n\t\t\ttry:\n\t\t\t\tif cls.sensor_method == \"coretemp\":\n\t\t\t\t\ttemp = max(0, int(subprocess.check_output([\"coretemp\", \"-p\"], universal_newlines=True).strip()))\n\t\t\t\t\tcores = [max(0, int(x)) for x in subprocess.check_output(\"coretemp\", universal_newlines=True).split()]\n\t\t\t\t\tif len(cores) == THREADS / 2:\n\t\t\t\t\t\tcls.cpu_temp[0].append(temp)\n\t\t\t\t\t\tfor n, t in enumerate(cores, start=1):\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tcls.cpu_temp[n].append(t)\n\t\t\t\t\t\t\t\tcls.cpu_temp[THREADS // 2 + n].append(t)\n\t\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\telse:\n\t\t\t\t\t\tcores.insert(0, temp)\n\t\t\t\t\t\tfor n, t in enumerate(cores):\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tcls.cpu_temp[n].append(t)\n\t\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\tif not cls.cpu_temp_high:\n\t\t\t\t\t\tcls.cpu_temp_high = 85\n\t\t\t\t\t\tcls.cpu_temp_crit = 100\n\t\t\t\telif cls.sensor_method == \"osx-cpu-temp\":\n\t\t\t\t\ttemp = max(0, round(float(subprocess.check_output(\"osx-cpu-temp\", universal_newlines=True).strip()[:-2])))\n\t\t\t\t\tif not cls.cpu_temp_high:\n\t\t\t\t\t\tcls.cpu_temp_high = 85\n\t\t\t\t\t\tcls.cpu_temp_crit = 100\n\t\t\t\telif cls.sensor_method == \"vcgencmd\":\n\t\t\t\t\ttemp = max(0, round(float(subprocess.check_output([\"vcgencmd\", \"measure_temp\"], universal_newlines=True).strip()[5:-2])))\n\t\t\t\t\tif not cls.cpu_temp_high:\n\t\t\t\t\t\tcls.cpu_temp_high = 60\n\t\t\t\t\t\tcls.cpu_temp_crit = 80\n\t\t\texcept Exception as e:\n\t\t\t\terrlog.exception(f'{e}')\n\t\t\t\tcls.got_sensors = False\n\t\t\t\tCpuBox._calc_size()\n\t\t\telse:\n\t\t\t\tif not cores:\n\t\t\t\t\tcls.cpu_temp[0].append(temp)\n\n\t\tif not core_dict and len(cores) <= 1:\n\t\t\tcls.cpu_temp_only = True\n\t\tif len(cls.cpu_temp[0]) > 5:\n\t\t\tfor n in range(len(cls.cpu_temp)):\n\t\t\t\tif cls.cpu_temp[n]:\n\t\t\t\t\tdel cls.cpu_temp[n][0]\n\n\t@classmethod\n\tdef _draw(cls):\n\t\tCpuBox._draw_fg()\n\nclass MemCollector(Collector):\n\t'''Collects memory and disks information'''\n\tvalues: Dict[str, int] = {}\n\tvlist: Dict[str, List[int]] = {}\n\tpercent: Dict[str, int] = {}\n\tstring: Dict[str, str] = {}\n\n\tswap_values: Dict[str, int] = {}\n\tswap_vlist: Dict[str, List[int]] = {}\n\tswap_percent: Dict[str, int] = {}\n\tswap_string: Dict[str, str] = {}\n\n\tdisks: Dict[str, Dict]\n\tdisk_hist: Dict[str, Tuple] = {}\n\ttimestamp: float = time()\n\tdisks_io_dict: Dict[str, Dict[str, List[int]]] = {}\n\trecheck_diskutil: bool = True\n\tdiskutil_map: Dict[str, str] = {}\n\n\tio_error: bool = False\n\n\told_disks: List[str] = []\n\told_io_disks: List[str] = []\n\n\tfstab_filter: List[str] = []\n\n\texcludes: List[str] = [\"squashfs\", \"nullfs\"]\n\tif SYSTEM == \"BSD\": excludes += [\"devfs\", \"tmpfs\", \"procfs\", \"linprocfs\", \"gvfs\", \"fusefs\"]\n\n\tbuffer: str = MemBox.buffer\n\n\t@classmethod\n\tdef _collect(cls):\n\t\t#* Collect memory\n\t\tmem = psutil.virtual_memory()\n\t\tif hasattr(mem, \"cached\"):\n\t\t\tcls.values[\"cached\"] = mem.cached\n\t\telse:\n\t\t\tcls.values[\"cached\"] = mem.active\n\t\tcls.values[\"total\"], cls.values[\"free\"], cls.values[\"available\"] = mem.total, mem.free, mem.available\n\t\tcls.values[\"used\"] = cls.values[\"total\"] - cls.values[\"available\"]\n\n\t\tfor key, value in cls.values.items():\n\t\t\tcls.string[key] = floating_humanizer(value)\n\t\t\tif key == \"total\": continue\n\t\t\tcls.percent[key] = round(value * 100 / cls.values[\"total\"])\n\t\t\tif CONFIG.mem_graphs:\n\t\t\t\tif not key in cls.vlist: cls.vlist[key] = []\n\t\t\t\tcls.vlist[key].append(cls.percent[key])\n\t\t\t\tif len(cls.vlist[key]) > MemBox.width: del cls.vlist[key][0]\n\n\t\t#* Collect swap\n\t\tif CONFIG.show_swap or CONFIG.swap_disk:\n\t\t\tswap = psutil.swap_memory()\n\t\t\tcls.swap_values[\"total\"], cls.swap_values[\"free\"] = swap.total, swap.free\n\t\t\tcls.swap_values[\"used\"] = cls.swap_values[\"total\"] - cls.swap_values[\"free\"]\n\n\t\t\tif swap.total:\n\t\t\t\tif not MemBox.swap_on:\n\t\t\t\t\tMemBox.redraw = True\n\t\t\t\tMemBox.swap_on = True\n\t\t\t\tfor key, value in cls.swap_values.items():\n\t\t\t\t\tcls.swap_string[key] = floating_humanizer(value)\n\t\t\t\t\tif key == \"total\": continue\n\t\t\t\t\tcls.swap_percent[key] = round(value * 100 / cls.swap_values[\"total\"])\n\t\t\t\t\tif CONFIG.mem_graphs:\n\t\t\t\t\t\tif not key in cls.swap_vlist: cls.swap_vlist[key] = []\n\t\t\t\t\t\tcls.swap_vlist[key].append(cls.swap_percent[key])\n\t\t\t\t\t\tif len(cls.swap_vlist[key]) > MemBox.width: del cls.swap_vlist[key][0]\n\t\t\telse:\n\t\t\t\tif MemBox.swap_on:\n\t\t\t\t\tMemBox.redraw = True\n\t\t\t\tMemBox.swap_on = False\n\t\telse:\n\t\t\tif MemBox.swap_on:\n\t\t\t\tMemBox.redraw = True\n\t\t\tMemBox.swap_on = False\n\n\n\t\tif not CONFIG.show_disks: return\n\t\t#* Collect disks usage\n\t\tdisk_read: int = 0\n\t\tdisk_write: int = 0\n\t\tdev_name: str\n\t\tdisk_name: str\n\t\tfiltering: Tuple = ()\n\t\tfilter_exclude: bool = False\n\t\tio_string_r: str\n\t\tio_string_w: str\n\t\tu_percent: int\n\t\tcls.disks = {}\n\n\t\tif CONFIG.disks_filter:\n\t\t\tif CONFIG.disks_filter.startswith(\"exclude=\"):\n\t\t\t\tfilter_exclude = True\n\t\t\t\tfiltering = tuple(v.strip() for v in CONFIG.disks_filter.replace(\"exclude=\", \"\").strip().split(\",\"))\n\t\t\telse:\n\t\t\t\tfiltering = tuple(v.strip() for v in CONFIG.disks_filter.strip().split(\",\"))\n\t\ttry:\n\t\t\tio_counters = psutil.disk_io_counters(perdisk=SYSTEM != \"BSD\", nowrap=True)\n\t\texcept ValueError as e:\n\t\t\tif not cls.io_error:\n\t\t\t\tcls.io_error = True\n\t\t\t\terrlog.error(f'Non fatal error during disk io collection!')\n\t\t\t\tif psutil.version_info[0] < 5 or (psutil.version_info[0] == 5 and psutil.version_info[1] < 7):\n\t\t\t\t\terrlog.error(f'Caused by outdated psutil version.')\n\t\t\t\terrlog.exception(f'{e}')\n\t\t\tio_counters = None\n\n\t\tif SYSTEM == \"MacOS\" and cls.recheck_diskutil:\n\t\t\tcls.recheck_diskutil = False\n\t\t\ttry:\n\t\t\t\tdutil_out = subprocess.check_output([\"diskutil\", \"list\", \"physical\"], universal_newlines=True)\n\t\t\t\tfor line in dutil_out.split(\"\\n\"):\n\t\t\t\t\tline = line.replace(\"\\u2068\", \"\").replace(\"\\u2069\", \"\")\n\t\t\t\t\tif line.startswith(\"/dev/\"):\n\t\t\t\t\t\txdisk = line.split()[0].replace(\"/dev/\", \"\")\n\t\t\t\t\telif \"Container\" in line:\n\t\t\t\t\t\tydisk = line.split()[3]\n\t\t\t\t\t\tif xdisk and ydisk:\n\t\t\t\t\t\t\tcls.diskutil_map[xdisk] = ydisk\n\t\t\t\t\t\t\txdisk = ydisk = \"\"\n\t\t\texcept:\n\t\t\t\tpass\n\n\t\tif CONFIG.use_fstab and SYSTEM != \"MacOS\" and not cls.fstab_filter:\n\t\t\ttry:\n\t\t\t\twith open('/etc/fstab','r') as fstab:\n\t\t\t\t\tfor line in fstab:\n\t\t\t\t\t\tline = line.strip()\n\t\t\t\t\t\tif line and not line.startswith('#'):\n\t\t\t\t\t\t\tmount_data = (line.split())\n\t\t\t\t\t\t\tif mount_data[2].lower() != \"swap\":\n\t\t\t\t\t\t\t\tcls.fstab_filter += [mount_data[1]]\n\t\t\t\terrlog.debug(f'new fstab_filter set : {cls.fstab_filter}')\n\t\t\texcept IOError:\n\t\t\t\tCONFIG.use_fstab = False\n\t\t\t\terrlog.warning(f'Error reading fstab, use_fstab flag reset to {CONFIG.use_fstab}')\n\t\tif not CONFIG.use_fstab and cls.fstab_filter:\n\t\t\tcls.fstab_filter = []\n\t\t\terrlog.debug(f'use_fstab flag has been turned to {CONFIG.use_fstab}, fstab_filter cleared')\n\n\t\tfor disk in psutil.disk_partitions(all=CONFIG.use_fstab or not CONFIG.only_physical):\n\t\t\tdisk_io = None\n\t\t\tio_string_r = io_string_w = \"\"\n\t\t\tif CONFIG.use_fstab and disk.mountpoint not in cls.fstab_filter:\n\t\t\t\tcontinue\n\t\t\tdisk_name = disk.mountpoint.rsplit('/', 1)[-1] if not disk.mountpoint == \"/\" else \"root\"\n\t\t\tif cls.excludes and disk.fstype in cls.excludes:\n\t\t\t\tcontinue\n\t\t\tif filtering and ((not filter_exclude and not disk.mountpoint in filtering) or (filter_exclude and disk.mountpoint in filtering)):\n\t\t\t\tcontinue\n\t\t\tif SYSTEM == \"MacOS\" and disk.mountpoint == \"/private/var/vm\":\n\t\t\t\tcontinue\n\t\t\ttry:\n\t\t\t\tdisk_u = psutil.disk_usage(disk.mountpoint)\n\t\t\texcept:\n\t\t\t\tpass\n\n\t\t\tu_percent = round(getattr(disk_u, \"percent\", 0))\n\t\t\tcls.disks[disk.device] = { \"name\" : disk_name, \"used_percent\" : u_percent, \"free_percent\" : 100 - u_percent }\n\t\t\tfor name in [\"total\", \"used\", \"free\"]:\n\t\t\t\tcls.disks[disk.device][name] = floating_humanizer(getattr(disk_u, name, 0))\n\n\t\t\t#* Collect disk io\n\t\t\tif io_counters:\n\t\t\t\ttry:\n\t\t\t\t\tif SYSTEM != \"BSD\":\n\t\t\t\t\t\tdev_name = os.path.realpath(disk.device).rsplit('/', 1)[-1]\n\t\t\t\t\t\tif not dev_name in io_counters:\n\t\t\t\t\t\t\tfor names in io_counters:\n\t\t\t\t\t\t\t\tif names in dev_name:\n\t\t\t\t\t\t\t\t\tdisk_io = io_counters[names]\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\tif cls.diskutil_map:\n\t\t\t\t\t\t\t\t\tfor names, items in cls.diskutil_map.items():\n\t\t\t\t\t\t\t\t\t\tif items in dev_name and names in io_counters:\n\t\t\t\t\t\t\t\t\t\t\tdisk_io = io_counters[names]\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tdisk_io = io_counters[dev_name]\n\t\t\t\t\telif disk.mountpoint == \"/\":\n\t\t\t\t\t\tdisk_io = io_counters\n\t\t\t\t\telse:\n\t\t\t\t\t\traise Exception\n\t\t\t\t\tdisk_read = round((disk_io.read_bytes - cls.disk_hist[disk.device][0]) / (time() - cls.timestamp)) #type: ignore\n\t\t\t\t\tdisk_write = round((disk_io.write_bytes - cls.disk_hist[disk.device][1]) / (time() - cls.timestamp)) #type: ignore\n\t\t\t\t\tif not disk.device in cls.disks_io_dict:\n\t\t\t\t\t\tcls.disks_io_dict[disk.device] = {\"read\" : [], \"write\" : [], \"rw\" : []}\n\t\t\t\t\tcls.disks_io_dict[disk.device][\"read\"].append(disk_read >> 20)\n\t\t\t\t\tcls.disks_io_dict[disk.device][\"write\"].append(disk_write >> 20)\n\t\t\t\t\tcls.disks_io_dict[disk.device][\"rw\"].append((disk_read + disk_write) >> 20)\n\n\t\t\t\t\tif len(cls.disks_io_dict[disk.device][\"read\"]) > MemBox.width:\n\t\t\t\t\t\tdel cls.disks_io_dict[disk.device][\"read\"][0], cls.disks_io_dict[disk.device][\"write\"][0], cls.disks_io_dict[disk.device][\"rw\"][0]\n\n\t\t\t\texcept:\n\t\t\t\t\tdisk_read = disk_write = 0\n\t\t\telse:\n\t\t\t\tdisk_read = disk_write = 0\n\n\t\t\tif disk_io:\n\t\t\t\tcls.disk_hist[disk.device] = (disk_io.read_bytes, disk_io.write_bytes)\n\t\t\t\tif CONFIG.io_mode or MemBox.disks_width > 30:\n\t\t\t\t\tif disk_read > 0:\n\t\t\t\t\t\tio_string_r = f'▲{floating_humanizer(disk_read, short=True)}'\n\t\t\t\t\tif disk_write > 0:\n\t\t\t\t\t\tio_string_w = f'▼{floating_humanizer(disk_write, short=True)}'\n\t\t\t\t\tif CONFIG.io_mode:\n\t\t\t\t\t\tcls.disks[disk.device][\"io_r\"] = io_string_r\n\t\t\t\t\t\tcls.disks[disk.device][\"io_w\"] = io_string_w\n\t\t\t\telif disk_read + disk_write > 0:\n\t\t\t\t\tio_string_r += f'▼▲{floating_humanizer(disk_read + disk_write, short=True)}'\n\n\t\t\tcls.disks[disk.device][\"io\"] = io_string_r + (\" \" if io_string_w and io_string_r else \"\") + io_string_w\n\n\t\tif CONFIG.swap_disk and MemBox.swap_on:\n\t\t\tcls.disks[\"__swap\"] = { \"name\" : \"swap\", \"used_percent\" : cls.swap_percent[\"used\"], \"free_percent\" : cls.swap_percent[\"free\"], \"io\" : \"\" }\n\t\t\tfor name in [\"total\", \"used\", \"free\"]:\n\t\t\t\tcls.disks[\"__swap\"][name] = cls.swap_string[name]\n\t\t\tif len(cls.disks) > 2:\n\t\t\t\ttry:\n\t\t\t\t\tnew = { list(cls.disks)[0] : cls.disks.pop(list(cls.disks)[0])}\n\t\t\t\t\tnew[\"__swap\"] = cls.disks.pop(\"__swap\")\n\t\t\t\t\tnew.update(cls.disks)\n\t\t\t\t\tcls.disks = new\n\t\t\t\texcept:\n\t\t\t\t\tpass\n\n\t\tif cls.old_disks != list(cls.disks) or cls.old_io_disks != list(cls.disks_io_dict):\n\t\t\tMemBox.redraw = True\n\t\t\tcls.recheck_diskutil = True\n\t\t\tcls.old_disks = list(cls.disks)\n\t\t\tcls.old_io_disks = list(cls.disks_io_dict)\n\n\t\tcls.timestamp = time()\n\n\t@classmethod\n\tdef _draw(cls):\n\t\tMemBox._draw_fg()\n\nclass NetCollector(Collector):\n\t'''Collects network stats'''\n\tbuffer: str = NetBox.buffer\n\tnics: List[str] = []\n\tnic_i: int = 0\n\tnic: str = \"\"\n\tnew_nic: str = \"\"\n\tnic_error: bool = False\n\treset: bool = False\n\tgraph_raise: Dict[str, int] = {\"download\" : 5, \"upload\" : 5}\n\tgraph_lower: Dict[str, int] = {\"download\" : 5, \"upload\" : 5}\n\t#min_top: int = 10<<10\n\t#* Stats structure = stats[netword device][download, upload][total, last, top, graph_top, offset, speed, redraw, graph_raise, graph_low] = int, List[int], bool\n\tstats: Dict[str, Dict[str, Dict[str, Any]]] = {}\n\t#* Strings structure strings[network device][download, upload][total, byte_ps, bit_ps, top, graph_top] = str\n\tstrings: Dict[str, Dict[str, Dict[str, str]]] = {}\n\tswitched: bool = False\n\ttimestamp: float = time()\n\tnet_min: Dict[str, int] = {\"download\" : -1, \"upload\" : -1}\n\tauto_min: bool = CONFIG.net_auto\n\tnet_iface: str = CONFIG.net_iface\n\tsync_top: int = 0\n\tsync_string: str = \"\"\n\taddress: str = \"\"\n\n\t@classmethod\n\tdef _get_nics(cls):\n\t\t'''Get a list of all network devices sorted by highest throughput'''\n\t\tcls.nic_i = 0\n\t\tcls.nics = []\n\t\tcls.nic = \"\"\n\t\ttry:\n\t\t\tio_all = psutil.net_io_counters(pernic=True)\n\t\texcept Exception as e:\n\t\t\tif not cls.nic_error:\n\t\t\t\tcls.nic_error = True\n\t\t\t\terrlog.exception(f'{e}')\n\t\tif not io_all: return\n\t\tup_stat = psutil.net_if_stats()\n\t\tfor nic in sorted(io_all.keys(), key=lambda nic: (getattr(io_all[nic], \"bytes_recv\", 0) + getattr(io_all[nic], \"bytes_sent\", 0)), reverse=True):\n\t\t\tif nic not in up_stat or not up_stat[nic].isup:\n\t\t\t\tcontinue\n\t\t\tcls.nics.append(nic)\n\t\tif not cls.nics: cls.nics = [\"\"]\n\t\tcls.nic = cls.nics[cls.nic_i]\n\t\tif cls.net_iface and cls.net_iface in cls.nics:\n\t\t\tcls.nic = cls.net_iface\n\t\t\tcls.nic_i = cls.nics.index(cls.nic)\n\n\n\t@classmethod\n\tdef switch(cls, key: str):\n\t\tif cls.net_iface: cls.net_iface = \"\"\n\t\tif len(cls.nics) < 2 and cls.nic in cls.nics:\n\t\t\treturn\n\n\t\tif cls.nic_i == -1:\n\t\t\tcls.nic_i = 0 if key == \"n\" else -1\n\t\telse:\n\t\t\tcls.nic_i += +1 if key == \"n\" else -1\n\n\t\tcls.nic_i %= len(cls.nics)\n\t\tcls.new_nic = cls.nics[cls.nic_i]\n\t\tcls.switched = True\n\t\tCollector.collect(NetCollector, redraw=True)\n\n\t@classmethod\n\tdef _collect(cls):\n\t\tspeed: int\n\t\tstat: Dict\n\t\tup_stat = psutil.net_if_stats()\n\n\t\tif sorted(cls.nics) != sorted(nic for nic in up_stat if up_stat[nic].isup):\n\t\t\told_nic = cls.nic\n\t\t\tcls._get_nics()\n\t\t\tcls.nic = old_nic\n\t\t\tif cls.nic not in cls.nics:\n\t\t\t\tcls.nic_i = -1\n\t\t\telse:\n\t\t\t\tcls.nic_i = cls.nics.index(cls.nic)\n\n\t\tif cls.switched:\n\t\t\tcls.nic = cls.new_nic\n\t\t\tcls.switched = False\n\n\t\tif not cls.nic or cls.nic not in up_stat:\n\t\t\tcls._get_nics()\n\t\t\tif not cls.nic: return\n\t\t\tNetBox.redraw = True\n\t\ttry:\n\t\t\tio_all = psutil.net_io_counters(pernic=True)[cls.nic]\n\t\texcept KeyError:\n\t\t\tpass\n\t\t\treturn\n\t\tif not cls.nic in cls.stats:\n\t\t\tcls.stats[cls.nic] = {}\n\t\t\tcls.strings[cls.nic] = { \"download\" : {}, \"upload\" : {}}\n\t\t\tfor direction, value in [\"download\", io_all.bytes_recv], [\"upload\", io_all.bytes_sent]:\n\t\t\t\tcls.stats[cls.nic][direction] = { \"total\" : value, \"last\" : value, \"top\" : 0, \"graph_top\" : 0, \"offset\" : 0, \"speed\" : [], \"redraw\" : True, \"graph_raise\" : 0, \"graph_lower\" : 7 }\n\t\t\t\tfor v in [\"total\", \"byte_ps\", \"bit_ps\", \"top\", \"graph_top\"]:\n\t\t\t\t\tcls.strings[cls.nic][direction][v] = \"\"\n\n\t\tcls.stats[cls.nic][\"download\"][\"total\"] = io_all.bytes_recv\n\t\tcls.stats[cls.nic][\"upload\"][\"total\"] = io_all.bytes_sent\n\t\tif cls.nic in psutil.net_if_addrs():\n\t\t\tcls.address = getattr(psutil.net_if_addrs()[cls.nic][0], \"address\", \"\")\n\n\t\tfor direction in [\"download\", \"upload\"]:\n\t\t\tstat = cls.stats[cls.nic][direction]\n\t\t\tstrings = cls.strings[cls.nic][direction]\n\t\t\t#* Calculate current speed\n\t\t\tstat[\"speed\"].append(round((stat[\"total\"] - stat[\"last\"]) / (time() - cls.timestamp)))\n\t\t\tstat[\"last\"] = stat[\"total\"]\n\t\t\tspeed = stat[\"speed\"][-1]\n\n\t\t\tif cls.net_min[direction] == -1:\n\t\t\t\tcls.net_min[direction] = units_to_bytes(getattr(CONFIG, \"net_\" + direction))\n\t\t\t\tstat[\"graph_top\"] = cls.net_min[direction]\n\t\t\t\tstat[\"graph_lower\"] = 7\n\t\t\t\tif not cls.auto_min:\n\t\t\t\t\tstat[\"redraw\"] = True\n\t\t\t\t\tstrings[\"graph_top\"] = floating_humanizer(stat[\"graph_top\"], short=True)\n\n\t\t\tif stat[\"offset\"] and stat[\"offset\"] > stat[\"total\"]:\n\t\t\t\tcls.reset = True\n\n\t\t\tif cls.reset:\n\t\t\t\tif not stat[\"offset\"]:\n\t\t\t\t\tstat[\"offset\"] = stat[\"total\"]\n\t\t\t\telse:\n\t\t\t\t\tstat[\"offset\"] = 0\n\t\t\t\tif direction == \"upload\":\n\t\t\t\t\tcls.reset = False\n\t\t\t\t\tNetBox.redraw = True\n\n\t\t\tif len(stat[\"speed\"]) > NetBox.width * 2:\n\t\t\t\tdel stat[\"speed\"][0]\n\n\t\t\tstrings[\"total\"] = floating_humanizer(stat[\"total\"] - stat[\"offset\"])\n\t\t\tstrings[\"byte_ps\"] = floating_humanizer(stat[\"speed\"][-1], per_second=True)\n\t\t\tstrings[\"bit_ps\"] = floating_humanizer(stat[\"speed\"][-1], bit=True, per_second=True)\n\n\t\t\tif speed > stat[\"top\"] or not stat[\"top\"]:\n\t\t\t\tstat[\"top\"] = speed\n\t\t\t\tstrings[\"top\"] = floating_humanizer(stat[\"top\"], bit=True, per_second=True)\n\n\t\t\tif cls.auto_min:\n\t\t\t\tif speed > stat[\"graph_top\"]:\n\t\t\t\t\tstat[\"graph_raise\"] += 1\n\t\t\t\t\tif stat[\"graph_lower\"] > 0: stat[\"graph_lower\"] -= 1\n\t\t\t\telif speed < stat[\"graph_top\"] // 10:\n\t\t\t\t\tstat[\"graph_lower\"] += 1\n\t\t\t\t\tif stat[\"graph_raise\"] > 0: stat[\"graph_raise\"] -= 1\n\n\t\t\t\tif stat[\"graph_raise\"] >= 5 or stat[\"graph_lower\"] >= 5:\n\t\t\t\t\tif stat[\"graph_raise\"] >= 5:\n\t\t\t\t\t\tstat[\"graph_top\"] = round(max(stat[\"speed\"][-5:]) / 0.8)\n\t\t\t\t\telif stat[\"graph_lower\"] >= 5:\n\t\t\t\t\t\tstat[\"graph_top\"] = max(10 << 10, max(stat[\"speed\"][-5:]) * 3)\n\t\t\t\t\tstat[\"graph_raise\"] = 0\n\t\t\t\t\tstat[\"graph_lower\"] = 0\n\t\t\t\t\tstat[\"redraw\"] = True\n\t\t\t\t\tstrings[\"graph_top\"] = floating_humanizer(stat[\"graph_top\"], short=True)\n\n\t\tcls.timestamp = time()\n\n\t\tif CONFIG.net_sync:\n\t\t\tc_max: int = max(cls.stats[cls.nic][\"download\"][\"graph_top\"], cls.stats[cls.nic][\"upload\"][\"graph_top\"])\n\t\t\tif c_max != cls.sync_top:\n\t\t\t\tcls.sync_top = c_max\n\t\t\t\tcls.sync_string = floating_humanizer(cls.sync_top, short=True)\n\t\t\t\tNetBox.redraw = True\n\n\t@classmethod\n\tdef _draw(cls):\n\t\tNetBox._draw_fg()\n\n\nclass ProcCollector(Collector):\n\t'''Collects process stats'''\n\tbuffer: str = ProcBox.buffer\n\tsearch_filter: str = \"\"\n\tcase_sensitive: bool = False\n\tprocesses: Dict = {}\n\tnum_procs: int = 0\n\tdet_cpu: float = 0.0\n\tdetailed: bool = False\n\tdetailed_pid: Union[int, None] = None\n\tdetails: Dict[str, Any] = {}\n\tdetails_cpu: List[int] = []\n\tdetails_mem: List[int] = []\n\texpand: int = 0\n\tcollapsed: Dict = {}\n\ttree_counter: int = 0\n\tp_values: List[str] = [\"pid\", \"name\", \"cmdline\", \"num_threads\", \"username\", \"memory_percent\", \"cpu_percent\", \"cpu_times\", \"create_time\"]\n\tsort_expr: Dict = {}\n\tsort_expr[\"pid\"] = compile(\"p.info['pid']\", \"str\", \"eval\")\n\tsort_expr[\"program\"] = compile(\"'' if p.info['name'] == 0.0 else p.info['name']\", \"str\", \"eval\")\n\tsort_expr[\"arguments\"] = compile(\"' '.join(str(p.info['cmdline'])) or ('' if p.info['name'] == 0.0 else p.info['name'])\", \"str\", \"eval\")\n\tsort_expr[\"threads\"] = compile(\"0 if p.info['num_threads'] == 0.0 else p.info['num_threads']\", \"str\", \"eval\")\n\tsort_expr[\"user\"] = compile(\"'' if p.info['username'] == 0.0 else p.info['username']\", \"str\", \"eval\")\n\tsort_expr[\"memory\"] = compile(\"p.info['memory_percent']\", \"str\", \"eval\")\n\tsort_expr[\"cpu lazy\"] = compile(\"(sum(p.info['cpu_times'][:2] if not p.info['cpu_times'] == 0.0 else [0.0, 0.0]) * 1000 / (time() - p.info['create_time']))\", \"str\", \"eval\")\n\tsort_expr[\"cpu responsive\"] = compile(\"(p.info['cpu_percent'] if CONFIG.proc_per_core else (p.info['cpu_percent'] / THREADS))\", \"str\", \"eval\")\n\n\t@classmethod\n\tdef _collect(cls):\n\t\t'''List all processes with pid, name, arguments, threads, username, memory percent and cpu percent'''\n\t\tif not \"proc\" in Box.boxes: return\n\t\tout: Dict = {}\n\t\tcls.det_cpu = 0.0\n\t\tsorting: str = CONFIG.proc_sorting\n\t\treverse: bool = not CONFIG.proc_reversed\n\t\tproc_per_cpu: bool = CONFIG.proc_per_core\n\t\tsearch: List[str] = []\n\t\tif cls.search_filter:\n\t\t\tif cls.case_sensitive:\n\t\t\t\tsearch = [i.strip() for i in cls.search_filter.split(\",\")]\n\t\t\telse:\n\t\t\t\tsearch = [i.strip() for i in cls.search_filter.lower().split(\",\")]\n\t\terr: float = 0.0\n\t\tn: int = 0\n\n\t\tif CONFIG.proc_tree and sorting == \"arguments\":\n\t\t\tsorting = \"program\"\n\n\t\tsort_cmd = cls.sort_expr[sorting]\n\n\t\tif CONFIG.proc_tree:\n\t\t\tcls._tree(sort_cmd=sort_cmd, reverse=reverse, proc_per_cpu=proc_per_cpu, search=search)\n\t\telse:\n\t\t\tfor p in sorted(psutil.process_iter(cls.p_values + ([\"memory_info\"] if CONFIG.proc_mem_bytes else []), err), key=lambda p: eval(sort_cmd), reverse=reverse):\n\t\t\t\tif cls.collect_interrupt or cls.proc_interrupt:\n\t\t\t\t\treturn\n\t\t\t\tif p.info[\"name\"] == \"idle\" or p.info[\"name\"] == err or p.info[\"pid\"] == err:\n\t\t\t\t\tcontinue\n\t\t\t\tif p.info[\"cmdline\"] == err:\n\t\t\t\t\tp.info[\"cmdline\"] = \"\"\n\t\t\t\tif p.info[\"username\"] == err:\n\t\t\t\t\tp.info[\"username\"] = \"\"\n\t\t\t\tif p.info[\"num_threads\"] == err:\n\t\t\t\t\tp.info[\"num_threads\"] = 0\n\t\t\t\tif search:\n\t\t\t\t\tif cls.detailed and p.info[\"pid\"] == cls.detailed_pid:\n\t\t\t\t\t\tcls.det_cpu = p.info[\"cpu_percent\"]\n\t\t\t\t\tfor value in [ p.info[\"name\"], \" \".join(p.info[\"cmdline\"]), str(p.info[\"pid\"]), p.info[\"username\"] ]:\n\t\t\t\t\t\tif not cls.case_sensitive:\n\t\t\t\t\t\t\tvalue = value.lower()\n\t\t\t\t\t\tfor s in search:\n\t\t\t\t\t\t\tif s in value:\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\telse: continue\n\t\t\t\t\t\tbreak\n\t\t\t\t\telse: continue\n\n\t\t\t\tcpu = p.info[\"cpu_percent\"] if proc_per_cpu else round(p.info[\"cpu_percent\"] / THREADS, 2)\n\t\t\t\tmem = p.info[\"memory_percent\"]\n\t\t\t\tif CONFIG.proc_mem_bytes and hasattr(p.info[\"memory_info\"], \"rss\"):\n\t\t\t\t\tmem_b = p.info[\"memory_info\"].rss\n\t\t\t\telse:\n\t\t\t\t\tmem_b = 0\n\n\t\t\t\tcmd = \" \".join(p.info[\"cmdline\"]) or \"[\" + p.info[\"name\"] + \"]\"\n\n\t\t\t\tout[p.info[\"pid\"]] = {\n\t\t\t\t\t\"name\" : p.info[\"name\"],\n\t\t\t\t\t\"cmd\" : cmd,\n\t\t\t\t\t\"threads\" : p.info[\"num_threads\"],\n\t\t\t\t\t\"username\" : p.info[\"username\"],\n\t\t\t\t\t\"mem\" : mem,\n\t\t\t\t\t\"mem_b\" : mem_b,\n\t\t\t\t\t\"cpu\" : cpu }\n\n\t\t\t\tn += 1\n\n\t\t\tcls.num_procs = n\n\t\t\tcls.processes = out.copy()\n\n\t\tif cls.detailed:\n\t\t\tcls.expand = ((ProcBox.width - 2) - ((ProcBox.width - 2) // 3) - 40) // 10\n\t\t\tif cls.expand > 5: cls.expand = 5\n\t\tif cls.detailed and not cls.details.get(\"killed\", False):\n\t\t\ttry:\n\t\t\t\tc_pid = cls.detailed_pid\n\t\t\t\tdet = psutil.Process(c_pid)\n\t\t\texcept (psutil.NoSuchProcess, psutil.ZombieProcess):\n\t\t\t\tcls.details[\"killed\"] = True\n\t\t\t\tcls.details[\"status\"] = psutil.STATUS_DEAD\n\t\t\t\tProcBox.redraw = True\n\t\t\telse:\n\t\t\t\tattrs: List[str] = [\"status\", \"memory_info\", \"create_time\"]\n\t\t\t\tif not SYSTEM == \"MacOS\": attrs.extend([\"cpu_num\"])\n\t\t\t\tif cls.expand:\n\t\t\t\t\tattrs.extend([\"nice\", \"terminal\"])\n\t\t\t\t\tif not SYSTEM == \"MacOS\": attrs.extend([\"io_counters\"])\n\n\t\t\t\tif not c_pid in cls.processes: attrs.extend([\"pid\", \"name\", \"cmdline\", \"num_threads\", \"username\", \"memory_percent\"])\n\n\t\t\t\tcls.details = det.as_dict(attrs=attrs, ad_value=\"\")\n\t\t\t\tif det.parent() != None: cls.details[\"parent_name\"] = det.parent().name()\n\t\t\t\telse: cls.details[\"parent_name\"] = \"\"\n\n\t\t\t\tcls.details[\"pid\"] = c_pid\n\t\t\t\tif c_pid in cls.processes:\n\t\t\t\t\tcls.details[\"name\"] = cls.processes[c_pid][\"name\"]\n\t\t\t\t\tcls.details[\"cmdline\"] = cls.processes[c_pid][\"cmd\"]\n\t\t\t\t\tcls.details[\"threads\"] = f'{cls.processes[c_pid][\"threads\"]}'\n\t\t\t\t\tcls.details[\"username\"] = cls.processes[c_pid][\"username\"]\n\t\t\t\t\tcls.details[\"memory_percent\"] = cls.processes[c_pid][\"mem\"]\n\t\t\t\t\tcls.details[\"cpu_percent\"] = round(cls.processes[c_pid][\"cpu\"] * (1 if CONFIG.proc_per_core else THREADS))\n\t\t\t\telse:\n\t\t\t\t\tcls.details[\"cmdline\"] = \" \".join(cls.details[\"cmdline\"]) or \"[\" + cls.details[\"name\"] + \"]\"\n\t\t\t\t\tcls.details[\"threads\"] = f'{cls.details[\"num_threads\"]}'\n\t\t\t\t\tcls.details[\"cpu_percent\"] = round(cls.det_cpu)\n\n\t\t\t\tcls.details[\"killed\"] = False\n\t\t\t\tif SYSTEM == \"MacOS\":\n\t\t\t\t\tcls.details[\"cpu_num\"] = -1\n\t\t\t\t\tcls.details[\"io_counters\"] = \"\"\n\n\n\t\t\t\tif hasattr(cls.details[\"memory_info\"], \"rss\"): cls.details[\"memory_bytes\"] = floating_humanizer(cls.details[\"memory_info\"].rss) # type: ignore\n\t\t\t\telse: cls.details[\"memory_bytes\"] = \"? Bytes\"\n\n\t\t\t\tif isinstance(cls.details[\"create_time\"], float):\n\t\t\t\t\tuptime = timedelta(seconds=round(time()-cls.details[\"create_time\"],0))\n\t\t\t\t\tif uptime.days > 0: cls.details[\"uptime\"] = f'{uptime.days}d {str(uptime).split(\",\")[1][:-3].strip()}'\n\t\t\t\t\telse: cls.details[\"uptime\"] = f'{uptime}'\n\t\t\t\telse: cls.details[\"uptime\"] = \"??:??:??\"\n\n\t\t\t\tif cls.expand:\n\t\t\t\t\tif cls.expand > 1 : cls.details[\"nice\"] = f'{cls.details[\"nice\"]}'\n\t\t\t\t\tif SYSTEM == \"BSD\":\n\t\t\t\t\t\tif cls.expand > 2:\n\t\t\t\t\t\t\tif hasattr(cls.details[\"io_counters\"], \"read_count\"): cls.details[\"io_read\"] = f'{cls.details[\"io_counters\"].read_count}'\n\t\t\t\t\t\t\telse: cls.details[\"io_read\"] = \"?\"\n\t\t\t\t\t\tif cls.expand > 3:\n\t\t\t\t\t\t\tif hasattr(cls.details[\"io_counters\"], \"write_count\"): cls.details[\"io_write\"] = f'{cls.details[\"io_counters\"].write_count}'\n\t\t\t\t\t\t\telse: cls.details[\"io_write\"] = \"?\"\n\t\t\t\t\telse:\n\t\t\t\t\t\tif cls.expand > 2:\n\t\t\t\t\t\t\tif hasattr(cls.details[\"io_counters\"], \"read_bytes\"): cls.details[\"io_read\"] = floating_humanizer(cls.details[\"io_counters\"].read_bytes)\n\t\t\t\t\t\t\telse: cls.details[\"io_read\"] = \"?\"\n\t\t\t\t\t\tif cls.expand > 3:\n\t\t\t\t\t\t\tif hasattr(cls.details[\"io_counters\"], \"write_bytes\"): cls.details[\"io_write\"] = floating_humanizer(cls.details[\"io_counters\"].write_bytes)\n\t\t\t\t\t\t\telse: cls.details[\"io_write\"] = \"?\"\n\t\t\t\t\tif cls.expand > 4 : cls.details[\"terminal\"] = f'{cls.details[\"terminal\"]}'.replace(\"/dev/\", \"\")\n\n\t\t\t\tcls.details_cpu.append(cls.details[\"cpu_percent\"])\n\t\t\t\tmem = cls.details[\"memory_percent\"]\n\t\t\t\tif mem > 80: mem = round(mem)\n\t\t\t\telif mem > 60: mem = round(mem * 1.2)\n\t\t\t\telif mem > 30: mem = round(mem * 1.5)\n\t\t\t\telif mem > 10: mem = round(mem * 2)\n\t\t\t\telif mem > 5: mem = round(mem * 10)\n\t\t\t\telse: mem = round(mem * 20)\n\t\t\t\tcls.details_mem.append(mem)\n\t\t\t\tif len(cls.details_cpu) > ProcBox.width: del cls.details_cpu[0]\n\t\t\t\tif len(cls.details_mem) > ProcBox.width: del cls.details_mem[0]\n\n\t@classmethod\n\tdef _tree(cls, sort_cmd, reverse: bool, proc_per_cpu: bool, search: List[str]):\n\t\t'''List all processes in a tree view with pid, name, threads, username, memory percent and cpu percent'''\n\t\tout: Dict = {}\n\t\terr: float = 0.0\n\t\tdet_cpu: float = 0.0\n\t\tinfolist: Dict = {}\n\t\tcls.tree_counter += 1\n\t\ttree = defaultdict(list)\n\t\tn: int = 0\n\t\tfor p in sorted(psutil.process_iter(cls.p_values + ([\"memory_info\"] if CONFIG.proc_mem_bytes else []), err), key=lambda p: eval(sort_cmd), reverse=reverse):\n\t\t\tif cls.collect_interrupt: return\n\t\t\ttry:\n\t\t\t\ttree[p.ppid()].append(p.pid)\n\t\t\texcept (psutil.NoSuchProcess, psutil.ZombieProcess):\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\tinfolist[p.pid] = p.info\n\t\t\t\tn += 1\n\t\tif 0 in tree and 0 in tree[0]:\n\t\t\ttree[0].remove(0)\n\n\t\tdef create_tree(pid: int, tree: defaultdict, indent: str = \"\", inindent: str = \" \", found: bool = False, depth: int = 0, collapse_to: Union[None, int] = None):\n\t\t\tnonlocal infolist, proc_per_cpu, search, out, det_cpu\n\t\t\tname: str; threads: int; username: str; mem: float; cpu: float; collapse: bool = False\n\t\t\tcont: bool = True\n\t\t\tgetinfo: Dict = {}\n\t\t\tif cls.collect_interrupt: return\n\t\t\ttry:\n\t\t\t\tname = psutil.Process(pid).name()\n\t\t\t\tif name == \"idle\": return\n\t\t\texcept psutil.Error:\n\t\t\t\tpass\n\t\t\t\tcont = False\n\t\t\t\tname = \"\"\n\t\t\tif pid in infolist:\n\t\t\t\tgetinfo = infolist[pid]\n\n\t\t\tif search and not found:\n\t\t\t\tif cls.detailed and pid == cls.detailed_pid:\n\t\t\t\t\tdet_cpu = getinfo[\"cpu_percent\"]\n\t\t\t\tif \"username\" in getinfo and isinstance(getinfo[\"username\"], float): getinfo[\"username\"] = \"\"\n\t\t\t\tif \"cmdline\" in getinfo and isinstance(getinfo[\"cmdline\"], float): getinfo[\"cmdline\"] = \"\"\n\t\t\t\tfor value in [ name, str(pid), getinfo.get(\"username\", \"\"), \" \".join(getinfo.get(\"cmdline\", \"\")) ]:\n\t\t\t\t\tif not cls.case_sensitive:\n\t\t\t\t\t\tvalue = value.lower()\n\t\t\t\t\tfor s in search:\n\t\t\t\t\t\tif s in value:\n\t\t\t\t\t\t\tfound = True\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\telse: continue\n\t\t\t\t\tbreak\n\t\t\t\telse: cont = False\n\t\t\tif cont:\n\t\t\t\tif getinfo:\n\t\t\t\t\tif getinfo[\"num_threads\"] == err: threads = 0\n\t\t\t\t\telse: threads = getinfo[\"num_threads\"]\n\t\t\t\t\tif getinfo[\"username\"] == err: username = \"\"\n\t\t\t\t\telse: username = getinfo[\"username\"]\n\t\t\t\t\tcpu = getinfo[\"cpu_percent\"] if proc_per_cpu else round(getinfo[\"cpu_percent\"] / THREADS, 2)\n\t\t\t\t\tmem = getinfo[\"memory_percent\"]\n\t\t\t\t\tif getinfo[\"cmdline\"] == err: cmd = \"\"\n\t\t\t\t\telse: cmd = \" \".join(getinfo[\"cmdline\"]) or \"[\" + getinfo[\"name\"] + \"]\"\n\t\t\t\t\tif CONFIG.proc_mem_bytes and hasattr(getinfo[\"memory_info\"], \"rss\"):\n\t\t\t\t\t\tmem_b = getinfo[\"memory_info\"].rss\n\t\t\t\t\telse:\n\t\t\t\t\t\tmem_b = 0\n\t\t\t\telse:\n\t\t\t\t\tthreads = mem_b = 0\n\t\t\t\t\tusername = \"\"\n\t\t\t\t\tmem = cpu = 0.0\n\n\t\t\t\tif pid in cls.collapsed:\n\t\t\t\t\tcollapse = cls.collapsed[pid]\n\t\t\t\telse:\n\t\t\t\t\tcollapse = depth > CONFIG.tree_depth\n\t\t\t\t\tcls.collapsed[pid] = collapse\n\n\t\t\t\tif collapse_to and not search:\n\t\t\t\t\tout[collapse_to][\"threads\"] += threads\n\t\t\t\t\tout[collapse_to][\"mem\"] += mem\n\t\t\t\t\tout[collapse_to][\"mem_b\"] += mem_b\n\t\t\t\t\tout[collapse_to][\"cpu\"] += cpu\n\t\t\t\telse:\n\t\t\t\t\tif pid in tree and len(tree[pid]) > 0:\n\t\t\t\t\t\tsign: str = \"+\" if collapse else \"-\"\n\t\t\t\t\t\tinindent = inindent.replace(\" ├─ \", \"[\" + sign + \"]─\").replace(\" └─ \", \"[\" + sign + \"]─\")\n\t\t\t\t\tout[pid] = {\n\t\t\t\t\t\t\"indent\" : inindent,\n\t\t\t\t\t\t\"name\": name,\n\t\t\t\t\t\t\"cmd\" : cmd,\n\t\t\t\t\t\t\"threads\" : threads,\n\t\t\t\t\t\t\"username\" : username,\n\t\t\t\t\t\t\"mem\" : mem,\n\t\t\t\t\t\t\"mem_b\" : mem_b,\n\t\t\t\t\t\t\"cpu\" : cpu,\n\t\t\t\t\t\t\"depth\" : depth,\n\t\t\t\t\t\t}\n\n\t\t\tif search: collapse = False\n\t\t\telif collapse and not collapse_to:\n\t\t\t\tcollapse_to = pid\n\n\t\t\tif pid not in tree:\n\t\t\t\treturn\n\t\t\tchildren = tree[pid][:-1]\n\n\t\t\tfor child in children:\n\t\t\t\tcreate_tree(child, tree, indent + \" │ \", indent + \" ├─ \", found=found, depth=depth+1, collapse_to=collapse_to)\n\t\t\tcreate_tree(tree[pid][-1], tree, indent + \"  \", indent + \" └─ \", depth=depth+1, collapse_to=collapse_to)\n\n\t\tcreate_tree(min(tree), tree)\n\t\tcls.det_cpu = det_cpu\n\n\t\tif cls.collect_interrupt: return\n\t\tif cls.tree_counter >= 100:\n\t\t\tcls.tree_counter = 0\n\t\t\tfor pid in list(cls.collapsed):\n\t\t\t\tif not psutil.pid_exists(pid):\n\t\t\t\t\tdel cls.collapsed[pid]\n\t\tcls.num_procs = len(out)\n\t\tcls.processes = out.copy()\n\n\t@classmethod\n\tdef sorting(cls, key: str):\n\t\tindex: int = CONFIG.sorting_options.index(CONFIG.proc_sorting) + (1 if key in [\"right\", \"l\"] else -1)\n\t\tif index >= len(CONFIG.sorting_options): index = 0\n\t\telif index < 0: index = len(CONFIG.sorting_options) - 1\n\t\tCONFIG.proc_sorting = CONFIG.sorting_options[index]\n\t\tif \"left\" in Key.mouse: del Key.mouse[\"left\"]\n\t\tCollector.collect(ProcCollector, interrupt=True, redraw=True)\n\n\t@classmethod\n\tdef _draw(cls):\n\t\tProcBox._draw_fg()\n\nclass Menu:\n\t'''Holds all menus'''\n\tactive: bool = False\n\tclose: bool = False\n\tresized: bool = True\n\tmenus: Dict[str, Dict[str, str]] = {}\n\tmenu_length: Dict[str, int] = {}\n\tbackground: str = \"\"\n\tfor name, menu in MENUS.items():\n\t\tmenu_length[name] = len(menu[\"normal\"][0])\n\t\tmenus[name] = {}\n\t\tfor sel in [\"normal\", \"selected\"]:\n\t\t\tmenus[name][sel] = \"\"\n\t\t\tfor i in range(len(menu[sel])):\n\t\t\t\tmenus[name][sel] += Fx.trans(f'{Color.fg(MENU_COLORS[sel][i])}{menu[sel][i]}')\n\t\t\t\tif i < len(menu[sel]) - 1: menus[name][sel] += f'{Mv.d(1)}{Mv.l(len(menu[sel][i]))}'\n\n\t@classmethod\n\tdef main(cls):\n\t\tif Term.width < 80 or Term.height < 24:\n\t\t\terrlog.warning(f'The menu system only works on a terminal size of 80x24 or above!')\n\t\t\treturn\n\t\tout: str = \"\"\n\t\tbanner: str = \"\"\n\t\tredraw: bool = True\n\t\tkey: str = \"\"\n\t\tmx: int = 0\n\t\tmy: int = 0\n\t\tskip: bool = False\n\t\tmouse_over: bool = False\n\t\tmouse_items: Dict[str, Dict[str, int]] = {}\n\t\tcls.active = True\n\t\tcls.resized = True\n\t\tmenu_names: List[str] = list(cls.menus.keys())\n\t\tmenu_index: int = 0\n\t\tmenu_current: str = menu_names[0]\n\t\tcls.background = f'{THEME.inactive_fg}' + Fx.uncolor(f'{Draw.saved_buffer()}') + f'{Term.fg}'\n\n\t\twhile not cls.close:\n\t\t\tkey = \"\"\n\t\t\tif cls.resized:\n\t\t\t\tbanner = (f'{Banner.draw(Term.height // 2 - 10, center=True)}{Mv.d(1)}{Mv.l(46)}{Colors.black_bg}{Colors.default}{Fx.b}← esc'\n\t\t\t\t\tf'{Mv.r(30)}{Fx.i}Version: {VERSION}{Fx.ui}{Fx.ub}{Term.bg}{Term.fg}')\n\t\t\t\tif UpdateChecker.version != VERSION:\n\t\t\t\t\tbanner += f'{Mv.to(Term.height, 1)}{Fx.b}{THEME.title}New release {UpdateChecker.version} available at https://github.com/aristocratos/bpytop{Fx.ub}{Term.fg}'\n\t\t\t\tcy = 0\n\t\t\t\tfor name, menu in cls.menus.items():\n\t\t\t\t\typos = Term.height // 2 - 2 + cy\n\t\t\t\t\txpos = Term.width // 2 - (cls.menu_length[name] // 2)\n\t\t\t\t\tmouse_items[name] = { \"x1\" : xpos, \"x2\" : xpos + cls.menu_length[name] - 1, \"y1\" : ypos, \"y2\" : ypos + 2 }\n\t\t\t\t\tcy += 3\n\t\t\t\tredraw = True\n\t\t\t\tcls.resized = False\n\n\t\t\tif redraw:\n\t\t\t\tout = \"\"\n\t\t\t\tfor name, menu in cls.menus.items():\n\t\t\t\t\tout += f'{Mv.to(mouse_items[name][\"y1\"], mouse_items[name][\"x1\"])}{menu[\"selected\" if name == menu_current else \"normal\"]}'\n\n\t\t\tif skip and redraw:\n\t\t\t\tDraw.now(out)\n\t\t\telif not skip:\n\t\t\t\tDraw.now(f'{cls.background}{banner}{out}')\n\t\t\tskip = redraw = False\n\n\t\t\tif Key.input_wait(Timer.left(), mouse=True):\n\t\t\t\tif Key.mouse_moved():\n\t\t\t\t\tmx, my = Key.get_mouse()\n\t\t\t\t\tfor name, pos in mouse_items.items():\n\t\t\t\t\t\tif pos[\"x1\"] <= mx <= pos[\"x2\"] and pos[\"y1\"] <= my <= pos[\"y2\"]:\n\t\t\t\t\t\t\tmouse_over = True\n\t\t\t\t\t\t\tif name != menu_current:\n\t\t\t\t\t\t\t\tmenu_current = name\n\t\t\t\t\t\t\t\tmenu_index = menu_names.index(name)\n\t\t\t\t\t\t\t\tredraw = True\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\telse:\n\t\t\t\t\t\tmouse_over = False\n\t\t\t\telse:\n\t\t\t\t\tkey = Key.get()\n\n\t\t\t\tif key == \"mouse_click\" and not mouse_over:\n\t\t\t\t\tkey = \"M\"\n\n\t\t\t\tif key == \"q\":\n\t\t\t\t\tclean_quit()\n\t\t\t\telif key in [\"escape\", \"M\"]:\n\t\t\t\t\tcls.close = True\n\t\t\t\t\tbreak\n\t\t\t\telif key in [\"up\", \"mouse_scroll_up\", \"shift_tab\"]:\n\t\t\t\t\tmenu_index -= 1\n\t\t\t\t\tif menu_index < 0: menu_index = len(menu_names) - 1\n\t\t\t\t\tmenu_current = menu_names[menu_index]\n\t\t\t\t\tredraw = True\n\t\t\t\telif key in [\"down\", \"mouse_scroll_down\", \"tab\"]:\n\t\t\t\t\tmenu_index += 1\n\t\t\t\t\tif menu_index > len(menu_names) - 1: menu_index = 0\n\t\t\t\t\tmenu_current = menu_names[menu_index]\n\t\t\t\t\tredraw = True\n\t\t\t\telif key == \"enter\" or (key == \"mouse_click\" and mouse_over):\n\t\t\t\t\tif menu_current == \"quit\":\n\t\t\t\t\t\tclean_quit()\n\t\t\t\t\telif menu_current == \"options\":\n\t\t\t\t\t\tcls.options()\n\t\t\t\t\t\tcls.resized = True\n\t\t\t\t\telif menu_current == \"help\":\n\t\t\t\t\t\tcls.help()\n\t\t\t\t\t\tcls.resized = True\n\n\t\t\tif Timer.not_zero() and not cls.resized:\n\t\t\t\tskip = True\n\t\t\telse:\n\t\t\t\tCollector.collect()\n\t\t\t\tCollector.collect_done.wait(2)\n\t\t\t\tif CONFIG.background_update: cls.background = f'{THEME.inactive_fg}' + Fx.uncolor(f'{Draw.saved_buffer()}') + f'{Term.fg}'\n\t\t\t\tTimer.stamp()\n\n\n\t\tDraw.now(f'{Draw.saved_buffer()}')\n\t\tcls.background = \"\"\n\t\tcls.active = False\n\t\tcls.close = False\n\n\t@classmethod\n\tdef help(cls):\n\t\tif Term.width < 80 or Term.height < 24:\n\t\t\terrlog.warning(f'The menu system only works on a terminal size of 80x24 or above!')\n\t\t\treturn\n\t\tout: str = \"\"\n\t\tout_misc : str = \"\"\n\t\tredraw: bool = True\n\t\tkey: str = \"\"\n\t\tskip: bool = False\n\t\tmain_active: bool = cls.active\n\t\tcls.active = True\n\t\tcls.resized = True\n\t\tif not cls.background:\n\t\t\tcls.background = f'{THEME.inactive_fg}' + Fx.uncolor(f'{Draw.saved_buffer()}') + f'{Term.fg}'\n\t\thelp_items: Dict[str, str] = {\n\t\t\t\"(Mouse 1)\" : \"Clicks buttons and selects in process list.\",\n\t\t\t\"Selected (Mouse 1)\" : \"Show detailed information for selected process.\",\n\t\t\t\"(Mouse scroll)\" : \"Scrolls any scrollable list/text under cursor.\",\n\t\t\t\"(Esc, shift+m)\" : \"Toggles main menu.\",\n\t\t\t\"(m)\" : \"Cycle view presets, order: full->proc->stat->user.\",\n\t\t\t\"(1)\" : \"Toggle CPU box.\",\n\t\t\t\"(2)\" : \"Toggle MEM box.\",\n\t\t\t\"(3)\" : \"Toggle NET box.\",\n\t\t\t\"(4)\" : \"Toggle PROC box.\",\n\t\t\t\"(d)\" : \"Toggle disks view in MEM box.\",\n\t\t\t\"(F2, o)\" : \"Shows options.\",\n\t\t\t\"(F1, shift+h)\" : \"Shows this window.\",\n\t\t\t\"(ctrl+z)\" : \"Sleep program and put in background.\",\n\t\t\t\"(ctrl+c, q)\" : \"Quits program.\",\n\t\t\t\"(+) / (-)\" : \"Add/Subtract 100ms to/from update timer.\",\n\t\t\t\"(Up, k) (Down, j)\" : \"Select in process list.\",\n\t\t\t\"(Enter)\" : \"Show detailed information for selected process.\",\n\t\t\t\"(Spacebar)\" : \"Expand/collapse the selected process in tree view.\",\n\t\t\t\"(Pg Up) (Pg Down)\" : \"Jump 1 page in process list.\",\n\t\t\t\"(Home) (End)\" : \"Jump to first or last page in process list.\",\n\t\t\t\"(Left, h) (Right, l)\" : \"Select previous/next sorting column.\",\n\t\t\t\"(b) (n)\" : \"Select previous/next network device.\",\n\t\t\t\"(s)\" : \"Toggle showing swap as a disk.\",\n\t\t\t\"(i)\" : \"Toggle disks io mode with big graphs.\",\n\t\t\t\"(z)\" : \"Toggle totals reset for current network device\",\n\t\t\t\"(a)\" : \"Toggle auto scaling for the network graphs.\",\n\t\t\t\"(y)\" : \"Toggle synced scaling mode for network graphs.\",\n\t\t\t\"(f, /)\" : \"Input a NON case-sensitive process filter.\",\n\t\t\t\"(shift+f)\" : \"Input a case-sensitive process filter.\",\n\t\t\t\"(c)\" : \"Toggle per-core cpu usage of processes.\",\n\t\t\t\"(r)\" : \"Reverse sorting order in processes box.\",\n\t\t\t\"(e)\" : \"Toggle processes tree view.\",\n\t\t\t\"(delete)\" : \"Clear any entered filter.\",\n\t\t\t\"Selected (shift+t)\" : \"Terminate selected process with SIGTERM - 15.\",\n\t\t\t\"Selected (shift+k)\" : \"Kill selected process with SIGKILL - 9.\",\n\t\t\t\"Selected (shift+i)\" : \"Interrupt selected process with SIGINT - 2.\",\n\t\t\t\"_1\" : \" \",\n\t\t\t\"_2\" : \"For bug reporting and project updates, visit:\",\n\t\t\t\"_3\" : \"https://github.com/aristocratos/bpytop\",\n\t\t}\n\n\t\twhile not cls.close:\n\t\t\tkey = \"\"\n\t\t\tif cls.resized:\n\t\t\t\ty = 8 if Term.height < len(help_items) + 10 else Term.height // 2 - len(help_items) // 2 + 4\n\t\t\t\tout_misc = (f'{Banner.draw(y-7, center=True)}{Mv.d(1)}{Mv.l(46)}{Colors.black_bg}{Colors.default}{Fx.b}← esc'\n\t\t\t\t\tf'{Mv.r(30)}{Fx.i}Version: {VERSION}{Fx.ui}{Fx.ub}{Term.bg}{Term.fg}')\n\t\t\t\tx = Term.width//2-36\n\t\t\t\th, w = Term.height-2-y, 72\n\t\t\t\tif len(help_items) > h:\n\t\t\t\t\tpages = ceil(len(help_items) / h)\n\t\t\t\telse:\n\t\t\t\t\th = len(help_items)\n\t\t\t\t\tpages = 0\n\t\t\t\tpage = 1\n\t\t\t\tout_misc += create_box(x, y, w, h+3, \"help\", line_color=THEME.div_line)\n\t\t\t\tredraw = True\n\t\t\t\tcls.resized = False\n\n\t\t\tif redraw:\n\t\t\t\tout = \"\"\n\t\t\t\tcy = 0\n\t\t\t\tif pages:\n\t\t\t\t\tout += (f'{Mv.to(y, x+56)}{THEME.div_line(Symbol.title_left)}{Fx.b}{THEME.title(\"pg\")}{Fx.ub}{THEME.main_fg(Symbol.up)} {Fx.b}{THEME.title}{page}/{pages} '\n\t\t\t\t\tf'pg{Fx.ub}{THEME.main_fg(Symbol.down)}{THEME.div_line(Symbol.title_right)}')\n\t\t\t\tout += f'{Mv.to(y+1, x+1)}{THEME.title}{Fx.b}{\"Keys:\":^20}Description:{THEME.main_fg}'\n\t\t\t\tfor n, (keys, desc) in enumerate(help_items.items()):\n\t\t\t\t\tif pages and n < (page - 1) * h: continue\n\t\t\t\t\tout += f'{Mv.to(y+2+cy, x+1)}{Fx.b}{(\"\" if keys.startswith(\"_\") else keys):^20.20}{Fx.ub}{desc:50.50}'\n\t\t\t\t\tcy += 1\n\t\t\t\t\tif cy == h: break\n\t\t\t\tif cy < h:\n\t\t\t\t\tfor i in range(h-cy):\n\t\t\t\t\t\tout += f'{Mv.to(y+2+cy+i, x+1)}{\" \" * (w-2)}'\n\n\t\t\tif skip and redraw:\n\t\t\t\tDraw.now(out)\n\t\t\telif not skip:\n\t\t\t\tDraw.now(f'{cls.background}{out_misc}{out}')\n\t\t\tskip = redraw = False\n\n\t\t\tif Key.input_wait(Timer.left()):\n\t\t\t\tkey = Key.get()\n\n\t\t\t\tif key == \"mouse_click\":\n\t\t\t\t\tmx, my = Key.get_mouse()\n\t\t\t\t\tif x <= mx < x + w and y <= my < y + h + 3:\n\t\t\t\t\t\tif pages and my == y and x + 56 <  mx < x + 61:\n\t\t\t\t\t\t\tkey = \"up\"\n\t\t\t\t\t\telif pages and my == y and x + 63 < mx < x + 68:\n\t\t\t\t\t\t\tkey = \"down\"\n\t\t\t\t\telse:\n\t\t\t\t\t\tkey = \"escape\"\n\n\t\t\t\tif key == \"q\":\n\t\t\t\t\tclean_quit()\n\t\t\t\telif key in [\"escape\", \"M\", \"enter\", \"backspace\", \"H\", \"f1\"]:\n\t\t\t\t\tcls.close = True\n\t\t\t\t\tbreak\n\t\t\t\telif key in [\"up\", \"mouse_scroll_up\", \"page_up\"] and pages:\n\t\t\t\t\tpage -= 1\n\t\t\t\t\tif page < 1: page = pages\n\t\t\t\t\tredraw = True\n\t\t\t\telif key in [\"down\", \"mouse_scroll_down\", \"page_down\"] and pages:\n\t\t\t\t\tpage += 1\n\t\t\t\t\tif page > pages: page = 1\n\t\t\t\t\tredraw = True\n\n\t\t\tif Timer.not_zero() and not cls.resized:\n\t\t\t\tskip = True\n\t\t\telse:\n\t\t\t\tCollector.collect()\n\t\t\t\tCollector.collect_done.wait(2)\n\t\t\t\tif CONFIG.background_update: cls.background = f'{THEME.inactive_fg}' + Fx.uncolor(f'{Draw.saved_buffer()}') + f'{Term.fg}'\n\t\t\t\tTimer.stamp()\n\n\t\tif main_active:\n\t\t\tcls.close = False\n\t\t\treturn\n\t\tDraw.now(f'{Draw.saved_buffer()}')\n\t\tcls.background = \"\"\n\t\tcls.active = False\n\t\tcls.close = False\n\n\t@classmethod\n\tdef options(cls):\n\t\tif Term.width < 80 or Term.height < 24:\n\t\t\terrlog.warning(f'The menu system only works on a terminal size of 80x24 or above!')\n\t\t\treturn\n\t\tout: str = \"\"\n\t\tout_misc : str = \"\"\n\t\tredraw: bool = True\n\t\tselected_cat: str = \"\"\n\t\tselected_int: int = 0\n\t\toption_items: Dict[str, List[str]] = {}\n\t\tcat_list: List[str] = []\n\t\tcat_int: int = 0\n\t\tchange_cat: bool = False\n\t\tkey: str = \"\"\n\t\tskip: bool = False\n\t\tmain_active: bool = cls.active\n\t\tcls.active = True\n\t\tcls.resized = True\n\t\td_quote: str\n\t\tinputting: bool = False\n\t\tinput_val: str = \"\"\n\t\tTheme.refresh()\n\t\tif not cls.background:\n\t\t\tcls.background = f'{THEME.inactive_fg}' + Fx.uncolor(f'{Draw.saved_buffer()}') + f'{Term.fg}'\n\t\tcategories: Dict[str, Dict[str, List[str]]] = {\n\t\t\t\"system\" : {\n\t\t\t\t\"color_theme\" : [\n\t\t\t\t\t'Set color theme.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Choose from all theme files in',\n\t\t\t\t\t'\"/usr/[local/]share/bpytop/themes\" and',\n\t\t\t\t\t'\"~/.config/bpytop/themes\".',\n\t\t\t\t\t'',\n\t\t\t\t\t'\"Default\" for builtin default theme.',\n\t\t\t\t\t'User themes are prefixed by a plus sign \"+\".',\n\t\t\t\t\t'',\n\t\t\t\t\t'For theme updates see:',\n\t\t\t\t\t'https://github.com/aristocratos/bpytop'],\n\t\t\t\t\"theme_background\" : [\n\t\t\t\t\t'If the theme set background should be shown.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Set to False if you want terminal background',\n\t\t\t\t\t'transparency.'],\n\t\t\t\t\"truecolor\" : [\n\t\t\t\t\t'Sets if 24-bit truecolor should be used.',\n\t\t\t\t\t'(Requires restart to take effect!)',\n\t\t\t\t\t'',\n\t\t\t\t\t'Will convert 24-bit colors to 256 color',\n\t\t\t\t\t'(6x6x6 color cube) if False.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Set to False if your terminal doesn\\'t have',\n\t\t\t\t\t'truecolor support and can\\'t convert to',\n\t\t\t\t\t'256-color.'],\n\t\t\t\t\"shown_boxes\" : [\n\t\t\t\t\t'Manually set which boxes to show.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Available values are \"cpu mem net proc\".',\n\t\t\t\t\t'Seperate values with whitespace.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Toggle between presets with mode key \"m\".'],\n\t\t\t\t\"update_ms\" : [\n\t\t\t\t\t'Update time in milliseconds.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Recommended 2000 ms or above for better sample',\n\t\t\t\t\t'times for graphs.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Min value: 100 ms',\n\t\t\t\t\t'Max value: 86400000 ms = 24 hours.'],\n\t\t\t\t\"draw_clock\" : [\n\t\t\t\t\t'Draw a clock at top of screen.',\n\t\t\t\t\t'(Only visible if cpu box is enabled!)',\n\t\t\t\t\t'',\n\t\t\t\t\t'Formatting according to strftime, empty',\n\t\t\t\t\t'string to disable.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Custom formatting options:',\n\t\t\t\t\t'\"/host\" = hostname',\n\t\t\t\t\t'\"/user\" = username',\n\t\t\t\t\t'\"/uptime\" = system uptime',\n\t\t\t\t\t'',\n\t\t\t\t\t'Examples of strftime formats:',\n\t\t\t\t\t'\"%X\" = locale HH:MM:SS',\n\t\t\t\t\t'\"%H\" = 24h hour, \"%I\" = 12h hour',\n\t\t\t\t\t'\"%M\" = minute, \"%S\" = second',\n\t\t\t\t\t'\"%d\" = day, \"%m\" = month, \"%y\" = year'],\n\t\t\t\t\"background_update\" : [\n\t\t\t\t\t'Update main ui when menus are showing.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Set this to false if the menus is flickering',\n\t\t\t\t\t'too much for a comfortable experience.'],\n\t\t\t\t\"show_battery\" : [\n\t\t\t\t\t'Show battery stats.',\n\t\t\t\t\t'(Only visible if cpu box is enabled!)',\n\t\t\t\t\t'',\n\t\t\t\t\t'Show battery stats in the top right corner',\n\t\t\t\t\t'if a battery is present.'],\n\t\t\t\t\"show_init\" : [\n\t\t\t\t\t'Show init screen at startup.',\n\t\t\t\t\t'',\n\t\t\t\t\t'The init screen is purely cosmetical and',\n\t\t\t\t\t'slows down start to show status messages.'],\n\t\t\t\t\"update_check\" : [\n\t\t\t\t\t'Check for updates at start.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Checks for latest version from:',\n\t\t\t\t\t'https://github.com/aristocratos/bpytop'],\n\t\t\t\t\"log_level\" : [\n\t\t\t\t\t'Set loglevel for error.log',\n\t\t\t\t\t'',\n\t\t\t\t\t'Levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".',\n\t\t\t\t\t'The level set includes all lower levels,',\n\t\t\t\t\t'i.e. \"DEBUG\" will show all logging info.']\n\t\t\t},\n\t\t\t\"cpu\" : {\n\t\t\t\t\"cpu_graph_upper\" : [\n\t\t\t\t\t'Sets the CPU stat shown in upper half of',\n\t\t\t\t\t'the CPU graph.',\n\t\t\t\t\t'',\n\t\t\t\t\t'\"total\" = Total cpu usage.',\n\t\t\t\t\t'\"user\" = User mode cpu usage.',\n\t\t\t\t\t'\"system\" = Kernel mode cpu usage.',\n\t\t\t\t\t'See:',\n\t\t\t\t\t'https://psutil.readthedocs.io/en/latest/',\n\t\t\t\t\t'#psutil.cpu_times',\n\t\t\t\t\t'for attributes available on specific platforms.'],\n\t\t\t\t\"cpu_graph_lower\" : [\n\t\t\t\t\t'Sets the CPU stat shown in lower half of',\n\t\t\t\t\t'the CPU graph.',\n\t\t\t\t\t'',\n\t\t\t\t\t'\"total\" = Total cpu usage.',\n\t\t\t\t\t'\"user\" = User mode cpu usage.',\n\t\t\t\t\t'\"system\" = Kernel mode cpu usage.',\n\t\t\t\t\t'See:',\n\t\t\t\t\t'https://psutil.readthedocs.io/en/latest/',\n\t\t\t\t\t'#psutil.cpu_times',\n\t\t\t\t\t'for attributes available on specific platforms.'],\n\t\t\t\t\"cpu_invert_lower\" : [\n\t\t\t\t\t\t'Toggles orientation of the lower CPU graph.',\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'True or False.'],\n\t\t\t\t\"cpu_single_graph\" : [\n\t\t\t\t\t\t'Completely disable the lower CPU graph.',\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Shows only upper CPU graph and resizes it',\n\t\t\t\t\t\t'to fit to box height.',\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'True or False.'],\n\t\t\t\t\"check_temp\" : [\n\t\t\t\t\t'Enable cpu temperature reporting.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"cpu_sensor\" : [\n\t\t\t\t\t'Cpu temperature sensor',\n\t\t\t\t\t'',\n\t\t\t\t\t'Select the sensor that corresponds to',\n\t\t\t\t\t'your cpu temperature.',\n\t\t\t\t\t'Set to \"Auto\" for auto detection.'],\n\t\t\t\t\"show_coretemp\" : [\n\t\t\t\t\t'Show temperatures for cpu cores.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Only works if check_temp is True and',\n\t\t\t\t\t'the system is reporting core temps.'],\n\t\t\t\t\"temp_scale\" : [\n\t\t\t\t\t'Which temperature scale to use.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Celsius, default scale.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Fahrenheit, the american one.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Kelvin, 0 = absolute zero, 1 degree change',\n\t\t\t\t\t'equals 1 degree change in Celsius.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Rankine, 0 = absolute zero, 1 degree change',\n\t\t\t\t\t'equals 1 degree change in Fahrenheit.'],\n\t\t\t\t\"show_cpu_freq\" : [\n\t\t\t\t\t'Show CPU frequency',\n\t\t\t\t\t'',\n\t\t\t\t\t'Can cause slowdowns on systems with many',\n\t\t\t\t\t'cores and psutil versions below 5.8.1'],\n\t\t\t\t\"custom_cpu_name\" : [\n\t\t\t\t\t'Custom cpu model name in cpu percentage box.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Empty string to disable.'],\n\t\t\t\t\"show_uptime\" : [\n\t\t\t\t\t'Shows the system uptime in the CPU box.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Can also be shown in the clock by using',\n\t\t\t\t\t'\"/uptime\" in the formatting.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t},\n\t\t\t\"mem\" : {\n\t\t\t\t\"mem_graphs\" : [\n\t\t\t\t\t'Show graphs for memory values.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"show_disks\" : [\n\t\t\t\t\t'Split memory box to also show disks.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"show_io_stat\" : [\n\t\t\t\t\t'Toggle small IO stat graphs.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Toggles the small IO graphs for the regular',\n\t\t\t\t\t'disk usage view.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"io_mode\" : [\n\t\t\t\t\t'Toggles io mode for disks.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Shows big graphs for disk read/write speeds',\n\t\t\t\t\t'instead of used/free percentage meters.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"io_graph_combined\" : [\n\t\t\t\t\t'Toggle combined read and write graphs.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Only has effect if \"io mode\" is True.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"io_graph_speeds\" : [\n\t\t\t\t\t'Set top speeds for the io graphs.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Manually set which speed in MiB/s that equals',\n\t\t\t\t\t'100 percent in the io graphs.',\n\t\t\t\t\t'(10 MiB/s by default).',\n\t\t\t\t\t'',\n\t\t\t\t\t'Format: \"device:speed\" separate disks with a',\n\t\t\t\t\t'comma \",\".',\n\t\t\t\t\t'',\n\t\t\t\t\t'Example: \"/dev/sda:100, /dev/sdb:20\".'],\n\t\t\t\t\"show_swap\" : [\n\t\t\t\t\t'If swap memory should be shown in memory box.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"swap_disk\" : [\n\t\t\t\t\t'Show swap as a disk.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Ignores show_swap value above.',\n\t\t\t\t\t'Inserts itself after first disk.'],\n\t\t\t\t\"only_physical\" : [\n\t\t\t\t\t'Filter out non physical disks.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Set this to False to include network disks,',\n\t\t\t\t\t'RAM disks and similar.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"use_fstab\" : [\n\t\t\t\t\t'Read disks list from /etc/fstab.',\n\t\t\t\t\t'(Has no effect on macOS X)',\n\t\t\t\t\t'',\n\t\t\t\t\t'This also disables only_physical.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"disks_filter\" : [\n\t\t\t\t\t'Optional filter for shown disks.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Should be full path of a mountpoint,',\n\t\t\t\t\t'\"root\" replaces \"/\", separate multiple values',\n\t\t\t\t\t'with a comma \",\".',\n\t\t\t\t\t'Begin line with \"exclude=\" to change to exclude',\n\t\t\t\t\t'filter.',\n\t\t\t\t\t'Otherwise defaults to \"most include\" filter.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Example: disks_filter=\"exclude=/boot, /home/user\"'],\n\t\t\t},\n\t\t\t\"net\" : {\n\t\t\t\t\"net_download\" : [\n\t\t\t\t\t'Fixed network graph download value.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Default \"10M\" = 10 MibiBytes.',\n\t\t\t\t\t'Possible units:',\n\t\t\t\t\t'\"K\" (KiB), \"M\" (MiB), \"G\" (GiB).',\n\t\t\t\t\t'',\n\t\t\t\t\t'Append \"bit\" for bits instead of bytes,',\n\t\t\t\t\t'i.e \"100Mbit\"',\n\t\t\t\t\t'',\n\t\t\t\t\t'Can be toggled with auto button.'],\n\t\t\t\t\"net_upload\" : [\n\t\t\t\t\t'Fixed network graph upload value.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Default \"10M\" = 10 MibiBytes.',\n\t\t\t\t\t'Possible units:',\n\t\t\t\t\t'\"K\" (KiB), \"M\" (MiB), \"G\" (GiB).',\n\t\t\t\t\t'',\n\t\t\t\t\t'Append \"bit\" for bits instead of bytes,',\n\t\t\t\t\t'i.e \"100Mbit\"',\n\t\t\t\t\t'',\n\t\t\t\t\t'Can be toggled with auto button.'],\n\t\t\t\t\"net_auto\" : [\n\t\t\t\t\t'Start in network graphs auto rescaling mode.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Ignores any values set above at start and',\n\t\t\t\t\t'rescales down to 10KibiBytes at the lowest.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"net_sync\" : [\n\t\t\t\t\t'Network scale sync.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Syncs the scaling for download and upload to',\n\t\t\t\t\t'whichever currently has the highest scale.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"net_color_fixed\" : [\n\t\t\t\t\t'Set network graphs color gradient to fixed.',\n\t\t\t\t\t'',\n\t\t\t\t\t'If True the network graphs color is based',\n\t\t\t\t\t'on the total bandwidth usage instead of',\n\t\t\t\t\t'the current autoscaling.',\n\t\t\t\t\t'',\n\t\t\t\t\t'The bandwidth usage is based on the',\n\t\t\t\t\t'\"net_download\" and \"net_upload\" values set',\n\t\t\t\t\t'above.'],\n\t\t\t\t\"net_iface\" : [\n\t\t\t\t\t'Network Interface.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Manually set the starting Network Interface.',\n\t\t\t\t\t'Will otherwise automatically choose the NIC',\n\t\t\t\t\t'with the highest total download since boot.'],\n\t\t\t},\n\t\t\t\"proc\" : {\n\t\t\t\t\"proc_update_mult\" : [\n\t\t\t\t\t'Processes update multiplier.',\n\t\t\t\t\t'Sets how often the process list is updated as',\n\t\t\t\t\t'a multiplier of \"update_ms\".',\n\t\t\t\t\t'',\n\t\t\t\t\t'Set to 2 or higher to greatly decrease bpytop',\n\t\t\t\t\t'cpu usage. (Only integers)'],\n\t\t\t\t\"proc_sorting\" : [\n\t\t\t\t\t'Processes sorting option.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Possible values: \"pid\", \"program\", \"arguments\",',\n\t\t\t\t\t'\"threads\", \"user\", \"memory\", \"cpu lazy\" and',\n\t\t\t\t\t'\"cpu responsive\".',\n\t\t\t\t\t'',\n\t\t\t\t\t'\"cpu lazy\" updates top process over time,',\n\t\t\t\t\t'\"cpu responsive\" updates top process directly.'],\n\t\t\t\t\"proc_reversed\" : [\n\t\t\t\t\t'Reverse processes sorting order.',\n\t\t\t\t\t'',\n\t\t\t\t\t'True or False.'],\n\t\t\t\t\"proc_tree\" : [\n\t\t\t\t\t'Processes tree view.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Set true to show processes grouped by parents,',\n\t\t\t\t\t'with lines drawn between parent and child',\n\t\t\t\t\t'process.'],\n\t\t\t\t\"tree_depth\" : [\n\t\t\t\t\t'Process tree auto collapse depth.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Sets the depth where the tree view will auto',\n\t\t\t\t\t'collapse processes at.'],\n\t\t\t\t\"proc_colors\" : [\n\t\t\t\t\t'Enable colors in process view.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Uses the cpu graph gradient colors.'],\n\t\t\t\t\"proc_gradient\" : [\n\t\t\t\t\t'Enable process view gradient fade.',\n\t\t\t\t\t'',\n\t\t\t\t\t'Fades from top or current selection.',\n\t\t\t\t\t'Max fade value is equal to current themes',\n\t\t\t\t\t'\"inactive_fg\" color value.'],\n\t\t\t\t\"proc_per_core\" : [\n\t\t\t\t\t'Process usage per core.',\n\t\t\t\t\t'',\n\t\t\t\t\t'If process cpu usage should be of the core',\n\t\t\t\t\t'it\\'s running on or usage of the total',\n\t\t\t\t\t'available cpu power.',\n\t\t\t\t\t'',\n\t\t\t\t\t'If true and process is multithreaded',\n\t\t\t\t\t'cpu usage can reach over 100%.'],\n\t\t\t\t\"proc_mem_bytes\" : [\n\t\t\t\t\t'Show memory as bytes in process list.',\n\t\t\t\t\t' ',\n\t\t\t\t\t'True or False.'],\n\t\t\t}\n\t\t}\n\n\t\tloglevel_i: int = CONFIG.log_levels.index(CONFIG.log_level)\n\t\tcpu_sensor_i: int = CONFIG.cpu_sensors.index(CONFIG.cpu_sensor)\n\t\tcpu_graph_i: Dict[str, int] = { \"cpu_graph_upper\" : CONFIG.cpu_percent_fields.index(CONFIG.cpu_graph_upper),\n\t\t\t\t\t\t\t\t\t\t\"cpu_graph_lower\" : CONFIG.cpu_percent_fields.index(CONFIG.cpu_graph_lower)}\n\t\ttemp_scale_i: int = CONFIG.temp_scales.index(CONFIG.temp_scale)\n\t\tcolor_i: int\n\t\tmax_opt_len: int = max([len(categories[x]) for x in categories]) * 2\n\t\tcat_list = list(categories)\n\t\twhile not cls.close:\n\t\t\tkey = \"\"\n\t\t\tif cls.resized or change_cat:\n\t\t\t\tcls.resized = change_cat = False\n\t\t\t\tselected_cat = list(categories)[cat_int]\n\t\t\t\toption_items = categories[cat_list[cat_int]]\n\t\t\t\toption_len: int = len(option_items) * 2\n\t\t\t\ty = 12 if Term.height < max_opt_len + 13 else Term.height // 2 - max_opt_len // 2 + 7\n\t\t\t\tout_misc = (f'{Banner.draw(y-10, center=True)}{Mv.d(1)}{Mv.l(46)}{Colors.black_bg}{Colors.default}{Fx.b}← esc'\n\t\t\t\t\tf'{Mv.r(30)}{Fx.i}Version: {VERSION}{Fx.ui}{Fx.ub}{Term.bg}{Term.fg}')\n\t\t\t\tx = Term.width//2-38\n\t\t\t\tx2 = x + 27\n\t\t\t\th, w, w2 = min(Term.height-1-y, option_len), 26, 50\n\t\t\t\th -= h % 2\n\t\t\t\tcolor_i = list(Theme.themes).index(THEME.current)\n\t\t\t\tout_misc += create_box(x, y - 3, w+w2+1, 3, f'tab{Symbol.right}', line_color=THEME.div_line)\n\t\t\t\tout_misc += create_box(x, y, w, h+2, \"options\", line_color=THEME.div_line)\n\t\t\t\tredraw = True\n\n\t\t\t\tcat_width = floor((w+w2) / len(categories))\n\t\t\t\tout_misc += f'{Fx.b}'\n\t\t\t\tfor cx, cat in enumerate(categories):\n\t\t\t\t\tout_misc += f'{Mv.to(y-2, x + 1 + (cat_width * cx) + round(cat_width / 2 - len(cat) / 2 ))}'\n\t\t\t\t\tif cat == selected_cat:\n\t\t\t\t\t\tout_misc += f'{THEME.hi_fg}[{THEME.title}{Fx.u}{cat}{Fx.uu}{THEME.hi_fg}]'\n\t\t\t\t\telse:\n\t\t\t\t\t\tout_misc += f'{THEME.hi_fg}{SUPERSCRIPT[cx+1]}{THEME.title}{cat}'\n\t\t\t\tout_misc += f'{Fx.ub}'\n\t\t\t\tif option_len > h:\n\t\t\t\t\tpages = ceil(option_len / h)\n\t\t\t\telse:\n\t\t\t\t\th = option_len\n\t\t\t\t\tpages = 0\n\t\t\t\tpage = pages if selected_int == -1 and pages > 0 else 1\n\t\t\t\tselected_int = 0 if selected_int >= 0 else len(option_items) - 1\n\t\t\tif redraw:\n\t\t\t\tout = \"\"\n\t\t\t\tcy = 0\n\n\t\t\t\tselected = list(option_items)[selected_int]\n\t\t\t\tif pages:\n\t\t\t\t\tout += (f'{Mv.to(y+h+1, x+11)}{THEME.div_line(Symbol.title_left)}{Fx.b}{THEME.title(\"pg\")}{Fx.ub}{THEME.main_fg(Symbol.up)} {Fx.b}{THEME.title}{page}/{pages} '\n\t\t\t\t\tf'pg{Fx.ub}{THEME.main_fg(Symbol.down)}{THEME.div_line(Symbol.title_right)}')\n\t\t\t\t#out += f'{Mv.to(y+1, x+1)}{THEME.title}{Fx.b}{\"Keys:\":^20}Description:{THEME.main_fg}'\n\t\t\t\tfor n, opt in enumerate(option_items):\n\t\t\t\t\tif pages and n < (page - 1) * ceil(h / 2): continue\n\t\t\t\t\tvalue = getattr(CONFIG, opt)\n\t\t\t\t\tt_color = f'{THEME.selected_bg}{THEME.selected_fg}' if opt == selected else f'{THEME.title}'\n\t\t\t\t\tv_color\t= \"\" if opt == selected else f'{THEME.title}'\n\t\t\t\t\td_quote = '\"' if isinstance(value, str) else \"\"\n\t\t\t\t\tif opt == \"color_theme\":\n\t\t\t\t\t\tcounter = f' {color_i + 1}/{len(Theme.themes)}'\n\t\t\t\t\telif opt == \"proc_sorting\":\n\t\t\t\t\t\tcounter = f' {CONFIG.sorting_options.index(CONFIG.proc_sorting) + 1}/{len(CONFIG.sorting_options)}'\n\t\t\t\t\telif opt == \"log_level\":\n\t\t\t\t\t\tcounter = f' {loglevel_i + 1}/{len(CONFIG.log_levels)}'\n\t\t\t\t\telif opt == \"cpu_sensor\":\n\t\t\t\t\t\tcounter = f' {cpu_sensor_i + 1}/{len(CONFIG.cpu_sensors)}'\n\t\t\t\t\telif opt in [\"cpu_graph_upper\", \"cpu_graph_lower\"]:\n\t\t\t\t\t\tcounter = f' {cpu_graph_i[opt] + 1}/{len(CONFIG.cpu_percent_fields)}'\n\t\t\t\t\telif opt == \"temp_scale\":\n\t\t\t\t\t\tcounter = f' {temp_scale_i + 1}/{len(CONFIG.temp_scales)}'\n\t\t\t\t\telse:\n\t\t\t\t\t\tcounter = \"\"\n\t\t\t\t\tout += f'{Mv.to(y+1+cy, x+1)}{t_color}{Fx.b}{opt.replace(\"_\", \" \").capitalize() + counter:^24.24}{Fx.ub}{Mv.to(y+2+cy, x+1)}{v_color}'\n\t\t\t\t\tif opt == selected:\n\t\t\t\t\t\tif isinstance(value, bool) or opt in [\"color_theme\", \"proc_sorting\", \"log_level\", \"cpu_sensor\", \"cpu_graph_upper\", \"cpu_graph_lower\", \"temp_scale\"]:\n\t\t\t\t\t\t\tout += f'{t_color} {Symbol.left}{v_color}{d_quote + str(value) + d_quote:^20.20}{t_color}{Symbol.right} '\n\t\t\t\t\t\telif inputting:\n\t\t\t\t\t\t\tout += f'{str(input_val)[-17:] + Fx.bl + \"█\" + Fx.ubl + \"\" + Symbol.enter:^33.33}'\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tout += ((f'{t_color} {Symbol.left}{v_color}' if type(value) is int else \"  \") +\n\t\t\t\t\t\t\tf'{str(value) + \" \" + Symbol.enter:^20.20}' + (f'{t_color}{Symbol.right} ' if type(value) is int else \"  \"))\n\t\t\t\t\telse:\n\t\t\t\t\t\tout += f'{d_quote + str(value) + d_quote:^24.24}'\n\t\t\t\t\tout += f'{Term.bg}'\n\t\t\t\t\tif opt == selected:\n\t\t\t\t\t\th2 = len(option_items[opt]) + 2\n\t\t\t\t\t\ty2 = y + (selected_int * 2) - ((page-1) * h)\n\t\t\t\t\t\tif y2 + h2 > Term.height: y2 = Term.height - h2\n\t\t\t\t\t\tout += f'{create_box(x2, y2, w2, h2, \"description\", line_color=THEME.div_line)}{THEME.main_fg}'\n\t\t\t\t\t\tfor n, desc in enumerate(option_items[opt]):\n\t\t\t\t\t\t\tout += f'{Mv.to(y2+1+n, x2+2)}{desc:.48}'\n\t\t\t\t\tcy += 2\n\t\t\t\t\tif cy >= h: break\n\t\t\t\tif cy < h:\n\t\t\t\t\tfor i in range(h-cy):\n\t\t\t\t\t\tout += f'{Mv.to(y+1+cy+i, x+1)}{\" \" * (w-2)}'\n\n\n\t\t\tif not skip or redraw:\n\t\t\t\tDraw.now(f'{cls.background}{out_misc}{out}')\n\t\t\tskip = redraw = False\n\n\t\t\tif Key.input_wait(Timer.left()):\n\t\t\t\tkey = Key.get()\n\t\t\t\tredraw = True\n\t\t\t\thas_sel = False\n\t\t\t\tif key == \"mouse_click\" and not inputting:\n\t\t\t\t\tmx, my = Key.get_mouse()\n\t\t\t\t\tif x < mx < x + w + w2 and y - 4 < my < y:\n\t\t\t\t\t\t# if my == y - 2:\n\t\t\t\t\t\tfor cx, cat in enumerate(categories):\n\t\t\t\t\t\t\tccx = x + (cat_width * cx) + round(cat_width / 2 - len(cat) / 2 )\n\t\t\t\t\t\t\tif ccx - 2 < mx < ccx + 2 + len(cat):\n\t\t\t\t\t\t\t\tkey = str(cx+1)\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\telif x < mx < x + w and y < my < y + h + 2:\n\t\t\t\t\t\tmouse_sel = ceil((my - y) / 2) - 1 + ceil((page-1) * (h / 2))\n\t\t\t\t\t\tif pages and my == y+h+1 and x+11 < mx < x+16:\n\t\t\t\t\t\t\tkey = \"page_up\"\n\t\t\t\t\t\telif pages and my == y+h+1 and x+19 < mx < x+24:\n\t\t\t\t\t\t\tkey = \"page_down\"\n\t\t\t\t\t\telif my == y+h+1:\n\t\t\t\t\t\t\tpass\n\t\t\t\t\t\telif mouse_sel == selected_int:\n\t\t\t\t\t\t\tif mx < x + 6:\n\t\t\t\t\t\t\t\tkey = \"left\"\n\t\t\t\t\t\t\telif mx > x + 19:\n\t\t\t\t\t\t\t\tkey = \"right\"\n\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\tkey = \"enter\"\n\t\t\t\t\t\telif mouse_sel < len(option_items):\n\t\t\t\t\t\t\tselected_int = mouse_sel\n\t\t\t\t\t\t\thas_sel = True\n\t\t\t\t\telse:\n\t\t\t\t\t\tkey = \"escape\"\n\t\t\t\tif inputting:\n\t\t\t\t\tif key in [\"escape\", \"mouse_click\"]:\n\t\t\t\t\t\tinputting = False\n\t\t\t\t\telif key == \"enter\":\n\t\t\t\t\t\tinputting = False\n\t\t\t\t\t\tif str(getattr(CONFIG, selected)) != input_val:\n\t\t\t\t\t\t\tif selected == \"update_ms\":\n\t\t\t\t\t\t\t\tif not input_val or int(input_val) < 100:\n\t\t\t\t\t\t\t\t\tCONFIG.update_ms = 100\n\t\t\t\t\t\t\t\telif int(input_val) > 86399900:\n\t\t\t\t\t\t\t\t\tCONFIG.update_ms = 86399900\n\t\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\t\tCONFIG.update_ms = int(input_val)\n\t\t\t\t\t\t\telif selected == \"proc_update_mult\":\n\t\t\t\t\t\t\t\tif not input_val or int(input_val) < 1:\n\t\t\t\t\t\t\t\t\tCONFIG.proc_update_mult = 1\n\t\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\t\tCONFIG.proc_update_mult = int(input_val)\n\t\t\t\t\t\t\t\tCollector.proc_counter = 1\n\t\t\t\t\t\t\telif selected == \"tree_depth\":\n\t\t\t\t\t\t\t\tif not input_val or int(input_val) < 0:\n\t\t\t\t\t\t\t\t\tCONFIG.tree_depth = 0\n\t\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\t\tCONFIG.tree_depth = int(input_val)\n\t\t\t\t\t\t\t\tProcCollector.collapsed = {}\n\t\t\t\t\t\t\telif selected == \"shown_boxes\":\n\t\t\t\t\t\t\t\tnew_boxes: List = []\n\t\t\t\t\t\t\t\tfor box in input_val.split():\n\t\t\t\t\t\t\t\t\tif box in [\"cpu\", \"mem\", \"net\", \"proc\"]:\n\t\t\t\t\t\t\t\t\t\tnew_boxes.append(box)\n\t\t\t\t\t\t\t\tCONFIG.shown_boxes = \" \".join(new_boxes)\n\t\t\t\t\t\t\t\tBox.view_mode = \"user\"\n\t\t\t\t\t\t\t\tBox.view_modes[\"user\"] = CONFIG.shown_boxes.split()\n\t\t\t\t\t\t\t\tDraw.clear(saved=True)\n\t\t\t\t\t\t\telif isinstance(getattr(CONFIG, selected), str):\n\t\t\t\t\t\t\t\tsetattr(CONFIG, selected, input_val)\n\t\t\t\t\t\t\t\tif selected.startswith(\"net_\"):\n\t\t\t\t\t\t\t\t\tNetCollector.net_min = {\"download\" : -1, \"upload\" : -1}\n\t\t\t\t\t\t\t\telif selected == \"draw_clock\":\n\t\t\t\t\t\t\t\t\tBox.clock_on = len(CONFIG.draw_clock) > 0\n\t\t\t\t\t\t\t\t\tif not Box.clock_on: Draw.clear(\"clock\", saved=True)\n\t\t\t\t\t\t\t\telif selected == \"io_graph_speeds\":\n\t\t\t\t\t\t\t\t\tMemBox.graph_speeds = {}\n\t\t\t\t\t\t\tTerm.refresh(force=True)\n\t\t\t\t\t\t\tcls.resized = False\n\t\t\t\t\telif key == \"backspace\" and len(input_val):\n\t\t\t\t\t\tinput_val = input_val[:-1]\n\t\t\t\t\telif key == \"delete\":\n\t\t\t\t\t\tinput_val = \"\"\n\t\t\t\t\telif isinstance(getattr(CONFIG, selected), str) and len(key) == 1:\n\t\t\t\t\t\tinput_val += key\n\t\t\t\t\telif isinstance(getattr(CONFIG, selected), int) and key.isdigit():\n\t\t\t\t\t\tinput_val += key\n\t\t\t\telif key == \"q\":\n\t\t\t\t\tclean_quit()\n\t\t\t\telif key in [\"escape\", \"o\", \"M\", \"f2\"]:\n\t\t\t\t\tcls.close = True\n\t\t\t\t\tbreak\n\t\t\t\telif key == \"tab\" or (key == \"down\" and selected_int == len(option_items) - 1 and page in [0, pages]):\n\t\t\t\t\tif cat_int == len(categories) - 1:\n\t\t\t\t\t\tcat_int = 0\n\t\t\t\t\telse:\n\t\t\t\t\t\tcat_int += 1\n\t\t\t\t\tchange_cat = True\n\t\t\t\telif key == \"shift_tab\" or (key == \"up\" and selected_int == 0 and page == 1):\n\t\t\t\t\tif cat_int == 0:\n\t\t\t\t\t\tcat_int = len(categories) - 1\n\t\t\t\t\telse:\n\t\t\t\t\t\tcat_int -= 1\n\t\t\t\t\tchange_cat = True\n\t\t\t\t\tselected_int = -1 if key != \"shift_tab\" else 0\n\t\t\t\telif key in list(map(str, range(1, len(cat_list)+1))) and key != str(cat_int + 1):\n\t\t\t\t\tcat_int = int(key) - 1\n\t\t\t\t\tchange_cat = True\n\t\t\t\telif key == \"enter\" and selected in [\"update_ms\", \"disks_filter\", \"custom_cpu_name\", \"net_download\",\n\t\t\t\t\t \"net_upload\", \"draw_clock\", \"tree_depth\", \"proc_update_mult\", \"shown_boxes\", \"net_iface\", \"io_graph_speeds\"]:\n\t\t\t\t\tinputting = True\n\t\t\t\t\tinput_val = str(getattr(CONFIG, selected))\n\t\t\t\telif key == \"left\" and selected == \"update_ms\" and CONFIG.update_ms - 100 >= 100:\n\t\t\t\t\tCONFIG.update_ms -= 100\n\t\t\t\t\tBox.draw_update_ms()\n\t\t\t\telif key == \"right\" and selected == \"update_ms\" and CONFIG.update_ms + 100 <= 86399900:\n\t\t\t\t\tCONFIG.update_ms += 100\n\t\t\t\t\tBox.draw_update_ms()\n\t\t\t\telif key == \"left\" and selected == \"proc_update_mult\" and CONFIG.proc_update_mult > 1:\n\t\t\t\t\tCONFIG.proc_update_mult -= 1\n\t\t\t\t\tCollector.proc_counter = 1\n\t\t\t\telif key == \"right\" and selected == \"proc_update_mult\":\n\t\t\t\t\tCONFIG.proc_update_mult += 1\n\t\t\t\t\tCollector.proc_counter = 1\n\t\t\t\telif key == \"left\" and selected == \"tree_depth\" and CONFIG.tree_depth > 0:\n\t\t\t\t\tCONFIG.tree_depth -= 1\n\t\t\t\t\tProcCollector.collapsed = {}\n\t\t\t\telif key == \"right\" and selected == \"tree_depth\":\n\t\t\t\t\tCONFIG.tree_depth += 1\n\t\t\t\t\tProcCollector.collapsed = {}\n\t\t\t\telif key in [\"left\", \"right\"] and isinstance(getattr(CONFIG, selected), bool):\n\t\t\t\t\tsetattr(CONFIG, selected, not getattr(CONFIG, selected))\n\t\t\t\t\tif selected == \"check_temp\":\n\t\t\t\t\t\tif CONFIG.check_temp:\n\t\t\t\t\t\t\tCpuCollector.get_sensors()\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tCpuCollector.sensor_method = \"\"\n\t\t\t\t\t\t\tCpuCollector.got_sensors = False\n\t\t\t\t\tif selected in [\"net_auto\", \"net_color_fixed\", \"net_sync\"]:\n\t\t\t\t\t\tif selected == \"net_auto\": NetCollector.auto_min = CONFIG.net_auto\n\t\t\t\t\t\tNetBox.redraw = True\n\t\t\t\t\tif selected == \"theme_background\":\n\t\t\t\t\t\tTerm.bg = f'{THEME.main_bg}' if CONFIG.theme_background else \"\\033[49m\"\n\t\t\t\t\t\tDraw.now(Term.bg)\n\t\t\t\t\tif selected == \"show_battery\":\n\t\t\t\t\t\tDraw.clear(\"battery\", saved=True)\n\t\t\t\t\tTerm.refresh(force=True)\n\t\t\t\t\tcls.resized = False\n\t\t\t\telif key in [\"left\", \"right\"] and selected == \"color_theme\" and len(Theme.themes) > 1:\n\t\t\t\t\tif key == \"left\":\n\t\t\t\t\t\tcolor_i -= 1\n\t\t\t\t\t\tif color_i < 0: color_i = len(Theme.themes) - 1\n\t\t\t\t\telif key == \"right\":\n\t\t\t\t\t\tcolor_i += 1\n\t\t\t\t\t\tif color_i > len(Theme.themes) - 1: color_i = 0\n\t\t\t\t\tCollector.collect_idle.wait()\n\t\t\t\t\tCONFIG.color_theme = list(Theme.themes)[color_i]\n\t\t\t\t\tTHEME(CONFIG.color_theme)\n\t\t\t\t\tTerm.refresh(force=True)\n\t\t\t\t\tTimer.finish()\n\t\t\t\telif key in [\"left\", \"right\"] and selected == \"proc_sorting\":\n\t\t\t\t\tProcCollector.sorting(key)\n\t\t\t\telif key in [\"left\", \"right\"] and selected == \"log_level\":\n\t\t\t\t\tif key == \"left\":\n\t\t\t\t\t\tloglevel_i -= 1\n\t\t\t\t\t\tif loglevel_i < 0: loglevel_i = len(CONFIG.log_levels) - 1\n\t\t\t\t\telif key == \"right\":\n\t\t\t\t\t\tloglevel_i += 1\n\t\t\t\t\t\tif loglevel_i > len(CONFIG.log_levels) - 1: loglevel_i = 0\n\t\t\t\t\tCONFIG.log_level = CONFIG.log_levels[loglevel_i]\n\t\t\t\t\terrlog.setLevel(getattr(logging, CONFIG.log_level))\n\t\t\t\t\terrlog.info(f'Loglevel set to {CONFIG.log_level}')\n\t\t\t\telif key in [\"left\", \"right\"] and selected in [\"cpu_graph_upper\", \"cpu_graph_lower\"]:\n\t\t\t\t\tif key == \"left\":\n\t\t\t\t\t\tcpu_graph_i[selected] -= 1\n\t\t\t\t\t\tif cpu_graph_i[selected] < 0: cpu_graph_i[selected] = len(CONFIG.cpu_percent_fields) - 1\n\t\t\t\t\tif key == \"right\":\n\t\t\t\t\t\tcpu_graph_i[selected] += 1\n\t\t\t\t\t\tif cpu_graph_i[selected] > len(CONFIG.cpu_percent_fields) - 1: cpu_graph_i[selected] = 0\n\t\t\t\t\tsetattr(CONFIG, selected, CONFIG.cpu_percent_fields[cpu_graph_i[selected]])\n\t\t\t\t\tsetattr(CpuCollector, selected.replace(\"_graph\", \"\"), [])\n\t\t\t\t\tTerm.refresh(force=True)\n\t\t\t\t\tcls.resized = False\n\t\t\t\telif key in [\"left\", \"right\"] and selected == \"temp_scale\":\n\t\t\t\t\tif key == \"left\":\n\t\t\t\t\t\ttemp_scale_i -= 1\n\t\t\t\t\t\tif temp_scale_i < 0: temp_scale_i = len(CONFIG.temp_scales) - 1\n\t\t\t\t\tif key == \"right\":\n\t\t\t\t\t\ttemp_scale_i += 1\n\t\t\t\t\t\tif temp_scale_i > len(CONFIG.temp_scales) - 1: temp_scale_i = 0\n\t\t\t\t\tCONFIG.temp_scale = CONFIG.temp_scales[temp_scale_i]\n\t\t\t\t\tTerm.refresh(force=True)\n\t\t\t\t\tcls.resized = False\n\t\t\t\telif key in [\"left\", \"right\"] and selected == \"cpu_sensor\" and len(CONFIG.cpu_sensors) > 1:\n\t\t\t\t\tif key == \"left\":\n\t\t\t\t\t\tcpu_sensor_i -= 1\n\t\t\t\t\t\tif cpu_sensor_i < 0: cpu_sensor_i = len(CONFIG.cpu_sensors) - 1\n\t\t\t\t\telif key == \"right\":\n\t\t\t\t\t\tcpu_sensor_i += 1\n\t\t\t\t\t\tif cpu_sensor_i > len(CONFIG.cpu_sensors) - 1: cpu_sensor_i = 0\n\t\t\t\t\tCollector.collect_idle.wait()\n\t\t\t\t\tCpuCollector.sensor_swap = True\n\t\t\t\t\tCONFIG.cpu_sensor = CONFIG.cpu_sensors[cpu_sensor_i]\n\t\t\t\t\tif CONFIG.check_temp and (CpuCollector.sensor_method != \"psutil\" or CONFIG.cpu_sensor == \"Auto\"):\n\t\t\t\t\t\tCpuCollector.get_sensors()\n\t\t\t\t\t\tTerm.refresh(force=True)\n\t\t\t\t\t\tcls.resized = False\n\t\t\t\telif key in [\"up\", \"mouse_scroll_up\"]:\n\t\t\t\t\tselected_int -= 1\n\t\t\t\t\tif selected_int < 0: selected_int = len(option_items) - 1\n\t\t\t\t\tpage = floor(selected_int * 2 / h) + 1\n\t\t\t\telif key in [\"down\", \"mouse_scroll_down\"]:\n\t\t\t\t\tselected_int += 1\n\t\t\t\t\tif selected_int > len(option_items) - 1: selected_int = 0\n\t\t\t\t\tpage = floor(selected_int * 2 / h) + 1\n\t\t\t\telif key == \"page_up\":\n\t\t\t\t\tif not pages or page == 1:\n\t\t\t\t\t\tselected_int = 0\n\t\t\t\t\telse:\n\t\t\t\t\t\tpage -= 1\n\t\t\t\t\t\tif page < 1: page = pages\n\t\t\t\t\tselected_int = (page-1) * ceil(h / 2)\n\t\t\t\telif key == \"page_down\":\n\t\t\t\t\tif not pages or page == pages:\n\t\t\t\t\t\tselected_int = len(option_items) - 1\n\t\t\t\t\telse:\n\t\t\t\t\t\tpage += 1\n\t\t\t\t\t\tif page > pages: page = 1\n\t\t\t\t\t\tselected_int = (page-1) * ceil(h / 2)\n\t\t\t\telif has_sel:\n\t\t\t\t\tpass\n\t\t\t\telse:\n\t\t\t\t\tredraw = False\n\n\t\t\tif Timer.not_zero() and not cls.resized:\n\t\t\t\tskip = True\n\t\t\telse:\n\t\t\t\tCollector.collect()\n\t\t\t\tCollector.collect_done.wait(2)\n\t\t\t\tif CONFIG.background_update: cls.background = f'{THEME.inactive_fg}' + Fx.uncolor(f'{Draw.saved_buffer()}') + f'{Term.fg}'\n\t\t\t\tTimer.stamp()\n\n\t\tif main_active:\n\t\t\tcls.close = False\n\t\t\treturn\n\t\tDraw.now(f'{Draw.saved_buffer()}')\n\t\tcls.background = \"\"\n\t\tcls.active = False\n\t\tcls.close = False\n\nclass Timer:\n\ttimestamp: float\n\treturn_zero = False\n\n\t@classmethod\n\tdef stamp(cls):\n\t\tcls.timestamp = time()\n\n\t@classmethod\n\tdef not_zero(cls) -> bool:\n\t\tif cls.return_zero:\n\t\t\tcls.return_zero = False\n\t\t\treturn False\n\t\treturn cls.timestamp + (CONFIG.update_ms / 1000) > time()\n\n\t@classmethod\n\tdef left(cls) -> float:\n\t\tt_left: float = cls.timestamp + (CONFIG.update_ms / 1000) - time()\n\t\tif t_left > CONFIG.update_ms / 1000:\n\t\t\tcls.stamp()\n\t\t\treturn CONFIG.update_ms / 1000\n\t\treturn t_left\n\n\t@classmethod\n\tdef finish(cls):\n\t\tcls.return_zero = True\n\t\tcls.timestamp = time() - (CONFIG.update_ms / 1000)\n\t\tKey.break_wait()\n\nclass UpdateChecker:\n\tversion: str = VERSION\n\tthread: threading.Thread\n\n\t@classmethod\n\tdef run(cls):\n\t\tcls.thread = threading.Thread(target=cls._checker)\n\t\tcls.thread.start()\n\n\t@classmethod\n\tdef _checker(cls):\n\t\ttry:\n\t\t\twith urllib.request.urlopen(\"https://github.com/aristocratos/bpytop/raw/master/bpytop.py\", timeout=5) as source: # type: ignore\n\t\t\t\tfor line in source:\n\t\t\t\t\tline = line.decode(\"utf-8\")\n\t\t\t\t\tif line.startswith(\"VERSION: str =\"):\n\t\t\t\t\t\tcls.version = line[(line.index(\"=\")+1):].strip('\" \\n')\n\t\t\t\t\t\tbreak\n\t\texcept Exception as e:\n\t\t\terrlog.exception(f'{e}')\n\t\telse:\n\t\t\tif cls.version != VERSION and which(\"notify-send\"):\n\t\t\t\ttry:\n\t\t\t\t\tsubprocess.run([\"notify-send\", \"-u\", \"normal\", \"BpyTop Update!\",\n\t\t\t\t\t\tf'New version of BpyTop available!\\nCurrent version: {VERSION}\\nNew version: {cls.version}\\nDownload at github.com/aristocratos/bpytop',\n\t\t\t\t\t\t\"-i\", \"update-notifier\", \"-t\", \"10000\"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\terrlog.exception(f'{e}')\n\nclass Init:\n\trunning: bool = True\n\tinitbg_colors: List[str] = []\n\tinitbg_data: List[int]\n\tinitbg_up: Graph\n\tinitbg_down: Graph\n\tresized = False\n\n\t@classmethod\n\tdef start(cls):\n\t\tDraw.buffer(\"init\", z=1)\n\t\tDraw.buffer(\"initbg\", z=10)\n\t\tfor i in range(51):\n\t\t\tfor _ in range(2): cls.initbg_colors.append(Color.fg(i, i, i))\n\t\tDraw.buffer(\"banner\", (f'{Banner.draw(Term.height // 2 - 10, center=True)}{Mv.d(1)}{Mv.l(11)}{Colors.black_bg}{Colors.default}'\n\t\t\t\tf'{Fx.b}{Fx.i}Version: {VERSION}{Fx.ui}{Fx.ub}{Term.bg}{Term.fg}{Color.fg(\"#50\")}'), z=2)\n\t\tfor _i in range(7):\n\t\t\tperc = f'{str(round((_i + 1) * 14 + 2)) + \"%\":>5}'\n\t\t\tDraw.buffer(\"+banner\", f'{Mv.to(Term.height // 2 - 2 + _i, Term.width // 2 - 28)}{Fx.trans(perc)}{Symbol.v_line}')\n\n\t\tDraw.out(\"banner\")\n\t\tDraw.buffer(\"+init!\", f'{Color.fg(\"#cc\")}{Fx.b}{Mv.to(Term.height // 2 - 2, Term.width // 2 - 21)}{Mv.save}')\n\n\t\tcls.initbg_data = [randint(0, 100) for _ in range(Term.width * 2)]\n\t\tcls.initbg_up = Graph(Term.width, Term.height // 2, cls.initbg_colors, cls.initbg_data, invert=True)\n\t\tcls.initbg_down = Graph(Term.width, Term.height // 2, cls.initbg_colors, cls.initbg_data, invert=False)\n\n\t@classmethod\n\tdef success(cls):\n\t\tif not CONFIG.show_init or cls.resized: return\n\t\tcls.draw_bg(5)\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Symbol.ok}\\n{Mv.r(Term.width // 2 - 22)}{Mv.save}')\n\n\t@staticmethod\n\tdef fail(err):\n\t\tif CONFIG.show_init:\n\t\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Symbol.fail}')\n\t\t\tsleep(2)\n\t\terrlog.exception(f'{err}')\n\t\tclean_quit(1, errmsg=f'Error during init! See {CONFIG_DIR}/error.log for more information.')\n\n\t@classmethod\n\tdef draw_bg(cls, times: int = 5):\n\t\tfor _ in range(times):\n\t\t\tsleep(0.05)\n\t\t\tx = randint(0, 100)\n\t\t\tDraw.buffer(\"initbg\", f'{Fx.ub}{Mv.to(0, 0)}{cls.initbg_up(x)}{Mv.to(Term.height // 2, 0)}{cls.initbg_down(x)}')\n\t\t\tDraw.out(\"initbg\", \"banner\", \"init\")\n\n\t@classmethod\n\tdef done(cls):\n\t\tcls.running = False\n\t\tif not CONFIG.show_init: return\n\t\tif cls.resized:\n\t\t\tDraw.now(Term.clear)\n\t\telse:\n\t\t\tcls.draw_bg(10)\n\t\tDraw.clear(\"initbg\", \"banner\", \"init\", saved=True)\n\t\tif cls.resized: return\n\t\tdel cls.initbg_up, cls.initbg_down, cls.initbg_data, cls.initbg_colors\n\n\n#? Functions ------------------------------------------------------------------------------------->\n\ndef get_cpu_name() -> str:\n\t'''Fetch a suitable CPU identifier from the CPU model name string'''\n\tname: str = \"\"\n\tnlist: List = []\n\tcommand: str = \"\"\n\tcmd_out: str = \"\"\n\trem_line: str = \"\"\n\tif SYSTEM == \"Linux\":\n\t\tcommand = \"cat /proc/cpuinfo\"\n\t\trem_line = \"model name\"\n\telif SYSTEM == \"MacOS\":\n\t\tcommand =\"sysctl -n machdep.cpu.brand_string\"\n\telif SYSTEM == \"BSD\":\n\t\tcommand =\"sysctl hw.model\"\n\t\trem_line = \"hw.model\"\n\n\ttry:\n\t\tcmd_out = subprocess.check_output(\"LANG=C \" + command, shell=True, universal_newlines=True)\n\texcept:\n\t\tpass\n\tif rem_line:\n\t\tfor line in cmd_out.split(\"\\n\"):\n\t\t\tif rem_line in line:\n\t\t\t\tname = re.sub(\".*\" + rem_line + \".*:\", \"\", line, count=1).lstrip()\n\telse:\n\t\tname = cmd_out\n\tnlist = name.split(\" \")\n\ttry:\n\t\tif \"Xeon\" in name and \"CPU\" in name:\n\t\t\tname = nlist[nlist.index(\"CPU\")+(-1 if name.endswith((\"CPU\", \"z\")) else 1)]\n\t\telif \"Ryzen\" in name:\n\t\t\tname = \" \".join(nlist[nlist.index(\"Ryzen\"):nlist.index(\"Ryzen\")+3])\n\t\telif \"Duo\" in name and \"@\" in name:\n\t\t\tname = \" \".join(nlist[:nlist.index(\"@\")])\n\t\telif \"CPU\" in name and not nlist[0] == \"CPU\" and not nlist[nlist.index(\"CPU\")-1].isdigit():\n\t\t\tname = nlist[nlist.index(\"CPU\")-1]\n\texcept:\n\t\tpass\n\n\tname = name.replace(\"Processor\", \"\").replace(\"CPU\", \"\").replace(\"(R)\", \"\").replace(\"(TM)\", \"\").replace(\"Intel\", \"\")\n\tname = re.sub(r\"\\d?\\.?\\d+[mMgG][hH][zZ]\", \"\", name)\n\tname = \" \".join(name.split())\n\n\treturn name\n\ndef get_cpu_core_mapping() -> List[int]:\n\tmapping: List[int] = []\n\tcore_ids: List[int] = []\n\n\tif SYSTEM == \"Linux\" and os.path.isfile(\"/proc/cpuinfo\"):\n\t\ttry:\n\t\t\tmapping = [0] * THREADS\n\t\t\tnum = 0\n\t\t\twith open(\"/proc/cpuinfo\", \"r\") as f:\n\t\t\t\tfor line in f:\n\t\t\t\t\tif line.startswith(\"processor\"):\n\t\t\t\t\t\tnum = int(line.strip()[(line.index(\": \")+2):])\n\t\t\t\t\t\tif num > THREADS - 1:\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\telif line.startswith(\"core id\"):\n\t\t\t\t\t\tcore_id = int(line.strip()[(line.index(\": \")+2):])\n\t\t\t\t\t\tif core_id not in core_ids:\n\t\t\t\t\t\t\tcore_ids.append(core_id)\n\t\t\t\t\t\tmapping[num] = core_ids.index(core_id)\n\t\t\tif num < THREADS - 1:\n\t\t\t\traise Exception\n\t\texcept:\n\t\t\tmapping = []\n\n\tif not mapping:\n\t\tmapping = []\n\t\tfor _ in range(THREADS // CORES):\n\t\t\tmapping.extend([x for x in range(CORES)])\n\n\treturn mapping\n\ndef create_box(x: int = 0, y: int = 0, width: int = 0, height: int = 0, title: str = \"\", title2: str = \"\", line_color: Color = None, title_color: Color = None, fill: bool = True, box = None) -> str:\n\t'''Create a box from a box object or by given arguments'''\n\tout: str = f'{Term.fg}{Term.bg}'\n\tnum: int = 0\n\tif not line_color: line_color = THEME.div_line\n\tif not title_color: title_color = THEME.title\n\n\t#* Get values from box class if given\n\tif box:\n\t\tx = box.x\n\t\ty = box.y\n\t\twidth = box.width\n\t\theight = box.height\n\t\ttitle = box.name\n\t\tnum = box.num\n\thlines: Tuple[int, int] = (y, y + height - 1)\n\n\tout += f'{line_color}'\n\n\t#* Draw all horizontal lines\n\tfor hpos in hlines:\n\t\tout += f'{Mv.to(hpos, x)}{Symbol.h_line * (width - 1)}'\n\n\t#* Draw all vertical lines and fill if enabled\n\tfor hpos in range(hlines[0]+1, hlines[1]):\n\t\tout += f'{Mv.to(hpos, x)}{Symbol.v_line}{\" \" * (width-2) if fill else Mv.r(width-2)}{Symbol.v_line}'\n\n\t#* Draw corners\n\tout += f'{Mv.to(y, x)}{Symbol.left_up}\\\n\t{Mv.to(y, x + width - 1)}{Symbol.right_up}\\\n\t{Mv.to(y + height - 1, x)}{Symbol.left_down}\\\n\t{Mv.to(y + height - 1, x + width - 1)}{Symbol.right_down}'\n\n\t#* Draw titles if enabled\n\tif title:\n\t\tnumbered: str = \"\" if not num else f'{THEME.hi_fg(SUPERSCRIPT[num])}'\n\t\tout += f'{Mv.to(y, x + 2)}{Symbol.title_left}{Fx.b}{numbered}{title_color}{title}{Fx.ub}{line_color}{Symbol.title_right}'\n\tif title2:\n\t\tout += f'{Mv.to(hlines[1], x + 2)}{Symbol.title_left}{title_color}{Fx.b}{title2}{Fx.ub}{line_color}{Symbol.title_right}'\n\n\treturn f'{out}{Term.fg}{Mv.to(y + 1, x + 1)}'\n\ndef now_sleeping(signum, frame):\n\t\"\"\"Reset terminal settings and stop background input read before putting to sleep\"\"\"\n\tKey.stop()\n\tCollector.stop()\n\tDraw.now(Term.clear, Term.normal_screen, Term.show_cursor, Term.mouse_off, Term.mouse_direct_off, Term.title())\n\tTerm.echo(True)\n\tos.kill(os.getpid(), signal.SIGSTOP)\n\ndef now_awake(signum, frame):\n\t\"\"\"Set terminal settings and restart background input read\"\"\"\n\tDraw.now(Term.alt_screen, Term.clear, Term.hide_cursor, Term.mouse_on, Term.title(\"BpyTOP\"))\n\tTerm.echo(False)\n\tKey.start()\n\tTerm.refresh()\n\tBox.calc_sizes()\n\tBox.draw_bg()\n\tCollector.start()\n\ndef quit_sigint(signum, frame):\n\t\"\"\"SIGINT redirection to clean_quit()\"\"\"\n\tclean_quit()\n\ndef clean_quit(errcode: int = 0, errmsg: str = \"\", thread: bool = False):\n\t\"\"\"Stop background input read, save current config and reset terminal settings before quitting\"\"\"\n\tglobal THREAD_ERROR\n\tif thread:\n\t\tTHREAD_ERROR = errcode\n\t\tinterrupt_main()\n\t\treturn\n\tif THREAD_ERROR: errcode = THREAD_ERROR\n\tKey.stop()\n\tCollector.stop()\n\tif not errcode: CONFIG.save_config()\n\tDraw.now(Term.clear, Term.normal_screen, Term.show_cursor, Term.mouse_off, Term.mouse_direct_off, Term.title())\n\tTerm.echo(True)\n\tif errcode == 0:\n\t\terrlog.info(f'Exiting. Runtime {timedelta(seconds=round(time() - SELF_START, 0))} \\n')\n\telse:\n\t\terrlog.warning(f'Exiting with errorcode ({errcode}). Runtime {timedelta(seconds=round(time() - SELF_START, 0))} \\n')\n\t\tif not errmsg: errmsg = f'Bpytop exited with errorcode ({errcode}). See {CONFIG_DIR}/error.log for more information!'\n\tif errmsg: print(errmsg)\n\n\traise SystemExit(errcode)\n\ndef floating_humanizer(value: Union[float, int], bit: bool = False, per_second: bool = False, start: int = 0, short: bool = False) -> str:\n\t'''Scales up in steps of 1024 to highest possible unit and returns string with unit suffixed\n\t* bit=True or defaults to bytes\n\t* start=int to set 1024 multiplier starting unit\n\t* short=True always returns 0 decimals and shortens unit to 1 character\n\t'''\n\tout: str = \"\"\n\tmult: int = 8 if bit else 1\n\tselector: int = start\n\tunit: Tuple[str, ...] = UNITS[\"bit\"] if bit else UNITS[\"byte\"]\n\n\tif isinstance(value, float): value = round(value * 100 * mult)\n\telif value > 0: value *= 100 * mult\n\telse: value = 0\n\n\twhile len(f'{value}') > 5 and value >= 102400:\n\t\tvalue >>= 10\n\t\tif value < 100:\n\t\t\tout = f'{value}'\n\t\t\tbreak\n\t\tselector += 1\n\telse:\n\t\tif len(f'{value}') == 4 and selector > 0:\n\t\t\tout = f'{value}'[:-2] + \".\" + f'{value}'[-2]\n\t\telif len(f'{value}') == 3 and selector > 0:\n\t\t\tout = f'{value}'[:-2] + \".\" + f'{value}'[-2:]\n\t\telif len(f'{value}') >= 2:\n\t\t\tout = f'{value}'[:-2]\n\t\telse:\n\t\t\tout = f'{value}'\n\n\n\tif short:\n\t\tif \".\" in out:\n\t\t\tout = f'{round(float(out))}'\n\t\tif len(out) > 3:\n\t\t\tout = f'{int(out[0]) + 1}'\n\t\t\tselector += 1\n\tout += f'{\"\" if short else \" \"}{unit[selector][0] if short else unit[selector]}'\n\tif per_second: out += \"ps\" if bit else \"/s\"\n\n\treturn out\n\ndef units_to_bytes(value: str) -> int:\n\tif not value: return 0\n\tout: int = 0\n\tmult: int = 0\n\tbit: bool = False\n\tvalue_i: int = 0\n\tunits: Dict[str, int] = {\"k\" : 1, \"m\" : 2, \"g\" : 3}\n\ttry:\n\t\tif value.lower().endswith(\"s\"):\n\t\t\tvalue = value[:-1]\n\t\tif value.lower().endswith(\"bit\"):\n\t\t\tbit = True\n\t\t\tvalue = value[:-3]\n\t\telif value.lower().endswith(\"byte\"):\n\t\t\tvalue = value[:-4]\n\n\t\tif value[-1].lower() in units:\n\t\t\tmult = units[value[-1].lower()]\n\t\t\tvalue = value[:-1]\n\n\t\tif \".\" in value and value.replace(\".\", \"\").isdigit():\n\t\t\tif mult > 0:\n\t\t\t\tvalue_i = round(float(value) * 1024)\n\t\t\t\tmult -= 1\n\t\t\telse:\n\t\t\t\tvalue_i = round(float(value))\n\t\telif value.isdigit():\n\t\t\tvalue_i = int(value)\n\n\t\tout = int(value_i) << (10 * mult)\n\t\tif bit: out = round(out / 8)\n\texcept ValueError:\n\t\tout = 0\n\treturn out\n\ndef min_max(value: int, min_value: int=0, max_value: int=100) -> int:\n\treturn max(min_value, min(value, max_value))\n\ndef readfile(file: str, default: str = \"\") -> str:\n\tout: Union[str, None] = None\n\tif os.path.isfile(file):\n\t\ttry:\n\t\t\twith open(file, \"r\") as f:\n\t\t\t\tout = f.read().strip()\n\t\texcept:\n\t\t\tpass\n\treturn default if out is None else out\n\ndef temperature(value: int, scale: str = \"celsius\") -> Tuple[int, str]:\n\t\"\"\"Returns a tuple with integer value and string unit converted from an integer in celsius to: celsius, fahrenheit, kelvin or rankine.\"\"\"\n\tif scale == \"celsius\":\n\t\treturn (value, \"°C\")\n\telif scale == \"fahrenheit\":\n\t\treturn (round(value * 1.8 + 32), \"°F\")\n\telif scale == \"kelvin\":\n\t\treturn (round(value + 273.15), \"K \")\n\telif scale == \"rankine\":\n\t\treturn (round(value * 1.8 + 491.67), \"°R\")\n\telse:\n\t\treturn (0, \"\")\n\ndef process_keys():\n\tmouse_pos: Tuple[int, int] = (0, 0)\n\tfiltered: bool = False\n\tbox_keys = {\"1\" : \"cpu\", \"2\" : \"mem\", \"3\" : \"net\", \"4\" : \"proc\"}\n\twhile Key.has_key():\n\t\tkey = Key.get()\n\t\tfound: bool = True\n\t\tif key in [\"mouse_scroll_up\", \"mouse_scroll_down\", \"mouse_click\"]:\n\t\t\tmouse_pos = Key.get_mouse()\n\t\t\tif mouse_pos[0] >= ProcBox.x and ProcBox.current_y + 1 <= mouse_pos[1] < ProcBox.current_y + ProcBox.current_h - 1:\n\t\t\t\tpass\n\t\t\telif key == \"mouse_click\":\n\t\t\t\tkey = \"mouse_unselect\"\n\t\t\telse:\n\t\t\t\tkey = \"_null\"\n\n\t\tif ProcBox.filtering:\n\t\t\tif key in [\"enter\", \"mouse_click\", \"mouse_unselect\"]:\n\t\t\t\tProcBox.filtering = False\n\t\t\t\tCollector.collect(ProcCollector, redraw=True, only_draw=True)\n\t\t\t\tcontinue\n\t\t\telif key in [\"escape\", \"delete\"]:\n\t\t\t\tProcCollector.search_filter = \"\"\n\t\t\t\tProcBox.filtering = False\n\t\t\telif len(key) == 1:\n\t\t\t\tProcCollector.search_filter += key\n\t\t\telif key == \"backspace\" and len(ProcCollector.search_filter) > 0:\n\t\t\t\tProcCollector.search_filter = ProcCollector.search_filter[:-1]\n\t\t\telse:\n\t\t\t\tcontinue\n\t\t\tCollector.collect(ProcCollector, proc_interrupt=True, redraw=True)\n\t\t\tif filtered: Collector.collect_done.wait(0.1)\n\t\t\tfiltered = True\n\t\t\tcontinue\n\n\t\tif key == \"_null\":\n\t\t\tcontinue\n\t\telif key == \"q\":\n\t\t\tclean_quit()\n\t\telif key == \"+\" and CONFIG.update_ms + 100 <= 86399900:\n\t\t\tCONFIG.update_ms += 100\n\t\t\tBox.draw_update_ms()\n\t\telif key == \"-\" and CONFIG.update_ms - 100 >= 100:\n\t\t\tCONFIG.update_ms -= 100\n\t\t\tBox.draw_update_ms()\n\t\telif key in [\"M\", \"escape\"]:\n\t\t\tMenu.main()\n\t\telif key in [\"o\", \"f2\"]:\n\t\t\tMenu.options()\n\t\telif key in [\"H\", \"f1\"]:\n\t\t\tMenu.help()\n\t\telif key == \"m\":\n\t\t\tif list(Box.view_modes).index(Box.view_mode) + 1 > len(list(Box.view_modes)) - 1:\n\t\t\t\tBox.view_mode = list(Box.view_modes)[0]\n\t\t\telse:\n\t\t\t\tBox.view_mode = list(Box.view_modes)[(list(Box.view_modes).index(Box.view_mode) + 1)]\n\t\t\tCONFIG.shown_boxes = \" \".join(Box.view_modes[Box.view_mode])\n\t\t\tDraw.clear(saved=True)\n\t\t\tTerm.refresh(force=True)\n\t\telif key in box_keys:\n\t\t\tboxes = CONFIG.shown_boxes.split()\n\t\t\tif box_keys[key] in boxes:\n\t\t\t\tboxes.remove(box_keys[key])\n\t\t\telse:\n\t\t\t\tboxes.append(box_keys[key])\n\t\t\tCONFIG.shown_boxes = \" \".join(boxes)\n\t\t\tBox.view_mode = \"user\"\n\t\t\tBox.view_modes[\"user\"] = CONFIG.shown_boxes.split()\n\t\t\tDraw.clear(saved=True)\n\t\t\tTerm.refresh(force=True)\n\t\telse:\n\t\t\tfound = False\n\n\t\tif found: continue\n\n\t\tif \"proc\" in Box.boxes:\n\t\t\tif key in [\"left\", \"right\", \"h\", \"l\"]:\n\t\t\t\tProcCollector.sorting(key)\n\t\t\telif key == \" \" and CONFIG.proc_tree and ProcBox.selected > 0:\n\t\t\t\tif ProcBox.selected_pid in ProcCollector.collapsed:\n\t\t\t\t\tProcCollector.collapsed[ProcBox.selected_pid] = not ProcCollector.collapsed[ProcBox.selected_pid]\n\t\t\t\tCollector.collect(ProcCollector, interrupt=True, redraw=True)\n\t\t\telif key == \"e\":\n\t\t\t\tCONFIG.proc_tree = not CONFIG.proc_tree\n\t\t\t\tCollector.collect(ProcCollector, interrupt=True, redraw=True)\n\t\t\telif key == \"r\":\n\t\t\t\tCONFIG.proc_reversed = not CONFIG.proc_reversed\n\t\t\t\tCollector.collect(ProcCollector, interrupt=True, redraw=True)\n\t\t\telif key == \"c\":\n\t\t\t\tCONFIG.proc_per_core = not CONFIG.proc_per_core\n\t\t\t\tCollector.collect(ProcCollector, interrupt=True, redraw=True)\n\t\t\telif key in [\"f\", \"F\", \"/\"]:\n\t\t\t\tProcBox.filtering = True\n\t\t\t\tProcCollector.case_sensitive = key == \"F\"\n\t\t\t\tif not ProcCollector.search_filter: ProcBox.start = 0\n\t\t\t\tCollector.collect(ProcCollector, redraw=True, only_draw=True)\n\t\t\telif key in [\"T\", \"K\", \"I\"] and (ProcBox.selected > 0 or ProcCollector.detailed):\n\t\t\t\tpid: int = ProcBox.selected_pid if ProcBox.selected > 0 else ProcCollector.detailed_pid # type: ignore\n\t\t\t\tif psutil.pid_exists(pid):\n\t\t\t\t\tif key == \"T\": sig = signal.SIGTERM\n\t\t\t\t\telif key == \"K\": sig = signal.SIGKILL\n\t\t\t\t\telif key == \"I\": sig = signal.SIGINT\n\t\t\t\t\ttry:\n\t\t\t\t\t\tos.kill(pid, sig)\n\t\t\t\t\texcept Exception as e:\n\t\t\t\t\t\terrlog.error(f'Exception when sending signal {sig} to pid {pid}')\n\t\t\t\t\t\terrlog.exception(f'{e}')\n\t\t\telif key == \"delete\" and ProcCollector.search_filter:\n\t\t\t\tProcCollector.search_filter = \"\"\n\t\t\t\tCollector.collect(ProcCollector, proc_interrupt=True, redraw=True)\n\t\t\telif key == \"enter\":\n\t\t\t\tif ProcBox.selected > 0 and ProcCollector.detailed_pid != ProcBox.selected_pid and psutil.pid_exists(ProcBox.selected_pid):\n\t\t\t\t\tProcCollector.detailed = True\n\t\t\t\t\tProcBox.last_selection = ProcBox.selected\n\t\t\t\t\tProcBox.selected = 0\n\t\t\t\t\tProcCollector.detailed_pid = ProcBox.selected_pid\n\t\t\t\t\tProcBox.resized = True\n\t\t\t\t\tCollector.proc_counter = 1\n\t\t\t\telif ProcCollector.detailed:\n\t\t\t\t\tProcBox.selected = ProcBox.last_selection\n\t\t\t\t\tProcBox.last_selection = 0\n\t\t\t\t\tProcCollector.detailed = False\n\t\t\t\t\tProcCollector.detailed_pid = None\n\t\t\t\t\tProcBox.resized = True\n\t\t\t\t\tCollector.proc_counter = 1\n\t\t\t\telse:\n\t\t\t\t\tcontinue\n\t\t\t\tProcCollector.details = {}\n\t\t\t\tProcCollector.details_cpu = []\n\t\t\t\tProcCollector.details_mem = []\n\t\t\t\tGraphs.detailed_cpu = NotImplemented\n\t\t\t\tGraphs.detailed_mem = NotImplemented\n\t\t\t\tCollector.collect(ProcCollector, proc_interrupt=True, redraw=True)\n\t\t\telif key in [\"up\", \"down\", \"mouse_scroll_up\", \"mouse_scroll_down\", \"page_up\", \"page_down\", \"home\", \"end\", \"mouse_click\", \"mouse_unselect\", \"j\", \"k\"]:\n\t\t\t\tProcBox.selector(key, mouse_pos)\n\n\t\tif \"net\" in Box.boxes:\n\t\t\tif key in [\"b\", \"n\"]:\n\t\t\t\tNetCollector.switch(key)\n\t\t\telif key == \"z\":\n\t\t\t\tNetCollector.reset = not NetCollector.reset\n\t\t\t\tCollector.collect(NetCollector, redraw=True)\n\t\t\telif key == \"y\":\n\t\t\t\tCONFIG.net_sync = not CONFIG.net_sync\n\t\t\t\tCollector.collect(NetCollector, redraw=True)\n\t\t\telif key == \"a\":\n\t\t\t\tNetCollector.auto_min = not NetCollector.auto_min\n\t\t\t\tNetCollector.net_min = {\"download\" : -1, \"upload\" : -1}\n\t\t\t\tCollector.collect(NetCollector, redraw=True)\n\n\t\tif \"mem\" in Box.boxes:\n\t\t\tif key == \"g\":\n\t\t\t\tCONFIG.mem_graphs = not CONFIG.mem_graphs\n\t\t\t\tCollector.collect(MemCollector, interrupt=True, redraw=True)\n\t\t\telif key == \"s\":\n\t\t\t\tCollector.collect_idle.wait()\n\t\t\t\tCONFIG.swap_disk = not CONFIG.swap_disk\n\t\t\t\tCollector.collect(MemCollector, interrupt=True, redraw=True)\n\t\t\telif key == \"d\":\n\t\t\t\tCollector.collect_idle.wait()\n\t\t\t\tCONFIG.show_disks = not CONFIG.show_disks\n\t\t\t\tCollector.collect(MemCollector, interrupt=True, redraw=True)\n\t\t\telif key == \"i\":\n\t\t\t\tCollector.collect_idle.wait()\n\t\t\t\tCONFIG.io_mode = not CONFIG.io_mode\n\t\t\t\tCollector.collect(MemCollector, interrupt=True, redraw=True)\n\n\n\n\n\n#? Pre main -------------------------------------------------------------------------------------->\n\n\nCPU_NAME: str = get_cpu_name()\n\nCORE_MAP: List[int] = get_cpu_core_mapping()\n\nTHEME: Theme\n\ndef main():\n\tglobal THEME\n\n\tTerm.width = os.get_terminal_size().columns\n\tTerm.height = os.get_terminal_size().lines\n\n\t#? Init -------------------------------------------------------------------------------------->\n\tif DEBUG: TimeIt.start(\"Init\")\n\n\t#? Switch to alternate screen, clear screen, hide cursor, enable mouse reporting and disable input echo\n\tDraw.now(Term.alt_screen, Term.clear, Term.hide_cursor, Term.mouse_on, Term.title(\"BpyTOP\"))\n\tTerm.echo(False)\n\t#Term.refresh(force=True)\n\n\t#? Start a thread checking for updates while running init\n\tif CONFIG.update_check: UpdateChecker.run()\n\n\t#? Draw banner and init status\n\tif CONFIG.show_init and not Init.resized:\n\t\tInit.start()\n\n\t#? Load theme\n\tif CONFIG.show_init:\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Fx.trans(\"Loading theme and creating colors... \")}{Mv.save}')\n\ttry:\n\t\tTHEME = Theme(CONFIG.color_theme)\n\texcept Exception as e:\n\t\tInit.fail(e)\n\telse:\n\t\tInit.success()\n\n\t#? Setup boxes\n\tif CONFIG.show_init:\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Fx.trans(\"Doing some maths and drawing... \")}{Mv.save}')\n\ttry:\n\t\tif CONFIG.check_temp: CpuCollector.get_sensors()\n\t\tBox.calc_sizes()\n\t\tBox.draw_bg(now=False)\n\texcept Exception as e:\n\t\tInit.fail(e)\n\telse:\n\t\tInit.success()\n\n\t#? Setup signal handlers for SIGSTP, SIGCONT, SIGINT and SIGWINCH\n\tif CONFIG.show_init:\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Fx.trans(\"Setting up signal handlers... \")}{Mv.save}')\n\ttry:\n\t\tsignal.signal(signal.SIGTSTP, now_sleeping) #* Ctrl-Z\n\t\tsignal.signal(signal.SIGCONT, now_awake)\t#* Resume\n\t\tsignal.signal(signal.SIGINT, quit_sigint)\t#* Ctrl-C\n\t\tsignal.signal(signal.SIGWINCH, Term.refresh) #* Terminal resized\n\texcept Exception as e:\n\t\tInit.fail(e)\n\telse:\n\t\tInit.success()\n\n\t#? Start a separate thread for reading keyboard input\n\tif CONFIG.show_init:\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Fx.trans(\"Starting input reader thread... \")}{Mv.save}')\n\ttry:\n\t\tif isinstance(sys.stdin, io.TextIOWrapper) and sys.version_info >= (3, 7):\n\t\t\tsys.stdin.reconfigure(errors=\"ignore\")  # type: ignore\n\t\tKey.start()\n\texcept Exception as e:\n\t\tInit.fail(e)\n\telse:\n\t\tInit.success()\n\n\t#? Start a separate thread for data collection and drawing\n\tif CONFIG.show_init:\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Fx.trans(\"Starting data collection and drawer thread... \")}{Mv.save}')\n\ttry:\n\t\tCollector.start()\n\texcept Exception as e:\n\t\tInit.fail(e)\n\telse:\n\t\tInit.success()\n\n\t#? Collect data and draw to buffer\n\tif CONFIG.show_init:\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Fx.trans(\"Collecting data and drawing... \")}{Mv.save}')\n\ttry:\n\t\tCollector.collect(draw_now=False)\n\t\tpass\n\texcept Exception as e:\n\t\tInit.fail(e)\n\telse:\n\t\tInit.success()\n\n\t#? Draw to screen\n\tif CONFIG.show_init:\n\t\tDraw.buffer(\"+init!\", f'{Mv.restore}{Fx.trans(\"Finishing up... \")}{Mv.save}')\n\ttry:\n\t\tCollector.collect_done.wait()\n\texcept Exception as e:\n\t\tInit.fail(e)\n\telse:\n\t\tInit.success()\n\n\tInit.done()\n\tTerm.refresh()\n\tDraw.out(clear=True)\n\tif CONFIG.draw_clock:\n\t\tBox.clock_on = True\n\tif DEBUG: TimeIt.stop(\"Init\")\n\n\t#? Main loop ------------------------------------------------------------------------------------->\n\n\tdef run():\n\t\twhile not False:\n\t\t\tTerm.refresh()\n\t\t\tTimer.stamp()\n\n\t\t\twhile Timer.not_zero():\n\t\t\t\tif Key.input_wait(Timer.left()):\n\t\t\t\t\tprocess_keys()\n\n\t\t\tCollector.collect()\n\n\t#? Start main loop\n\ttry:\n\t\trun()\n\texcept Exception as e:\n\t\terrlog.exception(f'{e}')\n\t\tclean_quit(1)\n\telse:\n\t\t#? Quit cleanly even if false starts being true...\n\t\tclean_quit()\n\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"bpytop\"\nversion = \"1.0.68\"\ndescription = \"Resource monitor that shows usage and stats for processor, memory, disks, network and processes.\"\nreadme = \"README.md\"\nauthors = [\"Aristocratos <jakob@qvantnet.com>\"]\nhomepage = \"https://github.com/aristocratos/bpytop\"\nlicense = \"Apache-2.0\"\ninclude = [\"bpytop-themes/*.theme\"]\n\n[tool.poetry.dependencies]\npython = \"^3.7\"\npsutil = \"^5.7.0\"\n\n[tool.poetry.dev-dependencies]\npytest = \"^6.1.1\"\nmypy = \"^0.790\"\npylint = \"^2.6.0\"\nmore-itertools = \"^8.7.0\"\n\n[tool.poetry.scripts]\nbpytop = \"bpytop:main\"\n\n[build-system]\nrequires = [\"poetry>=0.12\"]\nbuild-backend = \"poetry.masonry.api\"\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_classes.py",
    "content": "import bpytop, pytest\nfrom bpytop import Box, SubBox, CpuBox, MemBox, NetBox, ProcBox, Term, Draw\nfrom bpytop import Graph, Fx, Meter, Color, Banner\nfrom bpytop import Collector, CpuCollector, MemCollector, NetCollector, ProcCollector\nbpytop.Term.width, bpytop.Term.height = 80, 25\n\ndef test_Fx_uncolor():\n\tassert Fx.uncolor(\"\\x1b[38;2;102;238;142mTEST\\x1b[48;2;0;0;0m\") == \"TEST\"\n\ndef test_Color():\n\tassert Color.fg(\"#00ff00\") == \"\\x1b[38;2;0;255;0m\"\n\tassert Color.bg(\"#cc00cc\") == \"\\x1b[48;2;204;0;204m\"\n\tassert Color.fg(255, 255, 255) == \"\\x1b[38;2;255;255;255m\"\n\ndef test_Theme():\n\tbpytop.THEME = bpytop.Theme(\"Default\")\n\tassert str(bpytop.THEME.main_fg) == \"\\x1b[38;2;204;204;204m\"\n\tassert list(bpytop.THEME.main_fg) == [204, 204, 204]\n\tassert len(bpytop.THEME.gradient[\"cpu\"]) == 101\n\ndef test_Box_calc_sizes():\n\tBox.calc_sizes()\n\tassert CpuBox.width == MemBox.width + ProcBox.width == NetBox.width + ProcBox.width == 80\n\tassert CpuBox.height + ProcBox.height == CpuBox.height + MemBox.height + NetBox.height == 25\n\ndef test_Graph():\n\ttest_graph = Graph(width=20, height=10, color=None, data=[x for x in range(20)], invert=False, max_value=0, offset=0, color_max_value=None)\n\tassert len(str(test_graph)) > 1\n\tassert str(test_graph).endswith(\"⣀⣤⣴⣾⣿⣿⣿⣿⣿\")\n\tassert test_graph(5).endswith(\"⣧\")\n\ndef test_Meter():\n\ttest_meter = Meter(value=100, width=20, gradient_name=\"cpu\", invert=False)\n\tassert Fx.uncolor(str(test_meter)) == \"■■■■■■■■■■■■■■■■■■■■\"\n\ndef test_Banner():\n\tassert len(Banner.draw(line=1, col=1, center=False, now=False)) == 2477\n\ndef test_CpuCollector_collect():\n\tbpytop.CONFIG.check_temp = False\n\tCpuCollector._collect()\n\tassert len(CpuCollector.cpu_usage) == bpytop.THREADS + 1\n\tassert isinstance(CpuCollector.cpu_usage[0][0], int)\n\tassert isinstance(CpuCollector.load_avg, list)\n\tassert isinstance(CpuCollector.uptime, str)\n\ndef test_CpuCollector_get_sensors():\n\tbpytop.CONFIG.check_temp = True\n\tbpytop.CONFIG.cpu_sensor = \"Auto\"\n\tCpuCollector.get_sensors()\n\tif CpuCollector.got_sensors:\n\t\tassert CpuCollector.sensor_method != \"\"\n\telse:\n\t\tassert CpuCollector.sensor_method == \"\"\n\ndef test_CpuCollector_collect_temps():\n\tif not CpuCollector.got_sensors:\n\t\tpytest.skip(\"Not testing temperature collection if no sensors was detected!\")\n\tCpuCollector._collect_temps()\n\tassert len(CpuCollector.cpu_temp) == bpytop.THREADS + 1\n\tfor temp_instance in CpuCollector.cpu_temp:\n\t\tassert temp_instance\n\t\tassert isinstance(temp_instance[0], int)\n\tassert isinstance(CpuCollector.cpu_temp_high, int)\n\tassert isinstance(CpuCollector.cpu_temp_crit, int)\n\ndef test_MemCollector_collect():\n\tMemBox.width = 20\n\tbpytop.CONFIG.show_swap = True\n\tbpytop.CONFIG.show_disks = True\n\tbpytop.CONFIG.disks_filter = \"\"\n\tbpytop.CONFIG.swap_disk = True\n\tMemCollector._collect()\n\tassert isinstance(MemCollector.string[\"total\"], str) and MemCollector.string[\"total\"] != \"\"\n\tassert isinstance(MemCollector.values[\"used\"], int)\n\tassert isinstance(MemCollector.percent[\"free\"], int)\n\tif MemBox.swap_on:\n\t\tassert len(MemCollector.disks) > 1\n\t\tassert \"__swap\" in MemCollector.disks\n\telse:\n\t\tassert len(MemCollector.disks) > 0\n\ndef test_NetCollector_get_nics():\n\tNetCollector._get_nics()\n\tif NetCollector.nic == \"\":\n\t\tpytest.skip(\"No nic found, skipping tests!\")\n\tassert NetCollector.nic in NetCollector.nics\n\ndef test_NetCollector_collect():\n\tif NetCollector.nic == \"\":\n\t\tpytest.skip(\"No nic found, skipping tests!\")\n\tNetBox.width = 20\n\tNetCollector._collect()\n\tassert isinstance(NetCollector.strings[NetCollector.nic][\"download\"][\"total\"], str)\n\tassert isinstance(NetCollector.stats[NetCollector.nic][\"upload\"][\"total\"], int)\n\ndef test_ProcCollector_collect():\n\tbpytop.CONFIG.proc_tree = False\n\tbpytop.CONFIG.proc_mem_bytes = True\n\tbpytop.Box.boxes = [\"proc\"]\n\tProcCollector._collect()\n\tassert len(ProcCollector.processes) > 0\n\tbpytop.CONFIG.proc_tree = True\n\tProcCollector.processes = {}\n\tProcCollector._collect()\n\tassert len(ProcCollector.processes) > 0\n\ndef test_CpuBox_draw():\n\tBox.calc_sizes()\n\tassert len(CpuBox._draw_bg()) > 1\n\tCpuBox._draw_fg()\n\tassert \"cpu\" in Draw.strings\n\ndef test_MemBox_draw():\n\tbpytop.CONFIG.show_disks = True\n\tBox.calc_sizes()\n\tassert len(MemBox._draw_bg()) > 1\n\tMemBox._draw_fg()\n\tassert \"mem\" in Draw.strings\n\ndef test_NetBox_draw():\n\tBox.calc_sizes()\n\tassert len(NetBox._draw_bg()) > 1\n\tNetBox._draw_fg()\n\tassert \"net\" in Draw.strings\n\ndef test_ProcBox_draw():\n\tBox.calc_sizes()\n\tassert len(ProcBox._draw_bg()) > 1\n\tProcBox._draw_fg()\n\tassert \"proc\" in Draw.strings\n"
  },
  {
    "path": "tests/test_functions.py",
    "content": "from more_itertools import divide\n\nimport bpytop\nfrom bpytop import (CORES, SYSTEM, THREADS, Fx, create_box, floating_humanizer,\n                    get_cpu_core_mapping, get_cpu_name, units_to_bytes)\n\n\ndef test_get_cpu_name():\n\tassert isinstance(get_cpu_name(), str)\n\ndef test_get_cpu_core_mapping():\n\tcpu_core_mapping = get_cpu_core_mapping()\n\tassert isinstance(cpu_core_mapping, list)\n\t# Assert cpu submappings are sequential\n\tfor submapping in divide(THREADS//CORES, cpu_core_mapping):\n\t\tsubmapping = list(submapping)\n\t\tfor a, b in zip(submapping[:-1], submapping[1:]):\n\t\t\tassert b - a == 1\n\ndef test_create_box():\n\tassert len(create_box(x=1, y=1, width=10, height=10, title=\"\", title2=\"\", line_color=None, title_color=None, fill=True, box=None)) > 1\n\ndef test_floating_humanizer():\n\tassert floating_humanizer(100) == \"100 Byte\"\n\tassert floating_humanizer(100<<10) == \"100 KiB\"\n\tassert floating_humanizer(100<<20, bit=True) == \"800 Mib\"\n\tassert floating_humanizer(100<<20, start=1) == \"100 GiB\"\n\tassert floating_humanizer(100<<40, short=True) == \"100T\"\n\tassert floating_humanizer(100<<50, per_second=True) == \"100 PiB/s\"\n\ndef test_units_to_bytes():\n\tassert units_to_bytes(\"10kbits\") == 1280\n\tassert units_to_bytes(\"100Mbytes\") == 104857600\n\tassert units_to_bytes(\"1gbit\") == 134217728\n"
  },
  {
    "path": "tests/test_title.py",
    "content": "from bpytop import Term\nimport os\nfrom unittest import mock\n\n\ndef test_empty():\n    assert Term.title() == \"\\033]0;\\a\"\n\n\ndef test_nonempty():\n    assert Term.title(\"BpyTOP\") == \"\\033]0;BpyTOP\\a\"\n\n\ndef test_empty_with_environ():\n    with mock.patch.dict(\"os.environ\", {\"TERMINAL_TITLE\": \"hello\"}, clear=True):\n        assert Term.title() == \"\\033]0;hello\\a\"\n\n\ndef test_nonempty_with_environ():\n    with mock.patch.dict(\"os.environ\", {\"TERMINAL_TITLE\": \"hello\"}, clear=True):\n        assert Term.title(\"BpyTOP\") == \"\\033]0;hello BpyTOP\\a\"\n"
  },
  {
    "path": "themes/adapta.theme",
    "content": "#Bashtop Adapta theme\n#by olokelo\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#ffffff\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"\"\n\n# Main text color\ntheme[main_fg]=\"#cfd8dc\"\n\n# Title color for boxes\ntheme[title]=\"#ff\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#90\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#bb0040\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#ff\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#40\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#55bcea\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#00bcd4\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#00bcd4\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#00bcd4\"\n\n# Processes box outline color\ntheme[proc_box]=\"#00bcd4\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#50\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#00bcd4\"\ntheme[temp_mid]=\"#d4d400\"\ntheme[temp_end]=\"#ff0040\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#00bcd4\"\ntheme[cpu_mid]=\"#d4d400\"\ntheme[cpu_end]=\"#ff0040\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#00bcd4\"\ntheme[free_mid]=\"#1090a0\"\ntheme[free_end]=\"#206f79\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#991199\"\ntheme[cached_mid]=\"#770a55\"\ntheme[cached_end]=\"#550055\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#00b0ff\"\ntheme[available_mid]=\"#1099cc\"\ntheme[available_end]=\"#2070aa\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#ff0040\"\ntheme[used_mid]=\"#ff2060\"\ntheme[used_end]=\"#ff4080\"\n\n# Download graph colors\ntheme[download_start]=\"#00bcd4\"\ntheme[download_mid]=\"#991199\"\ntheme[download_end]=\"#ff0040\"\n\n# Upload graph colors\ntheme[upload_start]=\"#00bcd4\"\ntheme[upload_mid]=\"#991199\"\ntheme[upload_end]=\"#ff0040\"\n"
  },
  {
    "path": "themes/default_black.theme",
    "content": "#Bashtop theme with default colors and black background\n#by aristocratos\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#00\"\n\n# Main text color\ntheme[main_fg]=\"#cc\"\n\n# Title color for boxes\ntheme[title]=\"#ee\"\n\n# Highlight color for keyboard shortcuts\ntheme[hi_fg]=\"#90\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#7e2626\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#ee\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#40\"\n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#60\"\n\n# Background color of the percentage meters\ntheme[meter_bg]=\"#40\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#0de756\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#3d7b46\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#8a882e\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#423ba5\"\n\n# Processes box outline color\ntheme[proc_box]=\"#923535\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#30\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#4897d4\"\ntheme[temp_mid]=\"#5474e8\"\ntheme[temp_end]=\"#ff40b6\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#50f095\"\ntheme[cpu_mid]=\"#f2e266\"\ntheme[cpu_end]=\"#fa1e1e\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#223014\"\ntheme[free_mid]=\"#b5e685\"\ntheme[free_end]=\"#dcff85\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#0b1a29\"\ntheme[cached_mid]=\"#74e6fc\"\ntheme[cached_end]=\"#26c5ff\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#292107\"\ntheme[available_mid]=\"#ffd77a\"\ntheme[available_end]=\"#ffb814\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#3b1f1c\"\ntheme[used_mid]=\"#d9626d\"\ntheme[used_end]=\"#ff4769\"\n\n# Download graph colors\ntheme[download_start]=\"#231a63\"\ntheme[download_mid]=\"#4f43a3\"\ntheme[download_end]=\"#b0a9de\"\n\n# Upload graph colors\ntheme[upload_start]=\"#510554\"\ntheme[upload_mid]=\"#7d4180\"\ntheme[upload_end]=\"#dcafde\"\n\n# Process box color gradient for threads, mem and cpu usage\ntheme[process_start]=\"#80d0a3\"\ntheme[process_mid]=\"#dcd179\"\ntheme[process_end]=\"#d45454\""
  },
  {
    "path": "themes/dracula.theme",
    "content": "# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#282a36\"\n\n# Main text color\ntheme[main_fg]=\"#f8f8f2\"\n\n# Title color for boxes\ntheme[title]=\"#f8f8f2\"\n\n# Highlight color for keyboard shortcuts\ntheme[hi_fg]=\"#6272a4\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#ff79c6\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#f8f8f2\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#44475a\"\n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#f8f8f2\"\n\n# Background color of the percentage meters\ntheme[meter_bg]=\"#44475a\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#bd93f9\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#bd93f9\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#50fa7b\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#ff5555\"\n\n# Processes box outline color\ntheme[proc_box]=\"#8be9fd\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#44475a\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#bd93f9\"\ntheme[temp_mid]=\"#ff79c6\"\ntheme[temp_end]=\"#ff33a8\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#bd93f9\"\ntheme[cpu_mid]=\"#8be9fd\"\ntheme[cpu_end]=\"#50fa7b\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#ffa6d9\"\ntheme[free_mid]=\"#ff79c6\"\ntheme[free_end]=\"#ff33a8\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#b1f0fd\"\ntheme[cached_mid]=\"#8be9fd\"\ntheme[cached_end]=\"#26d7fd\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#ffd4a6\"\ntheme[available_mid]=\"#ffb86c\"\ntheme[available_end]=\"#ff9c33\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#96faaf\"\ntheme[used_mid]=\"#50fa7b\"\ntheme[used_end]=\"#0dfa49\"\n\n# Download graph colors\ntheme[download_start]=\"#bd93f9\"\ntheme[download_mid]=\"#50fa7b\"\ntheme[download_end]=\"#8be9fd\"\n\n# Upload graph colors\ntheme[upload_start]=\"#8c42ab\"\ntheme[upload_mid]=\"#ff79c6\"\ntheme[upload_end]=\"#ff33a8\"\n\n# Process box color gradient for threads, mem and cpu usage\ntheme[process_start]=\"#50fa7b\"\ntheme[process_mid]=\"#59b690\"\ntheme[process_end]=\"#6272a4\"\n"
  },
  {
    "path": "themes/dusklight.theme",
    "content": "#Bpytop theme comprised of blues, oranges, cyan, and yellow.\n#by Drazil100\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#04142E\"\n\n# Main text color\ntheme[main_fg]=\"#99DFFF\"\n\n# Title color for boxes\ntheme[title]=\"#99FFFF\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#FF7F00\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#722B01\" \n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#99FFFF\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#052E51\"\n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#79A1B4\"\n\n# Background color of the percentage meters\ntheme[meter_bg]=\"#052E51\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#B46718\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#00FFFF\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#00FFFF\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#00FFFF\"\n\n# Processes box outline color\ntheme[proc_box]=\"#00FFFF\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#A55800\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#00ADFF\"\ntheme[temp_mid]=\"#00FFFF\"\ntheme[temp_end]=\"#FFF86B\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#00D4FF\"\ntheme[cpu_mid]=\"#FFF86B\"\ntheme[cpu_end]=\"#FF7F00\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#0187CB\"\ntheme[free_mid]=\"\"\ntheme[free_end]=\"\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#B4BB63\"\ntheme[cached_mid]=\"\"\ntheme[cached_end]=\"\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#01C0CB\"\ntheme[available_mid]=\"\"\ntheme[available_end]=\"\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#B46718\"\ntheme[used_mid]=\"\"\ntheme[used_end]=\"\"\n\n# Download graph colors\ntheme[download_start]=\"#009EFF\"\ntheme[download_mid]=\"\"\ntheme[download_end]=\"#00FFFF\"\n\n# Upload graph colors\ntheme[upload_start]=\"#FF7F00\"\ntheme[upload_mid]=\"\"\ntheme[upload_end]=\"#FFF86B\"\n"
  },
  {
    "path": "themes/flat-remix-light.theme",
    "content": "#Bashtop theme with flat-remix colors\n#by Daniel Ruiz de Alegría <daniel@drasite.com>\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#ffffff\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#e4e4e7\"\n\n# Main text color\ntheme[main_fg]=\"#737680\"\n\n# Title color for boxes\ntheme[title]=\"#272a34\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#90\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#b8174c\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#ff\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#40\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#367bf0\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#367bf0\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#19a187\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#fd3535\"\n\n# Processes box outline color\ntheme[proc_box]=\"#4aaee6\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#50\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#367bf0\"\ntheme[temp_mid]=\"#b8174c\"\ntheme[temp_end]=\"#d41919\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#367bf0\"\ntheme[cpu_mid]=\"#4aaee6\"\ntheme[cpu_end]=\"#54bd8e\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#811035\"\ntheme[free_mid]=\"#b8174c\"\ntheme[free_end]=\"#d41919\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#2656a8\"\ntheme[cached_mid]=\"#4aaee6\"\ntheme[cached_end]=\"#23bac2\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#fea44c\"\ntheme[available_mid]=\"#fd7d00\"\ntheme[available_end]=\"#fe7171\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#12715f\"\ntheme[used_mid]=\"#19a187\"\ntheme[used_end]=\"#23bac2\"\n\n# Download graph colors\ntheme[download_start]=\"#367bf0\"\ntheme[download_mid]=\"#19a187\"\ntheme[download_end]=\"#4aaee6\"\n\n# Upload graph colors\ntheme[upload_start]=\"#8c42ab\"\ntheme[upload_mid]=\"#b8174c\"\ntheme[upload_end]=\"#d41919\"\n"
  },
  {
    "path": "themes/flat-remix.theme",
    "content": "#Bashtop theme with flat-remix colors\n#by Daniel Ruiz de Alegría <daniel@drasite.com>\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#ffffff\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"\"\n\n# Main text color\ntheme[main_fg]=\"#E6E6E6\"\n\n# Title color for boxes\ntheme[title]=\"#ff\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#90\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#b8174c\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#ff\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#40\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#367bf0\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#367bf0\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#19a187\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#fd3535\"\n\n# Processes box outline color\ntheme[proc_box]=\"#4aaee6\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#50\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#367bf0\"\ntheme[temp_mid]=\"#b8174c\"\ntheme[temp_end]=\"#d41919\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#367bf0\"\ntheme[cpu_mid]=\"#4aaee6\"\ntheme[cpu_end]=\"#54bd8e\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#811035\"\ntheme[free_mid]=\"#b8174c\"\ntheme[free_end]=\"#d41919\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#2656a8\"\ntheme[cached_mid]=\"#4aaee6\"\ntheme[cached_end]=\"#23bac2\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#fea44c\"\ntheme[available_mid]=\"#fd7d00\"\ntheme[available_end]=\"#fe7171\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#12715f\"\ntheme[used_mid]=\"#19a187\"\ntheme[used_end]=\"#23bac2\"\n\n# Download graph colors\ntheme[download_start]=\"#367bf0\"\ntheme[download_mid]=\"#19a187\"\ntheme[download_end]=\"#4aaee6\"\n\n# Upload graph colors\ntheme[upload_start]=\"#8c42ab\"\ntheme[upload_mid]=\"#b8174c\"\ntheme[upload_end]=\"#d41919\"\n"
  },
  {
    "path": "themes/greyscale.theme",
    "content": "#Bashtop grayscale theme\n#by aristocratos\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#00\"\n\n# Main text color\ntheme[main_fg]=\"#bb\"\n\n# Title color for boxes\ntheme[title]=\"#cc\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#90\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#ff\" \n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#00\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#30\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#90\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#90\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#90\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#90\"\n\n# Processes box outline color\ntheme[proc_box]=\"#90\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#30\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#50\"\ntheme[temp_mid]=\"\"\ntheme[temp_end]=\"#ff\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#50\"\ntheme[cpu_mid]=\"\"\ntheme[cpu_end]=\"#ff\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#50\"\ntheme[free_mid]=\"\"\ntheme[free_end]=\"#ff\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#50\"\ntheme[cached_mid]=\"\"\ntheme[cached_end]=\"#ff\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#50\"\ntheme[available_mid]=\"\"\ntheme[available_end]=\"#ff\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#50\"\ntheme[used_mid]=\"\"\ntheme[used_end]=\"#ff\"\n\n# Download graph colors\ntheme[download_start]=\"#30\"\ntheme[download_mid]=\"\"\ntheme[download_end]=\"#ff\"\n\n# Upload graph colors\ntheme[upload_start]=\"#30\"\ntheme[upload_mid]=\"\"\ntheme[upload_end]=\"#ff\""
  },
  {
    "path": "themes/gruvbox_dark.theme",
    "content": "#Bashtop gruvbox (https://github.com/morhetz/gruvbox) theme\n#by BachoSeven\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#1d2021\"\n\n# Main text color\ntheme[main_fg]=\"#a89984\"\n\n# Title color for boxes\ntheme[title]=\"#ebdbb2\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#d79921\"\n\n# Background color of selected items\ntheme[selected_bg]=\"#282828\"\n\n# Foreground color of selected items\ntheme[selected_fg]=\"#fabd2f\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#282828\"\n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#585858\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#98971a\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#a89984\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#a89984\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#a89984\"\n\n# Processes box outline color\ntheme[proc_box]=\"#a89984\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#a89984\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#458588\"\ntheme[temp_mid]=\"#d3869b\"\ntheme[temp_end]=\"#fb4394\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#b8bb26\"\ntheme[cpu_mid]=\"#d79921\"\ntheme[cpu_end]=\"#fb4934\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#4e5900\"\ntheme[free_mid]=\"\"\ntheme[free_end]=\"#98971a\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#458588\"\ntheme[cached_mid]=\"\"\ntheme[cached_end]=\"#83a598\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#d79921\"\ntheme[available_mid]=\"\"\ntheme[available_end]=\"#fabd2f\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#cc241d\"\ntheme[used_mid]=\"\"\ntheme[used_end]=\"#fb4934\"\n\n# Download graph colors\ntheme[download_start]=\"#3d4070\"\ntheme[download_mid]=\"#6c71c4\"\ntheme[download_end]=\"#a3a8f7\"\n\n# Upload graph colors\ntheme[upload_start]=\"#701c45\"\ntheme[upload_mid]=\"#b16286\"\ntheme[upload_end]=\"#d3869b\"\n"
  },
  {
    "path": "themes/gruvbox_dark_v2.theme",
    "content": "# Bashtop gruvbox (https://github.com/morhetz/gruvbox) theme\n# First version created By BachoSeven\n# Adjustments to proper colors by Pietryszak (https://github.com/pietryszak/)\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#282828\"\n\n# Main text color\ntheme[main_fg]=\"#EBDBB2\"\n\n# Title color for boxes\ntheme[title]=\"#EBDBB2\"\n\n# Highlight color for keyboard shortcuts\ntheme[hi_fg]=\"#CC241D\"\n\n# Background color of selected items\ntheme[selected_bg]=\"#32302F\"\n\n# Foreground color of selected items\ntheme[selected_fg]=\"#D3869B\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#3C3836\"  \n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#A89984\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#98971A\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#A89984\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#A89984\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#A89984\"\n\n# Processes box outline color\ntheme[proc_box]=\"#A89984\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#A89984\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#98971A\"\ntheme[temp_mid]=\"\"\ntheme[temp_end]=\"#CC241D\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#8EC07C\"\ntheme[cpu_mid]=\"#D79921\"\ntheme[cpu_end]=\"#CC241D\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#CC241D\"\ntheme[free_mid]=\"#D79921\"\ntheme[free_end]=\"#8EC07C\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#458588\"\ntheme[cached_mid]=\"#83A598\"\ntheme[cached_end]=\"#8EC07C\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#CC241D\"\ntheme[available_mid]=\"#D65D0E\"\ntheme[available_end]=\"#FABD2F\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#8EC07C\"\ntheme[used_mid]=\"#D65D0E\"\ntheme[used_end]=\"#CC241D\"\n\n# Download graph colors\ntheme[download_start]=\"#98971A\"\ntheme[download_mid]=\"#689d6A\"\ntheme[download_end]=\"#B8BB26\"\n\n# Upload graph colors\ntheme[upload_start]=\"#CC241D\"\ntheme[upload_mid]=\"#D65d0E\"\ntheme[upload_end]=\"#FABF2F\"\n\n# Process box color gradient for threads, mem and cpu usage\ntheme[process_start]=\"#8EC07C\"\ntheme[process_mid]=\"#FE8019\"\ntheme[process_end]=\"#CC241D\"\n"
  },
  {
    "path": "themes/kyli0x.theme",
    "content": "#Bashtop Kyli0x Theme\n#by Kyli0x <kyli0x@protonmail.ch>\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#23252e\"\n\n# Main text color\ntheme[main_fg]=\"#f8f8f2\"\n\n# Title color for boxes\ntheme[title]=\"#f8f8f2\"\n\n# Highlight color for keyboard shortcuts\ntheme[hi_fg]=\"#21d6c9\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#1aaba0\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#f8f8f2\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#497e7a\"\n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#21d6c9\"\n\n# Background color of the percentage meters\ntheme[meter_bg]=\"#80638e\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#21d6c9\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#d486d4\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#d486d4\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#d486d4\"\n\n# Processes box outline color\ntheme[proc_box]=\"#d486d4\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#80638e\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#21d6c9\"\ntheme[temp_mid]=\"#1aaba0\"\ntheme[temp_end]=\"#497e7a\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#21d6c9\"\ntheme[cpu_mid]=\"#1aaba0\"\ntheme[cpu_end]=\"#497e7a\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#21d6c9\"\ntheme[free_mid]=\"#1aaba0\"\ntheme[free_end]=\"#497e7a\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#21d6c9\"\ntheme[cached_mid]=\"#1aaba0\"\ntheme[cached_end]=\"#497e7a\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#21d6c9\"\ntheme[available_mid]=\"#1aaba0\"\ntheme[available_end]=\"#497e7a\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#21d6c9\"\ntheme[used_mid]=\"#1aaba0\"\ntheme[used_end]=\"#497e7a\"\n\n# Download graph colors\ntheme[download_start]=\"#21d6c9\"\ntheme[download_mid]=\"#1aaba0\"\ntheme[download_end]=\"#497e7a\"\n\n# Upload graph colors\ntheme[upload_start]=\"#ec95ec\"\ntheme[upload_mid]=\"#1aaba0\"\ntheme[upload_end]=\"#497e7a\"\n\n# Process box color gradient for threads, mem and cpu usage\ntheme[process_start]=\"#21d6c9\"\ntheme[process_mid]=\"#1aaba0\"\ntheme[process_end]=\"#d486d4\"\n"
  },
  {
    "path": "themes/matcha-dark-sea.theme",
    "content": "#Bashtop matcha-dark-sea theme\n#by TheCynicalTeam\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"\"\n\n# Main text color\ntheme[main_fg]=\"#F8F8F2\"\n\n# Title color for boxes\ntheme[title]=\"#F8F8F2\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#2eb398\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#0d493d\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#F8F8F2\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#595647\"\n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#797667\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#33b165\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#75715E\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#75715E\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#75715E\"\n\n# Processes box outline color\ntheme[proc_box]=\"#75715E\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#595647\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#7976B7\"\ntheme[temp_mid]=\"#D8B8B2\"\ntheme[temp_end]=\"#33b165\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#33b165\"\ntheme[cpu_mid]=\"#F8F8F2\" #2eb398\"\ntheme[cpu_end]=\"#2eb398\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#75715E\"\ntheme[free_mid]=\"#a9c474\"\ntheme[free_end]=\"#e2f5bc\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#75715E\"\ntheme[cached_mid]=\"#66D9EF\"\ntheme[cached_end]=\"#aae7f2\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#75715E\"\ntheme[available_mid]=\"#E6DB74\"\ntheme[available_end]=\"#f2ecb6\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#75715E\"\ntheme[used_mid]=\"#2eb398\"\ntheme[used_end]=\"#33b165\"\n\n# Download graph colors\ntheme[download_start]=\"#2d2042\"\ntheme[download_mid]=\"#2eb398\"\ntheme[download_end]=\"#33b165\"\n\n# Upload graph colors\ntheme[upload_start]=\"#0d493d\"\ntheme[upload_mid]=\"#2eb398\"\ntheme[upload_end]=\"#33b165\"\n"
  },
  {
    "path": "themes/monokai.theme",
    "content": "#Bashtop monokai theme\n#by aristocratos\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#060604\"\n\n# Main text color\ntheme[main_fg]=\"#F8F8F2\"\n\n# Title color for boxes\ntheme[title]=\"#F8F8F2\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#F92672\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#7a1137\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#F8F8F2\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#595647\"\n\n# Color of text appearing on top of graphs, i.e uptime and current network graph scaling\ntheme[graph_text]=\"#797667\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#A6E22E\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#75715E\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#75715E\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#75715E\"\n\n# Processes box outline color\ntheme[proc_box]=\"#75715E\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#595647\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#7976B7\"\ntheme[temp_mid]=\"#D8B8B2\"\ntheme[temp_end]=\"#F92672\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#A6E22E\"\ntheme[cpu_mid]=\"#F8F8F2\" #b05475\"\ntheme[cpu_end]=\"#F92672\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#75715E\"\ntheme[free_mid]=\"#a9c474\"\ntheme[free_end]=\"#e2f5bc\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#75715E\"\ntheme[cached_mid]=\"#66D9EF\"\ntheme[cached_end]=\"#aae7f2\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#75715E\"\ntheme[available_mid]=\"#E6DB74\"\ntheme[available_end]=\"#f2ecb6\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#75715E\"\ntheme[used_mid]=\"#F92672\"\ntheme[used_end]=\"#ff87b2\"\n\n# Download graph colors\ntheme[download_start]=\"#2d2042\"\ntheme[download_mid]=\"#7352a8\"\ntheme[download_end]=\"#ccaefc\"\n\n# Upload graph colors\ntheme[upload_start]=\"#570d33\"\ntheme[upload_mid]=\"#cf277d\"\ntheme[upload_end]=\"#fa91c7\"\n"
  },
  {
    "path": "themes/nord.theme",
    "content": "#Bashtop theme with nord palette (https://www.nordtheme.com)\n#by Justin Zobel <justin.zobel@gmail.com>\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#ffffff\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#2E3440\"\n\n# Main text color\ntheme[main_fg]=\"#D8DEE9\"\n\n# Title color for boxes\ntheme[title]=\"#8FBCBB\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#5E81AC\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#4C566A\"\n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#ECEFF4\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#4C566A\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#5E81AC\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#4C566A\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#4C566A\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#4C566A\"\n\n# Processes box outline color\ntheme[proc_box]=\"#4C566A\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#4C566A\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#81A1C1\"\ntheme[temp_mid]=\"#88C0D0\"\ntheme[temp_end]=\"#ECEFF4\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#81A1C1\"\ntheme[cpu_mid]=\"#88C0D0\"\ntheme[cpu_end]=\"#ECEFF4\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#81A1C1\"\ntheme[free_mid]=\"#88C0D0\"\ntheme[free_end]=\"#ECEFF4\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#81A1C1\"\ntheme[cached_mid]=\"#88C0D0\"\ntheme[cached_end]=\"#ECEFF4\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#81A1C1\"\ntheme[available_mid]=\"#88C0D0\"\ntheme[available_end]=\"#ECEFF4\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#81A1C1\"\ntheme[used_mid]=\"#88C0D0\"\ntheme[used_end]=\"#ECEFF4\"\n\n# Download graph colors\ntheme[download_start]=\"#81A1C1\"\ntheme[download_mid]=\"#88C0D0\"\ntheme[download_end]=\"#ECEFF4\"\n\n# Upload graph colors\ntheme[upload_start]=\"#81A1C1\"\ntheme[upload_mid]=\"#88C0D0\"\ntheme[upload_end]=\"#ECEFF4\"\n"
  },
  {
    "path": "themes/solarized_dark.theme",
    "content": "#Bashtop solarized theme\n#by aristocratos\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#002b36\"\n\n# Main text color\ntheme[main_fg]=\"#eee8d5\"\n\n# Title color for boxes\ntheme[title]=\"#fdf6e3\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#b58900\"\n\n# Background color of selected items\ntheme[selected_bg]=\"#073642\" \n\n# Foreground color of selected items\ntheme[selected_fg]=\"#d6a200\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#073642\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#bad600\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#586e75\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#586e75\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#586e75\"\n\n# Processes box outline color\ntheme[proc_box]=\"#586e75\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#586e75\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#268bd2\"\ntheme[temp_mid]=\"#ccb5f7\"\ntheme[temp_end]=\"#fc5378\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#adc700\"\ntheme[cpu_mid]=\"#d6a200\"\ntheme[cpu_end]=\"#e65317\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#4e5900\"\ntheme[free_mid]=\"\"\ntheme[free_end]=\"#bad600\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#114061\"\ntheme[cached_mid]=\"\"\ntheme[cached_end]=\"#268bd2\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#705500\"\ntheme[available_mid]=\"\"\ntheme[available_end]=\"#edb400\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#6e1718\"\ntheme[used_mid]=\"\"\ntheme[used_end]=\"#e02f30\"\n\n# Download graph colors\ntheme[download_start]=\"#3d4070\"\ntheme[download_mid]=\"#6c71c4\"\ntheme[download_end]=\"#a3a8f7\"\n\n# Upload graph colors\ntheme[upload_start]=\"#701c45\"\ntheme[upload_mid]=\"#d33682\"\ntheme[upload_end]=\"#f56caf\""
  },
  {
    "path": "themes/whiteout.theme",
    "content": "#Bashtop \"whiteout\" theme\n#by aristocratos\n\n# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: \"#RRGGBB\", \"#BW\" or \"0-255 0-255 0-255\"\n# example for white: \"#FFFFFF\", \"#ff\" or \"255 255 255\".\n\n# All graphs and meters can be gradients\n# For single color graphs leave \"mid\" and \"end\" variable empty.\n# Use \"start\" and \"end\" variables for two color gradient\n# Use \"start\", \"mid\" and \"end\" for three color gradient\n\n# Main background, empty for terminal default, need to be empty if you want transparent background\ntheme[main_bg]=\"#ff\"\n\n# Main text color\ntheme[main_fg]=\"#30\"\n\n# Title color for boxes\ntheme[title]=\"#10\"\n\n# Higlight color for keyboard shortcuts\ntheme[hi_fg]=\"#284d75\"\n\n# Background color of selected item in processes box\ntheme[selected_bg]=\"#15283d\" \n\n# Foreground color of selected item in processes box\ntheme[selected_fg]=\"#ff\"\n\n# Color of inactive/disabled text\ntheme[inactive_fg]=\"#dd\"\n\n# Misc colors for processes box including mini cpu graphs, details memory graph and details status text\ntheme[proc_misc]=\"#03521d\"\n\n# Cpu box outline color\ntheme[cpu_box]=\"#1a361e\"\n\n# Memory/disks box outline color\ntheme[mem_box]=\"#3d3c14\"\n\n# Net up/down box outline color\ntheme[net_box]=\"#1a1742\"\n\n# Processes box outline color\ntheme[proc_box]=\"#3b1515\"\n\n# Box divider line and small boxes line color\ntheme[div_line]=\"#80\"\n\n# Temperature graph colors\ntheme[temp_start]=\"#184567\"\ntheme[temp_mid]=\"#122c87\"\ntheme[temp_end]=\"#9e0061\"\n\n# CPU graph colors\ntheme[cpu_start]=\"#0b8e44\"\ntheme[cpu_mid]=\"#a49104\"\ntheme[cpu_end]=\"#8d0202\"\n\n# Mem/Disk free meter\ntheme[free_start]=\"#b0d090\"\ntheme[free_mid]=\"#70ba26\"\ntheme[free_end]=\"#496600\"\n\n# Mem/Disk cached meter\ntheme[cached_start]=\"#26c5ff\"\ntheme[cached_mid]=\"#74e6fc\"\ntheme[cached_end]=\"#0b1a29\"\n\n# Mem/Disk available meter\ntheme[available_start]=\"#ffb814\"\ntheme[available_mid]=\"#ffd77a\"\ntheme[available_end]=\"#292107\"\n\n# Mem/Disk used meter\ntheme[used_start]=\"#ff4769\"\ntheme[used_mid]=\"#d9626d\"\ntheme[used_end]=\"#3b1f1c\"\n\n# Download graph colors\ntheme[download_start]=\"#8d82de\"\ntheme[download_mid]=\"#413786\"\ntheme[download_end]=\"#130f29\"\n\n# Upload graph colors\ntheme[upload_start]=\"#f590f9\"\ntheme[upload_mid]=\"#722e76\"\ntheme[upload_end]=\"#2b062d\""
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nisolated_build = true\nenvlist = py37,py38,py39,mypy,pylint\n\n[gh-actions]\npython =\n    3.7: py37\n    3.8: py38\n    3.9: py39, mypy, pylint\n\n[testenv]\nwhitelist_externals = poetry\ncommands =\n    poetry install -v\n    poetry run pytest\n\n[testenv:mypy]\nbasepython = python3.9\ncommands =\n    poetry install -v\n    poetry run mypy .\n\n[testenv:pylint]\nbasepython = python3.9\ncommands =\n    poetry install -v\n    poetry run pylint -E bpytop\n"
  }
]