├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── doubt-or-ask.md
│ └── feature_request.md
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── data
├── Killer.ico
├── big-bmp
│ ├── 100.bmp
│ ├── 125.bmp
│ ├── 150.bmp
│ ├── 175.bmp
│ ├── 200.bmp
│ ├── 225.bmp
│ └── 250.bmp
├── samples
│ ├── dark.PNG
│ ├── kill.PNG
│ ├── light.PNG
│ └── search.PNG
└── small-bmp
│ ├── 100.bmp
│ ├── 125.bmp
│ ├── 150.bmp
│ ├── 175.bmp
│ ├── 200.bmp
│ ├── 225.bmp
│ └── 250.bmp
├── requirements.txt
└── src
├── icons
├── Compil32.exe.png
├── Defrag.exe.png
├── EpicGamesLauncher.exe.png
├── GitHubDesktop.exe.png
├── ISStudio.exe.png
├── Killer.exe.png
├── LaunchTM.exe.png
├── MicrosoftEdgeUpdate.exe.png
├── MicrosoftEdge_X64_88.0.705.81_88.0.705.74.exe.png
├── MpCmdRun.exe.png
├── MusNotifyIcon.exe.png
├── OfficeC2RClient.exe.png
├── OfficeClickToRun.exe.png
├── OpenConsole.exe.png
├── OriginWebHelperService.exe.png
├── ProcessHacker.exe.png
├── Rainmeter.exe.png
├── SearchFilterHost.exe.png
├── SearchIndexer.exe.png
├── SearchProtocolHost.exe.png
├── SetupHost.exe.png
├── Simplenote.exe.png
├── SystemSettings.exe.png
├── Taskmgr.exe.png
├── Teams.exe.png
├── Update.exe.png
├── WindowsTerminal.exe.png
├── WindowsUpdateBox.exe.png
├── WmiPrvSE.exe.png
├── Zoom.exe.png
├── chrome.exe.png
├── cleanmgr.exe.png
├── cmd.exe.png
├── conhost.exe.png
├── consent.exe.png
├── default.png
├── explorer.exe.png
├── git-lfs.exe.png
├── mighost.exe.png
├── pip.exe.png
├── powershell.exe.png
├── pycharm64.exe.png
├── python.exe.png
├── rundll32.exe.png
├── spoolsv.exe.png
├── wermgr.exe.png
└── winlogon.exe.png
├── killer_config.json
├── main.kv
├── main.py
├── utils.py
└── widgets.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 | *.png filter=lfs diff=lfs merge=lfs -text
4 | *.ico filter=lfs diff=lfs merge=lfs -text
5 | *.bmp filter=lfs diff=lfs merge=lfs -text
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Bug description**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop**
24 | - OS: [e.g. Windows 10 1709]
25 | - Version: [e.g. 0.1]
26 | - Other:
27 |
28 | **Additional context**
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/doubt-or-ask.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Doubt or Ask
3 | about: Ask about anything you want to clarify
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **The Doubt**
11 | A clear and concise description of your doubt
12 |
13 | **Additional context**
14 | Add any other context or screenshots about the feature request here, if any.
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **The feature**
11 | A clear and concise description of the ideia you want to share.
12 |
13 | **Is the feature request related to a problem?**
14 | A clear and concise description of what the problem is, if there is one.
15 | E.g. I'm always frustrated when [...]
16 |
17 | **Alternatives considered**
18 | A clear and concise description of any alternative solutions or features you've considered.
19 |
20 | **Additional context**
21 | Add any other context or screenshots about the feature request here, if any.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | PyInstaller/
2 | teste/
3 | .idea/
4 | InnoSetup/
5 | dependent-venv/
6 | teste.py
7 |
8 | # Byte-compiled / optimized / DLL files
9 | __pycache__/
10 | *.py[cod]
11 | *$py.class
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Distribution / packaging
17 | .Python
18 | build/
19 | develop-eggs/
20 | dist/
21 | downloads/
22 | eggs/
23 | .eggs/
24 | lib/
25 | lib64/
26 | parts/
27 | sdist/
28 | var/
29 | wheels/
30 | pip-wheel-metadata/
31 | share/python-wheels/
32 | *.egg-info/
33 | .installed.cfg
34 | *.egg
35 | MANIFEST
36 |
37 | # PyInstaller
38 | # Usually these files are written by a python script from a template
39 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
40 | *.manifest
41 | *.spec
42 |
43 | # Installer logs
44 | pip-log.txt
45 | pip-delete-this-directory.txt
46 |
47 | # Unit test / coverage reports
48 | htmlcov/
49 | .tox/
50 | .nox/
51 | .coverage
52 | .coverage.*
53 | .cache
54 | nosetests.xml
55 | coverage.xml
56 | *.cover
57 | *.py,cover
58 | .hypothesis/
59 | .pytest_cache/
60 |
61 | # Translations
62 | *.mo
63 | *.pot
64 |
65 | # Django stuff:
66 | *.log
67 | local_settings.py
68 | db.sqlite3
69 | db.sqlite3-journal
70 |
71 | # Flask stuff:
72 | instance/
73 | .webassets-cache
74 |
75 | # Scrapy stuff:
76 | .scrapy
77 |
78 | # Sphinx documentation
79 | docs/_build/
80 |
81 | # PyBuilder
82 | target/
83 |
84 | # Jupyter Notebook
85 | .ipynb_checkpoints
86 |
87 | # IPython
88 | profile_default/
89 | ipython_config.py
90 |
91 | # pyenv
92 | .python-version
93 |
94 | # pipenv
95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
98 | # install all needed dependencies.
99 | #Pipfile.lock
100 |
101 | # celery beat schedule file
102 | celerybeat-schedule
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Killer
2 |
3 | First off, thanks for taking the time to contribute! 😊
4 |
5 | If you want to contribute with a feature request, please send it on the [issues tab](https://github.com/ntaraujo/killer/issues)
6 |
7 | If you want to make this feature, but are not shure if it would be accepted, go to the [discussions tab](https://github.com/ntaraujo/killer/discussions)
8 |
9 | Also, in any moment of your developing, feel free to tell us the step you are, the difficults, ask for advices or any in the same
10 | [discussions tab](https://github.com/ntaraujo/killer/discussions), in the right category
11 |
12 | Are objectives of this project:
13 | * An lightweight app, when talking about memory and cpu usage
14 | * A well designed app, with colors and shapes coherence
15 | * An app with features not given by the default Windows Task Manager, or at least with no easy access
16 | * An efficient app for improving performance by killing undesired processes
17 | * An intuitive app
18 |
19 | Are not objectives of this project:
20 | * A complete solution, with more features than can be reconciled with the design
21 | * Another Task Manager, taking the place of the default Windows Task Manager
22 | * An app just for advanced users, without intuitive paths to the features
23 |
24 | # Starting
25 |
26 | Python, PIP and GIT needs to be installed and Python 3.8 inside a venv is recommended
27 | ```sh
28 | git clone https://github.com/ntaraujo/killer.git
29 | cd killer
30 | pip install -r requirements.txt
31 | cd src
32 | python main.py
33 | ```
34 | If it works, you are ready for making changes to the source code.
35 |
36 | # About the current used tools
37 |
38 | 1. [Python](https://www.python.org): A programming language to work quickly
39 | 2. [Kivy](https://kivy.org): A cross-platform Python framework for NUI development
40 | 3. [KivyMD](https://kivymd.readthedocs.io): A collection of material design compliant widgets for use with Kivy
41 | 4. [Psutil](https://psutil.readthedocs.io): A cross-platform library for retrieving information on running processes and system utilization in Python
42 | 5. [Pillow](https://pillow.readthedocs.io): The Python imaging library
43 | 6. [pywin32](https://github.com/mhammond/pywin32): Python extensions for Windows
44 | 7. [PyInstaller](https://www.pyinstaller.org): Bundles Python applications and all its dependencies into a single package
45 | 8. [Inno Setup](https://jrsoftware.org/isinfo.php): A free installer for Windows programs
46 |
47 | # About the current files
48 |
49 | ```
50 | killer/ - the project
51 | |
52 | data/ - images used by PyInstaller, Inno Setup and GitHub
53 | |
54 | src/ - the important files
55 | |
56 | icons/ - pre-saved icons of Windows processes to be used
57 | | |
58 | | Killer.exe.png - icon for the main.py window
59 | |
60 | killer_config.json - file with user preferences
61 | |
62 | main.kv - most of the app design (view)
63 | |
64 | main.py - the jack of all trades (control)
65 | |
66 | utils.py - functions which can be in another file not main.py (model)
67 | |
68 | widgets.py - widgets which can be in another file not main.py
69 | ```
70 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Nathan Araújo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Killer
2 |
3 |
4 |
5 | 
6 |
7 | 
8 |
9 |
10 |
11 |
12 |
13 | That damn Chrome
14 |
15 | 
16 |
17 |
18 |
19 | Tired of trying to kill processes with the default Windows Task Manager? Selecting one by one? So this is for you.
20 |
21 | 
22 |
23 | Killer is an app with concise design which displays the processes, let search between them, order by pid/name/cpu/memory
24 | and kill multiple apps (or even all) at once. Besides that, you can also kill the children of the selected process(es),
25 | which means, all the other processes created by these (no survival chance).
26 |
27 | When you download the installer it will guide you through the options, but there is no need to change anything. Just next next 😉
28 |
29 | 
30 |
31 | Tip: If you allow the creation of a desktop icon during the installation, "Ctrl+Shift+K" will open the Killer
32 |
33 | Any issue or improvement suggestion can be sent in [our discord](https://discord.gg/MKG6qgJ964) or in the [issues tab](https://github.com/ntaraujo/killer/issues)
34 |
35 | More features coming!
36 |
37 | ## 📝Notes
38 |
39 | Tested on: Windows 10 64 bits
40 |
41 | ### Requirements
42 | * Windows >= Vista, up to date, or Windows 10
43 | * Open GL >= 2.0
44 | * 100MB of free disk space for the installer, 37MB for the standalone
45 |
46 | If for any reason Killer didn't work on your Windows version/arch, please tell us.
47 |
48 | [Open an issue](https://github.com/ntaraujo/killer/issues) or go to [our discord](https://discord.gg/MKG6qgJ964)
49 |
50 | This app doesn't aims to be a complete task manager, even take place of the default one.
51 |
52 | Understand also, despite being not a complete solution, these are the requirements by the tools chosen to proceed with this project.
53 | Mainly the language and framework.
54 |
55 | ### The [standalone executable](https://github.com/ntaraujo/killer/releases/latest/download/Killer.exe)
56 |
57 | Despite requiring less space, it will be decompressed every time it is called, slowing down the startup and unallowing any configuration for being saved.
58 | That is why the recommended way is using the installer `killer-setup.exe`
59 |
60 | ## ⬆️Update
61 |
62 | For updating this app, just download [](https://github.com/ntaraujo/killer/releases/latest/download/killer-setup.exe)
63 | and follow with the installation to the same folder used before.
64 |
65 | It's known the hotkey "Ctrl+Shift+K" may not work until the next user logon
66 |
67 | As pointed out in [#2](https://github.com/ntaraujo/killer/issues/2), Killer versions < 0.7.3 can be misundertood as viruses due to specific compilation problems. If you experienced this, just update the app.
68 |
69 | ## 🔧 Contributing
70 |
71 | Details on how to contribute to this project [here](https://github.com/ntaraujo/killer/blob/main/CONTRIBUTING.md).
72 |
--------------------------------------------------------------------------------
/data/Killer.ico:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:7417fc5c1ae330ea9c022d97fdda0e33b548a6cbfcdd52d148014862a13748d5
3 | size 9662
4 |
--------------------------------------------------------------------------------
/data/big-bmp/100.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a1777c45cfcc51169ec2c07c520a32a968cc9f7f07249aa047e5b8a8c85b267f
3 | size 394522
4 |
--------------------------------------------------------------------------------
/data/big-bmp/125.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:ebeb6c81716bc66ce1819a3a5a1efe8133408304a8ce7dec9ed2f3bb63422244
3 | size 596122
4 |
--------------------------------------------------------------------------------
/data/big-bmp/150.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0105aea5bbd7a48ef5649fb4c5e4ba772d560e671205b18fd524228f86ae9f4d
3 | size 842862
4 |
--------------------------------------------------------------------------------
/data/big-bmp/175.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:03d7c66902a9e9eb17f1c16bb7db585180164df6175e1f659f6ca22b54e0637e
3 | size 1236682
4 |
--------------------------------------------------------------------------------
/data/big-bmp/200.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:b3900045bb6a40f469f9681b1ebd3c0f5e9c17bfa7ba4a231bcae355545a7b0e
3 | size 1459402
4 |
--------------------------------------------------------------------------------
/data/big-bmp/225.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9b7330fbc89b328521ae7a029f4bc5cbc8e098457ac9d4d61542253e206e907d
3 | size 1960138
4 |
--------------------------------------------------------------------------------
/data/big-bmp/250.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:2987a6247a442d768c3e927c7f0e867e4ecf8ce137725f42fda49d08e3bf4165
3 | size 2540974
4 |
--------------------------------------------------------------------------------
/data/samples/dark.PNG:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:abed312f53874b4238640b821437326df2c625417e45d82c31cc5aa04f1357c7
3 | size 45956
4 |
--------------------------------------------------------------------------------
/data/samples/kill.PNG:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:5f755779ebd93a5d49f2b981f122b3843f84a6a0706f8791ad34670b2764e9c4
3 | size 7713
4 |
--------------------------------------------------------------------------------
/data/samples/light.PNG:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:17f69a8a448f38a1293079ae14093ff2299cd3720f33563e0c7c4481591ad4b1
3 | size 44862
4 |
--------------------------------------------------------------------------------
/data/samples/search.PNG:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c5be7264c1ab8ab227b93c62143ec0743b86b01ecca2e1199cd313cf4fc25cb3
3 | size 13648
4 |
--------------------------------------------------------------------------------
/data/small-bmp/100.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:314cd847afafb5a52e3ba4ac16894780defb4169c37ea190b1dfca0eb2111cde
3 | size 12238
4 |
--------------------------------------------------------------------------------
/data/small-bmp/125.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:552d03a88054c8056f723d7242f00482c5e056f2c34728449287af54d694fe45
3 | size 18634
4 |
--------------------------------------------------------------------------------
/data/small-bmp/150.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:948afec7dcbd6cc5729c8d6fd736c9118b8c7aea106d19c8fc5c496c1dd33ef1
3 | size 27694
4 |
--------------------------------------------------------------------------------
/data/small-bmp/175.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:6ac8f92f122534840d5cc895f943585699b126dd4a540f3fe187179a83f31914
3 | size 37774
4 |
--------------------------------------------------------------------------------
/data/small-bmp/200.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9a6fe7eaa070bd790bc9facf524668c8c9905231ba196e586884c548322ffb69
3 | size 48538
4 |
--------------------------------------------------------------------------------
/data/small-bmp/225.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:6af530d910618072d0965f29ab04f68a0147a902fc6393737a09a4b54ca7624e
3 | size 60654
4 |
--------------------------------------------------------------------------------
/data/small-bmp/250.bmp:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:7938bfd3c96eb1ffe6bc15c482aab2f7dc113f3d4bbf6e40384f5246524a773b
3 | size 78538
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Kivy==2.0.0
2 | Pillow==8.1.1
3 | psutil==5.8.0
4 | pypiwin32==223
5 | pywin32==228
6 | kivymd==0.104.1
7 | requests==2.25.1
--------------------------------------------------------------------------------
/src/icons/Compil32.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:60654094fe55d2ec38b9fad345bc2b1c232340b2960a02890b87bc347c2359a7
3 | size 2202
4 |
--------------------------------------------------------------------------------
/src/icons/Defrag.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:2994814f5e5e09a8c7cb391338711e9dd1a749385e0b042b82ee144a230a7de3
3 | size 2355
4 |
--------------------------------------------------------------------------------
/src/icons/EpicGamesLauncher.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:64b9d752b2182bb9e0fccc342c56157a3160bd5ba8107deacf7096b6770a8f11
3 | size 1368
4 |
--------------------------------------------------------------------------------
/src/icons/GitHubDesktop.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:f67891887cf9e0f0180b39ddf8205d2ca754a9902cf9ae978262b7163fdbef72
3 | size 2064
4 |
--------------------------------------------------------------------------------
/src/icons/ISStudio.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:6cc7715a0a8fe63a031a878e558bc7ca59d71348a75b44a72106b47157864487
3 | size 2422
4 |
--------------------------------------------------------------------------------
/src/icons/Killer.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0d91faffb1426e3277d6ade39a5c12254072e04b09b05b2eea6db11d9b6668c6
3 | size 569
4 |
--------------------------------------------------------------------------------
/src/icons/LaunchTM.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:1fdf782ee0dc30351d88d84afda8907391aa0fac4783afba544aaa2e8f645b59
3 | size 2367
4 |
--------------------------------------------------------------------------------
/src/icons/MicrosoftEdgeUpdate.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:e295a7d0673cfe18a85528b54b960ec84ada190d19c9397f1b4badba8ddea824
3 | size 1449
4 |
--------------------------------------------------------------------------------
/src/icons/MicrosoftEdge_X64_88.0.705.81_88.0.705.74.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:69bcb23ab822017b9b221dfa823b441b38c8f13e72875b583e6aa0e5f412bfef
3 | size 551
4 |
--------------------------------------------------------------------------------
/src/icons/MpCmdRun.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:3bf9b53cd7928d0a807fe6c8e350eaeb24ea5df8c78611a1c4f3e8b9290d32fa
3 | size 700
4 |
--------------------------------------------------------------------------------
/src/icons/MusNotifyIcon.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:33bc539bd0ea043ae1af39935aa078ae48396a1c54bd6085779bdf96aff23f97
3 | size 438
4 |
--------------------------------------------------------------------------------
/src/icons/OfficeC2RClient.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:268ac538146deca598e51f52d2ae082cebf5b0b8834f5edf6ca3bc4d9ecc77de
3 | size 793
4 |
--------------------------------------------------------------------------------
/src/icons/OfficeClickToRun.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:268ac538146deca598e51f52d2ae082cebf5b0b8834f5edf6ca3bc4d9ecc77de
3 | size 793
4 |
--------------------------------------------------------------------------------
/src/icons/OpenConsole.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c24570b5b44367c352ad4b303a76d65c8be94787e57ac2fd738c0ca91f434597
3 | size 613
4 |
--------------------------------------------------------------------------------
/src/icons/OriginWebHelperService.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:ac783ae7c64b8269deecb0a979bf165c241a31e28cbfd97037f7a93a8946da8c
3 | size 1026
4 |
--------------------------------------------------------------------------------
/src/icons/ProcessHacker.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:d8b8bc8c4ef4d64cd28e2f2d39138394ca317fc24f36ddd27e783440f47d44e6
3 | size 2506
4 |
--------------------------------------------------------------------------------
/src/icons/Rainmeter.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0766a6bd5bb8c7692b91b3cf4021a21b6187c2f5e85a40f0eb304bb4e4ca0b59
3 | size 1332
4 |
--------------------------------------------------------------------------------
/src/icons/SearchFilterHost.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:8fe5de9fc93885783b183962330286ec644a3b60f349e0a93126c856c4e2bf43
3 | size 2093
4 |
--------------------------------------------------------------------------------
/src/icons/SearchIndexer.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:8fe5de9fc93885783b183962330286ec644a3b60f349e0a93126c856c4e2bf43
3 | size 2093
4 |
--------------------------------------------------------------------------------
/src/icons/SearchProtocolHost.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:8fe5de9fc93885783b183962330286ec644a3b60f349e0a93126c856c4e2bf43
3 | size 2093
4 |
--------------------------------------------------------------------------------
/src/icons/SetupHost.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:3c905fbf61a7d9637557ff1fe8786b5588cae287d4deae7325199a5578d830c3
3 | size 1270
4 |
--------------------------------------------------------------------------------
/src/icons/Simplenote.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:f57c1f49b21d969921a6412db0aca7cc20984b3ebb80b65e584434b1874bb262
3 | size 1648
4 |
--------------------------------------------------------------------------------
/src/icons/SystemSettings.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:3d48cc2efff7a0bb342eb31a579c2f2b1f9672a6c94401fba29211b9f7e7f282
3 | size 831
4 |
--------------------------------------------------------------------------------
/src/icons/Taskmgr.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9adcb8158b67609e2dedfeb2b3693689261ea24e5cf5060394f31616134c367b
3 | size 2325
4 |
--------------------------------------------------------------------------------
/src/icons/Teams.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:54dd8a8047e8b68d7daaa6ae0a4c6898e5295c02221d2ffe1b5f6d2cbe56cd91
3 | size 674
4 |
--------------------------------------------------------------------------------
/src/icons/Update.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:40f942838ad96d2e338b3919ce627f74b5ef283d35413b2a24fbb9b72108d9c1
3 | size 1989
4 |
--------------------------------------------------------------------------------
/src/icons/WindowsTerminal.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:2538a3f4426ce2d2764f709cc29524df567a382408cc76ab777df5941a3b85bb
3 | size 593
4 |
--------------------------------------------------------------------------------
/src/icons/WindowsUpdateBox.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:3c905fbf61a7d9637557ff1fe8786b5588cae287d4deae7325199a5578d830c3
3 | size 1270
4 |
--------------------------------------------------------------------------------
/src/icons/WmiPrvSE.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:2874937ff5b10f16a11f6ef62a7fa4bcfce5009457d4fb5758340dad77d5352b
3 | size 2011
4 |
--------------------------------------------------------------------------------
/src/icons/Zoom.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:1b4a6d2441ff2374b7cdb56282f1c98b978152e4d4f551942f68497ad435cb2f
3 | size 1317
4 |
--------------------------------------------------------------------------------
/src/icons/chrome.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:ac1cb3df883e2fefc58ef5f7810cba18752790b7821dae6b82481b9a35a05381
3 | size 1701
4 |
--------------------------------------------------------------------------------
/src/icons/cleanmgr.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c168cabea288c8e94c315c48b21267d57ff789a57f6cddfbecb93e424c567560
3 | size 1318
4 |
--------------------------------------------------------------------------------
/src/icons/cmd.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c24570b5b44367c352ad4b303a76d65c8be94787e57ac2fd738c0ca91f434597
3 | size 613
4 |
--------------------------------------------------------------------------------
/src/icons/conhost.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c24570b5b44367c352ad4b303a76d65c8be94787e57ac2fd738c0ca91f434597
3 | size 613
4 |
--------------------------------------------------------------------------------
/src/icons/consent.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:385f0df3bef966e20ca2d185262903ed833c20aebd3a90f305adfa397c21efac
3 | size 881
4 |
--------------------------------------------------------------------------------
/src/icons/default.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a0a97541820eaa5b33aa5a988f8653a5863301bf7e2ad6880771209c81c6745a
3 | size 985
4 |
--------------------------------------------------------------------------------
/src/icons/explorer.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:81ade5f6c9946dd0cac8492249783ce966ecfb620515f6f032d51dade4ddcaf9
3 | size 676
4 |
--------------------------------------------------------------------------------
/src/icons/git-lfs.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c1402ee6190453d991f52c64daacd48a48304099a8984d533f8e9f5a1c928072
3 | size 1726
4 |
--------------------------------------------------------------------------------
/src/icons/mighost.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:1fbe4c7d754a44aebd887a8e41201cba2736b9b79480011b7e2d5c4ea550d39b
3 | size 2653
4 |
--------------------------------------------------------------------------------
/src/icons/pip.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a1895f0b6f4cbaf08690f383ae774248f30e2cc54a59b41ec9b373acd28b662e
3 | size 2086
4 |
--------------------------------------------------------------------------------
/src/icons/powershell.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0ac9b77f01b621c8b08d9ffd7752ccfa4bc63be0ddf71402dd1f1d7f0531de7b
3 | size 1165
4 |
--------------------------------------------------------------------------------
/src/icons/pycharm64.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c625bcb75a9ba586f50723710cbe49595d400361de5a70b3b6c384782d7e7f37
3 | size 1352
4 |
--------------------------------------------------------------------------------
/src/icons/python.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:2c452a74b74929c2a7f739ef355e17e019a95015c367e4eeecb2acc8e71c7ee6
3 | size 1098
4 |
--------------------------------------------------------------------------------
/src/icons/rundll32.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:fb10ecae1f293b21c93f3341b474f15903b9bc3a8bd0e3d5dadaca2f15b36a3c
3 | size 1172
4 |
--------------------------------------------------------------------------------
/src/icons/spoolsv.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:d59294693ca4816eeb4868159db8d3c40ba5ef5a43637205d59f78a78e311ec6
3 | size 1973
4 |
--------------------------------------------------------------------------------
/src/icons/wermgr.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:ab9b4a156c911d14e4722419a55ebebc075eeb342e42941fc1d8aecf0ab6495a
3 | size 2694
4 |
--------------------------------------------------------------------------------
/src/icons/winlogon.exe.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:8a19f7c979a4f9692534c9990ba9448a7427ad0667e1de0456c4a993d5342563
3 | size 995
4 |
--------------------------------------------------------------------------------
/src/killer_config.json:
--------------------------------------------------------------------------------
1 | {"zoom": "0.5x", "dark": false, "desc": false, "order_by": "proc_pid", "refresh_interval": 1.0}
--------------------------------------------------------------------------------
/src/main.kv:
--------------------------------------------------------------------------------
1 | #:import hex_from_color kivy.utils.get_hex_from_color
2 | #:import web_open webbrowser.open
3 | #:import Thread threading.Thread
4 | :
5 |
6 | IconLeftWidget:
7 | icon: root.icon
8 |
9 |
10 | :
11 |
12 | MDBoxLayout:
13 | orientation: 'vertical'
14 | spacing: dp(10)
15 | padding: dp(20)
16 |
17 | MDBoxLayout:
18 | adaptive_height: True
19 | MDIconButton:
20 | icon: 'magnify'
21 | MDTextField:
22 | id: search_field
23 | hint_text: 'Search process'
24 | on_text: root.fast_answer(self.text.lower())
25 | MDIconButton:
26 | icon: 'menu'
27 | on_release: app.navigator.ids.nav_drawer.set_state("open")
28 |
29 | MDBoxLayout:
30 | adaptive_height: True
31 | MDCheckbox:
32 | id: multiple_select
33 | size_hint: None, None
34 | size: "48dp", "48dp"
35 | pos_hint: {'center_x': .5, 'center_y': .5}
36 | on_press: app.selection_lock.acquire()
37 | on_release:
38 | app.select_rows(self.active)
39 | app.selection_lock.release()
40 | selected_color: app.theme_cls.primary_color
41 | unselected_color: app.theme_cls.secondary_text_color
42 | AnchorLayout:
43 | anchor_x: "center"
44 | anchor_y: "center"
45 | size_hint: None, None
46 | size: 50, 50
47 | IButton:
48 | id: order
49 | size_hint: None, None
50 | size: 32, 32
51 | icon: "arrow-up" if app.desc else "arrow-down"
52 | color: app.theme_cls.opposite_bg_normal
53 | on_release: app.desc = not app.desc
54 | LButton:
55 | size_hint_x: None
56 | width: "48dp"
57 | text: "PID"
58 | order_name: 'proc_pid'
59 | LButton:
60 | text: "Process Name"
61 | order_name: 'proc_name'
62 | LButton:
63 | text: "CPU Usage"
64 | order_name: 'proc_cpu'
65 | LButton:
66 | text: "Memory Usage"
67 | order_name: 'proc_mem'
68 |
69 | RecycleView:
70 | id: rv
71 | viewclass: 'ProcessCell'
72 |
73 | RecycleBoxLayout:
74 | default_size: None, app.proc_height
75 | default_size_hint: 1, None
76 | size_hint_y: None
77 | height: self.minimum_height
78 | orientation: 'vertical'
79 |
80 | MDBoxLayout:
81 | adaptive_height: True
82 | MDIconButton:
83 | icon: "arrow-up-drop-circle-outline"
84 | on_release: rv.scroll_y = 1
85 | MDIconButton:
86 | icon: "arrow-down-drop-circle-outline"
87 | on_release: rv.scroll_y = 0
88 | MDLabel:
89 | id: selection_label
90 | shorten: True
91 | shorten_from: "left"
92 | color: app.theme_cls.opposite_bg_normal
93 | MDBoxLayout:
94 | adaptive_width: True
95 | spacing: "10dp"
96 | MDRaisedButton:
97 | text: "KILL"
98 | on_release: Thread(target=app.kill_selected).start()
99 | MDRaisedButton:
100 | text: "THE CHILDREN TOO"
101 | on_release: Thread(target=app.kill_selected_and_children).start()
102 |
103 |
104 | adaptive_height: True
105 | RVCheckBox:
106 | size_hint: None, None
107 | size: "48dp", app.proc_height
108 | pos_hint: {'center_x': .5, 'center_y': .5}
109 | active: root.proc_pid in app.current_selection
110 | on_active: app.select_row(root.proc_pid, self.active, self)
111 | selected_color: app.theme_cls.primary_color
112 | unselected_color: app.theme_cls.secondary_text_color
113 | AnchorLayout:
114 | anchor_x: "center"
115 | anchor_y: "center"
116 | size_hint: None, None
117 | size: 50, app.proc_height
118 | MDIcon:
119 | size_hint: None, None
120 | size: 32, 32
121 | icon: root.proc_icon
122 | MDLabel:
123 | size_hint_x: None
124 | width: "48dp"
125 | text: str(root.proc_pid)
126 | color: app.theme_cls.opposite_bg_normal
127 | font_style: app.proc_style
128 | MDLabel:
129 | text: root.proc_name
130 | color: app.theme_cls.opposite_bg_normal
131 | font_style: app.proc_style
132 | MDLabel:
133 | text: "{:.4f}%".format(root.proc_cpu)
134 | halign: "center"
135 | color: app.theme_cls.opposite_bg_normal
136 | font_style: app.proc_style
137 | MDLabel:
138 | text: "{:.4f}%".format(root.proc_mem)
139 | halign: "center"
140 | color: app.theme_cls.opposite_bg_normal
141 | font_style: app.proc_style
142 |
143 |
144 | order_name: 'proc_pid'
145 | ordered: self.order_name == app.order_by
146 | canvas.before:
147 | Color:
148 | rgba: app.theme_cls.primary_color if self.ordered else (0, 0, 0, 0)
149 | Rectangle:
150 | pos: self.pos
151 | size: self.size
152 | on_release:
153 | app.desc = not app.desc if self.ordered else app.desc
154 | app.order_by = self.order_name
155 | halign: "center"
156 | color: (1, 1, 1, 1) if self.ordered else app.theme_cls.opposite_bg_normal
157 |
158 |
159 | color: app.theme_cls.opposite_bg_normal
160 |
161 |
162 | text: self.proc_name
163 | on_size:
164 | self.ids._right_container.width = container.width
165 | self.ids._right_container.x = container.width
166 | IconLeftWidget:
167 | size_hint: None, None
168 | size: 32, 32
169 | icon: root.proc_icon
170 | Container:
171 | id: container
172 | MDLabel:
173 | text: root.proc_pid
174 | halign: "center"
175 | color: app.theme_cls.opposite_bg_normal
176 | MDLabel:
177 | text: root.proc_user
178 | halign: "center"
179 | font_size: self.font_size if root.little_font is None else root.little_font
180 | color: app.theme_cls.opposite_bg_normal
181 |
182 |
183 |
184 |
185 | ScreenManager
186 | id: sm
187 | MDNavigationDrawer:
188 | id: nav_drawer
189 | anchor: 'right'
190 | on_state:
191 | app.check_for_updates(self.state)
192 |
193 | MDBoxLayout:
194 | orientation: 'vertical'
195 | MDBoxLayout:
196 | adaptive_height: True
197 | MDLabel:
198 | text: 'Killer' if app.version is None else f'Killer {app.version}'
199 | halign: 'center'
200 | font_style: 'H6'
201 | color: app.theme_cls.opposite_bg_normal
202 |
203 | ScrollView:
204 | scroll_type: ['bars']
205 | bar_width: dp(10)
206 | MDBoxLayout:
207 | orientation: "vertical"
208 | adaptive_height: True
209 | padding: dp(20), 0, 0, 0
210 | MDBoxLayout:
211 | adaptive_height: True
212 | MDLabel:
213 | text: 'Zoom'
214 | color: app.theme_cls.opposite_bg_normal
215 | CheckZoom:
216 | zid: '0.5x'
217 | MDLabel:
218 | text: '0.5x'
219 | color: app.theme_cls.opposite_bg_normal
220 | CheckZoom:
221 | zid: '1x'
222 | MDLabel:
223 | text: '1x'
224 | color: app.theme_cls.opposite_bg_normal
225 | MDBoxLayout:
226 | adaptive_height: True
227 | padding: 0, 0, dp(20), 0
228 | MDLabel:
229 | text: 'Refresh interval' if refresh_interval.text else f'Refresh interval ({app.refresh_interval})'
230 | color: app.theme_cls.opposite_bg_normal
231 | RefreshInput:
232 | id: refresh_interval
233 | text: f'{app.refresh_interval}'
234 | size_hint_x: None
235 | width: self.minimum_width if self.text else dp(10)
236 | on_text: app.refresh_interval = self.text if self.text else app.refresh_interval
237 |
238 | MDBoxLayout:
239 | adaptive_height: True
240 | MDLabel:
241 | href: 'https://github.com/ntaraujo/killer'
242 | a: 'Killer {}'.format(app.update)
243 | a_color: hex_from_color(app.theme_cls.primary_color)
244 | main_text: '[ref={}][color={}]{}[/color][/ref] available!'.format(self.href, self.a_color, self.a)
245 | text: '' if app.update is None else self.main_text
246 | markup: True
247 | halign: 'center'
248 | color: app.theme_cls.opposite_bg_normal
249 | on_ref_press: web_open(self.href)
250 | MDIconButton:
251 | icon: 'white-balance-sunny' if app.dark else 'moon-waxing-crescent'
252 | on_release: app.dark = not app.dark
253 |
254 | :
255 | zid: '0.5x'
256 | group: 'zoom'
257 | size_hint: None, None
258 | size: dp(48), dp(48)
259 | on_active: app.zoom = self.zid
260 | on_release: self.active = app.zoom == self.zid
261 | active: app.zoom == self.zid
262 | selected_color: app.theme_cls.primary_color
263 | unselected_color: app.theme_cls.secondary_text_color
--------------------------------------------------------------------------------
/src/main.py:
--------------------------------------------------------------------------------
1 | from psutil import process_iter, NoSuchProcess, cpu_count, AccessDenied
2 | from kivymd.app import MDApp
3 | from kivy.uix.screenmanager import Screen
4 | from kivy.lang import Builder
5 | from os.path import join as p_join
6 | from kivy.clock import mainthread
7 | from time import sleep
8 | from threading import Thread, Lock
9 | from kivy.metrics import dp
10 | from utils import icon_path, this_dir # noqa
11 | from widgets import MiniProcessCell, Navigator # noqa
12 | from kivy.config import Config
13 |
14 | Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
15 | del Config
16 |
17 | Builder.load_file(p_join(this_dir, 'main.kv'))
18 | del Builder
19 |
20 | cpus = cpu_count()
21 | del cpu_count
22 |
23 | processes = {proc.pid: proc for proc in process_iter(['name', 'exe'])}
24 | processes_lock = Lock()
25 |
26 |
27 | def update_processes():
28 | global processes
29 | temp_processes = {}
30 |
31 | processes_lock.acquire()
32 |
33 | for proc in process_iter(['name', 'exe']):
34 | pid = proc.pid
35 | temp_processes[pid] = proc
36 |
37 | proc_now = processes.get(pid)
38 |
39 | if (proc_now is None) or (proc.info['name'] != proc_now.info['name']):
40 | processes[pid] = proc
41 |
42 | update_label = False
43 | for pid in [*processes]:
44 | if pid not in temp_processes:
45 | app.select_row(pid, False, label=False)
46 | if pid in app.current_selection:
47 | app.current_selection.remove(pid)
48 | del processes[pid]
49 | update_label = True
50 |
51 | if update_label:
52 | app.update_selection_label()
53 |
54 | processes_lock.release()
55 |
56 |
57 | def always_updating_processes():
58 | while True:
59 | update_processes()
60 | sleep(1)
61 |
62 |
63 | class Main(Screen):
64 |
65 | def __init__(self, **kw):
66 | self.data_lock = Lock()
67 | self.scroll_lock = Lock()
68 | self.answer_lock = Lock()
69 | self.answered = self.ordered = False
70 | self.visible_range = range(0)
71 | self.special_order_cells = []
72 | self.order_cells = []
73 | self.answerers = []
74 | self.last_search = None
75 |
76 | self.order_by = order_by = Killer.killer_config["order_by"]
77 | if order_by == "proc_name":
78 | self.key_func = lambda c: c["proc_name"].lower()
79 | else:
80 | self.key_func = lambda c: c[order_by] # overwrote
81 | self.reverse = Killer.killer_config["desc"]
82 | super().__init__(**kw)
83 |
84 | def on_scroll_start(instance, event):
85 | if not self.scroll_lock.locked():
86 | if event.is_mouse_scrolling:
87 | pos = instance.scroll_y
88 | if pos >= 1 or pos <= 0:
89 | return
90 | Thread(target=self.scroll_lock.acquire, daemon=True).start()
91 |
92 | def on_scroll_stop(*args): # noqa
93 | if self.scroll_lock.locked():
94 | Thread(target=self.scroll_lock.release).start()
95 |
96 | def on_touch_up(*args): # noqa
97 | if self.scroll_lock.locked():
98 | Thread(target=self.scroll_lock.release).start()
99 |
100 | self.ids.rv.bind(on_scroll_start=on_scroll_start,
101 | on_scroll_stop=on_scroll_stop,
102 | on_touch_up=on_touch_up
103 | )
104 |
105 | @mainthread
106 | def assign_data(self, data):
107 | self.ids.rv.data = data
108 |
109 | @mainthread
110 | def set_multiple_select(self, active):
111 | self.ids.multiple_select.active = active
112 |
113 | def new_special_order_cell(self, proc, proc_pid, proc_name, cpu, mem):
114 | proc_cpu = proc_mem = 0.0
115 |
116 | proc_exe = proc.info['exe']
117 | proc_icon = icon_path(proc_exe, proc_name)
118 |
119 | try:
120 | if cpu:
121 | proc_cpu = proc.cpu_percent(app.refresh_interval) / cpus
122 | if mem:
123 | proc_mem = proc.memory_percent()
124 | except NoSuchProcess:
125 | pass
126 |
127 | cell = {"proc_pid": proc_pid,
128 | "proc_icon": proc_icon,
129 | "proc_name": proc_name,
130 | "proc_cpu": proc_cpu,
131 | "proc_mem": proc_mem}
132 |
133 | self.special_order_cells.append(cell)
134 |
135 | def correct_special_order_cell(self, index, cpu, mem):
136 | cell = self.special_order_cells[index]
137 | proc_pid = cell['proc_pid']
138 | proc = processes[proc_pid]
139 | try:
140 | if cpu:
141 | cell["proc_cpu"] = proc.cpu_percent(app.refresh_interval) / cpus
142 | if mem:
143 | cell["proc_mem"] = proc.memory_percent()
144 | except NoSuchProcess:
145 | pass
146 |
147 | def special_order_update_data(self):
148 | search = self.ids.search_field.text.lower()
149 |
150 | cpu = self.order_by == "proc_cpu"
151 | mem = self.order_by == "proc_mem"
152 |
153 | self.special_order_cells = []
154 | singles = []
155 | correct_singles = []
156 |
157 | processes_lock.acquire()
158 |
159 | for proc_pid, proc in processes.items():
160 |
161 | proc_name = proc.info['name']
162 |
163 | if (not search) or (search in f'{proc_pid}{proc_name.lower()}'):
164 | new_special_order_cell_thread = Thread(target=self.new_special_order_cell,
165 | args=(proc, proc_pid, proc_name, cpu, mem))
166 | new_special_order_cell_thread.start()
167 | singles.append(new_special_order_cell_thread)
168 |
169 | for single in singles:
170 | single.join()
171 |
172 | self.special_order_cells = sorted(self.special_order_cells, key=self.key_func, reverse=self.reverse)
173 | data_max = len(self.special_order_cells)
174 |
175 | for index in self.visible_range:
176 | if index >= data_max:
177 | break
178 | correct_special_order_cell_thread = \
179 | Thread(target=self.correct_special_order_cell, args=(index, not cpu, not mem))
180 | correct_special_order_cell_thread.start()
181 | correct_singles.append(correct_special_order_cell_thread)
182 | for single in correct_singles:
183 | single.join()
184 |
185 | processes_lock.release()
186 |
187 | self.update_data_base(self.special_order_cells)
188 |
189 | def correct_order_cell(self, index, cpu=True, mem=True):
190 | cell = self.order_cells[index]
191 | proc_pid = cell['proc_pid']
192 | proc = processes[proc_pid]
193 | try:
194 | with proc.oneshot():
195 | if cpu:
196 | cell["proc_cpu"] = proc.cpu_percent(app.refresh_interval) / cpus
197 | if mem:
198 | cell["proc_mem"] = proc.memory_percent()
199 | except NoSuchProcess:
200 | pass
201 |
202 | def order_update_data(self):
203 | search = self.ids.search_field.text.lower()
204 |
205 | self.order_cells = []
206 | correct_singles = []
207 |
208 | processes_lock.acquire()
209 |
210 | for proc_pid, proc in processes.items():
211 | proc_name = proc.info["name"]
212 |
213 | if (not search) or (search in f'{proc_pid}{proc_name.lower()}'):
214 | proc_exe = proc.info["exe"]
215 | proc_icon = icon_path(proc_exe, proc_name)
216 |
217 | cell = {"proc_pid": proc_pid,
218 | "proc_icon": proc_icon,
219 | "proc_name": proc_name,
220 | "proc_cpu": 0.0,
221 | "proc_mem": 0.0}
222 |
223 | self.order_cells.append(cell)
224 |
225 | self.order_cells = sorted(self.order_cells, key=self.key_func, reverse=self.reverse)
226 | data_max = len(self.order_cells)
227 |
228 | if self.last_search is not None and len(self.ids.search_field.text) < len(self.last_search):
229 | self.update_data_base(self.order_cells)
230 | self.last_search = None
231 |
232 | for index in self.visible_range:
233 | if index >= data_max:
234 | break
235 | correct_order_cell_thread = Thread(target=self.correct_order_cell, args=(index, True, True))
236 | correct_order_cell_thread.start()
237 | correct_singles.append(correct_order_cell_thread)
238 | for single in correct_singles:
239 | single.join()
240 |
241 | processes_lock.release()
242 |
243 | self.update_data_base(self.order_cells)
244 |
245 | def first_update_data(self):
246 | order_cells = []
247 |
248 | processes_lock.acquire()
249 | for proc_pid, proc in processes.items():
250 | proc_name = proc.info["name"]
251 | proc_exe = proc.info["exe"]
252 | proc_icon = icon_path(proc_exe, proc_name)
253 |
254 | cell = {"proc_pid": proc_pid,
255 | "proc_icon": proc_icon,
256 | "proc_name": proc_name,
257 | "proc_cpu": 0.0,
258 | "proc_mem": 0.0}
259 |
260 | order_cells.append(cell)
261 | processes_lock.release()
262 |
263 | order_cells = sorted(order_cells, key=self.key_func, reverse=self.reverse)
264 |
265 | self.assign_data(order_cells)
266 |
267 | def update_data_base(self, new_data):
268 | if not self.answer_lock.locked():
269 | if not self.answered and not self.ordered:
270 | with self.data_lock:
271 | with self.scroll_lock:
272 | self.assign_data(new_data)
273 | else:
274 | self.answered = self.ordered = False
275 |
276 | def always_updating_data(self):
277 | while True:
278 | if self.order_by in {"proc_cpu", "proc_mem"}:
279 | self.special_order_update_data()
280 | else:
281 | self.order_update_data()
282 |
283 | def order(self, order_by, reverse):
284 | if order_by == "proc_name":
285 | self.key_func = lambda c: c["proc_name"].lower()
286 | else:
287 | self.key_func = lambda c: c[order_by]
288 | self.reverse = reverse
289 | self.order_by = order_by
290 |
291 | self.ordered = True
292 | with self.data_lock:
293 | temp_data = sorted(self.ids.rv.data, key=self.key_func, reverse=reverse)
294 | self.assign_data(temp_data)
295 | self.ordered = True
296 |
297 | def set_visible_range(self):
298 | rv = self.ids.rv
299 | to_local = rv.to_local
300 | center_x = rv.center_x
301 | get_view_index_at = rv.layout_manager.get_view_index_at
302 | try:
303 | top_pos = to_local(center_x, rv.height)
304 | top_i = get_view_index_at(top_pos)
305 | bottom_pos = to_local(center_x, 0)
306 | bottom_i = get_view_index_at(bottom_pos)
307 | self.visible_range = range(top_i, bottom_i + 1)
308 | except TypeError:
309 | pass # random kivy error
310 |
311 | def always_setting_visible_range(self):
312 | while True:
313 | self.set_visible_range()
314 | sleep(0.1)
315 |
316 | def fast_answer(self, search):
317 | if search == "":
318 | return
319 | if not self.answer_lock.locked():
320 | from threading import Event
321 | start_event = Event()
322 | Thread(target=self.answerers_control, args=(start_event,)).start()
323 | else:
324 | start_event = None
325 | fast_thread = Thread(target=self.fast_answer_base, args=(search,))
326 | fast_thread.start()
327 | self.answerers.append(fast_thread)
328 | if start_event is not None:
329 | start_event.set()
330 |
331 | def answerers_control(self, start_event):
332 | self.answer_lock.acquire()
333 | start_event.wait()
334 | while self.answerers:
335 | fast_thread = self.answerers.pop(0)
336 | fast_thread.join()
337 | self.answered = True
338 | self.answer_lock.release()
339 |
340 | def fast_answer_base(self, search):
341 | temp_data = []
342 | for cell in self.ids.rv.data:
343 | if search in f'{cell["proc_pid"]}{cell["proc_name"].lower()}':
344 | temp_data.append(cell)
345 | self.assign_data(temp_data)
346 | self.last_search = search
347 |
348 |
349 | class Killer(MDApp):
350 | from kivy.properties import StringProperty, ListProperty, NumericProperty, BooleanProperty
351 |
352 | version = StringProperty(None, allownone=True)
353 | update = StringProperty(None, allownone=True)
354 | current_selection = ListProperty()
355 |
356 | from json import load
357 | killer_config_file = p_join(this_dir, 'killer_config.json')
358 | with open(killer_config_file, "r") as killer_read_file:
359 | killer_config = load(killer_read_file)
360 | del killer_read_file, load
361 |
362 | zooms = {'0.5x': (32, 'Body2'),
363 | '1x': (dp(48), 'Body1')}
364 |
365 | z = killer_config['zoom']
366 | zoom = StringProperty(z)
367 | proc_height = NumericProperty(zooms[z][0])
368 | proc_style = StringProperty(zooms[z][1])
369 | del z
370 |
371 | dark = BooleanProperty(killer_config['dark'])
372 |
373 | desc = BooleanProperty(killer_config['desc'])
374 |
375 | order_by = StringProperty(killer_config['order_by'])
376 |
377 | refresh_interval = NumericProperty(killer_config['refresh_interval'])
378 |
379 | del StringProperty, ListProperty, NumericProperty, BooleanProperty
380 |
381 | @staticmethod
382 | def on_zoom(self, value):
383 | self.proc_height, self.proc_style = self.zooms[value]
384 | Thread(target=self.update_config, args=('zoom', value)).start()
385 |
386 | @staticmethod
387 | def on_dark(self, value):
388 | self.theme_cls.theme_style = "Dark" if value else "Light"
389 | Thread(target=self.update_config, args=('dark', value)).start()
390 |
391 | @staticmethod
392 | def on_desc(self, value):
393 | Thread(target=self.main.order, args=(self.order_by, value)).start()
394 | Thread(target=self.update_config, args=('desc', value)).start()
395 |
396 | @staticmethod
397 | def on_order_by(self, value):
398 | Thread(target=self.main.order, args=(value, self.desc)).start()
399 | Thread(target=self.update_config, args=('order_by', value)).start()
400 |
401 | @staticmethod
402 | def on_refresh_interval(self, value):
403 | Thread(target=self.update_config, args=('refresh_interval', value)).start()
404 |
405 | def __init__(self, **kwargs):
406 | self.icon = p_join(this_dir, 'icons\\Killer.exe.png')
407 | super().__init__(**kwargs)
408 | self.selection_lock = Lock()
409 | # List[List[Union[str, bool, Set[str], Set[str]]]]
410 | self.selection_control = []
411 |
412 | self.navigator = Navigator()
413 | self.main = Main()
414 | self.navigator.ids.sm.add_widget(self.main)
415 | self.theme_cls.theme_style = "Dark" if self.dark else "Light"
416 |
417 | def update_config(self, key, value):
418 | from json import dump
419 | self.killer_config[key] = value
420 | with open(self.killer_config_file, "w") as write_file:
421 | dump(self.killer_config, write_file)
422 |
423 | def build(self):
424 | return self.navigator
425 |
426 | def on_start(self):
427 | self.main.first_update_data()
428 | from kivy.clock import Clock
429 | Clock.schedule_once(self.search_focus)
430 | Thread(target=self.main.always_updating_data, daemon=True).start()
431 | Thread(target=self.main.always_setting_visible_range, daemon=True).start()
432 | Thread(target=always_updating_processes, daemon=True).start()
433 | Thread(target=self.always_selecting, daemon=True).start()
434 |
435 | def check_for_updates(self, state):
436 | if state == "open":
437 | Thread(target=self.check_for_updates_base).start()
438 |
439 | def check_for_updates_base(self):
440 | if self.version is None:
441 | from utils import proc_version_tag, this_pid # noqa
442 | self.version = proc_version_tag(processes[this_pid])
443 | if self.version is not None:
444 | from utils import update_to # noqa
445 | self.update = update_to(self.version, 'ntaraujo', 'killer')
446 |
447 | def search_focus(*args):
448 | args[0].main.ids.search_field.focus = True
449 |
450 | def always_selecting(self):
451 | while True:
452 | if len(self.main.ids.rv.data) == 0:
453 | self.main.set_multiple_select(False)
454 | sleep(1)
455 | continue
456 |
457 | state = True
458 | self.selection_lock.acquire()
459 | self.main.data_lock.acquire()
460 | for cell in self.main.ids.rv.data:
461 | if cell["proc_pid"] not in self.current_selection:
462 | state = False
463 | break
464 | self.main.data_lock.release()
465 | self.main.set_multiple_select(state)
466 | self.selection_lock.release()
467 | sleep(1)
468 |
469 | def update_selection_label(self):
470 | selection_strings = []
471 | lonely_ones = []
472 | searches = []
473 | exceptions = []
474 | # _search: what was the search when general checkbox was clicked, or empty if it wasn't clicked
475 | # _check: if general checkbox was clicked
476 | # _added: related PIDs
477 | # _removed: related PIDs but unmarked
478 | for _search, _check, _added, _removed in self.selection_control:
479 | if _check:
480 | searches.append(_search)
481 | for pid in _removed:
482 | if pid not in exceptions:
483 | exceptions.append(pid)
484 | else:
485 | for one_pid in _added:
486 | lonely_ones.append(one_pid)
487 |
488 | lonely_ones_amount = len(lonely_ones)
489 | if lonely_ones_amount:
490 | lonely_ones = sorted(lonely_ones)
491 | last_lonely = lonely_ones[-1]
492 | if lonely_ones_amount == 1:
493 | selection_strings.append(f'process {last_lonely}')
494 | else:
495 | lonely_string = "processes " + ', '.join([str(lo) for lo in lonely_ones])
496 | lonely_string = lonely_string.replace(f', {last_lonely}', f' and {last_lonely}')
497 | selection_strings.append(lonely_string)
498 |
499 | searches_amount = len(searches)
500 | if searches_amount:
501 | searches = sorted(searches)
502 | last_search = searches[-1]
503 | if searches_amount == 1:
504 | if last_search == "":
505 | selection_strings.append("all")
506 | else:
507 | selection_strings.append(f'all with "{last_search}"')
508 | else:
509 | search_string = 'all with "{}"'.format('" or "'.join(searches))
510 | selection_strings.append(search_string)
511 |
512 | exceptions_amount = len(exceptions)
513 | if exceptions_amount:
514 | exceptions = sorted(exceptions)
515 | last_exception = exceptions[-1]
516 | if exceptions_amount == 1:
517 | selection_strings.append(f"except process {last_exception}")
518 | else:
519 | exception_string = 'except processes ' + ', '.join([str(ex) for ex in exceptions])
520 | exception_string = exception_string.replace(f', {last_exception}', f' and {last_exception}')
521 | selection_strings.append(exception_string)
522 |
523 | if selection_strings:
524 | self.main.ids.selection_label.text = f'Selected: {"; ".join(selection_strings)} '
525 | else:
526 | self.main.ids.selection_label.text = ''
527 |
528 | def select_row(self, pid, active, instance=None, label=True):
529 | if active and pid not in self.current_selection:
530 | self.current_selection.append(pid)
531 |
532 | changed = False
533 | for _search, _check, _added, _removed in self.selection_control:
534 | if pid in _removed:
535 | # pid was related to a search before and was unmarked, now its being remarked
536 | _removed.remove(pid)
537 | changed = True
538 | if not changed: # pid was not related to a previous search
539 | self.selection_control.append(["", False, {pid}, set()]) # _search is "" bcs doesn't matter
540 | elif not active and pid in self.current_selection:
541 | self.current_selection.remove(pid)
542 |
543 | for _search, _check, _added, _removed in [*self.selection_control]:
544 | if pid in _added:
545 | _removed.add(pid)
546 | if not _added - _removed:
547 | # all related PIDs were unmarked, doesn't matter _check
548 | # the set _removed is still linked bcs there wasn't a deepcopy, so:
549 | self.selection_control.remove([_search, _check, _added, _removed])
550 | else:
551 | return
552 |
553 | if instance is not None:
554 | instance.check_anim_in.cancel(instance)
555 | instance.check_anim_out.start(instance)
556 |
557 | if label:
558 | self.update_selection_label()
559 |
560 | def select_rows(self, active):
561 | if active:
562 | pids = set()
563 | self.main.data_lock.acquire()
564 | for cell in self.main.ids.rv.data:
565 | pid = cell['proc_pid']
566 | if pid not in self.current_selection:
567 | self.current_selection.append(pid)
568 | pids.add(pid)
569 | self.main.data_lock.release()
570 |
571 | search = self.main.ids.search_field.text.lower()
572 | need_to_add = True
573 | for _search, _check, _added, _removed in [*self.selection_control]:
574 | # selected all
575 | # selected a group which includes all _added bcs _search was more specific or as specific as
576 | surely_include_all = not search or (_check and search in _search)
577 | # selected a pid lonely selected before
578 | iter_include_all = surely_include_all or (not _check and not _added.difference(pids))
579 | if iter_include_all:
580 | self.selection_control.remove([_search, _check, _added, _removed])
581 | elif _removed:
582 | # if there was exceptions
583 | for pid in pids:
584 | if pid in _removed:
585 | # if a marked pid was in these exceptions
586 | _removed.remove(pid)
587 | if _check and _search in search and not iter_include_all:
588 | # if a previous search was less specific than, or as specific as now,
589 | # and was not removed, it includes all PIDs and there is no need to be redundant
590 | need_to_add = False
591 | if need_to_add:
592 | self.selection_control.append([search, True, pids, set()])
593 | else:
594 | self.current_selection = []
595 | self.selection_control = []
596 | self.update_selection_label()
597 |
598 | def kill_selected(self):
599 | from utils import kill # noqa
600 | fails = []
601 | with processes_lock:
602 | for pid in self.current_selection:
603 | proc = processes[pid]
604 | if not kill(proc):
605 | fails.append(proc)
606 | self.show_fails(fails)
607 |
608 | def kill_selected_and_children(self):
609 | from utils import kill_proc_tree # noqa
610 | fails = []
611 | with processes_lock:
612 | for pid in self.current_selection:
613 | fails.extend(kill_proc_tree(processes[pid]))
614 | self.show_fails(fails)
615 |
616 | @mainthread
617 | def show_fails(self, fails):
618 | if len(fails) == 0:
619 | return
620 |
621 | items = []
622 |
623 | cell = MiniProcessCell()
624 | cell.proc_name = "Process Name"
625 | cell.proc_pid = "PID"
626 | cell.proc_user = "Owner"
627 | items.append(cell)
628 |
629 | for proc in fails:
630 | cell = MiniProcessCell()
631 | cell.proc_name = proc.info["name"]
632 | cell.proc_icon = icon_path('', cell.proc_name)
633 | cell.proc_pid = str(proc.pid)
634 | cell.little_font = dp(10)
635 | try:
636 | cell.proc_user = proc.username()
637 | except AccessDenied:
638 | pass
639 | except NoSuchProcess:
640 | continue
641 | finally:
642 | items.append(cell)
643 |
644 | leni = len(items)
645 | if leni == 1:
646 | return
647 |
648 | if leni > 2:
649 | title = "Was not possible to kill the following processes:"
650 | else:
651 | title = "Was not possible to kill the following process:"
652 |
653 | from kivymd.uix.dialog import MDDialog
654 | from kivymd.uix.button import MDRaisedButton
655 |
656 | fails_dialog = MDDialog(
657 | title=title,
658 | items=items,
659 | type="simple",
660 | buttons=[MDRaisedButton(text="OK")]
661 | )
662 |
663 | fails_dialog.ids.title.color = self.theme_cls.opposite_bg_normal
664 |
665 | fails_dialog.open()
666 |
667 |
668 | app = Killer()
669 | if __name__ == '__main__':
670 | app.run()
671 |
--------------------------------------------------------------------------------
/src/utils.py:
--------------------------------------------------------------------------------
1 | from psutil import NoSuchProcess, AccessDenied, Process
2 | from win32con import SM_CXICON
3 | from win32api import GetSystemMetrics, GetFileVersionInfo, LOWORD, HIWORD
4 | from os.path import dirname, abspath
5 | from os.path import join as p_join
6 | from os.path import exists as p_exists
7 | from win32ui import CreateDCFromHandle, CreateBitmap
8 | from win32gui import ExtractIconEx, DestroyIcon, GetDC # noqa
9 | from PIL import Image
10 | from os import PathLike, getpid
11 | from typing import Union, Dict, Callable, Any, List, Optional, Collection, Tuple
12 | from pywintypes import error # noqa
13 | from bisect import bisect_left, bisect_right
14 | import sys
15 | from time import perf_counter
16 |
17 | this_pid = getpid()
18 | del getpid
19 |
20 | path_type = Union[str, bytes, PathLike]
21 | del PathLike
22 |
23 | this_dir: path_type = getattr(sys, '_MEIPASS', abspath(dirname(__file__)))
24 | del sys, abspath, dirname
25 |
26 | default_icon_path: path_type = p_join(this_dir, 'icons', 'default.png')
27 |
28 | funcs_results: Dict[Callable[[Any], Any], float] = {}
29 | custom_results: Dict[str, List[Optional[Union[float, int]]]] = {}
30 | del Dict, List
31 |
32 |
33 | def get_version_number(exe: path_type) -> Optional[Tuple[int, int, int, int]]:
34 | try:
35 | info = GetFileVersionInfo(exe, "\\")
36 | ms = info['FileVersionMS']
37 | ls = info['FileVersionLS']
38 | return HIWORD(ms), LOWORD(ms), HIWORD(ls), LOWORD(ls)
39 | except error:
40 | return
41 |
42 |
43 | def github_version_tag(xyz: Tuple[int, int, int, int]):
44 | versioned = []
45 | zero_removed = False
46 | for component in xyz[::-1]:
47 | if component >= 1 or zero_removed:
48 | zero_removed = True
49 | versioned.insert(0, str(component))
50 | return 'v' + '.'.join(versioned)
51 |
52 |
53 | def proc_version_tag(proc: Process):
54 | exe = proc.__dict__.get("info", {}).get("exe", None)
55 | if exe is None:
56 | try:
57 | exe = proc.exe()
58 | except (NoSuchProcess, AccessDenied):
59 | return
60 | version = get_version_number(exe)
61 | if version is not None:
62 | return github_version_tag(version)
63 |
64 |
65 | def latest_version(user: str, repo: str) -> str:
66 | from requests import get as get_request
67 | return get_request(f"https://api.github.com/repos/{user}/{repo}/releases/latest").json()["tag_name"]
68 |
69 |
70 | def update_to(version: str, user: str, repo: str):
71 | latest = latest_version(user, repo)
72 | if latest > version:
73 | return latest
74 | else:
75 | return
76 |
77 |
78 | def icon_path(exe: path_type, name: str):
79 | id_file_name = f'{name}.png'
80 | id_path = p_join(this_dir, 'icons', id_file_name)
81 |
82 | if not p_exists(id_path):
83 |
84 | ico_x = GetSystemMetrics(SM_CXICON)
85 |
86 | try:
87 | large, small = ExtractIconEx(exe, 0)
88 | except error:
89 | return default_icon_path
90 |
91 | if not len(large):
92 | return default_icon_path
93 |
94 | if len(small):
95 | DestroyIcon(small[0])
96 |
97 | hdc = CreateDCFromHandle(GetDC(0))
98 | h_bmp = CreateBitmap()
99 | h_bmp.CreateCompatibleBitmap(hdc, ico_x, ico_x)
100 | hdc = hdc.CreateCompatibleDC()
101 |
102 | hdc.SelectObject(h_bmp)
103 | hdc.DrawIcon((0, 0), large[0])
104 |
105 | bmp_str = h_bmp.GetBitmapBits(True)
106 | img = Image.frombuffer(
107 | 'RGBA',
108 | (32, 32),
109 | bmp_str, 'raw', 'BGRA', 0, 1
110 | )
111 |
112 | img.save(id_path)
113 |
114 | print(f'Icon of {exe} saved in {id_path}')
115 |
116 | return id_path
117 |
118 |
119 | def is_responding(pid: int):
120 | from subprocess import Popen, PIPE
121 |
122 | cmd = f'tasklist /FI "PID eq {pid}" /FI "STATUS eq running"'
123 | status = Popen(cmd, stdout=PIPE).stdout.read()
124 | return str(pid) in str(status)
125 |
126 |
127 | def keyring_bisect_left(seq: Collection, item, key_func: Callable[[Any], Any], reverse=False):
128 | k = key_func(item)
129 | keys = [key_func(e) for e in seq]
130 | return ordering_bisect_left(keys, k, reverse)
131 |
132 |
133 | def ordering_bisect_left(seq: Collection, e, reverse: bool, lo: Optional[int] = None, hi: Optional[int] = None):
134 | """Find first index, starting from left, to insert e."""
135 | lo = lo or 0
136 | hi = hi or len(seq)
137 | if reverse:
138 | return len(seq) - bisect_right(seq[::-1], e, lo, hi)
139 | else:
140 | return bisect_left(seq, e, lo, hi)
141 |
142 |
143 | def kill_proc_tree(parent: Process, include_parent=True):
144 | fails = []
145 | try:
146 | children = parent.children(recursive=True)
147 | except NoSuchProcess:
148 | children = []
149 | if include_parent:
150 | children.append(parent)
151 | for p in children:
152 | if not kill(p):
153 | fails.append(p)
154 | return fails
155 |
156 |
157 | def kill(proc: Process):
158 | if proc.pid != this_pid:
159 | try:
160 | proc.kill()
161 | return True
162 | except NoSuchProcess:
163 | return True
164 | except AccessDenied:
165 | return False
166 | else:
167 | return False
168 |
169 |
170 | def timer(function: Union[Callable[[Any], Any], str]) -> Optional[Callable[[Any], Any]]:
171 | if type(function) == str:
172 | if function in custom_results:
173 | tic = custom_results[function][0]
174 | toe = custom_results[function][1]
175 | if tic is None:
176 | custom_results[function][0] = perf_counter()
177 | elif toe is None:
178 | custom_results[function][1] = perf_counter() - tic
179 | else:
180 | custom_results[function][1] = (perf_counter() - tic + toe) / 2
181 | if tic is not None:
182 | custom_results[function][0] = None
183 | print(f'"{function}" is taking about {custom_results[function][1]:.5f} seconds.')
184 | else:
185 | custom_results[function] = [None, None]
186 | return
187 |
188 | def new_func(*args, **kwargs):
189 | _tic = perf_counter()
190 | to_return = function(*args, **kwargs)
191 | tac = perf_counter()
192 | if function in funcs_results:
193 | _toe = (tac - _tic + funcs_results[function]) / 2
194 | else:
195 | _toe = tac - _tic
196 | funcs_results[function] = _toe
197 | print(f'Function {function.__qualname__} is taking about {_toe:.5f} seconds.')
198 | return to_return
199 |
200 | return new_func
201 |
--------------------------------------------------------------------------------
/src/widgets.py:
--------------------------------------------------------------------------------
1 | from kivy.animation import Animation
2 | from kivy.base import EventLoop
3 | from kivy.graphics.vertex_instructions import Rectangle
4 | from kivy.properties import StringProperty, NumericProperty
5 | from kivy.uix.textinput import FL_IS_LINEBREAK # noqa
6 | from kivymd.uix.boxlayout import MDBoxLayout
7 | from kivymd.uix.list import OneLineAvatarIconListItem
8 | from kivymd.uix.navigationdrawer import NavigationLayout
9 | from kivymd.uix.selectioncontrol import MDCheckbox
10 | from kivymd.uix.textfield import MDTextField
11 |
12 |
13 | class MiniProcessCell(OneLineAvatarIconListItem):
14 | proc_pid = StringProperty()
15 | proc_icon = StringProperty()
16 | proc_name = StringProperty()
17 | proc_user = StringProperty("N/A")
18 | little_font = NumericProperty(None)
19 |
20 |
21 | class Navigator(NavigationLayout):
22 | pass
23 |
24 |
25 | class RVCheckBox(MDCheckbox):
26 | def on_state(self, instance, value):
27 | self.active = value == "down"
28 | self.update_icon()
29 |
30 |
31 | class ProcessCell(MDBoxLayout):
32 | proc_pid = NumericProperty()
33 | proc_icon = StringProperty()
34 | proc_name = StringProperty()
35 | proc_cpu = NumericProperty()
36 | proc_mem = NumericProperty()
37 |
38 |
39 | class MyTextInput(MDTextField):
40 | minimum_width = NumericProperty(0)
41 |
42 | def _split_smart(self, text):
43 | # modified to always split on newline only
44 | lines = text.split(u'\n')
45 | lines_flags = [0] + [FL_IS_LINEBREAK] * (len(lines) - 1)
46 | return lines, lines_flags
47 |
48 | def _refresh_text(self, text, *largs):
49 | # this is modified slightly just to calculate minimum_width
50 |
51 | # Refresh all the lines from a new text.
52 | # By using cache in internal functions, this method should be fast.
53 | if len(largs) > 1:
54 | mode, start, finish, _lines, _lines_flags, len_lines = largs
55 | # start = max(0, start)
56 | cursor = None
57 | else:
58 | mode = 'all'
59 | start = finish = _lines = _lines_flags = len_lines = None
60 | cursor = self.cursor_index()
61 | _lines, self._lines_flags = self._split_smart(text)
62 | _lines_labels = []
63 | _line_rects = []
64 | _create_label = self._create_line_label
65 |
66 | # calculate minimum width
67 | min_width = 0
68 | for x in _lines:
69 | lbl = _create_label(x)
70 | min_width = max(min_width, lbl.width)
71 | _lines_labels.append(lbl)
72 | _line_rects.append(Rectangle(size=lbl.size))
73 | self.minimum_width = min_width + self.padding[0] + self.padding[2]
74 |
75 | if mode == 'all':
76 | self._lines_labels = _lines_labels
77 | self._lines_rects = _line_rects
78 | self._lines = _lines
79 | elif mode == 'del':
80 | if finish > start:
81 | self._insert_lines(start,
82 | finish if start == finish else (finish + 1),
83 | len_lines, _lines_flags,
84 | _lines, _lines_labels, _line_rects)
85 | elif mode == 'insert':
86 | self._insert_lines(
87 | start,
88 | finish if (start == finish and not len_lines)
89 | else (finish + 1),
90 | len_lines, _lines_flags, _lines, _lines_labels,
91 | _line_rects)
92 |
93 | min_line_ht = self._label_cached.get_extents('_')[1]
94 | # with markup texture can be of height `1`
95 | self.line_height = max(_lines_labels[0].height, min_line_ht)
96 | # self.line_spacing = 2
97 | # now, if the text change, maybe the cursor is not at the same place as
98 | # before. so, try to set the cursor on the good place
99 | row = self.cursor_row
100 | self.cursor = self.get_cursor_from_index(self.cursor_index()
101 | if cursor is None else cursor)
102 | # if we back to a new line, reset the scroll, otherwise, the effect is
103 | # ugly
104 | if self.cursor_row != row:
105 | self.scroll_x = 0
106 | # with the new text don't forget to update graphics again
107 | self._trigger_update_graphics()
108 |
109 | def on_text(self, instance, value):
110 | # added to update minimum width on each change
111 | cc, cr = self.cursor
112 | text = self._lines[cr]
113 | lbl = self._create_line_label(text)
114 | self.minimum_width = max(self.minimum_width, lbl.width + self.padding[0] + self.padding[2])
115 |
116 | def on_error(self, *args):
117 | _current_hint_text_color = _current_line_color = self.error_color if args[1] else self.line_color_focus
118 |
119 | Animation(
120 | duration=0.2,
121 | _current_hint_text_color=_current_hint_text_color,
122 | _current_line_color=_current_line_color,
123 | ).start(self)
124 |
125 |
126 | class RefreshInput(MyTextInput):
127 | def insert_text(self, substring, from_undo=False):
128 | if self.readonly or not substring or not self._lines:
129 | return
130 |
131 | if isinstance(substring, bytes):
132 | substring = substring.decode('utf8')
133 |
134 | if self.replace_crlf:
135 | substring = substring.replace(u'\r\n', u'\n')
136 |
137 | self._hide_handles(EventLoop.window)
138 |
139 | if not from_undo and self.multiline and self.auto_indent \
140 | and substring == u'\n':
141 | substring = self._auto_indent(substring)
142 |
143 | cc, cr = self.cursor
144 | sci = self.cursor_index
145 | ci = sci()
146 | text = self._lines[cr]
147 | len_str = len(substring)
148 | new_text = text[:cc] + substring + text[cc:]
149 |
150 | if new_text:
151 | try:
152 | number = float(new_text)
153 | if not (0 <= number <= 10):
154 | return
155 | except ValueError:
156 | return
157 |
158 | self._set_line_text(cr, new_text)
159 |
160 | wrap = (self._get_text_width(
161 | new_text,
162 | self.tab_width,
163 | self._label_cached) > (self.width - self.padding[0] -
164 | self.padding[2]))
165 | if len_str > 1 or substring == u'\n' or wrap:
166 | # Avoid refreshing text on every keystroke.
167 | # Allows for faster typing of text when the amount of text in
168 | # TextInput gets large.
169 |
170 | start, finish, lines, lineflags, len_lines = \
171 | self._get_line_from_cursor(cr, new_text)
172 | # calling trigger here could lead to wrong cursor positioning
173 | # and repeating of text when keys are added rapidly in a automated
174 | # fashion. From Android Keyboard for example.
175 | self._refresh_text_from_property('insert', start, finish, lines,
176 | lineflags, len_lines)
177 |
178 | self.cursor = self.get_cursor_from_index(ci + len_str)
179 | # handle undo and redo
180 | self._set_unredo_insert(ci, ci + len_str, substring, from_undo)
181 |
--------------------------------------------------------------------------------