mirror of https://github.com/aristocratos/bpytop
Init
commit
7d28636b04
|
@ -0,0 +1,76 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at admin@qvantnet.com. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Contributing guidelines
|
||||||
|
|
||||||
|
## When submitting pull requests
|
||||||
|
|
||||||
|
* Explain your thinking in why a change or addition is needed.
|
||||||
|
* Is it a requested change or feature?
|
||||||
|
* If not, open a feature request to get feedback before making a pull request.
|
||||||
|
|
||||||
|
* Split up multiple unrelated changes in multiple pull requests.
|
||||||
|
|
||||||
|
|
||||||
|
* Purely cosmetic changes won't be accepted without a very good explanation of its value.
|
||||||
|
* (Some design choices are for better configurability of syntax highlighting.)
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
### Follow the current syntax design
|
||||||
|
|
||||||
|
* Indent type: Tabs
|
||||||
|
|
||||||
|
* Tab size: 4
|
||||||
|
|
||||||
|
## Optimization
|
||||||
|
|
||||||
|
* Avoid writing to disk if possible.
|
||||||
|
|
||||||
|
* Make sure variables/arrays are cleaned up if not reused.
|
||||||
|
|
||||||
|
* Compare cpu and memory usage with and without your code and look for alternatives if they cause a noticeable negative impact.
|
||||||
|
|
||||||
|
For questions contact Aristocratos at admin@qvantnet.com
|
||||||
|
|
||||||
|
For proposing changes to this document create a [new issue](https://github.com/aristocratos/bashtop/issues/new/choose).
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,16 @@
|
||||||
|
PREFIX ?= /usr/local
|
||||||
|
DOCDIR ?= $(PREFIX)/share/doc/bpytop
|
||||||
|
|
||||||
|
all:
|
||||||
|
@echo Run \'make install\' to install bpytop.
|
||||||
|
|
||||||
|
install:
|
||||||
|
@mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||||
|
@cp -p bpytop $(DESTDIR)$(PREFIX)/bin/bpytop
|
||||||
|
@mkdir -p $(DESTDIR)$(DOCDIR)
|
||||||
|
@cp -p README.md $(DESTDIR)$(DOCDIR)
|
||||||
|
@chmod 755 $(DESTDIR)$(PREFIX)/bin/bpytop
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
@rm -rf $(DESTDIR)$(PREFIX)/bin/bpytop
|
||||||
|
@rm -rf $(DESTDIR)$(DOCDIR)
|
|
@ -0,0 +1,243 @@
|
||||||
|
# 
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
[](https://paypal.me/aristocratos)
|
||||||
|
[](https://github.com/sponsors/aristocratos)
|
||||||
|
[](https://ko-fi.com/aristocratos)
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
* [Documents](#documents)
|
||||||
|
* [Description](#description)
|
||||||
|
* [Features](#features)
|
||||||
|
* [Themes](#themes) (Updated)
|
||||||
|
* [Upcoming](#upcoming) (Python port)
|
||||||
|
* [Support and funding](#support-and-funding)
|
||||||
|
* [Compatibility](#compatibility) (OSX and FreeBSD Support)
|
||||||
|
* [Dependencies](#dependencies) (Updated)
|
||||||
|
* [Screenshots](#screenshots)
|
||||||
|
* [Installation](#installation) (Updated)
|
||||||
|
* [Configurability](#configurability) (Updated)
|
||||||
|
* [TODO](#todo) (Updated)
|
||||||
|
* [License](#license)
|
||||||
|
|
||||||
|
## Documents
|
||||||
|
|
||||||
|
#### [CHANGELOG.md](CHANGELOG.md)
|
||||||
|
|
||||||
|
#### [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
|
|
||||||
|
#### [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Resource monitor that shows usage and stats for processor, memory, disks, network and processes.
|
||||||
|
|
||||||
|
Python port of [bashtop](https://github.com/aristocratos/bashtop).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Easy to use, with a game inspired menu system.
|
||||||
|
* Fast and responsive UI with UP, DOWN keys process selection.
|
||||||
|
* Function for showing detailed stats for selected process.
|
||||||
|
* Ability to filter processes.
|
||||||
|
* Easy switching between sorting options.
|
||||||
|
* Send SIGTERM, SIGKILL, SIGINT to selected process.
|
||||||
|
* UI menu for changing all config file options.
|
||||||
|
* Auto scaling graph for network usage.
|
||||||
|
* Shows message in menu if new version is available
|
||||||
|
* Shows current read and write speeds for disks
|
||||||
|
|
||||||
|
## Themes
|
||||||
|
|
||||||
|
Bpytop uses the same the files as bashtop.
|
||||||
|
|
||||||
|
See bashtop/[themes](https://github.com/aristocratos/bashtop/themes) folder for available themes.
|
||||||
|
|
||||||
|
The builtin theme downloader places the default themes in `$HOME/.config/bpytop/themes`.
|
||||||
|
User created themes should be placed in `$HOME/.config/bpytop/user_themes` to be safe from overwrites.
|
||||||
|
|
||||||
|
Let me know if you want to contribute with new themes.
|
||||||
|
|
||||||
|
|
||||||
|
## Support and funding
|
||||||
|
|
||||||
|
You can sponsor this project through github, see [my sponsors page](https://github.com/sponsors/aristocratos) for options.
|
||||||
|
|
||||||
|
Or donate through [paypal](https://paypal.me/aristocratos) or [ko-fi](https://ko-fi.com/aristocratos).
|
||||||
|
|
||||||
|
Any support is greatly appreciated!
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
Should work on most modern linux distributions, on Mac OS X and on FreeBSD.
|
||||||
|
|
||||||
|
Will not display correctly on the standard terminal on OSX!
|
||||||
|
Recommended alternative [iTerm2](https://www.iterm2.com/)
|
||||||
|
|
||||||
|
Will also need to be run as superuser on OSX to display stats for processes not owned by user.
|
||||||
|
|
||||||
|
The disk io stats on OSX and FreeBSD shows iostats for all disks at the top instead of per disk.
|
||||||
|
|
||||||
|
For correct display, a terminal with support for:
|
||||||
|
|
||||||
|
* 24-bit truecolor
|
||||||
|
* Wide characters
|
||||||
|
|
||||||
|
Also needs a UTF8 locale and a font that covers:
|
||||||
|
|
||||||
|
* Unicode Block “Braille Patterns” U+2800 - U+28FF
|
||||||
|
* Unicode Block “Geometric Shapes” U+25A0 - U+25FF
|
||||||
|
* Unicode Block "Box Drawing" and "Block Elements" U+2500 - U+259F
|
||||||
|
|
||||||
|
#### Notice
|
||||||
|
|
||||||
|
Dropbear seems to not be able to set correct locale. So if accessing bashtop over ssh, OpenSSH is recommended.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
**[Python3](https://www.python.org/downloads/)** (v3.6 or later)
|
||||||
|
|
||||||
|
**[psutil module](https://github.com/giampaolo/psutil)** (v5.7.0 or later)
|
||||||
|
|
||||||
|
## Optionals for additional stats
|
||||||
|
|
||||||
|
(Optional OSX) **[osx-cpu-temp](https://github.com/lavoiesl/osx-cpu-temp)** Needed to show CPU temperatures.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
Main UI showing details for a selected process.
|
||||||
|

|
||||||
|
|
||||||
|
Main menu.
|
||||||
|

|
||||||
|
|
||||||
|
Options menu.
|
||||||
|

|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
#### Dependencies installation OSX
|
||||||
|
|
||||||
|
>Install homebrew if not already installed
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
|
>Install python3 if not already installed
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
brew install python3 git
|
||||||
|
```
|
||||||
|
|
||||||
|
>Install psutil python module
|
||||||
|
``` bash
|
||||||
|
pip3 install psutil
|
||||||
|
```
|
||||||
|
|
||||||
|
>Install optional dependency osx-cpu-temp
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
brew install osx-cpu-temp
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Dependencies installation FreeBSD
|
||||||
|
|
||||||
|
>Install with pkg and pip
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
sudo pkg install python3 git
|
||||||
|
sudo python3 -m ensurepip
|
||||||
|
sudo pip3 install psutil
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Manual installation Linux, OSX and FreeBSD
|
||||||
|
|
||||||
|
>Clone and install
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
git clone https://github.com/aristocratos/bashtop.git
|
||||||
|
cd bashtop
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
>to uninstall it
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
sudo make uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Configurability
|
||||||
|
|
||||||
|
All options changeable from within UI.
|
||||||
|
Config files stored in "$HOME/.config/bpytop" folder
|
||||||
|
|
||||||
|
#### bashtop.cfg: (auto generated if not found)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#? Config file for bpytop v. 0.0.1
|
||||||
|
#* Color theme, looks for a .theme file in "~/.config/bpytop/themes" and "~/.config/bpytop/user_themes", "Default" for builtin default theme
|
||||||
|
color_theme = "Default"
|
||||||
|
|
||||||
|
#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs
|
||||||
|
update_ms = 2500
|
||||||
|
|
||||||
|
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive"
|
||||||
|
#* "cpu lazy" updates sorting over time, "cpu responsive" updates sorting directly
|
||||||
|
proc_sorting = "cpu lazy"
|
||||||
|
|
||||||
|
#* Reverse sorting order, True or False
|
||||||
|
proc_reversed = False
|
||||||
|
|
||||||
|
#* Show processes as a tree
|
||||||
|
proc_tree = False
|
||||||
|
|
||||||
|
#* Check cpu temperature, only works if "sensors", "vcgencmd" or "osx-cpu-temp" commands is available
|
||||||
|
check_temp = True
|
||||||
|
|
||||||
|
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable
|
||||||
|
draw_clock = "%X"
|
||||||
|
|
||||||
|
#* Update main ui when menus are showing, set this to false if the menus is flickering too much for comfort
|
||||||
|
background_update = True
|
||||||
|
|
||||||
|
#* Custom cpu model name, empty string to disable
|
||||||
|
custom_cpu_name = ""
|
||||||
|
|
||||||
|
#* Show color gradient in process list, True or False
|
||||||
|
proc_gradient = True
|
||||||
|
|
||||||
|
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power
|
||||||
|
proc_per_core = False
|
||||||
|
|
||||||
|
#* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space
|
||||||
|
disks_filter = ""
|
||||||
|
|
||||||
|
#* Enable check for new version from github.com/aristocratos/bpytop at start
|
||||||
|
update_check = True
|
||||||
|
|
||||||
|
#* Enable graphs with double the horizontal resolution, increases cpu usage
|
||||||
|
hires_graphs = False
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Command line options: (not yet implemented)
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
USAGE: bpytop
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- [ ] See TODOs from [Bashtop](https://github.com/aristocratos/bashtop#todo).
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
|
[Apache License 2.0](LICENSE)
|
|
@ -0,0 +1,927 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# pylint: disable=not-callable, no-member
|
||||||
|
|
||||||
|
# Copyright 2020 Aristocratos (jakob@qvantnet.com)
|
||||||
|
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os, sys, time, threading, signal, re, subprocess, logging, logging.handlers
|
||||||
|
from select import select
|
||||||
|
from pathlib import Path
|
||||||
|
from distutils.util import strtobool
|
||||||
|
from typing import List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable
|
||||||
|
|
||||||
|
errors: List[str] = []
|
||||||
|
try: import fcntl, termios, tty
|
||||||
|
except Exception as e: errors.append(f'{e}')
|
||||||
|
|
||||||
|
try: import psutil
|
||||||
|
except Exception as e: errors.append(f'{e}')
|
||||||
|
|
||||||
|
|
||||||
|
SYSTEM: str
|
||||||
|
if "linux" in sys.platform: SYSTEM = "Linux"
|
||||||
|
elif "bsd" in sys.platform: SYSTEM = "BSD"
|
||||||
|
elif "darwin" in sys.platform: SYSTEM = "MacOS"
|
||||||
|
else: SYSTEM = "Other"
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print ("ERROR!")
|
||||||
|
for error in errors:
|
||||||
|
print(error)
|
||||||
|
if SYSTEM == "Other":
|
||||||
|
print("\nUnsupported platform!\n")
|
||||||
|
else:
|
||||||
|
print("\nInstall required modules!\n")
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
print: partial = partial(print, sep="", end="", flush=True) #* Setup print function to default to empty seperator and no new line
|
||||||
|
|
||||||
|
#? Variables ------------------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
BANNER_SRC: Dict[str, str] = {
|
||||||
|
"#E62525" : "██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ██████╗",
|
||||||
|
"#CD2121" : "██╔══██╗██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔═══██╗██╔══██╗",
|
||||||
|
"#B31D1D" : "██████╔╝██████╔╝ ╚████╔╝ ██║ ██║ ██║██████╔╝",
|
||||||
|
"#9A1919" : "██╔══██╗██╔═══╝ ╚██╔╝ ██║ ██║ ██║██╔═══╝ ",
|
||||||
|
"#801414" : "██████╔╝██║ ██║ ██║ ╚██████╔╝██║",
|
||||||
|
"#000000" : "╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝",
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSION: str = "0.0.1"
|
||||||
|
|
||||||
|
DEFAULT_CONF: str = f'#? Config file for bpytop v. {VERSION}\n'
|
||||||
|
DEFAULT_CONF += '''
|
||||||
|
#* Color theme, looks for a .theme file in "~/.config/bpytop/themes" and "~/.config/bpytop/user_themes", "Default" for builtin default theme
|
||||||
|
color_theme = "Default"
|
||||||
|
|
||||||
|
#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs
|
||||||
|
update_ms = 2500
|
||||||
|
|
||||||
|
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive"
|
||||||
|
#* "cpu lazy" updates sorting over time, "cpu responsive" updates sorting directly
|
||||||
|
proc_sorting = "cpu lazy"
|
||||||
|
|
||||||
|
#* Reverse sorting order, True or False
|
||||||
|
proc_reversed = False
|
||||||
|
|
||||||
|
#* Show processes as a tree
|
||||||
|
proc_tree = False
|
||||||
|
|
||||||
|
#* Check cpu temperature, only works if "sensors", "vcgencmd" or "osx-cpu-temp" commands is available
|
||||||
|
check_temp = True
|
||||||
|
|
||||||
|
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable
|
||||||
|
draw_clock = "%X"
|
||||||
|
|
||||||
|
#* Update main ui when menus are showing, set this to false if the menus is flickering too much for comfort
|
||||||
|
background_update = True
|
||||||
|
|
||||||
|
#* Custom cpu model name, empty string to disable
|
||||||
|
custom_cpu_name = ""
|
||||||
|
|
||||||
|
#* Show color gradient in process list, True or False
|
||||||
|
proc_gradient = True
|
||||||
|
|
||||||
|
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power
|
||||||
|
proc_per_core = False
|
||||||
|
|
||||||
|
#* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space
|
||||||
|
disks_filter = ""
|
||||||
|
|
||||||
|
#* Enable check for new version from github.com/aristocratos/bpytop at start
|
||||||
|
update_check = True
|
||||||
|
|
||||||
|
#* Enable graphs with double the horizontal resolution, increases cpu usage
|
||||||
|
hires_graphs = False
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
conf: Path = Path(f'{Path.home()}/.config/bpytop')
|
||||||
|
if not conf.is_dir():
|
||||||
|
try:
|
||||||
|
conf.mkdir(mode=0o777, parents=True)
|
||||||
|
except PermissionError:
|
||||||
|
print(f'ERROR!\nNo permission to write to "{conf}" directory!')
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
CONFIG_DIR: str = str(conf)
|
||||||
|
del conf
|
||||||
|
|
||||||
|
CORES: int = psutil.cpu_count(logical=False) or 1
|
||||||
|
THREADS: int = psutil.cpu_count(logical=True) or 1
|
||||||
|
|
||||||
|
DEFAULT_THEME: Dict[str, str] = {
|
||||||
|
"main_bg" : "",
|
||||||
|
"main_fg" : "#cc",
|
||||||
|
"title" : "#ee",
|
||||||
|
"hi_fg" : "#90",
|
||||||
|
"selected_bg" : "#7e2626",
|
||||||
|
"selected_fg" : "#ee",
|
||||||
|
"inactive_fg" : "#40",
|
||||||
|
"proc_misc" : "#0de756",
|
||||||
|
"cpu_box" : "#3d7b46",
|
||||||
|
"mem_box" : "#8a882e",
|
||||||
|
"net_box" : "#423ba5",
|
||||||
|
"proc_box" : "#923535",
|
||||||
|
"div_line" : "#30",
|
||||||
|
"temp_start" : "#4897d4",
|
||||||
|
"temp_mid" : "#5474e8",
|
||||||
|
"temp_end" : "#ff40b6",
|
||||||
|
"cpu_start" : "#50f095",
|
||||||
|
"cpu_mid" : "#f2e266",
|
||||||
|
"cpu_end" : "#fa1e1e",
|
||||||
|
"free_start" : "#223014",
|
||||||
|
"free_mid" : "#b5e685",
|
||||||
|
"free_end" : "#dcff85",
|
||||||
|
"cached_start" : "#0b1a29",
|
||||||
|
"cached_mid" : "#74e6fc",
|
||||||
|
"cached_end" : "#26c5ff",
|
||||||
|
"available_start" : "#292107",
|
||||||
|
"available_mid" : "#ffd77a",
|
||||||
|
"available_end" : "#ffb814",
|
||||||
|
"used_start" : "#3b1f1c",
|
||||||
|
"used_mid" : "#d9626d",
|
||||||
|
"used_end" : "#ff4769",
|
||||||
|
"download_start" : "#231a63",
|
||||||
|
"download_mid" : "#4f43a3",
|
||||||
|
"download_end" : "#b0a9de",
|
||||||
|
"upload_start" : "#510554",
|
||||||
|
"upload_mid" : "#7d4180",
|
||||||
|
"upload_end" : "#dcafde"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#? Setup error logger ---------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
try:
|
||||||
|
errlog = logging.getLogger("ErrorLogger")
|
||||||
|
errlog.setLevel(logging.DEBUG)
|
||||||
|
eh = logging.handlers.RotatingFileHandler(f'{CONFIG_DIR}/error.log', maxBytes=1048576, backupCount=4)
|
||||||
|
eh.setLevel(logging.DEBUG)
|
||||||
|
eh.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s: %(message)s", datefmt="%d/%m/%y (%X)"))
|
||||||
|
errlog.addHandler(eh)
|
||||||
|
except PermissionError:
|
||||||
|
print(f'ERROR!\nNo permission to write to "{CONFIG_DIR}" directory!')
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
errlog.info(f'New instance of bpytop version {VERSION} started with pid {os.getpid()}')
|
||||||
|
|
||||||
|
#? Classes --------------------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
class Term:
|
||||||
|
"""Terminal info and commands"""
|
||||||
|
width: int = os.get_terminal_size().columns #* Current terminal width in columns
|
||||||
|
height: int = os.get_terminal_size().lines #* Current terminal height in lines
|
||||||
|
resized: bool = False #* Flag indicating if terminal was recently resized
|
||||||
|
fg: str = "" #* Default foreground color
|
||||||
|
bg: str = "" #* Default background color
|
||||||
|
hide_cursor = "\033[?25l" #* Hide terminal cursor
|
||||||
|
show_cursor = "\033[?25h" #* Show terminal cursor
|
||||||
|
alt_screen = "\033[?1049h" #* Switch to alternate screen
|
||||||
|
normal_screen = "\033[?1049l" #* Switch to normal screen
|
||||||
|
clear = "\033[2J\033[0;0f" #* Clear screen and set cursor to position 0,0
|
||||||
|
@classmethod
|
||||||
|
def refresh(cls, *args):
|
||||||
|
"""Update width, height and set resized flag if terminal has been resized"""
|
||||||
|
w: int; h: int
|
||||||
|
w, h = os.get_terminal_size()
|
||||||
|
if (w, h) != (cls.width, cls.height):
|
||||||
|
cls.resized = True
|
||||||
|
cls.width, cls.height = w, h
|
||||||
|
@staticmethod
|
||||||
|
def echo(on: bool):
|
||||||
|
"""Toggle input echo"""
|
||||||
|
(iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(sys.stdin.fileno())
|
||||||
|
if on:
|
||||||
|
lflag |= termios.ECHO # type: ignore
|
||||||
|
else:
|
||||||
|
lflag &= ~termios.ECHO # type: ignore
|
||||||
|
new_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
|
||||||
|
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, new_attr)
|
||||||
|
|
||||||
|
class Fx:
|
||||||
|
"""Text effects"""
|
||||||
|
start = "\033[" #* Escape sequence start
|
||||||
|
sep = ";" #* Escape sequence separator
|
||||||
|
end = "m" #* Escape sequence end
|
||||||
|
reset = rs = "\033[0m" #* Reset foreground/background color and text effects
|
||||||
|
bold = b = "\033[1m" #* Bold on
|
||||||
|
unbold = ub = "\033[22m" #* Bold off
|
||||||
|
dark = d = "\033[2m" #* Dark on
|
||||||
|
undark = ud = "\033[22m" #* Dark off
|
||||||
|
italic = i = "\033[3m" #* Italic on
|
||||||
|
unitalic = ui = "\033[23m" #* Italic off
|
||||||
|
underline = u = "\033[4m" #* Underline on
|
||||||
|
ununderline = uu = "\033[24m" #* Underline off
|
||||||
|
blink = bl = "\033[5m" #* Blink on
|
||||||
|
unblink = ubl = "\033[25m" #* Blink off
|
||||||
|
strike = s = "\033[9m" #* Strike / crossed-out on
|
||||||
|
unstrike = us = "\033[29m" #* Strike / crossed-out off
|
||||||
|
|
||||||
|
class Raw(object):
|
||||||
|
"""Set raw input mode for device"""
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
self.fd = self.stream.fileno()
|
||||||
|
def __enter__(self):
|
||||||
|
self.original_stty = termios.tcgetattr(self.stream)
|
||||||
|
tty.setcbreak(self.stream)
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
termios.tcsetattr(self.stream, termios.TCSANOW, self.original_stty)
|
||||||
|
|
||||||
|
class Nonblocking(object):
|
||||||
|
"""Set nonblocking mode for device"""
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
self.fd = self.stream.fileno()
|
||||||
|
def __enter__(self):
|
||||||
|
self.orig_fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl | os.O_NONBLOCK)
|
||||||
|
def __exit__(self, *args):
|
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl)
|
||||||
|
|
||||||
|
class Mv:
|
||||||
|
"""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()"""
|
||||||
|
@staticmethod
|
||||||
|
def to(line: int, col: int) -> str:
|
||||||
|
return f'\033[{line};{col}f' #* Move cursor to line, column
|
||||||
|
@staticmethod
|
||||||
|
def right(x: int) -> str: #* Move cursor right x columns
|
||||||
|
return f'\033[{x}C'
|
||||||
|
@staticmethod
|
||||||
|
def left(x: int) -> str: #* Move cursor left x columns
|
||||||
|
return f'\033[{x}D'
|
||||||
|
@staticmethod
|
||||||
|
def up(x: int) -> str: #* Move cursor up x lines
|
||||||
|
return f'\033[{x}A'
|
||||||
|
@staticmethod
|
||||||
|
def down(x: int) -> str: #* Move cursor down x lines
|
||||||
|
return f'\033[{x}B'
|
||||||
|
@staticmethod
|
||||||
|
def save() -> str: #* Save cursor position
|
||||||
|
return "\033[s"
|
||||||
|
@staticmethod
|
||||||
|
def restore() -> str: #* Restore saved cursor postion
|
||||||
|
return "\033[u"
|
||||||
|
t = to
|
||||||
|
r = right
|
||||||
|
l = left
|
||||||
|
u = up
|
||||||
|
d = down
|
||||||
|
|
||||||
|
class Key:
|
||||||
|
""".list = Input queue | .new = Input threading event | .reader = Input reader thread | .start() = Start input reader | .stop() = Stop input reader"""
|
||||||
|
list: List[str] = []
|
||||||
|
new = threading.Event()
|
||||||
|
stopping: bool = False
|
||||||
|
@classmethod
|
||||||
|
def start(cls):
|
||||||
|
cls.stopping = False
|
||||||
|
cls.reader = threading.Thread(target=get_key)
|
||||||
|
cls.reader.start()
|
||||||
|
@classmethod
|
||||||
|
def stop(cls):
|
||||||
|
if cls.reader.is_alive():
|
||||||
|
cls.stopping = True
|
||||||
|
try:
|
||||||
|
cls.reader.join()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Color:
|
||||||
|
'''self.__init__ accepts 6 digit hexadecimal: string "#RRGGBB", 2 digit hexadecimal: string "#FF" and decimal RGB "0-255 0-255 0-255" as a string.\n
|
||||||
|
self.__init__ also accepts depth="fg" or "bg" | default=bool\n
|
||||||
|
self.__call__(*args) converts arguments to a string and apply color\n
|
||||||
|
self.__str__ returns escape sequence to set color. __iter__ returns iteration over red, green and blue in integer values of 0-255.\n
|
||||||
|
Values: .hex: str | .dec: Tuple[int] | .red: int | .green: int | .blue: int | .depth: str | .escape: str\n
|
||||||
|
'''
|
||||||
|
hex: str; dec: Tuple[int, int, int]; red: int; green: int; blue: int; depth: str; escape: str; default: bool
|
||||||
|
|
||||||
|
def __init__(self, color: str, depth: str = "fg", default: bool = False):
|
||||||
|
self.depth = depth
|
||||||
|
self.default = default
|
||||||
|
try:
|
||||||
|
if not color:
|
||||||
|
if depth != "bg" and not default: raise ValueError("No RGB values given")
|
||||||
|
self.dec = (0, 0, 0)
|
||||||
|
self.hex = ""
|
||||||
|
self.red = self.green = self.blue = 0
|
||||||
|
self.escape = "\033[49m"
|
||||||
|
return
|
||||||
|
|
||||||
|
elif color.startswith("#"):
|
||||||
|
self.hex = color
|
||||||
|
if len(self.hex) == 3:
|
||||||
|
self.hex += self.hex[1:3] + self.hex[1:3]
|
||||||
|
c = int(self.hex[1:3], base=16)
|
||||||
|
self.dec = (c, c, c)
|
||||||
|
elif len(self.hex) == 7:
|
||||||
|
self.dec = (int(self.hex[1:3], base=16), int(self.hex[3:5], base=16), int(self.hex[5:7], base=16))
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Incorrectly formatted hexadeciaml rgb string: {self.hex}')
|
||||||
|
|
||||||
|
else:
|
||||||
|
c_t = tuple(map(int, color.split(" ")))
|
||||||
|
if len(c_t) == 3:
|
||||||
|
self.dec = c_t #type: ignore
|
||||||
|
else:
|
||||||
|
raise ValueError(f'RGB dec should be "0-255 0-255 0-255"')
|
||||||
|
|
||||||
|
ct = self.dec[0] + self.dec[1] + self.dec[2]
|
||||||
|
if ct > 255*3 or ct < 0:
|
||||||
|
raise ValueError(f'RGB values out of range: {color}')
|
||||||
|
except Exception as e:
|
||||||
|
errlog.exception(str(e))
|
||||||
|
self.escape = ""
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.dec and not self.hex: self.hex = 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)}'
|
||||||
|
|
||||||
|
if self.dec and self.hex:
|
||||||
|
self.red, self.green, self.blue = self.dec
|
||||||
|
self.escape = f'\033[{38 if self.depth == "fg" else 48};2;{";".join(str(c) for c in self.dec)}m'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.escape
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return repr(self.escape)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterable:
|
||||||
|
for c in self.dec: yield c
|
||||||
|
|
||||||
|
def __call__(self, *args) -> str:
|
||||||
|
if len(args) == 0: return ""
|
||||||
|
return f'{self.escape}{"".join(map(str, args))}{getattr(Term, self.depth)}'
|
||||||
|
|
||||||
|
class Theme:
|
||||||
|
'''__init__ accepts a dict containing { "color_element" : "color" } , errors defaults to default theme color'''
|
||||||
|
|
||||||
|
main_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 = NotImplemented
|
||||||
|
|
||||||
|
def __init__(self, tdict: Dict[str, str]):
|
||||||
|
for item, value in tdict.items():
|
||||||
|
if hasattr(self, item):
|
||||||
|
default = False if item not in ["main_fg", "main_bg"] else True
|
||||||
|
depth = "fg" if item not in ["main_bg", "selected_bg"] else "bg"
|
||||||
|
setattr(self, item, Color(value, depth=depth, default=default))
|
||||||
|
if getattr(self, item).escape == "":
|
||||||
|
setattr(self, item, Color(DEFAULT_THEME[item], depth=depth, default=default))
|
||||||
|
|
||||||
|
Term.fg, Term.bg = self.main_fg, self.main_bg
|
||||||
|
print(self.main_fg, self.main_bg) #* Set terminal colors
|
||||||
|
|
||||||
|
class Draw:
|
||||||
|
'''Holds the draw buffer\n
|
||||||
|
Add to buffer: .buffer(name, *args, now=False, clear=False)\n
|
||||||
|
Print buffer: .out()\n
|
||||||
|
'''
|
||||||
|
strings: Dict[str, str] = {}
|
||||||
|
last_screen: str = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def buffer(cls, name: str, *args, now: bool = False, clear: bool = False):
|
||||||
|
string: str = ""
|
||||||
|
if args: string = "".join(map(str, args))
|
||||||
|
if name not in cls.strings or clear: cls.strings[name] = ""
|
||||||
|
cls.strings[name] += string
|
||||||
|
if now: print(string)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def out(cls):
|
||||||
|
cls.last_screen = "".join(cls.strings.values())
|
||||||
|
#cls.strings = {}
|
||||||
|
print(cls.last_screen)
|
||||||
|
|
||||||
|
class Symbol:
|
||||||
|
h_line: str = "─"
|
||||||
|
v_line: str = "│"
|
||||||
|
left_up: str = "┌"
|
||||||
|
right_up: str = "┐"
|
||||||
|
left_down: str = "└"
|
||||||
|
right_down: str = "┘"
|
||||||
|
title_left: str = "┤"
|
||||||
|
title_right: str = "├"
|
||||||
|
div_up: str = "┬"
|
||||||
|
div_down: str = "┴"
|
||||||
|
graph_up: Dict[float, str] = {
|
||||||
|
0.0 : "⠀", 0.1 : "⢀", 0.2 : "⢠", 0.3 : "⢰", 0.4 : "⢸",
|
||||||
|
1.0 : "⡀", 1.1 : "⣀", 1.2 : "⣠", 1.3 : "⣰", 1.4 : "⣸",
|
||||||
|
2.0 : "⡄", 2.1 : "⣄", 2.2 : "⣤", 2.3 : "⣴", 2.4 : "⣼",
|
||||||
|
3.0 : "⡆", 3.1 : "⣆", 3.2 : "⣦", 3.3 : "⣶", 3.4 : "⣾",
|
||||||
|
4.0 : "⡇", 4.1 : "⣇", 4.2 : "⣧", 4.3 : "⣷", 4.4 : "⣿"
|
||||||
|
}
|
||||||
|
graph_down: Dict[float, str] = {
|
||||||
|
0.0 : "⠀", 0.1 : "⠈", 0.2 : "⠘", 0.3 : "⠸", 0.4 : "⢸",
|
||||||
|
1.0 : "⠁", 1.1 : "⠉", 1.2 : "⠙", 1.3 : "⠹", 1.4 : "⢹",
|
||||||
|
2.0 : "⠃", 2.1 : "⠋", 2.2 : "⠛", 2.3 : "⠻", 2.4 : "⢻",
|
||||||
|
3.0 : "⠇", 3.1 : "⠏", 3.2 : "⠟", 3.3 : "⠿", 3.4 : "⢿",
|
||||||
|
4.0 : "⡇", 4.1 : "⡏", 4.2 : "⡟", 4.3 : "⡿", 4.4 : "⣿"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#? Functions ------------------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
def get_cpu_name():
|
||||||
|
'''Fetch a suitable CPU identifier from the CPU model name string'''
|
||||||
|
name: str = ""
|
||||||
|
nlist: List = []
|
||||||
|
command: str = ""
|
||||||
|
cmd_out: str = ""
|
||||||
|
rem_line: str = ""
|
||||||
|
if SYSTEM == "Linux":
|
||||||
|
command = "cat /proc/cpuinfo"
|
||||||
|
rem_line = "model name"
|
||||||
|
elif SYSTEM == "MacOS":
|
||||||
|
command ="sysctl -n machdep.cpu.brand_string"
|
||||||
|
elif SYSTEM == "BSD":
|
||||||
|
command ="sysctl hw.model"
|
||||||
|
rem_line = "hw.model"
|
||||||
|
|
||||||
|
cmd_out = subprocess.check_output("LANG=C " + command, shell=True, universal_newlines=True)
|
||||||
|
if rem_line:
|
||||||
|
for line in cmd_out.split("\n"):
|
||||||
|
if rem_line in line:
|
||||||
|
name = re.sub( ".*" + rem_line + ".*:", "", line,1).lstrip()
|
||||||
|
else:
|
||||||
|
name = cmd_out
|
||||||
|
nlist = name.split(" ")
|
||||||
|
if "Xeon" in name:
|
||||||
|
name = nlist[nlist.index("CPU")+1]
|
||||||
|
elif "Ryzen" in name:
|
||||||
|
name = " ".join(nlist[nlist.index("Ryzen"):nlist.index("Ryzen")+3])
|
||||||
|
elif "CPU" in name:
|
||||||
|
name = nlist[nlist.index("CPU")-1]
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
def load_theme(path: str) -> Dict[str, str]:
|
||||||
|
'''Load a bashtop formatted theme file'''
|
||||||
|
new_theme: Dict[str, str] = {}
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
for line in f:
|
||||||
|
if not line.startswith("theme["): continue
|
||||||
|
key = line[6:line.find("]")]
|
||||||
|
s = line.find('"')
|
||||||
|
value = line[s + 1:line.find('"', s + 1)]
|
||||||
|
new_theme[key] = value
|
||||||
|
except Exception as e:
|
||||||
|
errlog.exception(str(e))
|
||||||
|
|
||||||
|
return new_theme
|
||||||
|
|
||||||
|
def fg(h_r: Union[str, int], g: int = 0, b: int = 0) -> str:
|
||||||
|
"""Returns escape sequence to set foreground color, accepts either 6 digit hexadecimal: "#RRGGBB", 2 digit hexadecimal: "#FF" or decimal RGB: 0-255, 0-255, 0-255"""
|
||||||
|
color: str = ""
|
||||||
|
if isinstance(h_r, int): #* Check for decimal RGB
|
||||||
|
color = f'\033[38;2;{h_r};{g};{b}m'
|
||||||
|
|
||||||
|
else: #* Check for 2 or 6 digit hexadecimal RGB
|
||||||
|
if h_r[0] == "#": h_r = h_r[1:]
|
||||||
|
try:
|
||||||
|
if len(h_r) == 2:
|
||||||
|
c = int(h_r, base=16)
|
||||||
|
color = f'\033[38;2;{c};{c};{c}m'
|
||||||
|
elif len(h_r) == 6:
|
||||||
|
color = f'\033[38;2;{int(h_r[0:2], base=16)};{int(h_r[2:4], base=16)};{int(h_r[4:6], base=16)}m'
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return color
|
||||||
|
|
||||||
|
def bg(h_r: Union[str, int], g: int = 0, b: int = 0) -> str:
|
||||||
|
"""Returns escape sequence to set background color, accepts either 6 digit hexadecimal: "#RRGGBB", 2 digit hexadecimal: "#FF" or decimal RGB: 0-255, 0-255, 0-255"""
|
||||||
|
color: str = ""
|
||||||
|
if isinstance(h_r, int): #* Check for decimal RGB
|
||||||
|
color = f'\033[48;2;{h_r};{g};{b}m'
|
||||||
|
|
||||||
|
else: #* Check for 2 or 6 digit hexadecimal RGB
|
||||||
|
if h_r[0] == "#": h_r = h_r[1:]
|
||||||
|
try:
|
||||||
|
if len(h_r) == 2:
|
||||||
|
c = int(h_r, base=16)
|
||||||
|
color = f'\033[48;2;{c};{c};{c}m'
|
||||||
|
elif len(h_r) == 6:
|
||||||
|
color = f'\033[48;2;{int(h_r[0:2], base=16)};{int(h_r[2:4], base=16)};{int(h_r[4:6], base=16)}m'
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return color
|
||||||
|
|
||||||
|
def get_key():
|
||||||
|
"""Get a single key from stdin, convert to readable format and save to keys list"""
|
||||||
|
input_key: str = ""
|
||||||
|
clean_key: str = ""
|
||||||
|
with Raw(sys.stdin): #* Set raw mode
|
||||||
|
with Nonblocking(sys.stdin): #* Set nonblocking mode
|
||||||
|
while not Key.stopping:
|
||||||
|
if not select([sys.stdin], [], [], 0.1)[0]: #* Wait 100ms for input then restart loop to check for stop signal
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
input_key = sys.stdin.read(1) #* Read 1 character from stdin
|
||||||
|
if input_key == "\033": #* Read 3 additional characters if first is escape character
|
||||||
|
input_key += sys.stdin.read(3)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if input_key == "\033": clean_key = "escape"
|
||||||
|
elif input_key == "\n": clean_key = "enter"
|
||||||
|
elif input_key == "\x7f" or input_key == "\x08": clean_key = "backspace"
|
||||||
|
|
||||||
|
elif input_key.isprintable(): clean_key = input_key #* Return character if input key is printable
|
||||||
|
|
||||||
|
if clean_key:
|
||||||
|
Key.list.append(clean_key) #* Store keys in input queue for later processing
|
||||||
|
clean_key = ""
|
||||||
|
Key.new.set() #* Set threading event to interrupt main thread sleep
|
||||||
|
input_key = ""
|
||||||
|
sys.stdin.read(100) #* Clear stdin
|
||||||
|
|
||||||
|
def now_sleeping(signum, frame):
|
||||||
|
"""Reset terminal settings and stop background input read before putting to sleep"""
|
||||||
|
Key.stop()
|
||||||
|
print(Term.clear, Term.normal_screen, Term.show_cursor)
|
||||||
|
os.kill(os.getpid(), signal.SIGSTOP)
|
||||||
|
|
||||||
|
def now_awake(signum, frame):
|
||||||
|
"""Set terminal settings and restart background input read"""
|
||||||
|
print(Term.alt_screen, Term.clear, Term.hide_cursor)
|
||||||
|
Key.start()
|
||||||
|
|
||||||
|
def quit_sigint(signum, frame):
|
||||||
|
"""SIGINT redirection to clean_quit()"""
|
||||||
|
clean_quit()
|
||||||
|
|
||||||
|
def clean_quit(errcode: int = 0):
|
||||||
|
"""Reset terminal settings, save settings to config and stop background input read before quitting"""
|
||||||
|
Key.stop()
|
||||||
|
print(Term.clear, Term.normal_screen, Term.show_cursor)
|
||||||
|
raise SystemExit(errcode)
|
||||||
|
|
||||||
|
def calc_sizes():
|
||||||
|
'''Calculate sizes of boxes'''
|
||||||
|
|
||||||
|
#* Calculate lines and columns from percentage values
|
||||||
|
for box in boxes:
|
||||||
|
setattr(box, "height", round(Term.height * getattr(box, "height_p") / 100))
|
||||||
|
setattr(box, "width", round(Term.width * getattr(box, "width_p") / 100))
|
||||||
|
|
||||||
|
#* Set position values
|
||||||
|
proc.x = mem.width + 1
|
||||||
|
mem.y = proc.y = cpu.height + 1
|
||||||
|
net.y = cpu.height + mem.height + 2
|
||||||
|
|
||||||
|
#* Set values for detailed view in process box
|
||||||
|
proc.detailed_x = proc.x
|
||||||
|
proc.detailed_y = proc.y
|
||||||
|
proc.detailed_height = 8
|
||||||
|
proc.detailed_width = proc.width
|
||||||
|
|
||||||
|
#THREADS = 8
|
||||||
|
|
||||||
|
#* Set values for cpu info sub box
|
||||||
|
if THREADS > (cpu.height - 5) * 3 and cpu.width > 200: cpu.box_width = 24 * 4; cpu.box_height = round(THREADS / 4) + 4; cpu.box_columns = 4
|
||||||
|
elif THREADS > (cpu.height - 5) * 2 and cpu.width > 150: cpu.box_width = 24 * 3; cpu.box_height = round(THREADS / 3) + 5; cpu.box_columns = 3
|
||||||
|
elif THREADS > cpu.height - 5 and cpu.width > 100: cpu.box_width = 24 * 2; cpu.box_height = round(THREADS / 2) + 4; cpu.box_columns = 2
|
||||||
|
else: cpu.box_width = 24; cpu.box_height = THREADS + 4; cpu.box_columns = 1
|
||||||
|
|
||||||
|
if Config.check_temp: cpu.box_width += 13 * cpu.box_columns
|
||||||
|
if cpu.box_height > cpu.height - 3: cpu.box_height = cpu.height - 3
|
||||||
|
cpu.box_x = (cpu.width - 2) - cpu.box_width
|
||||||
|
cpu.box_y = cpu.y + ((cpu.height - 2) // 2) - round(cpu.box_height / 2) + 2
|
||||||
|
|
||||||
|
#* Set value for mem box divider
|
||||||
|
mem.mem_width = mem.disks_width = round((mem.width-2) / 2)
|
||||||
|
if mem.mem_width + mem.disks_width < mem.width - 2: mem.mem_width += 1
|
||||||
|
mem.divider = mem.x + mem.mem_width + 2
|
||||||
|
|
||||||
|
#* Set values for net sub box
|
||||||
|
net.box_width = 24
|
||||||
|
net.box_height = 9 if net.height > 12 else net.height - 4
|
||||||
|
net.box_x = net.width - net.box_width - 2
|
||||||
|
net.box_y = net.y + ((net.height - 2) // 2) - round(net.box_height / 2)
|
||||||
|
|
||||||
|
def create_box(x: int = 0, y: int = 0, width: int = 0, height: int = 0, title: str = "", line_color: Color = None, fill: bool = False, box_object: object = None):
|
||||||
|
'''Create a box from a box object or by given arguments'''
|
||||||
|
out: str = f'{Term.fg}{Term.bg}'
|
||||||
|
if not line_color: line_color = theme.div_line
|
||||||
|
|
||||||
|
#* Get values from box object if given
|
||||||
|
if box_object:
|
||||||
|
x = getattr(box_object, "x")
|
||||||
|
y = getattr(box_object, "y")
|
||||||
|
width = getattr(box_object, "width")
|
||||||
|
height = getattr(box_object, "height")
|
||||||
|
title = getattr(box_object, "name")
|
||||||
|
vlines: Tuple[int, int] = (x, x + width)
|
||||||
|
hlines: Tuple[int, int] = (y, y + height)
|
||||||
|
|
||||||
|
#* Fill box if enabled
|
||||||
|
if fill:
|
||||||
|
i: int
|
||||||
|
for i in range(y + 1, y + height):
|
||||||
|
out += f'{Mv.to(i, x)}{" " * (width - 1)}'
|
||||||
|
|
||||||
|
out += f'{line_color}'
|
||||||
|
|
||||||
|
#* Draw all horizontal lines
|
||||||
|
hpos: int
|
||||||
|
for hpos in hlines:
|
||||||
|
out += f'{Mv.to(hpos, x)}{Symbol.h_line * width}'
|
||||||
|
|
||||||
|
#* Draw all vertical lines
|
||||||
|
vpos: int
|
||||||
|
for vpos in vlines:
|
||||||
|
for hpos in range(y, y + height):
|
||||||
|
out += f'{Mv.to(hpos, vpos)}{Symbol.v_line}'
|
||||||
|
|
||||||
|
#* Draw corners
|
||||||
|
out += f'{Mv.to(y, x)}{Symbol.left_up}\
|
||||||
|
{Mv.to(y, x + width)}{Symbol.right_up}\
|
||||||
|
{Mv.to(y + height, x)}{Symbol.left_down}\
|
||||||
|
{Mv.to(y + height, x + width)}{Symbol.right_down}'
|
||||||
|
|
||||||
|
#* Draw title if enabled
|
||||||
|
if title:
|
||||||
|
out += f'{Mv.to(y, x + 2)}{Symbol.title_left}{theme.title}{Fx.b}{title}{Fx.ub}{line_color}{Symbol.title_right}'
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
#? Main function --------------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
def main():
|
||||||
|
line: str = ""
|
||||||
|
this_key: str = ""
|
||||||
|
count: int = 0
|
||||||
|
while True:
|
||||||
|
count += 1
|
||||||
|
print(f'{Mv.to(1,1)}{Fx.b}{blue("Count:")} {count} {lime("Time:")} {time.strftime("%H:%M:%S", time.localtime())}')
|
||||||
|
print(f'{fg("#ff")} Width: {Term.width} Height: {Term.height} Resized: {Term.resized}')
|
||||||
|
while Key.list:
|
||||||
|
Key.new.clear()
|
||||||
|
this_key = Key.list.pop()
|
||||||
|
print(f'{Mv.to(2,1)}{fg("#ff9050")}{Fx.b}Last key= {Term.fg}{Fx.ub}{repr(this_key)}{" " * 40}')
|
||||||
|
if this_key == "backspace":
|
||||||
|
line = line[:-1]
|
||||||
|
elif this_key == "enter":
|
||||||
|
line += "\n"
|
||||||
|
else:
|
||||||
|
line += this_key
|
||||||
|
print(f'{Mv.to(3,1)}{fg("#90ff50")}{Fx.b}Full line= {Term.fg}{Fx.ub}{line}{Fx.bl}| {Fx.ubl}')
|
||||||
|
if this_key == "q":
|
||||||
|
clean_quit()
|
||||||
|
if this_key == "R":
|
||||||
|
raise Exception("Test ERROR")
|
||||||
|
if not Key.reader.is_alive():
|
||||||
|
clean_quit(1)
|
||||||
|
Key.new.wait(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
#? Init Classes ---------------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
class Banner:
|
||||||
|
out: List[str] = []
|
||||||
|
c_color: str = ""
|
||||||
|
length: int = 0
|
||||||
|
if not out:
|
||||||
|
for num, (color, line) in enumerate(BANNER_SRC.items()):
|
||||||
|
if len(line) > length: length = len(line)
|
||||||
|
out += [""]
|
||||||
|
line_color = fg(color)
|
||||||
|
line_dark = fg(f'#{80 - num * 6}')
|
||||||
|
for letter in line:
|
||||||
|
if letter == "█" and c_color != line_color:
|
||||||
|
c_color = line_color
|
||||||
|
out[num] += line_color
|
||||||
|
elif letter == " ":
|
||||||
|
letter = f'{Mv.r(1)}'
|
||||||
|
elif letter != "█" and c_color != line_dark:
|
||||||
|
c_color = line_dark
|
||||||
|
out[num] += line_dark
|
||||||
|
out[num] += letter
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def draw(cls, line: int, col: int = 0, center: bool = False, now: bool = False):
|
||||||
|
out: str = ""
|
||||||
|
if center: col = Term.width // 2 - cls.length // 2
|
||||||
|
for n, o in enumerate(cls.out):
|
||||||
|
out += f'{Mv.to(line + n, col)}{o}'
|
||||||
|
out += f'{Term.fg}'
|
||||||
|
if now: print(out)
|
||||||
|
else: return out
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
'''Holds all config variables'''
|
||||||
|
check_temp: bool = True
|
||||||
|
|
||||||
|
class Graphs:
|
||||||
|
'''Holds all graph objects and dicts for dynamically created graphs'''
|
||||||
|
cpu: object = None
|
||||||
|
cores: Dict[int, object] = {}
|
||||||
|
temps: Dict[int, object] = {}
|
||||||
|
net: object = None
|
||||||
|
detailed_cpu: object = None
|
||||||
|
detailed_mem: object = None
|
||||||
|
pid_cpu: Dict[int, object] = {}
|
||||||
|
|
||||||
|
class Meters:
|
||||||
|
'''Holds created meters to reuse instead of recreating meters of past values'''
|
||||||
|
cpu: Dict[int, str] = {}
|
||||||
|
mem_used: Dict[int, str] = {}
|
||||||
|
mem_available: Dict[int, str] = {}
|
||||||
|
mem_cached: Dict[int, str] = {}
|
||||||
|
mem_free: Dict[int, str] = {}
|
||||||
|
swap_used: Dict[int, str] = {}
|
||||||
|
swap_free: Dict[int, str] = {}
|
||||||
|
disks_used: Dict[int, str] = {}
|
||||||
|
disks_free: Dict[int, str] = {}
|
||||||
|
|
||||||
|
class Box:
|
||||||
|
'''Box object with all needed attributes for create_box() function'''
|
||||||
|
def __init__(self, name: str, height_p: int, width_p: int):
|
||||||
|
self.name: str = name
|
||||||
|
self.height_p: int = height_p
|
||||||
|
self.width_p: int = width_p
|
||||||
|
self.x: int = 0
|
||||||
|
self.y: int = 0
|
||||||
|
self.width: int = 0
|
||||||
|
self.height: int = 0
|
||||||
|
self.out: str = ""
|
||||||
|
if name == "proc":
|
||||||
|
self.detailed: bool = False
|
||||||
|
self.detailed_x: int = 0
|
||||||
|
self.detailed_y: int = 0
|
||||||
|
self.detailed_width: int = 0
|
||||||
|
self.detailed_height: int = 8
|
||||||
|
if name == "mem":
|
||||||
|
self.divider: int = 0
|
||||||
|
self.mem_width: int = 0
|
||||||
|
self.disks_width: int = 0
|
||||||
|
if name in ("cpu", "net"):
|
||||||
|
self.box_x: int = 0
|
||||||
|
self.box_y: int = 0
|
||||||
|
self.box_width: int = 0
|
||||||
|
self.box_height: int = 0
|
||||||
|
self.box_columns: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
#? Init variables -------------------------------------------------------------------------------->
|
||||||
|
|
||||||
|
CPU_NAME: str = get_cpu_name()
|
||||||
|
|
||||||
|
|
||||||
|
#theme = Theme(load_theme("/home/gnm/.config/bashtop/themes/monokai.theme"))
|
||||||
|
theme = Theme(DEFAULT_THEME)
|
||||||
|
|
||||||
|
cpu = Box("cpu", height_p=32, width_p=100)
|
||||||
|
mem = Box("mem", height_p=40, width_p=45)
|
||||||
|
net = Box("net", height_p=28, width_p=mem.width_p)
|
||||||
|
proc = Box("proc", height_p=100 - cpu.height_p, width_p=100 - mem.width_p)
|
||||||
|
|
||||||
|
boxes: List[object] = [cpu, mem, net, proc]
|
||||||
|
|
||||||
|
blue = theme.temp_start
|
||||||
|
lime = theme.cached_mid
|
||||||
|
orange = theme.available_end
|
||||||
|
green = theme.cpu_start
|
||||||
|
dfg = theme.main_fg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def draw_bg(now: bool = True):
|
||||||
|
'''Draw all boxes to buffer and print to screen if now=True'''
|
||||||
|
Draw.buffer("bg", clear=True)
|
||||||
|
|
||||||
|
#* Draw cpu box and cpu sub box
|
||||||
|
cpu_box = f'{create_box(box_object=cpu, line_color=theme.cpu_box, fill=True)}\
|
||||||
|
{Mv.to(cpu.y, cpu.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)}\
|
||||||
|
{create_box(x=cpu.box_x, y=cpu.box_y, width=cpu.box_width, height=cpu.box_height, line_color=theme.div_line, title=CPU_NAME[:18 if Config.check_temp else 9])}'
|
||||||
|
|
||||||
|
#* Draw mem/disk box and divider
|
||||||
|
mem_box = f'{create_box(box_object=mem, line_color=theme.mem_box, fill=True)}\
|
||||||
|
{Mv.to(mem.y, mem.divider + 2)}{theme.mem_box(Symbol.title_left)}{Fx.b}{theme.title("disks")}{Fx.ub}{theme.mem_box(Symbol.title_right)}\
|
||||||
|
{Mv.to(mem.y, mem.divider)}{theme.mem_box(Symbol.div_up)}\
|
||||||
|
{Mv.to(mem.y + mem.height, mem.divider)}{theme.mem_box(Symbol.div_down)}{theme.div_line}'
|
||||||
|
for i in range(1, mem.height):
|
||||||
|
mem_box += f'{Mv.to(mem.y + i, mem.divider)}{Symbol.v_line}'
|
||||||
|
|
||||||
|
#* Draw net box and net sub box
|
||||||
|
net_box = f'{create_box(box_object=net, line_color=theme.net_box, fill=True)}\
|
||||||
|
{create_box(x=net.box_x, y=net.box_y, width=net.box_width, height=net.box_height, line_color=theme.div_line, title="Download")}\
|
||||||
|
{Mv.to(net.box_y + net.box_height, net.box_x + 1)}{theme.div_line(Symbol.title_left)}{Fx.b}{theme.title("Upload")}{Fx.ub}{theme.div_line(Symbol.title_right)}'
|
||||||
|
|
||||||
|
#* Draw proc box
|
||||||
|
proc_box = create_box(box_object=proc, line_color=theme.proc_box, fill=True)
|
||||||
|
|
||||||
|
Draw.buffer("bg", cpu_box, mem_box, net_box, proc_box)
|
||||||
|
|
||||||
|
def testing_colors():
|
||||||
|
for item, _ in DEFAULT_THEME.items():
|
||||||
|
print(Fx.b, getattr(theme, item)(f'{item:<20}'), Fx.ub, f'{"hex=" + getattr(theme, item).hex:<20} dec={getattr(theme, item).dec}', end="\n")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(theme.temp_start, "Hej!\n")
|
||||||
|
print(Term.fg, "\nHEJ\n")
|
||||||
|
print(repr(Term.fg), repr(Term.bg))
|
||||||
|
|
||||||
|
quit()
|
||||||
|
|
||||||
|
def testing_banner():
|
||||||
|
print(Term.normal_screen, Term.alt_screen)
|
||||||
|
|
||||||
|
#try:
|
||||||
|
#sad
|
||||||
|
#except Exception as e:
|
||||||
|
# errlog.exception(f'{e}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#eprint("Test")
|
||||||
|
calc_sizes()
|
||||||
|
|
||||||
|
draw_bg()
|
||||||
|
|
||||||
|
Draw.buffer("banner", Banner.draw(18, 45), clear=True)
|
||||||
|
Draw.out()
|
||||||
|
|
||||||
|
print(Mv.to(35, 1), repr(Term.fg), " ", repr(Term.bg), "\n")
|
||||||
|
|
||||||
|
# quit()
|
||||||
|
|
||||||
|
# global theme
|
||||||
|
|
||||||
|
# path = "/home/gnm/.config/bashtop/themes/"
|
||||||
|
# for file in os.listdir(path):
|
||||||
|
# if file.endswith(".theme"):
|
||||||
|
# theme = Theme(load_theme(path + file))
|
||||||
|
# draw_bg()
|
||||||
|
# Draw.out()
|
||||||
|
# time.sleep(1)
|
||||||
|
|
||||||
|
#draw_bg()
|
||||||
|
#Draw.buffer("banner", Banner.draw(5, center=True))
|
||||||
|
|
||||||
|
#Draw.out()
|
||||||
|
|
||||||
|
# print(f'\n{Fx.b}Terminal Height={Term.height} Width={Term.width}')
|
||||||
|
# total_h = total_w = 0
|
||||||
|
# for box in boxes:
|
||||||
|
# print(f'\n{getattr(box, "name")} Height={getattr(box, "height")} Width={getattr(box, "width")}')
|
||||||
|
# total_h += getattr(box, "height")
|
||||||
|
# total_w += getattr(box, "width")
|
||||||
|
# print(f'\nTotal Height={cpu.height + net.height + mem.height} Width={net.width + proc.width}')
|
||||||
|
|
||||||
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#testing_colors()
|
||||||
|
#error_log("/home/gnm/bashtop/misc/error.log")
|
||||||
|
|
||||||
|
|
||||||
|
#testing_banner()
|
||||||
|
|
||||||
|
|
||||||
|
#quit()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
#? Setup signal handlers for SIGSTP, SIGCONT, SIGINT and SIGWINCH
|
||||||
|
signal.signal(signal.SIGTSTP, now_sleeping) #* Ctrl-Z
|
||||||
|
signal.signal(signal.SIGCONT, now_awake) #* Resume
|
||||||
|
signal.signal(signal.SIGINT, quit_sigint) #* Ctrl-C
|
||||||
|
signal.signal(signal.SIGWINCH, Term.refresh) #* Terminal resized
|
||||||
|
|
||||||
|
#? Switch to alternate screen, clear screen and hide cursor
|
||||||
|
print(Term.alt_screen, Term.clear, Term.hide_cursor)
|
||||||
|
|
||||||
|
#? Start a separate thread for reading keyboard input
|
||||||
|
try:
|
||||||
|
Key.start()
|
||||||
|
except Exception as e:
|
||||||
|
errlog.exception(f'{e}')
|
||||||
|
clean_quit(1)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception as e:
|
||||||
|
errlog.exception(f'{e}')
|
||||||
|
clean_quit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
clean_quit()
|
Loading…
Reference in New Issue