├── .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 |

License 4 | Contributions Welcome 5 | Github All Releases

6 |

killer-setup.exe 7 | Discord Server

8 | 9 | 10 | 11 | 12 | 13 |

That damn Chrome

14 | 15 |

Killer

16 | 17 |

⬇Download here · 👋 Join our discord

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 | ![Dark Mode](https://github.com/ntaraujo/killer/blob/main/data/samples/dark.PNG) 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 | ![Search and select all](https://github.com/ntaraujo/killer/blob/main/data/samples/search.PNG) 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 [![killer-setup.exe](https://img.shields.io/badge/-the%20newest%20release-blue)](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 | --------------------------------------------------------------------------------