[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: AdnanHodzic\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report-or-feature-request.md",
    "content": "---\nname: Bug report or feature request\nabout: Report an issue or request a feature request\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n### Fill out information requested in this template, without doing so issue will be ignored & closed!\n\n#### Before submitting an issue, it is strongly recommended to use the **[auto-cpufreq-genAI-chatbot](https://foolcontrol.org/?p=4903)** to get an immediate answer to your question.\n\n### Have you tried?\n\n- [Searching through existing/closed issues](https://github.com/AdnanHodzic/auto-cpufreq/issues) to see if your bug has already been already submitted? \n- If installation failed with [auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq/#auto-cpufreq-installer),have you tried installing auto-cpufreq using [Snap package](https://github.com/AdnanHodzic/auto-cpufreq/#snap-store)?\n- Have you tried [configuring auto-cpufreq](https://github.com/AdnanHodzic/auto-cpufreq/#configuring-auto-cpufreq)?\n- Have you tried suggestions in [troubleshooting section](https://github.com/AdnanHodzic/auto-cpufreq#troubleshooting)?\n\n### Error output:\n```text\nAdd/paste error output in case of failed installation or other failing component\n```\n---\n\n### System information:\n\nAdd/paste output of:\n\n```\nauto-cpufreq --debug\n```\n\nAlso please be descriptive about the issue you're reporting, i.e: what you tried & what's the expected behaviour.\n\n---\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false"
  },
  {
    "path": ".github/workflows/build-linux.yml",
    "content": "name: Linux Build\n\non:\n  push:\n    paths-ignore:\n      - \"README.md\"\n      - \".gitignore\"\n      - \"LICENSE\"\n  pull_request:\n\njobs:\n  build_linux:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4.1.1\n\n      - name: Install poetry\n        run: pipx install poetry\n\n      - name: \"Setup Python\"\n        uses: actions/setup-python@v5.0.0\n        with:\n          python-version: 3.12\n          cache: \"poetry\"\n\n      - name: \"Install auto-cpufreq\"\n        run: sudo ./auto-cpufreq-installer --install\n      \n"
  },
  {
    "path": ".github/workflows/build-nix.yaml",
    "content": "name: Nix Flake\n\non:\n  push:\n    paths-ignore:\n      - \"README.md\"\n      - \".gitignore\"\n      - \"LICENSE\"\n  pull_request:\n\njobs:\n  build-nix:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: \"Install Nix ❄️\"\n        uses: nixbuild/nix-quick-install-action@v30\n\n      - name: \"Nix Cache ❄️\"\n        uses: nix-community/cache-nix-action@v6\n        with:\n          primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}\n          restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}\n\n      - name: \"Build Nix Flake ❄️\"\n        run: nix build\n\n      \n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\nfiles.txt\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n.direnv\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# vim\n*.swp\n*.swo\n\n# snap\n*.snap\n\n# pycharm\n.idea/\n\n# nix build\n/result\n\n# vs code \n.vscode/\n"
  },
  {
    "path": "LICENSE",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "README.md",
    "content": "# auto-cpufreq\n[![Linux Build](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-linux.yml/badge.svg?event=push)](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-linux.yml)\n[![Nix Flake](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-nix.yaml/badge.svg?event=push)](https://github.com/AdnanHodzic/auto-cpufreq/actions/workflows/build-nix.yaml)\n\nAutomatic CPU speed & power optimizer for Linux. Actively monitors laptop battery state, CPU usage, CPU temperature, and system load, ultimately allowing you to improve battery life without making any compromises.\n\nFor tl;dr folks:\n\n* Youtube: \n[From 10 day vacation project to 100k users: auto-cpufreq v3 story](https://www.youtube.com/watch?v=VKqNjczvI88)\n* Blog post: [From 10 day vacation project to 100k users: auto-cpufreq v3 story](https://foolcontrol.org/?p=5114)\n\n[![](https://img.youtube.com/vi/VKqNjczvI88/0.jpg)](https://www.youtube.com/watch?v=VKqNjczvI88)\n\n* Youtube: [auto-cpufreq v2.0 release & demo of all available features and options](https://www.youtube.com/watch?v=SPGpkZ0AZVU)\n* Blog post: [auto-cpufreq v2.0](https://foolcontrol.org/?p=4603)\n\n[![](https://img.youtube.com/vi/SPGpkZ0AZVU/0.jpg)](https://www.youtube.com/watch?v=QkYRpVEEIlg)\n\n* Youtube: [auto-cpufreq - tool demo](https://www.youtube.com/watch?v=QkYRpVEEIlg)\n* Blog post: [auto-cpufreq – Automatic CPU speed & power optimizer for Linux](https://foolcontrol.org/?p=3124)\n\n[![](https://img.youtube.com/vi/QkYRpVEEIlg/0.jpg)](https://www.youtube.com/watch?v=QkYRpVEEIlg)\n\nIf you're having a problem with auto-cpufreq, before ([submitting an issue](https://github.com/AdnanHodzic/auto-cpufreq/issues)), it is strongly recommended to use the **[auto-cpufreq-genAI-chatbot](https://foolcontrol.org/?p=4903)** to get an immediate answer to your question.\n\n[![](https://img.youtube.com/vi/a-UcwAAXOoc/0.jpg)](https://www.youtube.com/watch?v=a-UcwAAXOoc)\n\nExample of auto-cpufreq GUI (available >= v2.0)\n\n<img src=\"https://github.com/user-attachments/assets/3e33bd92-964c-48f0-9825-4a66f9039261\" width=\"480\" alt=\"Example of auto-cpufreq GUI (available >= v2.0)\" />\n\nExample of `auto-cpufreq --stats` CLI output\n\n<img src=\"https://github.com/user-attachments/assets/1114e937-35bd-4943-8a74-a932c04c367e\" width=\"480\" alt=\"Example of auto-cpufreq CLI output\"/>\n\n## Looking for developers and co-maintainers\n\n- If you would like to discuss anything regarding auto-cpufreq or its development, please join the [auto-cpufreq Discord server!](https://discord.gg/Sjauxtj6kH)\n- auto-cpufreq is looking for [co-maintainers & open source developers to help shape the future of the project!](https://github.com/AdnanHodzic/auto-cpufreq/discussions/312)\n\n## Index\n\n- [Why do I need auto-cpufreq?](#why-do-i-need-auto-cpufreq)\n  - [Supported architectures and devices](#supported-architectures-and-devices)\n- [Features](#features)\n- [Installing auto-cpufreq](#installing-auto-cpufreq)\n  - [auto-cpufreq-installer](#auto-cpufreq-installer)\n  - [Snap Store](#snap-store)\n  - [AUR package (Arch based distributions)](#aur-package-arch-based-distributions)\n  - [NixOS](#nixos)\n  - [For developers](#installation-development-mode-only)\n- [Post-installation](#post-installation)\n- [Configuring auto-cpufreq](#configuring-auto-cpufreq)\n  - [1: power_helper.py script (Snap package install only)](#1-power_helperpy-script-snap-package-install-only)\n  - [2: `--force` governor override](#2---force-governor-override)\n  - [3: `--turbo` mode override](#3---turbo-mode-override)\n  - [4: auto-cpufreq config file](#4-auto-cpufreq-config-file)\n    - [Example config file contents](#example-config-file-contents)\n- [How to run auto-cpufreq](#how-to-run-auto-cpufreq)\n- [auto-cpufreq modes and options](#auto-cpufreq-modes-and-options)\n  - [monitor](#monitor)\n  - [live](#live)\n  - [overriding governor](#overriding-governor)\n  - [overriding turbo mode](#overriding-turbo-mode)\n  - [Install - auto-cpufreq daemon](#install---auto-cpufreq-daemon)\n  - [Update - auto-cpufreq update](#update---auto-cpufreq-update)\n  - [Remove - auto-cpufreq daemon](#remove---auto-cpufreq-daemon)\n  - [stats](#stats)\n  - [bluetooth_boot_off](#bluetooth_boot_off)\n  - [bluetooth_boot_on](#bluetooth_boot_on)\n- [Battery charging thresholds](#battery-charging-thresholds)\n  - [Supported Devices](#supported-devices)\n  - [Battery config](#battery-config)\n  - [Ignoring power supplies](#Ignoring-power-supplies)\n- [Troubleshooting](#troubleshooting)\n  - [AUR](#aur)\n- [Discussion](#discussion)\n- [Donate](#donate)\n  - [Financial donation](#financial-donation)\n    - [Paypal](#paypal)\n    - [BitCoin](#bitcoin)\n  - [Code contribution](#code-contribution)\n\n## Why do I need auto-cpufreq?\n\nOne of the problems with Linux today on laptops is that the CPU will run in an unoptimized manner which will negatively impact battery life. For example, the CPU may run using the \"performance\" governor with turbo boost enabled regardless of whether it's plugged into a power outlet or not.\n\nThese issues can be mitigated by using tools like [indicator-cpufreq](https://itsfoss.com/cpufreq-ubuntu/) or [cpufreq](https://github.com/konkor/cpufreq), but those still require manual action from your side which can be daunting and cumbersome.\n\nTools like [TLP](https://github.com/linrunner/TLP) (which I used for numerous years) can help extend battery life, but may also create their own set of problems, such as losing turbo boost.\n\nGiven all of the above, I needed a simple tool that would automatically make CPU frequency-related changes and save battery life, but let the Linux kernel do most of the heavy lifting. That's how auto-cpufreq was born.\n\nPlease note: auto-cpufreq aims to replace TLP in terms of functionality, so after you install auto-cpufreq _it's recommended to remove TLP_. Using both for the same functionality (i.e., to set CPU frequencies) will lead to unwanted results like overheating. Hence, only use [both tools in tandem](https://github.com/AdnanHodzic/auto-cpufreq/discussions/176) if you know what you're doing.\n\nOne tool/daemon that does not conflict with auto-cpufreq in any way, and is even recommended to have running alongside, is [thermald](https://wiki.debian.org/thermald).\n\n#### Supported architectures and devices\n\nOnly devices with an Intel, AMD, or ARM CPU are supported. This tool was developed to improve performance and battery life on laptops, but running it on desktops/servers (to lower power consumption) should also be possible.\n\n## Features\n\n- Monitoring\n  - Basic system information\n  - CPU frequency (system total & per core)\n  - CPU usage (system total & per core)\n  - CPU temperature (total average & per core)\n  - Battery state\n  - System load\n- CPU frequency scaling, governor, and [turbo boost](https://en.wikipedia.org/wiki/Intel_Turbo_Boost) management based on\n  - Battery state\n  - CPU usage (total & per core)\n  - CPU temperature in combination with CPU utilization/load (to prevent overheating)\n  - System load\n- Automatic CPU & power optimization (temporary and persistent)\n- Settings battery charging thresholds (limited support)\n\n## Installing auto-cpufreq\n\n### auto-cpufreq-installer\n\n> As auto-cpufreq relies on git based versioning, users are advised to install `auto-cpufreq`  using `git clone` method only. Downloading source code as a zip/from release will emit build error like [these](https://github.com/AdnanHodzic/auto-cpufreq/issues/623).\n\nGet source code, run installer, and follow on-screen instructions:\n\n```\ngit clone https://github.com/AdnanHodzic/auto-cpufreq.git\ncd auto-cpufreq && sudo ./auto-cpufreq-installer\n```\n\n### Snap Store\n\n*Please note: while all [auto-cpufreq >= v2.0 CLI functionality](https://www.youtube.com/watch?v=SPGpkZ0AZVU&t=295s) will work as intended, [the GUI won't be available on Snap package installs](http://foolcontrol.org/wp-content/uploads/2023/10/auto-cpufreq-v2-snap-deprecation-notice.png) due to [Snap package confinement limitations](https://forum.snapcraft.io/t/pkexec-not-found-python-gtk-gnome-app/36579). Hence, please consider installing auto-cpufreq using [auto-cpufreq-installer](#auto-cpufreq-installer)*.\n\nauto-cpufreq is available on the [Snap Store](https://snapcraft.io/auto-cpufreq) or via CLI:\n\n```\nsudo snap install auto-cpufreq\n```\n\n**Please note:**\n- Make sure [snapd](https://snapcraft.io/docs/installing-snapd) is installed and `snap version` is >= 2.44 for `auto-cpufreq` to fully work due to [recent snapd changes](https://github.com/snapcore/snapd/pull/8127).\n\n- Fedora users will [encounter the following error](https://twitter.com/killyourfm/status/1291697985236144130) due to `cgroups v2` [being in development](https://github.com/snapcore/snapd/pull/7825). This problem can be resolved by either running `sudo snap run auto-cpufreq` after the snap installation or by using the [auto-cpufreq-installer](#auto-cpufreq-installer) which doesn't have this issue.\n\n### AUR package (Arch based distributions)\n\n[![AUR package](https://repology.org/badge/version-for-repo/aur/auto-cpufreq.svg)](https://aur.archlinux.org/packages/auto-cpufreq)\n\nThe AUR [Release Package](https://aur.archlinux.org/packages/auto-cpufreq) is currently being maintained by [MusicalArtist12](https://github.com/MusicalArtist12), [liljaylj](https://github.com/liljaylj), and [parmjotsinghrobot](https://github.com/parmjotsinghrobot). \n\n**Notices**\n\n- The [Git Package](https://aur.archlinux.org/packages/auto-cpufreq-git) is seperately maintained and was last updated on version 1.9.6. \n- The build process links to `/usr/share/` instead of `/usr/local/share/`\n- The daemon installer provided does not work, instead start the daemon with \n\n``` \n# systemctl enable --now auto-cpufreq \n```\n- The GNOME Power Profiles daemon is [automatically disabled by auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq#1-power_helperpy-script-snap-package-install-only) due to it's conflict with auto-cpufreq.service. However, this doesn't happen with AUR installs, which can lead to problems (e.g., [#463](https://github.com/AdnanHodzic/auto-cpufreq/issues/463)) if not masked manually.\n  - Open a terminal and run `sudo systemctl mask power-profiles-daemon.service` (then `enable` and `start` the auto-cpufreq.service if you haven't already).\n- The TuneD daemon(enabled by default with Fedora 41) is [automatically disabled by auto-cpufreq-installer](https://github.com/AdnanHodzic/auto-cpufreq#1-power_helperpy-script-snap-package-install-only) due to it's conflict with auto-cpufreq.service.\n\n### Gentoo Linux (GURU Repository)\n\nNew versions of auto-cpufreq were recently added to GURU, Gentoo's official community-maintained ebuild repository. The [ebuild](https://github.com/gentoo-mirror/guru/tree/master/sys-power/auto-cpufreq) is maintaned by [S41G0N](https://github.com/S41G0N) and other [GURU contributors](https://bugs.gentoo.org), who can respond in case of issues.\n\nIn order to build auto-cpufreq, it is necessary to add & sync GURU repository first. Adding ~amd64 keyword is also needed to unmask the package.\n\n``` \n# echo \"sys-power/auto-cpufreq ~amd64\" >> /etc/portage/package.accept_keywords\n# eselect repository enable guru\n# emaint sync -r guru\n# emerge --ask auto-cpufreq\n```\n\n**Notices**\n\n- The build process links to `/usr/share/` instead of `/usr/local/share/`\n- The build works on both systemd/OpenRC systems (both systemd and OpenRC will have a service called auto-cpufreq which can be started automatically)\n- The daemon installer provided does work, but it is RECOMMENDED to install the daemon with:\n``` \n# systemctl enable --now auto-cpufreq \n# rc-update add auto-cpufreq default && rc-service auto-cpufreq start\n```\n\n### NixOS\n\n<details>\n<summary>Flakes</summary>\n<br>\n\nThis repo contains a flake that exposes a NixOS Module that manages and offers options for auto-cpufreq. To use it, add the flake as an input to your `flake.nix` file and enable the module:\n\n```nix \n# flake.nix\n\n{\n\n    inputs = {\n        # ---Snip---\n        auto-cpufreq = {\n            url = \"github:AdnanHodzic/auto-cpufreq\";\n            inputs.nixpkgs.follows = \"nixpkgs\";\n        };\n        # ---Snip---\n    }\n\n    outputs = {nixpkgs, auto-cpufreq, ...} @ inputs: {\n        nixosConfigurations.HOSTNAME = nixpkgs.lib.nixosSystem {\n            specialArgs = { inherit inputs; };\n            modules = [\n                ./configuration.nix\n                auto-cpufreq.nixosModules.default\n            ];\n        };\n    } \n}\n```\nThen you can enable the program in your `configuration.nix` file:\n```nix\n# configuration.nix\n\n{inputs, pkgs, ...}: {\n    # ---Snip---\n    programs.auto-cpufreq.enable = true;\n    # optionally, you can configure your auto-cpufreq settings, if you have any\n    programs.auto-cpufreq.settings = {\n    charger = {\n      governor = \"performance\";\n      turbo = \"auto\";\n    };\n\n    battery = {\n      governor = \"powersave\";\n      turbo = \"auto\";\n    };\n  };\n    # ---Snip---\n}\n```\n</details>\n\n<details>\n<summary>Nixpkgs</summary>\n<br>\n\nThere is a nixpkg available, but it is more prone to being outdated, whereas the flake pulls from the latest commit. You can install it in your `configuration.nix` and enable the system service:\n```nix\n# configuration.nix\n\n# ---Snip---\nenvironment.systemPackages = with pkgs; [\n    auto-cpufreq\n];\n\nservices.auto-cpufreq.enable = true;\n# ---Snip---\n```\n</details>\n\n### Installation (development mode only)\n\n- If you have `poetry` installed:\n  ```bash\n  git clone https://github.com/AdnanHodzic/auto-cpufreq.git\n  cd auto-cpufreq\n  poetry install\n  poetry run auto-cpufreq --help\n  ```\n\n- To install with GUI support:\n  ```bash\n  poetry install --extras gui\n  poetry run auto-cpufreq-gtk\n  ```\n\n- Alternatively, we can use an editable pip install for development purposes:\n  ```bash\n  git clone https://github.com/AdnanHodzic/auto-cpufreq.git\n  cd auto-cpufreq\n  # set up virtual environment (details removed for brevity)\n  pip3 install -e .\n  auto-cpufreq\n  ```\n- Regularly run `poetry update` if you get any inconsistent lock file issues.\n\n## Post-installation\n\nAfter installation, `auto-cpufreq` is available as a binary. Refer to [auto-cpufreq modes and options](https://github.com/AdnanHodzic/auto-cpufreq#auto-cpufreq-modes-and-options) for detailed information on how to run and configure `auto-cpufreq`.\n\n## Configuring auto-cpufreq\n\nauto-cpufreq makes all decisions automatically based on various factors such as CPU usage, temperature, and system load. However, it's possible to perform additional configurations:\n\n### 1: power_helper.py script (Snap package install **only**)\n\nWhen installing auto-cpufreq via [auto-cpufreq-installer](#auto-cpufreq-installer), if it detects the [GNOME Power Profiles service](https://twitter.com/fooctrl/status/1467469508373884933) is running, it will automatically disable it. Otherwise, that daemon will cause conflicts and various other performance issues. \n\nHowever, when auto-cpufreq is installed as a Snap package it's running as part of a container with limited permissions, hence it's *highly recommended* to disable the GNOME Power Profiles daemon using the `power_helper.py` script.\n\n**Please Note:**<br>\nThe [`power_helper.py`](https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto_cpufreq/power_helper.py) script is located within the auto-cpufreq repo at `auto_cpufreq/power_helper.py`. In order to access it, first clone\nthe repository:\n\n`git clone https://github.com/AdnanHodzic/auto-cpufreq`\n\nMake sure to have `psutil` & `pyinotify` Python library installed before next step:\n\nIf you're using Debian based distro install them by running:\n\n`sudo apt install python3-psutil python3-pyinotify` \n\nor manually using pip, e.g: \n\n`sudo pip3 install psutil pyinotify --break-system-packages`\n\nThen disable the GNOME Power Profiles daemon:\n\n`sudo python3 -m auto_cpufreq.power_helper --gnome_power_disable`\n\nfor full list of options run --help, e.g:\n\n`sudo python3 -m auto_cpufreq.power_helper --help`\n\n### 2: `--force` governor override\n\nBy default, auto-cpufreq uses `balanced` mode which works best for many systems and situations.\n\nHowever, you can override this behaviour by switching to `performance` or `powersave` mode manually. The `performance` mode results in higher default frequencies, but also higher energy use (battery consumption) and should only be used if maximum performance is needed. The `powersave` mode does the opposite and extends battery life to its maximum.\n\nSee [`--force` flag](#overriding-governor) for more info.\n\n### 3: `--turbo` mode override\n\nBy default, auto-cpufreq handles CPU turbo mode automatically, enabling it under load and disabling it otherwise to balance performance and efficiency.\n\nHowever, you can override this behavior by forcing CPU turbo's mode to `always` or `never`. Setting to `always` keeps turbo mode always enabled, allowing the CPU to reach its maximum frequency at the cost of higher energy use (battery consumption). `never`, on the other hand, keeps turbo mode always disabled, limiting the CPU's maximum frequency to extend battery life.\n\nSee [`--turbo` flag](#overriding-turbo-mode) for more info.\n\n### 4: auto-cpufreq config file\n\nYou can configure separate profiles for the battery and power supply. These profiles will let you pick which governor to use, as well as how and when turbo boost is enabled. The possible values for turbo boost behavior are `always`, `auto`, and `never`. The default behavior is `auto`, which only activates turbo during high load.\n\nBy default, auto-cpufreq does not use a config file. If you wish to configure auto-cpufreq statically, we look for a configuration file in the following order:\n\n1. Commandline argument: `--config <FILE>` if passed as commandline argument to `auto-cpufreq`\n2. User-specific configuration: `$XDG_CONFIG_HOME/auto-cpufreq/auto-cpufreq.conf`\n3. System-wide configuration: `/etc/auto-cpufreq.conf`\n\n#### Example config file contents\n```python\n# settings for when connected to a power source\n[charger]\n# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors\n# preferred governor\ngovernor = performance\n\n# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences\nenergy_performance_preference = performance\n\n# EPB (Energy Performance Bias) for the intel_pstate driver\n# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html\n# available EPB options include a numeric value between 0-15\n# (where 0 = maximum performance and 15 = maximum power saving),\n# or one of the following strings:\n# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)\n# if the parameter is missing in the config and the hardware supports this setting, the default value will be used\n# the default value is `balance_performance` (for charger)\n# energy_perf_bias = balance_performance\n\n# Platform Profiles\n# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html\n# See available options by running:\n# cat /sys/firmware/acpi/platform_profile_choices\n# platform_profile = performance\n\n# minimum cpu frequency (in kHz)\n# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_min_freq = 800000\n\n# maximum cpu frequency (in kHz)\n# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_max_freq = 1000000\n\n# turbo boost setting. possible values: always, auto, never\nturbo = auto\n\n# settings for when using battery power\n[battery]\n# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors\n# preferred governor\ngovernor = powersave\n\n# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences\nenergy_performance_preference = power\n\n# EPB (Energy Performance Bias) for the intel_pstate driver\n# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html\n# available EPB options include a numeric value between 0-15\n# (where 0 = maximum performance and 15 = maximum power saving),\n# or one of the following strings:\n# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)\n# if the parameter is missing in the config and the hardware supports this setting, the default value will be used\n# the default value is `balance_power` (for battery)\n# energy_perf_bias = balance_power\n\n# Platform Profiles\n# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html\n# See available options by running:\n# cat /sys/firmware/acpi/platform_profile_choices\n# platform_profile = low-power\n\n# minimum cpu frequency (in kHz)\n# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_min_freq = 800000\n\n# maximum cpu frequency (in kHz)\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_max_freq = 1000000\n\n# turbo boost setting (always, auto, or never)\nturbo = auto\n\n# battery charging threshold\n# reference: https://github.com/AdnanHodzic/auto-cpufreq/#battery-charging-thresholds\n#enable_thresholds = true\n#start_threshold = 20\n#stop_threshold = 80\n```\n\n## How to run auto-cpufreq\nauto-cpufreq should be run with with one of the following options:\n\n- [monitor](#monitor)\n  - Monitor and see suggestions for CPU optimizations\n\n- [live](#live)\n  - Monitor and automatically make (temporary) CPU optimizations\n\n- [install](#install---auto-cpufreq-daemon) / [remove](#remove---auto-cpufreq-daemon)\n  - Install/remove daemon for (permanent) automatic CPU optimizations\n \n- [install (GUI)](#install---auto-cpufreq-daemon)\n  - Install daemon via GUI for (permanent) automatic CPU optimizations\n\n- [update](#update---auto-cpufreq-update)\n  - Update auto-cpufreq to the latest release\n\n- [install_performance](#1-power_helperpy-script)\n  - Install daemon in \"performance\" mode\n\n- [stats](#stats)\n  - View live stats of CPU optimizations made by daemon\n\n- [bluetooth_boot_off](#bluetooth_boot_off)\n  - Turn off Bluetooth on boot (only)! Can be turned on any time later on.\n\n- [bluetooth_boot_on](#bluetooth_boot_on)\n  - Turn on Bluetooth on boot.\n\n- [force=TEXT](#overriding-governor)\n  - Force use of either the \"powersave\" or \"performance\" governor, or set to \"reset\" to go back to normal mode\n\n- [turbo=TEXT](#overriding-turbo-mode)\n  - Force use of CPU turbo mode, if supported, with \"never\" or \"always\", or set to \"auto\" to automatically handle turbo mode\n\n- config=TEXT\n  - Use config file at designated path\n\n- debug\n  - Show debug info (include when submitting bugs)\n\n- version\n  - Show currently installed version\n\n- [donate](#financial-donation)\n  - To support the project\n\n- help\n  - Shows all of the above options\n\nRunning `auto-cpufreq --help` will print the same list of options as above. Read [auto-cpufreq modes and options](#auto-cpufreq-modes-and-options) for more details.\n\n## auto-cpufreq modes and options\n\n### Monitor\n\n`sudo auto-cpufreq --monitor`\n\nNo changes are made to the system. This is solely to demonstrate what auto-cpufreq could do for your system.\n\n### Live\n\n`sudo auto-cpufreq --live`\n\nNecessary changes are temporarily made to the system over time, but this process and its changes are lost at system reboot. This mode is provided to evaluate how the system would behave with auto-cpufreq permanently running on the system.\n\n### Overriding governor\n\n`sudo auto-cpufreq --force=governor`\n\nForce use of either the \"powersave\" or \"performance\" governor, or set to \"reset\" to go back to normal mode.\nPlease note that any set override will persist even after reboot.\n\n### Overriding Turbo mode\n\n`sudo auto-cpufreq --turbo=mode`\n\nForce use of CPU turbo mode, if supported, with \"never\" or \"always\", or set to \"auto\" to automatically handle turbo mode.\nPlease note that any set override will persist even after reboot.\n\n### Install - auto-cpufreq daemon\n\nNecessary changes are made to the system over time and this process will continue across reboots. The daemon is deployed and started as a systemd service. Changes are made automatically and live stats are generated for monitoring purposes.\n\n**Install the daemon using CLI ([after installing auto-cpufreq](#installing-auto-cpufreq)):**\n\nInstalling the auto-cpufreq daemon using CLI is as simple as running the following command:\n\n`sudo auto-cpufreq --install`\n\nAfter the daemon is installed, `auto-cpufreq` is available as a binary and runs in the background. Its stats can be viewed by running: `auto-cpufreq --stats`\n\n*Please note:* if the daemon is installed within a desktop environment, then its stats and options can be accessed via CLI or GUI. See \"Install the daemon using GUI\" below for more details.\n\n**Install the daemon using GUI**\n\nStarting with >= v2.0 [after installing auto-cpufreq](#installing-auto-cpufreq), an auto-cpufreq desktop entry (icon) is available, i.e.:\n\n<img src=\"https://github.com/user-attachments/assets/f426d62b-00b0-4fa5-a72e-b352016ed448\" width=\"640\" alt=\"Example of auto-cpufreq desktop entry (icon)\"/>\n\nAfter selecting it to open the GUI, the auto-cpufreq daemon can be installed by clicking the \"Install\" button:\n\n<img src=\"https://github.com/user-attachments/assets/5af47e5e-8b9e-4ff6-9ffc-e78acb623ce4\" width=\"480\" alt=\"The auto-cpufreq GUI's 'Install' button\"/>\n\nAfter that, the full auto-cpufreq GUI is available:\n\n<img src=\"https://github.com/user-attachments/assets/9c7715c4-16b7-4a5c-86be-4c390276d9e8\" width=\"640\" alt=\"The full auto-cpufreq GUI\"/>\n\n*Please note:* after the daemon is installed (by any method), its stats and options are accessible via both CLI and GUI.\n\n**auto-cpufreq daemon service**\n\nInstalling the auto-cpufreq daemon also enables the associated service (equivalent to `systemctl enable auto-cpufreq`), causing it to start on boot, and immediately starts it (equivalent to `systemctl start auto-cpufreq`).\n\nSince the daemon is running as a systemd service, its status can be seen by running:\n\n`systemctl status auto-cpufreq`\n\nIf installed via Snap package, daemon status can be viewed as follows:\n\n`systemctl status snap.auto-cpufreq.service.service`\n\n### Update - auto-cpufreq update\n\nUpdate functionality works by cloning the auto-cpufreq repo, installing it via [auto-cpufreq-installer](#auto-cpufreq-installer), and performing a fresh [auto-cpufreq daemon install](#install---auto-cpufreq-daemon) to provide the [latest version's](https://github.com/AdnanHodzic/auto-cpufreq/releases) changes.\n\nUpdate auto-cpufreq by running: `sudo auto-cpufreq --update`. By default, the latest revision is cloned to `/opt/auto-cpufreq/source`, thus maintaining existing directory structure.\n\nUpdate and clone to a custom directory by running: `sudo auto-cpufreq --update=/path/to/directory`\n\n### Remove - auto-cpufreq daemon\n\nThe auto-cpufreq daemon, its systemd service, and all its persistent changes can be removed by running:\n\n`sudo auto-cpufreq --remove`\n\nThis does, in part, the equivalent of `systemctl stop auto-cpufreq && systemctl disable auto-cpufreq`, but the above command should be used instead of using `systemctl`.\n\n*Please note:* after the daemon is removed, the auto-cpufreq GUI and desktop entry (icon) are also removed.\n\n### Stats\n\nIf the daemon has been installed, live stats of CPU/system load monitoring and optimization can be seen by running:\n\n`auto-cpufreq --stats`\n\n### bluetooth_boot_off\n\nTurn off Bluetooth on boot (only)! Bluetooth can still be turned on manually when needed. This option is executed during the installation of the auto-cpufreq daemon, but it can also be run independently without installing the daemon.\n\nIt prevents GNOME from automatically enabling Bluetooth on every reboot or after suspend/wake up even if you manually disable it, GNOME will turn it back on unless this option is used.\n\n### bluetooth_boot_on\n\nUseful if you prefer Bluetooth to be enabled at boot time, especially after installing the auto-cpufreq daemon, which will disable it by default.\n\n## Battery charging thresholds\n\n***Please note:** [Original implementor](https://github.com/AdnanHodzic/auto-cpufreq/pull/637) is looking for user input & testing to further improve this functionality. If you would like to help in this process, please refer to [Looking for developers and co-maintainers](https://github.com/AdnanHodzic/auto-cpufreq/#looking-for-developers-and-co-maintainers)*.\n\nAs of [v2.2.0](https://github.com/AdnanHodzic/auto-cpufreq/releases/tag/v2.2.0), battery charging thresholds can be set in the config file. This enforces your battery to start and stop charging at defined values.\n\n### Supported devices\n\n- **Lenovo ThinkPad** (thinkpad_acpi)*\n- **Lenovo IdeaPad** (ideapad_acpi)*\n- **Lenovo Laptop** (ideapad_laptop)*\n- **ASUS :Laptops** (asus_wmi)*\n\n**Please note, your laptop must have an installed ACPI kernel driver specific to the manufacturer.** To check if you have the correct module installed and loaded run `lsmod [module]`\n\nAdditionally, **you should make sure that you have no other software running that may conflict with auto-cpufreq's battery threshold management** (e.g., GNOME's *Preserve Battery Health* option). Using both at the same time will lead to conflicts and to your battery potentially never charging.\n\n**To request that your device be supported, please open an [issue](https://github.com/AdnanHodzic/auto-cpufreq/issues/new). In your issue, make us aware of the driver that works with your laptop**\n\n### Battery config\n\nEdit the config at `/etc/auto-cpufreq.conf`\n\nExample config for battery ([already part of example config file](https://github.com/AdnanHodzic/auto-cpufreq/#example-config-file-contents))\n\n```ini\n[battery]\nenable_thresholds = true\nstart_threshold = 20\nstop_threshold = 80\n```\n\nYou will need to specify both `start_threshold` AND `stop_threshold` in most cases.\nFor these changes to have an effect, you will need to restart the daemon:\n\n```shell\nsystemctl restart auto-cpufreq.service\n```\n\nSee more here on the kernel doc pages: [docs.kernel.org](https://docs.kernel.org/admin-guide/laptops/thinkpad-acpi.html#battery-charge-control)\n\n### A few notes about these battery config options\n\nWhen you remove/uninstall the auto-cpufreq daemon, the last applied settings for battery thresholds will still apply. You might need to manually set these yourself to whatever default they were before. E.g. (as sudo):\n\n```shell\necho 0 > /sys/class/power_supply/BAT0/charge_start_threshold\necho 100 > /sys/class/power_supply/BAT0/charge_stop_threshold\n```\n\n### Ideapad conservation mode\n\nThis works only with laptops that have the `ideapad_laptop` kernel module.  \n\nadd `ideapad_laptop_conservation_mode = true` to your `auto-cpufreq.conf` file\n\n```ini\n[battery]\nideapad_laptop_conservation_mode = true\n```\n\nThis is a special mode which limits the maximum charge level of the battery to around 60-80% depending on the model.\n\n\n### Ignoring power supplies\n\nyou may have a controler or headphones and when ever they may be on battery they might cause auto-cpufreq\nto limit preformence to ignore them add to you config file the name of the power supply, under `[power_supply_ignore_list]`\n\nthe name of the power supply can be found with  `ls /sys/class/power_supply/`\n\n```ini\n[power_supply_ignore_list]\n\nname1 = this\nname2 = is \nname3 = an\nname4 = example\n\n# like this\nxboxctrl = {the xbox controler power supply name}\n\n```\n\n## Troubleshooting\n\n**Q:** If after installing auto-cpufreq you're (still) experiencing:\n- high CPU temperatures\n- CPU not scaling to minimum/maximum frequencies\n- suboptimal CPU performance\n- turbo mode not available\n\n**A:** If you're using the `intel_pstate/amd-pstate` CPU management driver, consider changing it to `acpi-cpufreq`.\n\nThis can be done by editing the `GRUB_CMDLINE_LINUX_DEFAULT` params in `/etc/default/grub`. For instance:\n\n```shell\n    sudo nano /etc/default/grub\n    # make sure you have nano installed, or you can use your favorite text editor\n```\n\nFor Intel users:\n\n```\nGRUB_CMDLINE_LINUX_DEFAULT=\"quiet splash intel_pstate=disable\"\n```\n\nFor AMD users:\n\n```\nGRUB_CMDLINE_LINUX_DEFAULT=\"quiet splash initcall_blacklist=amd_pstate_init amd_pstate.enable=0\"\n```\n\nOnce you have made the necessary changes to the GRUB configuration file, you can update GRUB by running `sudo update-grub` on Debian/Ubuntu, `sudo grub-mkconfig -o /boot/grub/grub.cfg` on Arch Linux, or one of the following on Fedora:\n\n```shell\n    sudo grub2-mkconfig -o /etc/grub2.cfg\n```\n\n```shell\n    sudo grub2-mkconfig -o /etc/grub2-efi.cfg\n```\n\n```shell\n    sudo grub2-mkconfig -o /boot/grub2/grub.cfg\n    # legacy boot method\n```\n\nFor systemd-boot users:\n\n```shell\n    sudo nano /etc/kernel/cmdline\n    # make sure you have nano installed, or you can use your favorite text editor\n```\n\nFor Intel users:\n\n```shell\nquiet splash intel_pstate=disable\n```\n\nFor AMD users:\n\n```shell\nquiet splash initcall_blacklist=amd_pstate_init amd_pstate.enable=0\n```\n\nOnce you have made the necessary changes to the `cmdline` file, you can update it by running `sudo reinstall-kernels`.\n\n**Q:** If auto-cpufreq-gtk doesn't launch with exit code 1 (Workaround)\n\n**A:** Try launching it with `env -i XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR auto-cpufreq-gtk` and add it to `/usr/share/applications/auto-cpufreq-gtk.desktop` if it works.\n\n### AUR\n\n- If the AUR installer does not work for your system, fallback to `auto-cpufreq-installer` and open an issue.\n\n## Discussion:\n\n- Blogpost: [auto-cpufreq - Automatic CPU speed & power optimizer for Linux](http://foolcontrol.org/?p=3124)\n\n## Donate\n\nShowing your support and appreciation for the auto-cpufreq project can be done in two ways:\n\n- Financial donation\n- Code contribution\n\n### Financial donation\n\nIf auto-cpufreq helped you out and you find it useful, show your appreciation by donating (any amount) to the project!\n\n##### Become Github Sponsor\n\n[Become a sponsor to Adnan Hodzic on Github](https://github.com/sponsors/AdnanHodzic) to acknowledge my efforts and help project's further open source development.\n\n##### PayPal\n[![paypal](https://www.paypalobjects.com/en_US/NL/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=7AHCP5PU95S4Y&item_name=Contribution+for+work+on+auto-cpufreq&currency_code=EUR&source=url)\n\n##### BitCoin\n[bc1qlncmgdjyqy8pe4gad4k2s6xtyr8f2r3ehrnl87](bitcoin:bc1qlncmgdjyqy8pe4gad4k2s6xtyr8f2r3ehrnl87)\n\n[![bitcoin](https://foolcontrol.org/wp-content/uploads/2019/08/btc-donate-displaylink-debian.png)](bitcoin:bc1qlncmgdjyqy8pe4gad4k2s6xtyr8f2r3ehrnl87)\n\n### Code contribution\n\nOther ways of supporting the project consist of making a code or documentation contribution. If you have an idea for a new feature or you want to implement some of the existing feature requests or fix some of the [bugs & issues](https://github.com/AdnanHodzic/auto-cpufreq/issues), please make your changes and submit a [pull request](https://github.com/AdnanHodzic/auto-cpufreq/pulls). I'll be glad to review it and, if your changes are accepted, you'll be credited on the [releases page](https://github.com/AdnanHodzic/auto-cpufreq/releases).\n\n**Please note: auto-cpufreq is looking for co-maintainers & open source developers to [help shape the future of the project!](https://github.com/AdnanHodzic/auto-cpufreq/discussions/312)**\n"
  },
  {
    "path": "auto-cpufreq-installer",
    "content": "#!/usr/bin/env bash\n\n# auto-cpufreq-installer:\n# auto-cpufreq source code based installer\n\ncd \"$(dirname \"$(readlink -f \"$0\")\")\" || exit 1\n\nCOLOUMNS=\"`tput cols`\"\nMID=\"$((COLOUMNS / 2))\"\n\nAPPLICATIONS_PATH=\"/usr/share/applications\"\nVENV_PATH=\"/opt/auto-cpufreq\"\n\nSHARE_DIR=\"/usr/local/share/auto-cpufreq/\"\n\nAUTO_CPUFREQ_FILE=\"/usr/local/bin/auto-cpufreq\"\nAUTO_CPUFREQ_GTK_FILE=$AUTO_CPUFREQ_FILE-gtk\nAUTO_CPUFREQ_GTK_DESKTOP_FILE=\"$(basename $AUTO_CPUFREQ_GTK_FILE).desktop\"\n\nIMG_FILE=\"/usr/share/pixmaps/auto-cpufreq.png\"\nORG_FILE=\"/usr/share/polkit-1/actions/org.auto-cpufreq.pkexec.policy\"\n\nfunction header {\n  echo\n\tprintf \"%0.s─\" $(seq $((MID-(${#1}/2)-2)))\n\tprintf \" $1 \"\n\tprintf \"%0.s─\" $(seq $((MID-(${#1}/2)-2)))\n\techo; echo\n}\n\nfunction ask_operation {\n\theader \"auto-cpufreq installer\"\n  echo \"Welcome to auto-cpufreq tool installer.\"; echo\n  read -p \"Select a key [I]nstall/[R]emove or press ctrl+c to quit: \" answer\n}\n\nfunction manual_install {\n  if command -v lsb_release > /dev/null; then\n    distro=\"$(lsb_release -is)\"\n    release=\"$(lsb_release -rs)\"\n    codename=\"$(lsb_release -cs)\"\n  fi\n\n\techo \"Didn't detect Debian or RedHat or Arch based distro.\"; echo\n  echo \"To complete installation, you need to:\"\n  echo \"Install: python3, pip3, python3-setuptools, gobject-introspection, cairo (or cairo-devel), gcc, and gtk3\"; echo\n  echo \"Install necessary Python packages:\"\n  echo \"pip3 install psutil click distro power requests PyGObject\"\n  echo \"Run following sequence of lines:\"; echo\n  echo \"-----\"; echo\n  echo \"pip3 install .\"\n  echo \"mkdir -p $SHARE_DIR\"\n  echo \"cp -r scripts/ $SHARE_DIR\"; echo\n  echo \"-----\"; echo\n  echo \"After which tool is installed, for full list of options run:\";echo\n  echo \"auto-cpufreq --help\"\n\n  echo; printf \"%0.s─\" $(seq $COLOUMNS); echo\n  \n  echo \"Consider creating a feature request to add support for your distro:\"\n  echo \"https://github.com/AdnanHodzic/auto-cpufreq/issues/new\"; echo\n  echo \"Make sure to include following information:\"; echo\n  echo \"Distribution: $distro\"\n  echo \"Release: $release\"\n  echo \"Codename: $codename\"\n  echo\n\n  exit 1\n}\n\nfunction tool_install {\n  echo\n  # First argument is the distro\n  function detected_distro {\n    header \"Detected $1 distribution\"\n    header \"Setting up Python environment\"\n  }\n\n  if [ -f /etc/debian_version ]; then  \n    detected_distro \"Debian based\"\n    VERSION=$(cat /etc/debian_version) \n\n    # install necessary libgirepository debian package dependencies\n    # https://github.com/AdnanHodzic/auto-cpufreq/pull/826#issuecomment-2794549837\n    apt update\n\n    if apt-cache show libgirepository-2.0-dev > /dev/null 2>&1; then\n        LIB_GI_REPO=\"libgirepository-2.0-dev\"\n        PYGOBJECT_VER=\"^3.50.0\"\n    else\n        LIB_GI_REPO=\"libgirepository1.0-dev\"\n        # pin PYGOBJECT_VER to libgirepository1.0-dev\n        # https://github.com/AdnanHodzic/auto-cpufreq/issues/813#issuecomment-2712543486\n        PYGOBJECT_VER=\"3.50.0\"\n    fi\n\n    # Update PyGObject in pyproject.toml\n    # https://github.com/AdnanHodzic/auto-cpufreq/pull/826#issuecomment-2794549837\n    if [ -f ./pyproject.toml ]; then\n        if grep -q 'PyGObject *= *{[^}]*version *= *\"' pyproject.toml; then\n            sed -i \"s/\\(PyGObject *= *{[^}]*version *= *\\\"\\)[^\\\"]*\\(.*\\)/\\1$PYGOBJECT_VER\\2/\" pyproject.toml\n            echo \"PyGObject version updated to $PYGOBJECT_VER in pyproject.toml\"\n        else\n            echo \"Warning: Could not find PyGObject version entry in pyproject.toml!\"\n        fi\n    else\n        echo \"Error: pyproject.toml not found and PyGObject version not updated!\"\n    fi\n\n\n    echo \"$LIB_GI_REPO needs to be installed for version $VERSION\"\n    echo '---- '\n    apt install -y python3-dev python3-pip python3-venv python3-setuptools dmidecode \\\n    \"$LIB_GI_REPO\" libcairo2-dev libgtk-3-dev gcc python3-gi\n\n  elif [ -f /etc/redhat-release ]; then\n    detected_distro \"RedHat based\"\n    if [ -f /etc/centos-release ]; then yum install platform-python-devel\n    else yum install python-devel\n    fi\n    yum install dmidecode gcc cairo-devel gobject-introspection-devel cairo-gobject-devel gtk3-devel\n\n  elif [ -f /etc/solus-release ]; then\n    detected_distro \"Solus\"\n    eopkg install pip python3 python3-devel dmidecode gobject-introspection-devel libcairo-devel gcc libgtk-3\n    eopkg install -c system.devel\n\n  elif [ -f /etc/arch-release ]; then\n    detected_distro \"Arch Linux based\"\n    pacman -S --noconfirm --needed python python-pip python-setuptools base-devel dmidecode gobject-introspection gtk3 gcc\n    \nelif [ -f /etc/os-release ];then\n    . /etc/os-release\n    case $ID in\n      opensuse-leap)\n        detected_distro \"OpenSUSE\"\n        zypper install -y python3 python3-pip python311-setuptools python3-devel gcc dmidecode gobject-introspection-devel python3-cairo-devel gtk3 gtk3-devel\n      ;;\n      opensuse-tumbleweed)\n        detected_distro \"OpenSUSE\"\n        zypper install -y python3 python3-pip python311-setuptools python3-devel gcc dmidecode gobject-introspection-devel python3-cairo-devel gtk3 gtk3-devel\n      ;;\n      void)\n        detected_distro \"Void Linux\"\n        xbps-install -Sy python3 python3-pip python3-devel python3-setuptools base-devel dmidecode cairo-devel gobject-introspection gcc gtk+3\n      ;;\n      nixos)\n        echo \"NixOS detected\"\n        echo \"This installer is not supported on NixOS.\"\n        echo \"Please refer to the install instructions for NixOS at https://github.com/AdnanHodzic/auto-cpufreq#nixos\"\n        exit 1\n      ;;\n      *) manual_install;;\n    esac\n  else # In case /etc/os-release doesn't exist\n    manual_install\n  fi\n\n  header \"Installing necessary Python packages\"\n\n  venv_dir=$VENV_PATH/venv\n  mkdir -p \"$venv_dir\"\n  python3 -m venv --system-site-packages \"$venv_dir\"\n\n  source \"$venv_dir/bin/activate\"\n  python3 -m pip install --upgrade pip wheel\n\n\n  # debian specific PyGObject Installation\n  if [ -f /etc/debian_version ]; then  \n    VERSION=$(cat /etc/debian_version | cut -d'.' -f1)  \n\n    if [[ \"$VERSION\" =~ ^12(\\.[0-9]+)?$ ]]; then \n        python3 -m pip install PyGObject==3.50.0\n    fi\n  fi\n  python3 -m pip install PyGObject\n\n  header \"Installing auto-cpufreq tool\"\n  \n  git config --global --add safe.directory $(pwd)\n  python -m pip install .\n\n  mkdir -p $SHARE_DIR\n  cp -r scripts/ $SHARE_DIR\n  cp -r images/ $SHARE_DIR\n  cp images/icon.png $IMG_FILE\n  cp scripts/$(basename $ORG_FILE) $(dirname $ORG_FILE)\n\n  # this is necessary since we need this script before we can run auto-cpufreq itself\n  cp scripts/auto-cpufreq-venv-wrapper $AUTO_CPUFREQ_FILE\n  chmod a+x $AUTO_CPUFREQ_FILE\n  cp scripts/start_app $AUTO_CPUFREQ_GTK_FILE\n  chmod a+x $AUTO_CPUFREQ_GTK_FILE\n\n  desktop-file-install --dir=$APPLICATIONS_PATH scripts/$AUTO_CPUFREQ_GTK_DESKTOP_FILE\n  update-desktop-database $APPLICATIONS_PATH\n  \n  header \"auto-cpufreq tool successfully installed\"\n  echo \"For list of options, run:\"\n  echo \"auto-cpufreq --help\"; echo\n}\n\nfunction tool_remove {\n  # stop any running auto-cpufreq argument (daemon/live/monitor)\n  tool_arg_pids=($(pgrep -f \"auto-cpufreq --\"))\n  for pid in \"${tool_arg_pids[@]}\"; do [ $pid != $$ ] && kill \"$pid\"; done\n\n  function remove_directory {\n    [ -d $1 ] && rm -rf $1\n  }\n  function remove_file {\n    [ -f $1 ] && rm $1\n  }\n\n  srv_remove=\"$AUTO_CPUFREQ_FILE-remove\"\n\n  # run uninstall in case of installed daemon\n  if [ -f $srv_remove -o -f $AUTO_CPUFREQ_FILE ]; then\n    eval \"$AUTO_CPUFREQ_FILE --remove\"\n  else\n    echo; echo \"Couldn't remove the auto-cpufreq daemon, $srv_remove do not exist.\"\n  fi\n\n  # remove auto-cpufreq and all its supporting files\n  remove_directory $SHARE_DIR\n\n  remove_file \"$AUTO_CPUFREQ_FILE-install\"\n  remove_file $srv_remove\n  remove_file $AUTO_CPUFREQ_FILE\n  remove_file $AUTO_CPUFREQ_GTK_FILE\n  remove_file $IMG_FILE\n  remove_file $ORG_FILE\n  remove_file \"/usr/local/bin/cpufreqctl.auto-cpufreq\"\n  remove_file \"/var/run/auto-cpufreq.stats\"\n\n  remove_file \"$APPLICATIONS_PATH/$AUTO_CPUFREQ_GTK_DESKTOP_FILE\"\n  update-desktop-database $APPLICATIONS_PATH\n\n  # remove python virtual environment\n  remove_directory $VENV_PATH\n\n  echo; echo \"auto-cpufreq tool and all its supporting files successfully removed\"; echo\n}\n\n# root check\nif ((EUID != 0)); then\n  echo; echo \"Must be run as root (i.e: 'sudo $0').\"; echo\n  exit 1\nfi\n\nif [[ -z \"$1\" ]]; then ask_operation\nelse\n  case \"$1\" in\n    --install) answer=\"i\";;\n    --remove) answer=\"r\";;\n    *) ask_operation;;\n  esac\nfi\n\ncase $answer in\n\tI|i) tool_install;;\n\tR|r) tool_remove;;\n\t*)\n    echo \"Unknown key, aborting ...\"; echo\n    exit 1\n  ;;\nesac\n"
  },
  {
    "path": "auto-cpufreq.conf-example",
    "content": "# settings for when connected to a power source\n[charger]\n# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors\n# preferred governor.\ngovernor = performance\n\n# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences\nenergy_performance_preference = performance\n\n# EPB (Energy Performance Bias) for the intel_pstate driver\n# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html\n# available EPB options include a numeric value between 0-15\n# (where 0 = maximum performance and 15 = maximum power saving),\n# or one of the following strings:\n# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)\n# if the parameter is missing in the config and the hardware supports this setting, the default value will be used\n# the default value is `balance_performance` (for charger)\n# energy_perf_bias = balance_performance\n\n# Platform Profiles\n# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html\n# See available options by running:\n# cat /sys/firmware/acpi/platform_profile_choices\n# platform_profile = performance\n\n# minimum cpu frequency (in kHz)\n# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_min_freq = 800000\n\n# maximum cpu frequency (in kHz)\n# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_max_freq = 1000000\n\n# turbo boost setting. possible values: always, auto, never\nturbo = auto\n\n\n# this is for ignoring controllers and other connected devices battery from affecting \n# laptop preformence\n# [power_supply_ignore_list]\n\n# name1 = this\n# name2 = is\n# name3 = an\n# name4 = example\n\n\n# settings for when using battery power\n[battery]\n# Specify which battery device to use for reading battery information. see available batteries by running: ls /sys/class/power_supply/\n# If not set, auto-cpufreq will automatically detect and use the first battery found\n# battery_device = BAT1\n\n# see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors\n# preferred governor\ngovernor = powersave\n\n# EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences\nenergy_performance_preference = power\n\n# EPB (Energy Performance Bias) for the intel_pstate driver\n# see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html\n# available EPB options include a numeric value between 0-15\n# (where 0 = maximum performance and 15 = maximum power saving),\n# or one of the following strings:\n# performance (0), balance_performance (4), default (6), balance_power (8), or power (15)\n# if the parameter is missing in the config and the hardware supports this setting, the default value will be used\n# the default value is `balance_power` (for battery)\n# energy_perf_bias = balance_power\n\n# Platform Profiles\n# https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html\n# See available options by running:\n# cat /sys/firmware/acpi/platform_profile_choices\n# platform_profile = low-power\n\n# minimum cpu frequency (in kHz)\n# example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_min_freq = 800000\n\n# maximum cpu frequency (in kHz)\n# see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n# example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000\n# to use this feature, uncomment the following line and set the value accordingly\n# scaling_max_freq = 1000000\n\n# turbo boost setting. possible values: always, auto, never\nturbo = auto\n\n# experimental \n\n# Add battery charging threshold (currently only available to Lenovo)\n# checkout README.md for more info\n\n# enable thresholds true or false\n#enable_thresholds = true\n#\n# start threshold (0 is off ) can be 0-99\n#start_threshold = 0\n#\n# stop threshold (100 is off) can be 1-100\n#stop_threshold = 100\n"
  },
  {
    "path": "auto-cpufreq.conf-example.nix",
    "content": "services.auto-cpufreq.enable = true; # enable the service\n\nservices.auto-cpufreq.settings = {\n  # settings for when connected to a power source\n  charger = {\n    # see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors\n    # preferred governor.\n    governor = \"performance\";\n\n    # EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences\n    energy_performance_preference = \"performance\";\n\n    # EPB (Energy Performance Bias) for the intel_pstate driver\n    # see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html\n    # available EPB options include a numeric value between 0-15\n    # (where 0 = maximum performance and 15 = maximum power saving),\n    # or one of the following strings:\n    # performance (0), balance_performance (4), default (6), balance_power (8), or power (15)\n    # if the parameter is missing in the config and the hardware supports this setting, the default value will be used\n    # the default value is `balance_performance` (for charger)\n    # energy_perf_bias = \"balance_performance\";\n\n    # Platform Profiles\n    # https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html\n    # See available options by running:\n    # cat /sys/firmware/acpi/platform_profile_choices\n    # platform_profile = \"performance\";\n\n    # minimum cpu frequency (in kHz)\n    # example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000\n    # see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n    # to use this feature, uncomment the following line and set the value accordingly\n    # scaling_min_freq = 800000;\n\n    # maximum cpu frequency (in kHz)\n    # example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000\n    # see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n    # to use this feature, uncomment the following line and set the value accordingly\n    # scaling_max_freq = 1000000;\n\n    # turbo boost setting. possible values: always, auto, never\n    turbo = \"auto\";\n  };\n\n  # this is for ignoring controllers and other connected devices battery from affecting\n  # laptop performance\n  # power_supply_ignore_list = {\n  #   name1 = \"this\";\n  #   name2 = \"is\";\n  #   name3 = \"an\";\n  #   name4 = \"example\";\n  # };\n\n  # settings for when using battery power\n  battery = {\n    # Specify which battery device to use for reading battery information. see available batteries by running: ls /sys/class/power_supply/\n    # If not set, auto-cpufreq will automatically detect and use the first battery found\n    # battery_device = BAT1\n\n    # see available governors by running: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors\n    # preferred governor\n    governor = \"powersave\";\n\n    # EPP: see available preferences by running: cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences\n    energy_performance_preference = \"power\";\n\n    # EPB (Energy Performance Bias) for the intel_pstate driver\n    # see conversion info: https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html\n    # available EPB options include a numeric value between 0-15\n    # (where 0 = maximum performance and 15 = maximum power saving),\n    # or one of the following strings:\n    # performance (0), balance_performance (4), default (6), balance_power (8), or power (15)\n    # if the parameter is missing in the config and the hardware supports this setting, the default value will be used\n    # the default value is `balance_power` (for battery)\n    # energy_perf_bias = \"balance_power\";\n\n    # Platform Profiles\n    # https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html\n    # See available options by running:\n    # cat /sys/firmware/acpi/platform_profile_choices\n    # platform_profile = \"low-power\";\n\n    # minimum cpu frequency (in kHz)\n    # example: for 800 MHz = 800000 kHz --> scaling_min_freq = 800000\n    # see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n    # to use this feature, uncomment the following line and set the value accordingly\n    # scaling_min_freq = 800000;\n\n    # maximum cpu frequency (in kHz)\n    # see conversion info: https://www.rapidtables.com/convert/frequency/mhz-to-hz.html\n    # example: for 1GHz = 1000 MHz = 1000000 kHz -> scaling_max_freq = 1000000\n    # to use this feature, uncomment the following line and set the value accordingly\n    # scaling_max_freq = 1000000;\n\n    # turbo boost setting. possible values: always, auto, never\n    turbo = \"auto\";\n\n    # experimental\n\n    # Add battery charging threshold (currently only available to Lenovo)\n    # checkout README.md for more info\n\n    # enable thresholds true or false\n    # enable_thresholds = true;\n    #\n    # start threshold (0 is off ) can be 0-99\n    # start_threshold = 0;\n    #\n    # stop threshold (100 is off) can be 1-100\n    # stop_threshold = 100;\n  };\n};\n"
  },
  {
    "path": "auto_cpufreq/battery_scripts/asus.py",
    "content": "#!/usr/bin/env python3\n\nfrom auto_cpufreq.battery_scripts.shared import BatteryDevice\n\n\nclass AsusBatteryDevice(BatteryDevice):\n    def __init__(self):\n        super().__init__()\n"
  },
  {
    "path": "auto_cpufreq/battery_scripts/battery.py",
    "content": "#!/usr/bin/env python3\nfrom subprocess import PIPE, run\nfrom threading import Thread\nfrom time import sleep\n\nfrom auto_cpufreq.battery_scripts.asus import AsusBatteryDevice\nfrom auto_cpufreq.battery_scripts.ideapad_laptop import IdeapadBatteryDevice\nfrom auto_cpufreq.battery_scripts.shared import BatteryDevice\n\nBATTERY_APPLY_INTERVAL = 3600  # 1 hour\n\n\ndef lsmod(module):\n    return (\n        module in run([\"lsmod\"], stdout=PIPE, stderr=PIPE, text=True).stdout\n    )\n\n\ndef battery_get_thresholds():\n    dev = get_battery_device()\n    if dev is not None:\n        return dev.print_thresholds()\n\n\ndef start_battery_daemon():\n    \"\"\"Battery daemon that applies battery charge thresholds at regular intervals.\"\"\"\n    dev = get_battery_device()\n    if dev is None:\n        print(\n            \"WARNING: No supported battery device found, battery thresholds will not be applied.\"\n        )\n        return\n\n    def battery_daemon():\n        while True:\n            try:\n                dev.apply_threshold_settings()\n            except Exception as e:\n                print(\n                    f\"ERROR: An error occurred while applying battery thresholds: {e}\"\n                )\n            sleep(BATTERY_APPLY_INTERVAL)\n\n    Thread(target=battery_daemon, daemon=True).start()\n\n\ndef get_battery_device():\n    if lsmod(\"ideapad_acpi\"):\n        return BatteryDevice()\n    elif lsmod(\"ideapad_laptop\"):\n        return IdeapadBatteryDevice()\n    elif lsmod(\"thinkpad_acpi\"):\n        return BatteryDevice()\n    elif lsmod(\"asus_wmi\"):\n        return AsusBatteryDevice()\n    else:\n        return None\n"
  },
  {
    "path": "auto_cpufreq/battery_scripts/ideapad_laptop.py",
    "content": "#!/usr/bin/env python3\n\nfrom typing import Any\nfrom auto_cpufreq.battery_scripts.shared import BatteryDevice\n\n# The Arch wiki suggests this path may vary on different models, but TLP uses\n# the same hardcoded value\nCONSERVATION_MODE_FILE = \"/sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/conservation_mode\"\n\nclass IdeapadBatteryDevice(BatteryDevice):\n    def is_conservation_mode(self) -> bool:\n        val = self._read_value_from_file(CONSERVATION_MODE_FILE)\n        if val not in (\"0\", \"1\"):\n            print(\n                f\"WARNING: could not get value from conservation mode, unexpected value!: {val}\"\n            )\n            return False\n        return val == \"1\"\n\n    def set_conservation_mode(self, value: int) -> bool:\n        if not self._write_value_to_file(CONSERVATION_MODE_FILE, value):\n            print(\"WARNING: unable to set conservation mode\")\n            return False\n        return True\n\n    def _parse_threshold_values(\n        self, start: None | str, stop: None | str\n    ) -> tuple[int, int]:\n        # Ideapad laptops don't use start/stop thresholds.\n        # They only use conservation mode, which is either on or off. So we return dummy values here.\n        return 0, 100\n\n    def _parse_ideapad_conservation_mode(self, param: None | str) -> None | bool:\n        if param is None:\n            return None\n        param = param.lower().strip()\n        if param == \"true\":\n            return True\n        elif param == \"false\":\n            return False\n        else:\n            raise ValueError(f\"Invalid value for ideapad_conservation_mode: {param}\")\n\n    def apply_threshold_settings_to_bat(self, bat: str, config: dict[str, Any]):\n        mode = config[\"ideapad_conservation_mode\"]\n        if mode is None:\n            # If conservation mode is not explicitly set, we don't change it\n            return True\n        elif mode:\n            return self.set_conservation_mode(1)\n        else:\n            return self.set_conservation_mode(0)\n\n    def print_battery_info(self, bat: str):\n        if self.is_conservation_mode():\n            print(f\"{bat} conservation mode is on\")\n        else:\n            print(f\"{bat} conservation mode is off\")\n"
  },
  {
    "path": "auto_cpufreq/battery_scripts/shared.py",
    "content": "#!/usr/bin/env python3\nimport os\nimport time\nfrom typing import Any\n\nfrom auto_cpufreq.config.config import config\nfrom auto_cpufreq.globals import POWER_SUPPLY_DIR\n\n# The charge_control_{start,end}_threshold files\n# are officially documented and preferred,\n# but some models or older kernels may only have charge_{start,stop}_threshold files\nCHARGE_START_THRESHOLD_FILES = [\n    \"charge_control_start_threshold\",\n    \"charge_start_threshold\",\n]\nCHARGE_STOP_THRESHOLD_FILES = [\n    \"charge_control_end_threshold\",\n    \"charge_stop_threshold\",\n]\n\n\nclass BatteryDevice:\n    def __init__(self):\n        self.batteries = self._get_batteries()\n        self.start_paths = {\n            bat: path\n            for bat in self.batteries\n            if (path := self._choose_threshold_file(bat, CHARGE_START_THRESHOLD_FILES))\n            is not None\n        }\n        self.stop_paths = {\n            bat: path\n            for bat in self.batteries\n            if (path := self._choose_threshold_file(bat, CHARGE_STOP_THRESHOLD_FILES))\n            is not None\n        }\n\n    def _get_batteries(self) -> list[str]:\n        \"\"\"\n        Get list of battery names from POWER_SUPPLY_DIR\n        Return list of battery names (e.g., ['BAT0', 'BAT1'])\n        \"\"\"\n        if not os.path.isdir(POWER_SUPPLY_DIR):\n            print(f\"WARNING: {POWER_SUPPLY_DIR} does NOT exist\")\n            return []\n        return [name for name in os.listdir(POWER_SUPPLY_DIR) if name.startswith(\"BAT\")]\n\n    def _choose_threshold_file(self, bat: str, files: list[str]) -> str | None:\n        \"\"\"\n        Get the charge threshold file path for given battery\n        Return the first found file path from files list, or None if not found\n        \"\"\"\n\n        for filename in files:\n            path = os.path.join(POWER_SUPPLY_DIR, bat, filename)\n            # File must exist and be writable\n            if os.path.isfile(path) and os.access(path, os.W_OK):\n                return path\n        return None\n\n    def _get_config(self) -> dict[str, str]:\n        conf = config.get_config()\n        if not conf.has_section(\"battery\"):\n            return {}\n        return dict(conf.items(\"battery\"))\n\n    def get_parsed_config(self) -> dict[str, Any]:\n        \"\"\"\n        Parse battery configuration from config file\n        Return validated and parsed config as dictionary\n        If invalid, thresholds_enabled will always be False\n        So see valid values and more info about different devices,\n        the TLP documentation is a good reference:\n        https://linrunner.de/tlp/settings/bc-vendors.html\n        \"\"\"\n        config = self._get_config()\n\n        parsed_config = {\n            \"thresholds_enabled\": False,\n            \"start_threshold\": 99,\n            \"stop_threshold\": 100,\n            \"ideapad_conservation_mode\": None,\n        }\n\n        if config.get(\"enable_thresholds\") != \"true\":\n            # Return early without further validation\n            return parsed_config\n\n        try:\n            start_val, stop_val = self._parse_threshold_values(\n                config.get(\"start_threshold\"), config.get(\"stop_threshold\")\n            )\n            parsed_config[\"start_threshold\"] = start_val\n            parsed_config[\"stop_threshold\"] = stop_val\n\n            parsed_config[\"ideapad_conservation_mode\"] = (\n                self._parse_ideapad_conservation_mode(\n                    config.get(\"ideapad_laptop_conservation_mode\")\n                )\n            )\n\n            parsed_config[\"thresholds_enabled\"] = True\n        except ValueError as e:\n            # Thresholds will not be enabled if config is invalid\n            print(f\"ERROR: {e}\")\n        return parsed_config\n\n    def _parse_threshold_values(\n        self, start: None | str, stop: None | str\n    ) -> tuple[int, int]:\n        \"\"\"\n        Parse and validate start and stop threshold values\n        This method should be overridden in subclasses if needed\n        Return tuple of (start, stop) as integers if valid\n        Raise ValueError if invalid\n        \"\"\"\n        if start is None or stop is None:\n            raise ValueError(\"Start and stop thresholds must be set\")\n        if not start.isdigit() or not stop.isdigit():\n            raise ValueError(\"Start and stop thresholds must be integers\")\n        start_val = int(start)\n        stop_val = int(stop)\n        if not (0 <= start_val <= 99):\n            raise ValueError(\"Start threshold must be between 0 and 99\")\n        if not (1 <= stop_val <= 100):\n            raise ValueError(\"Stop threshold must be between 1 and 100\")\n        if start_val >= stop_val:\n            raise ValueError(\"Start threshold must be less than stop threshold\")\n        return start_val, stop_val\n\n    def _parse_ideapad_conservation_mode(self, param: None | str) -> None | bool:\n        \"\"\"\n        Parse ideapad conservation mode value from config\n        This method is overridden in IdeapadBatteryDevice subclass\n        Returns None when the conservation mode is explicitly not set.\n        \"\"\"\n        return None\n\n    def set_battery_thresholds(self, bat, start: int, stop: int) -> bool:\n        \"\"\"\n        Set battery thresholds for given battery\n        Return true/false depending on if command is executed and fails (or succeeds)\n        \"\"\"\n\n        if bat not in self.start_paths or bat not in self.stop_paths:\n            print(f\"WARNING: battery {bat} has no threshold attributes\")\n            return False\n\n        # First set stop to 100 to avoid potential 'invalid argument'\n        # errors when start >= stop\n        if not self._write_value_to_file(self.stop_paths[bat], 100):\n            return False\n\n        time.sleep(0.1)\n\n        if not self._write_value_to_file(self.start_paths[bat], start):\n            return False\n        if not self._write_value_to_file(self.stop_paths[bat], stop):\n            return False\n\n        return True\n\n    def _write_value_to_file(self, path: str, value: str | int) -> bool:\n        try:\n            with open(path, \"w\") as f:\n                f.write(str(value))\n            return True\n        except Exception as e:\n            print(f\"ERROR: Could not write value {value} to {path}: {e}\")\n            return False\n\n    def _read_value_from_file(self, path: str, default: str = \"\") -> str:\n        try:\n            with open(path, \"r\") as f:\n                output = f.read()\n            return output.strip()\n        except Exception as e:\n            print(f\"ERROR: Could not read value from {path}: {e}\")\n            return default\n\n    def get_current_threshold(self, bat: str) -> tuple[int | None, int | None]:\n\n        if bat not in self.start_paths or bat not in self.stop_paths:\n            print(f\"WARNING: battery {bat} has no threshold attributes\")\n            return None, None\n\n        start = self._read_value_from_file(self.start_paths[bat])\n        stop = self._read_value_from_file(self.stop_paths[bat])\n        start = int(start) if start.isdigit() else None\n        stop = int(stop) if stop.isdigit() else None\n        return start, stop\n\n    def print_thresholds(self):\n        print(\n            \"\\n-------------------------------- Battery Info ---------------------------------\\n\"\n        )\n        print(f\"battery count = {len(self.batteries)}\")\n        for bat in self.batteries:\n            self.print_battery_info(bat)\n\n    def print_battery_info(self, bat: str):\n        start_value, stop_value = self.get_current_threshold(bat)\n        if start_value is None or stop_value is None:\n            print(f\"ERROR: failed to read battery {bat} thresholds\")\n        else:\n            print(f\"{bat} start threshold = {start_value}\")\n            print(f\"{bat} stop threshold = {stop_value}\")\n\n    def apply_threshold_settings_to_bat(self, bat: str, config: dict[str, Any]):\n        return self.set_battery_thresholds(\n            bat,\n            config[\"start_threshold\"],\n            config[\"stop_threshold\"],\n        )\n\n    def apply_threshold_settings(self):\n        parsed_config = self.get_parsed_config()\n        if not parsed_config[\"thresholds_enabled\"]:\n            return\n        if not self.batteries:\n            print(\"WARNING: no batteries found to set thresholds for\")\n            return\n\n        for bat in self.batteries:\n            if not self.apply_threshold_settings_to_bat(bat, parsed_config):\n                print(f\"ERROR: failed to set thresholds for battery {bat}\")\n"
  },
  {
    "path": "auto_cpufreq/bin/auto_cpufreq.py",
    "content": "#!/usr/bin/env python3\n#\n# auto-cpufreq - Automatic CPU speed & power optimizer for Linux\n#\n# Blog post: https://foolcontrol.org/?p=3124\n\n# core import\nimport sys, time, os\nfrom subprocess import run\nfrom shutil import rmtree\n\nfrom auto_cpufreq.battery_scripts.battery import *\nfrom auto_cpufreq.config.config import config as conf, find_config_file\nfrom auto_cpufreq.core import *\nfrom auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_AUR, IS_INSTALLED_WITH_SNAP\nfrom auto_cpufreq.modules.system_monitor import ViewType, SystemMonitor\n# import everything from power_helper, including bluetooth_disable and bluetooth_enable\nfrom auto_cpufreq.power_helper import *\nfrom threading import Thread\n\n@click.command()\n@click.option(\"--monitor\", is_flag=True, help=\"Monitor and see suggestions for CPU optimizations\")\n@click.option(\"--live\", is_flag=True, help=\"Monitor and make (temp.) suggested CPU optimizations\")\n@click.option(\"--daemon\", is_flag=True, hidden=True)\n@click.option(\"--install\", is_flag=True, help=\"Install daemon for (permanent) automatic CPU optimizations\")\n@click.option(\"--update\", is_flag=False, help=\"Update daemon and package for (permanent) automatic CPU optimizations\", flag_value=\"--update\")\n@click.option(\"--remove\", is_flag=True, help=\"Remove daemon for (permanent) automatic CPU optimizations\")\n@click.option(\"--force\", is_flag=False, help=\"Force use of either \\\"powersave\\\" or \\\"performance\\\" governors. Setting to \\\"reset\\\" will go back to normal mode\")\n@click.option(\"--turbo\", is_flag=False, help=\"Force use of CPU turbo mode, if supported, with \\\"never\\\" or \\\"always\\\". Setting to \\\"auto\\\" automatically handles turbo mode\")\n@click.option(\"--config\", is_flag=False, required=False, help=\"Use config file at defined path\",)\n@click.option(\"--stats\", is_flag=True, help=\"View live stats of CPU optimizations made by daemon\")\n@click.option(\"--get-state\", is_flag=True, hidden=True)\n@click.option(\"--bluetooth_boot_off\", is_flag=True, help=\"Turn off Bluetooth on boot\")\n@click.option(\"--bluetooth_boot_on\", is_flag=True, help=\"Turn on Bluetooth on boot\")\n@click.option(\"--debug\", is_flag=True, help=\"Show debug info (include when submitting bugs)\")\n@click.option(\"--version\", is_flag=True, help=\"Show currently installed version\")\n@click.option(\"--donate\", is_flag=True, help=\"Support the project\")\ndef main(monitor, live, daemon, install, update, remove, force, turbo, config, stats, get_state,\n          bluetooth_boot_off, bluetooth_boot_on, debug, version, donate):\n    # display info if config file is used\n    config_path = find_config_file(config)\n    conf.set_path(config_path)\n    def config_info_dialog():\n        if conf.has_config():\n            print(\"\\nUsing settings defined in \" + config_path + \" file\")\n\n    if len(sys.argv) == 1:\n        print(\"\\n\" + \"-\" * 32 + \" auto-cpufreq \" + \"-\" * 33 + \"\\n\")\n        print(\"Automatic CPU speed & power optimizer for Linux\")\n \n        print(\"\\nExample usage:\\nauto-cpufreq --monitor\")\n        print(\"\\n-----\\n\")\n\n        run([\"auto-cpufreq\", \"--help\"])\n        footer()\n    else:\n        # set governor override unless None or invalid\n        if force is not None:\n            not_running_daemon_check()\n            root_check() # Calling root_check before set_override as it will require sudo access\n            set_override(force) # Calling set override, only if force has some values\n        \n        if turbo is not None:\n            not_running_daemon_check()\n            root_check()\n            set_turbo_override(turbo)\n\n        if monitor:\n            root_check()\n            conf.notifier.start()\n            if IS_INSTALLED_WITH_SNAP:\n                gnome_power_detect_snap()\n                tlp_service_detect_snap()\n            else:\n                gnome_power_detect()\n                tlp_service_detect()\n                \n            if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):\n                try:\n                    input(\"press Enter to continue or Ctrl + c to exit...\")\n                except KeyboardInterrupt:\n                    conf.notifier.stop()\n                    sys.exit(0)\n            \n            monitor = SystemMonitor(suggestion=True, type=ViewType.MONITOR)\n            monitor.run(on_quit=conf.notifier.stop)\n        elif live:\n            root_check()\n            start_battery_daemon()\n            conf.notifier.start()\n            if IS_INSTALLED_WITH_SNAP:\n                gnome_power_detect_snap()\n                tlp_service_detect_snap()\n            else:\n                gnome_power_detect_install()\n                gnome_power_stop_live()\n                tuned_stop_live()\n                tlp_service_detect()\n            \n            if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):\n                try:\n                    input(\"press Enter to continue or Ctrl + c to exit...\")\n                except KeyboardInterrupt:\n                    conf.notifier.stop()\n                    sys.exit(0)\n            \n            cpufreqctl()\n            def live_daemon():\n                # Redirect stdout to suppress prints\n                class NullWriter:\n                    def write(self, _): pass\n                    def flush(self): pass\n                try:\n                    sys.stdout = NullWriter()\n                    \n                    while True:\n                        time.sleep(1)\n                        set_autofreq()\n                except:\n                    pass\n            \n            def live_daemon_off():\n                gnome_power_start_live()\n                tuned_start_live()\n                cpufreqctl_restore()\n                conf.notifier.stop()\n            \n            thread = Thread(target=live_daemon, daemon=True)\n            thread.start()\n            \n            monitor = SystemMonitor(type=ViewType.LIVE)\n            monitor.run(on_quit=live_daemon_off)\n        elif daemon:\n            config_info_dialog()\n            root_check()\n            file_stats()\n            if IS_INSTALLED_WITH_SNAP and SNAP_DAEMON_CHECK == \"enabled\":\n                gnome_power_detect_snap()\n                tlp_service_detect_snap()\n            elif not IS_INSTALLED_WITH_SNAP:\n                gnome_power_detect()\n                tlp_service_detect()\n            start_battery_daemon()\n            conf.notifier.start()\n            while True:\n                try:\n                    footer()\n                    gov_check()\n                    cpufreqctl()\n                    distro_info()\n                    sysinfo()\n                    set_autofreq()\n                    countdown(2)\n                except KeyboardInterrupt: break\n            conf.notifier.stop()\n        elif install:\n            root_check()\n            if IS_INSTALLED_WITH_SNAP:\n                running_daemon_check()\n                gnome_power_detect_snap()\n                tlp_service_detect_snap()\n                bluetooth_notif_snap()\n                gov_check()\n                run(\"snapctl set daemon=enabled\", shell=True)\n                run(\"snapctl start --enable auto-cpufreq\", shell=True)\n            else:\n                running_daemon_check()\n                gov_check()\n                deploy_daemon()\n            deploy_complete_msg()\n        elif update:\n            root_check()\n            custom_dir = \"/opt/auto-cpufreq/source\"\n            for arg in sys.argv:\n                if arg.startswith(\"--update=\"):\n                    custom_dir = arg.split(\"=\")[1]\n                    sys.argv.remove(arg)\n                    \n            if \"--update\" in sys.argv:\n                update = True\n                sys.argv.remove(\"--update\")\n                if len(sys.argv) == 2: custom_dir = sys.argv[1] \n                    \n            if IS_INSTALLED_WITH_SNAP:\n                print(\"Detected auto-cpufreq was installed using snap\")\n                # refresh snap directly using this command\n                # path wont work in this case\n\n                print(\"Please update using snap package manager, i.e: `sudo snap refresh auto-cpufreq`.\")\n                #check for AUR \n            elif IS_INSTALLED_WITH_AUR: print(\"Arch-based distribution with AUR support detected. Please refresh auto-cpufreq using your AUR helper.\")\n            else:\n                is_new_update = check_for_update()\n                if not is_new_update: return\n                ans = input(\"Do you want to update auto-cpufreq to the latest release? [Y/n]: \").strip().lower()\n                if not os.path.exists(custom_dir): os.makedirs(custom_dir)\n                if os.path.exists(os.path.join(custom_dir, \"auto-cpufreq\")): rmtree(os.path.join(custom_dir, \"auto-cpufreq\"))\n                if ans in ['', 'y', 'yes']:\n                    remove_daemon()\n                    remove_complete_msg()\n                    new_update(custom_dir)\n                    print(\"enabling daemon\")\n                    run([\"auto-cpufreq\", \"--install\"])\n                    print(\"auto-cpufreq is installed with the latest version\")\n                    run([\"auto-cpufreq\", \"--version\"])\n                else: print(\"Aborted\")\n        elif remove:\n            root_check()\n            if IS_INSTALLED_WITH_SNAP:\n                run(\"snapctl set daemon=disabled\", shell=True)\n                run(\"snapctl stop --disable auto-cpufreq\", shell=True)\n                if auto_cpufreq_stats_path.exists():\n                    if auto_cpufreq_stats_file is not None:\n                        auto_cpufreq_stats_file.close()\n\n                    auto_cpufreq_stats_path.unlink()\n                # ToDo: \n                # {the following snippet also used in --update, update it there too(if required)}\n                # * undo bluetooth boot disable\n                gnome_power_rm_reminder_snap()\n            else: remove_daemon()\n            remove_complete_msg()\n        elif stats:\n            not_running_daemon_check()\n            config_info_dialog()\n            if IS_INSTALLED_WITH_SNAP:\n                gnome_power_detect_snap()\n                tlp_service_detect_snap()\n            else:\n                gnome_power_detect()\n                tlp_service_detect()\n            \n            if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):\n                try:\n                    input(\"press Enter to continue or Ctrl + c to exit...\")\n                except KeyboardInterrupt:\n                    conf.notifier.stop()\n                    sys.exit(0)\n            \n            monitor = SystemMonitor(type=ViewType.STATS)\n            monitor.run()\n        elif get_state:\n            not_running_daemon_check()\n            override = get_override()\n            print(override)\n        elif bluetooth_boot_off:\n            if IS_INSTALLED_WITH_SNAP:\n                footer()\n                bluetooth_notif_snap()\n                footer()\n            else:\n                footer()\n                root_check()\n                bluetooth_disable()\n                footer()\n        elif bluetooth_boot_on:\n            if IS_INSTALLED_WITH_SNAP:\n                footer()\n                bluetooth_on_notif_snap()\n                footer()\n            else:\n                footer()\n                root_check()\n                bluetooth_enable()\n                footer()\n        elif debug:\n            # ToDo: add status of GNOME Power Profile service status\n            config_info_dialog()\n            root_check()\n            battery_get_thresholds()\n            cpufreqctl()\n            footer()\n            distro_info()\n            sysinfo()\n            print()\n            app_version()\n            print()\n            python_info()\n            print()\n            device_info()\n            print(f\"Battery is: {'' if charging() else 'dis'}charging\")\n            print()\n            app_res_use()\n            get_load()\n            get_current_gov()\n            get_turbo()\n            footer()\n        elif version:\n            footer()\n            distro_info()\n            app_version()\n            footer()\n        elif donate:\n            footer()\n            print(\"If auto-cpufreq helped you out and you find it useful ...\\n\")\n            print(\"Show your appreciation by donating!\")\n            print(GITHUB+\"#donate\")\n            footer()\n                \nif __name__ == \"__main__\": main()\n"
  },
  {
    "path": "auto_cpufreq/bin/auto_cpufreq_gtk.py",
    "content": "#!/usr/bin/env python3\nimport gi\ngi.require_version(\"Gtk\", \"3.0\")\nfrom gi.repository import Gtk, GLib\n\nfrom auto_cpufreq.gui.app import ToolWindow\n\ndef main():\n    GLib.set_prgname(\"auto-cpufreq-gtk\")\n    win = ToolWindow()\n    win.connect(\"destroy\", Gtk.main_quit)\n    win.show_all()\n    win.handle_update()\n    Gtk.main()\n\nif __name__ == \"__main__\": main()\n"
  },
  {
    "path": "auto_cpufreq/config/config.py",
    "content": "import os, pyinotify, sys\nfrom configparser import ConfigParser, ParsingError\nfrom subprocess import run, PIPE\n\nfrom auto_cpufreq.config.config_event_handler import ConfigEventHandler\n\ndef find_config_file(args_config_file) -> str:\n    \"\"\"\n    Find the config file to use.\n\n    Look for a config file in the following priorization order:\n    1. Command line argument\n    2. User config file\n    3. System config file\n\n    :param args_config_file: Path to the config file provided as a command line argument\n    :return: The path to the config file to use\n    \"\"\"\n    # Prepare paths\n\n    # use $SUDO_USER or $USER to get home dir since sudo can't access\n    # user env vars\n    home = run([\"getent passwd ${SUDO_USER:-$USER} | cut -d: -f6\"],\n        shell=True,\n        stdout=PIPE,\n        universal_newlines=True\n    ).stdout.rstrip()\n    user_config_dir = os.getenv(\"XDG_CONFIG_HOME\", default=os.path.join(home, \".config\"))\n    user_config_file = os.path.join(user_config_dir, \"auto-cpufreq/auto-cpufreq.conf\")\n    system_config_file = \"/etc/auto-cpufreq.conf\"\n\n    if args_config_file is not None:                                # (1) Command line argument was specified\n        # Check if the config file path points to a valid file\n        if os.path.isfile(args_config_file): return args_config_file\n        else:\n            # Not a valid file\n            print(f\"Config file specified with '--config {args_config_file}' not found.\")\n            sys.exit(1)\n    elif os.path.isfile(user_config_file): return user_config_file  # (2) User config file\n    else: return system_config_file                                 # (3) System config file (default if nothing else is found)\n\nclass _Config:\n    def __init__(self) -> None:\n        self.path: str = \"\"\n        self._config: ConfigParser = ConfigParser()\n        self.watch_manager: pyinotify.WatchManager = pyinotify.WatchManager()\n        self.config_handler = ConfigEventHandler(self)\n\n        # check for file changes using threading\n        self.notifier: pyinotify.ThreadedNotifier = pyinotify.ThreadedNotifier(self.watch_manager, self.config_handler)\n        \n    def set_path(self, path: str) -> None:\n        self.path = path\n        mask = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY | pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO\n        self.watch_manager.add_watch(os.path.dirname(path), mask=mask)\n        if os.path.isfile(path): self.update_config()\n\n    def has_config(self) -> bool: return os.path.isfile(self.path)\n    \n    def get_config(self) -> ConfigParser: return self._config\n    \n    def update_config(self) -> None:\n        # create new ConfigParser to prevent old data from remaining\n        self._config = ConfigParser()\n        try: self._config.read(self.path)\n        except ParsingError as e: print(f\"The following error occured while parsing the config file: \\n{repr(e)}\")\n\nconfig = _Config()"
  },
  {
    "path": "auto_cpufreq/config/config_event_handler.py",
    "content": "from pyinotify import Event, ProcessEvent\n\nclass ConfigEventHandler(ProcessEvent):\n    def __init__(self, config) -> None:\n        self.config = config\n\n    def _process_update(self, event: Event):\n        if event.pathname.rstrip(\"~\") == self.config.path: self.config.update_config()\n\n    # activates when auto-cpufreq config file is modified\n    def process_IN_MODIFY(self, event: Event) -> None: self._process_update(event)\n\n    # activates when auto-cpufreq config file is deleted\n    def process_IN_DELETE(self, event: Event) -> None: self._process_update(event)\n\n    # activates when auto-cpufreq config file is created\n    def process_IN_CREATE(self, event: Event) -> None: self._process_update(event)\n\n    # activates when auto-cpufreq config file is moved from watched directory\n    def process_IN_MOVED_FROM(self, event: Event) -> None: self._process_update(event)\n\n    # activates when auto-cpufreq config file is moved into the watched directory\n    def process_IN_MOVED_TO(self, event: Event) -> None: self._process_update(event)"
  },
  {
    "path": "auto_cpufreq/core.py",
    "content": "#!/usr/bin/env python3\n#\n# auto-cpufreq - core functionality\nimport click, distro, os, platform, psutil, sys\nfrom importlib.metadata import metadata, PackageNotFoundError\nfrom math import isclose\nfrom pathlib import Path\nfrom pickle import dump, load\nfrom re import search\nfrom requests import get, exceptions\nfrom shutil import copy\nfrom subprocess import call, check_output, DEVNULL, getoutput, run\nfrom time import sleep\nfrom warnings import filterwarnings\n\nfrom auto_cpufreq.config.config import config\nfrom auto_cpufreq.globals import (\n    ALL_GOVERNORS, AVAILABLE_GOVERNORS, AVAILABLE_GOVERNORS_SORTED, GITHUB, IS_INSTALLED_WITH_AUR, IS_INSTALLED_WITH_SNAP, POWER_SUPPLY_DIR, SNAP_DAEMON_CHECK\n)\nfrom auto_cpufreq.power_helper import *\n\nfilterwarnings(\"ignore\")\n\n# add path to auto-cpufreq executables for GUI\nif \"PATH\" in os.environ:\n    os.environ[\"PATH\"] += os.pathsep + \"/usr/local/bin\"\nelse:\n    os.environ[\"PATH\"] = \"/usr/local/bin\"\n\n# ToDo:\n# - replace get system/CPU load from: psutil.getloadavg() | available in 5.6.2)\n\nSCRIPTS_DIR = Path(\"/usr/local/share/auto-cpufreq/scripts/\")\nCPUS = os.cpu_count()\n\n\n\n# Note:\n# \"load1m\" & \"cpuload\" can't be global vars and to in order to show correct data must be\n# decraled where their execution takes place\n\n# powersave/performance system load thresholds\nperformance_load_threshold = (50 * CPUS) / 100\npowersave_load_threshold = (75 * CPUS) / 100\n\n# auto-cpufreq stats file path\nauto_cpufreq_stats_file = None\nauto_cpufreq_stats_path = None\n\n# track governor override and turbo boost override\nif IS_INSTALLED_WITH_SNAP:\n    auto_cpufreq_stats_path = Path(\"/var/snap/auto-cpufreq/current/auto-cpufreq.stats\")\n    governor_override_state = Path(\"/var/snap/auto-cpufreq/current/override.pickle\")\n    turbo_override_state    = Path(\"/var/snap/auto-cpufreq/current/turbo-override.pickle\")\nelse:\n    auto_cpufreq_stats_path = Path(\"/var/run/auto-cpufreq.stats\")\n    governor_override_state = Path(\"/opt/auto-cpufreq/override.pickle\")\n    turbo_override_state    = Path(\"/opt/auto-cpufreq/turbo-override.pickle\")\n\ndef file_stats():\n    global auto_cpufreq_stats_file\n    auto_cpufreq_stats_file = open(auto_cpufreq_stats_path, \"w\")\n    sys.stdout = auto_cpufreq_stats_file\n\ndef get_override():\n    if os.path.isfile(governor_override_state):\n        with open(governor_override_state, \"rb\") as store: return load(store)\n    else: return \"default\"\n\ndef set_override(override):\n    if override in [\"powersave\", \"performance\"]:\n        with open(governor_override_state, \"wb\") as store:\n            dump(override, store)\n        print(f\"Set governor override to {override}\")\n    elif override == \"reset\":\n        if os.path.isfile(governor_override_state):\n            os.remove(governor_override_state)\n        print(\"Governor override removed\")\n    elif override is not None: print(\"Invalid option.\\nUse force=performance, force=powersave, or force=reset\")\n\ndef get_turbo_override():\n    if os.path.isfile(turbo_override_state):\n        with open(turbo_override_state, \"rb\") as store: return load(store)\n    else: return \"auto\"\n\ndef set_turbo_override(override):\n    if override in [\"never\", \"always\"]:\n        with open(turbo_override_state, \"wb\") as store:\n            dump(override, store)\n        print(f\"Set turbo boost override to {override}\")\n    elif override == \"auto\":\n        if os.path.isfile(turbo_override_state):\n            os.remove(turbo_override_state)\n        print(\"Turbo override removed\")\n    elif override is not None: print(\"Invalid option.\\nUse turbo=always, turbo=never, or turbo=auto\")\n\n# get distro name\ntry: dist_name = distro.id()\nexcept PermissionError:\n    # Current work-around for Pop!_OS where symlink causes permission issues\n    print(\"[!] Warning: Cannot get distro name\")\n    if IS_INSTALLED_WITH_SNAP and os.path.exists(\"/etc/pop-os/os-release\"):\n        print(\"[!] Snap install on PopOS detected, you must manually run the following\"\n                \" commands in another terminal:\\n\")\n        print(\"[!] Backup the /etc/os-release file:\")\n        print(\"sudo mv /etc/os-release /etc/os-release-backup\\n\")\n        print(\"[!] Create hardlink to /etc/os-release:\")\n        print(\"sudo ln /etc/pop-os/os-release /etc/os-release\\n\")\n        print(\"[!] Aborting. Restart auto-cpufreq when you created the hardlink\")\n    else:\n        print(\"[!] Check /etc/os-release permissions and make sure it is not a symbolic link\")\n        print(\"[!] Aborting...\")\n    sys.exit(1)\n\n# display running version of auto-cpufreq\ndef app_version():\n    print(\"auto-cpufreq version: \", end=\"\")\n\n    if IS_INSTALLED_WITH_SNAP: print(getoutput(r\"echo \\(Snap\\) $SNAP_VERSION\"))\n    elif IS_INSTALLED_WITH_AUR: print(getoutput(\"pacman -Qi auto-cpufreq | grep Version\"))\n    else:\n        try: print(get_formatted_version())\n        except Exception as e: print(repr(e))\n\ndef check_for_update():\n    # returns True if a new release is available from the GitHub repo\n\n    # Specify the repository and package name\n    # IT IS IMPORTANT TO  THAT IF THE REPOSITORY STRUCTURE IS CHANGED, THE FOLLOWING FUNCTION NEEDS TO BE UPDATED ACCORDINGLY\n    # Fetch the latest release information from GitHub API\n    latest_release_url = GITHUB.replace(\"github.com\", \"api.github.com/repos\") + \"/releases/latest\"\n    try:\n        response = get(latest_release_url)\n        if response.status_code == 200: latest_release = response.json()\n        else:\n            message = response.json().get(\"message\")\n            print(\"Error fetching recent release!\")\n            if message is not None and message.startswith(\"API rate limit exceeded\"):\n                print(\"GitHub Rate limit exceeded. Please try again later within 1 hour or use different network/VPN.\")\n            else: print(\"Unexpected status code:\", response.status_code)\n            return False\n    except (exceptions.ConnectionError, exceptions.Timeout,\n            exceptions.RequestException, exceptions.HTTPError):\n        print(\"Error Connecting to server!\")\n        return False\n\n    latest_version = latest_release.get(\"tag_name\")\n\n    if latest_version is not None:\n        # Get the current version of auto-cpufreq\n        # Extract version number from the output string\n        output = check_output(['auto-cpufreq', '--version']).decode('utf-8')\n        try: version_line = next((search(r'\\d+\\.\\d+\\.\\d+', line).group() for line in output.split('\\n') if line.startswith('auto-cpufreq version')), None)\n        except AttributeError:\n            print(\"Error Retrieving Current Version!\")\n            exit(1)\n        installed_version = \"v\" + version_line\n        #Check whether the same is installed or not\n        # Compare the latest version with the installed version and perform update if necessary\n        if latest_version == installed_version:\n            print(\"auto-cpufreq is up to date\")\n            return False\n        else:\n            print(f\"Updates are available,\\nCurrent version: {installed_version}\\nLatest version: {latest_version}\")\n            print(\"Note that your previous custom settings might be erased with the following update\")\n            return True\n    # Handle the case where \"tag_name\" key doesn't exist\n    else: print(\"Malformed Released data!\\nReinstall manually or Open an issue on GitHub for help!\")\n\ndef new_update(custom_dir):\n    os.chdir(custom_dir)\n    print(f\"Cloning the latest release to {custom_dir}\")\n    run([\"git\", \"clone\", GITHUB+\".git\"])\n    os.chdir(\"auto-cpufreq\")\n    print(f\"package cloned to directory {custom_dir}\")\n    run(['./auto-cpufreq-installer'], input='i\\n', encoding='utf-8')\n\ndef get_literal_version(package_name):\n    try:\n        package_metadata = metadata(package_name)\n        package_name = package_metadata['Name']\n        numbered_version, _, git_version = package_metadata['Version'].partition(\"+\")\n\n        return f\"{numbered_version}+{git_version}\" # Construct the literal version string\n\n    except PackageNotFoundError: return f\"Package '{package_name}' not found\"\n\n# return formatted version for a better readability\ndef get_formatted_version():\n    splitted_version = get_literal_version(\"auto-cpufreq\").split(\"+\")\n    return splitted_version[0] + (\"\" if len(splitted_version) > 1 else \" (git: \" + splitted_version[1] + \")\")\n\ndef app_res_use():\n    p = psutil.Process()\n    print(\"auto-cpufreq system resource consumption:\")\n    print(\"cpu usage:\", p.cpu_percent(), \"%\")\n    print(\"memory use:\", round(p.memory_percent(), 2), \"%\")\n\n# set/change state of turbo\ndef turbo(value: bool = None):\n    \"\"\"\n    Get and set turbo mode\n    \"\"\"\n    p_state = Path(\"/sys/devices/system/cpu/intel_pstate/no_turbo\")\n    cpufreq = Path(\"/sys/devices/system/cpu/cpufreq/boost\")\n    amd_pstate = Path(\"/sys/devices/system/cpu/amd_pstate/status\")\n\n    if p_state.exists():\n        inverse = True\n        f = p_state\n    elif cpufreq.exists():\n        f = cpufreq\n        inverse = False\n    elif amd_pstate.exists():\n        amd_value = amd_pstate.read_text().strip()\n        if amd_value == \"active\":\n            print(\"CPU turbo is controlled by amd-pstate-epp driver\")\n        # Basically, no other value should exist.\n        return False\n    else:\n        print(\"Warning: CPU turbo is not available\")\n        return False\n    \n    turbo_override = get_turbo_override()\n    if turbo_override != \"auto\":\n        # Set the value in respect to if turbo override is enabled or not.\n        if turbo_override == \"always\":\n            value = True\n        elif turbo_override == \"never\":\n            value = False\n\n    if value is not None:\n        try: f.write_text(f\"{int(value ^ inverse)}\\n\")\n        except PermissionError:\n            print(\"Warning: Changing CPU turbo is not supported. Skipping.\")\n            return False\n\n    return bool(int(f.read_text().strip())) ^ inverse\n\ndef get_turbo(): print(\"Currently turbo boost is:\", \"on\" if turbo() else \"off\")\ndef set_turbo(value:bool):\n    print(\"Setting turbo boost:\", \"on\" if value else \"off\")\n    turbo(value)\n\n\n# ignore these devices under /sys/class/power_supply/\ndef get_power_supply_ignore_list():\n\n    conf = config.get_config()\n\n    list = []\n\n    if conf.has_section(\"power_supply_ignore_list\"):\n        for i in conf[\"power_supply_ignore_list\"]:\n            list.append(conf[\"power_supply_ignore_list\"][i])\n\n    # these are hard coded power supplies that will always be ignored\n    list.append(\"hidpp_battery\")\n    return list\n\n\ndef charging():\n    \"\"\"\n    get charge state: is battery charging or discharging\n    \"\"\"\n    # sort it so AC is 'always' first\n    if os.path.exists(Path(POWER_SUPPLY_DIR)):\n        power_supplies = sorted(os.listdir(Path(POWER_SUPPLY_DIR)))\n    else: return True # no sysfs entries, nothing to do.\n    POWER_SUPPLY_IGNORELIST = get_power_supply_ignore_list()\n\n    # check if we found power supplies. on a desktop these are not found and we assume we are on a powercable.\n    if len(power_supplies) == 0: return True # nothing found, so nothing to check\n\n    # we found some power supplies, lets check their state\n    for supply in power_supplies:\n        # Check if supply is in ignore list, if found in ignore list, skip it.\n        if any(item in supply for item in POWER_SUPPLY_IGNORELIST): continue\n\n        power_supply_type_path = Path(POWER_SUPPLY_DIR + supply + \"/type\")\n        if not power_supply_type_path.exists(): continue\n        with open(power_supply_type_path) as f: supply_type = f.read()[:-1]\n\n        if supply_type == \"Mains\":\n            # we found an AC\n            power_supply_online_path = Path(POWER_SUPPLY_DIR + supply + \"/online\")\n            if not power_supply_online_path.exists(): continue\n            with open(power_supply_online_path) as f:\n                if int(f.read()[:-1]) == 1: return True # we are definitely charging\n        elif supply_type == \"Battery\":\n            # we found a battery, check if its being discharged\n            power_supply_status_path = Path(POWER_SUPPLY_DIR + supply + \"/status\")\n            if not power_supply_status_path.exists(): continue\n            with open(power_supply_status_path) as f:\n                # we found a discharging battery\n                if str(f.read()[:-1]) == \"Discharging\": return False\n\n    return True # we cannot determine discharging state, assume we are on powercable\n\ndef get_current_gov():\n    return print(\n        \"Currently using:\",\n        getoutput(\"cpufreqctl.auto-cpufreq --governor\").strip().split(\" \")[0],\n        \"governor\",\n    )\n\ndef cpufreqctl():\n    \"\"\"\n    deploy cpufreqctl.auto-cpufreq script\n    \"\"\"\n    if not (IS_INSTALLED_WITH_SNAP or os.path.isfile(\"/usr/local/bin/cpufreqctl.auto-cpufreq\")):\n        copy(SCRIPTS_DIR / \"cpufreqctl.sh\", \"/usr/local/bin/cpufreqctl.auto-cpufreq\")\n        call([\"chmod\", \"a+x\", \"/usr/local/bin/cpufreqctl.auto-cpufreq\"])\n\ndef cpufreqctl_restore():\n    \"\"\"\n    remove cpufreqctl.auto-cpufreq script\n    \"\"\"\n    if not IS_INSTALLED_WITH_SNAP and os.path.isfile(\"/usr/local/bin/cpufreqctl.auto-cpufreq\"):\n        os.remove(\"/usr/local/bin/cpufreqctl.auto-cpufreq\")\n\ndef footer(l=79): print(\"\\n\" + \"-\" * l + \"\\n\")\n\ndef deploy_complete_msg():\n    print(\"\\n\" + \"-\" * 17 + \" auto-cpufreq daemon installed and running \" + \"-\" * 17 + \"\\n\")\n    print(\"To view live stats, run:\\nauto-cpufreq --stats\")\n    print(\"\\nauto-cpufreq makes all decisions automatically, if you would like to\")\n    print(\"configure certain setting to your own liking, please refer to:\\nhttps://github.com/AdnanHodzic/auto-cpufreq#configuring-auto-cpufreq\")\n    print(\"\\nTo disable and remove auto-cpufreq daemon, run:\\nsudo auto-cpufreq --remove\")\n    footer()\n\ndef remove_complete_msg():\n    print(\"\\n\" + \"-\" * 25 + \" auto-cpufreq daemon removed \" + \"-\" * 25 + \"\\n\")\n    print(\"auto-cpufreq successfully removed.\")\n    footer()\n\ndef deploy_daemon():\n    print(\"\\n\" + \"-\" * 21 + \" Deploying auto-cpufreq as a daemon \" + \"-\" * 22 + \"\\n\")\n\n    cpufreqctl() # deploy cpufreqctl script func call\n\n    bluetooth_disable() # turn off bluetooth on boot\n\n    auto_cpufreq_stats_path.touch(exist_ok=True)\n\n    print(\"\\n* Deploy auto-cpufreq install script\")\n    copy(SCRIPTS_DIR / \"auto-cpufreq-install.sh\", \"/usr/local/bin/auto-cpufreq-install\")\n    call([\"chmod\", \"a+x\", \"/usr/local/bin/auto-cpufreq-install\"])\n\n    print(\"\\n* Deploy auto-cpufreq remove script\")\n    copy(SCRIPTS_DIR / \"auto-cpufreq-remove.sh\", \"/usr/local/bin/auto-cpufreq-remove\")\n    call([\"chmod\", \"a+x\", \"/usr/local/bin/auto-cpufreq-remove\"])\n\n    # output warning if gnome power profile is running\n    gnome_power_detect_install()\n    gnome_power_svc_disable()\n\n    tuned_svc_disable()\n\n    tlp_service_detect() # output warning if TLP service is detected\n\n    call(\"/usr/local/bin/auto-cpufreq-install\", shell=True)\n\ndef deploy_daemon_performance():\n    print(\"\\n\" + \"-\" * 21 + \" Deploying auto-cpufreq as a daemon (performance) \" + \"-\" * 22 + \"\\n\")\n\n    # check that performance is in scaling_available_governors\n    if \"performance\" not in AVAILABLE_GOVERNORS_SORTED:\n        print(\"\\\"performance\\\" governor is unavailable on this system, run:\\n\"\n            \"sudo sudo auto-cpufreq --install\\n\\n\"\n            \"to install auto-cpufreq using default \\\"balanced\\\" governor.\\n\")\n\n    cpufreqctl() # deploy cpufreqctl script func call\n\n    bluetooth_disable() # turn off bluetooth on boot\n\n    auto_cpufreq_stats_path.touch(exist_ok=True)\n\n    print(\"\\n* Deploy auto-cpufreq install script\")\n    copy(SCRIPTS_DIR / \"auto-cpufreq-install.sh\", \"/usr/local/bin/auto-cpufreq-install\")\n\n    print(\"\\n* Deploy auto-cpufreq remove script\")\n    copy(SCRIPTS_DIR / \"auto-cpufreq-remove.sh\", \"/usr/local/bin/auto-cpufreq-remove\")\n\n    # output warning if gnome power profile is running\n    gnome_power_detect_install()\n    #\"gnome_power_svc_disable_performance\" is not defined\n    #gnome_power_svc_disable_performance()\n   \n    tlp_service_detect() # output warning if TLP service is detected\n\n    call(\"/usr/local/bin/auto-cpufreq-install\", shell=True)\n\ndef remove_daemon():\n    # check if auto-cpufreq is installed\n    if not os.path.exists(\"/usr/local/bin/auto-cpufreq-remove\"):\n        print(\"\\nauto-cpufreq daemon is not installed.\\n\")\n        sys.exit(1)\n\n    print(\"\\n\" + \"-\" * 21 + \" Removing auto-cpufreq daemon \" + \"-\" * 22 + \"\\n\")\n\n    bluetooth_enable() # turn on bluetooth on boot\n\n    # output warning if gnome power profile is stopped\n    gnome_power_rm_reminder()\n    gnome_power_svc_enable()\n\n    tuned_svc_enable()\n\n    # run auto-cpufreq daemon remove script\n    call(\"/usr/local/bin/auto-cpufreq-remove\", shell=True)\n\n    # remove auto-cpufreq-remove\n    os.remove(\"/usr/local/bin/auto-cpufreq-remove\")\n\n    # delete override pickle if it exists\n    if os.path.exists(governor_override_state):  os.remove(governor_override_state)\n\n    # delete stats file\n    if auto_cpufreq_stats_path.exists():\n        if auto_cpufreq_stats_file is not None: auto_cpufreq_stats_file.close()\n        auto_cpufreq_stats_path.unlink()\n\n    cpufreqctl_restore() # restore original cpufrectl script\n\ndef gov_check():\n    for gov in AVAILABLE_GOVERNORS:\n        if gov not in ALL_GOVERNORS:\n            print(\"\\n\" + \"-\" * 18 + \" Checking for necessary scaling governors \" + \"-\" * 19 + \"\\n\")\n            sys.exit(\"ERROR:\\n\\nCouldn't find any of the necessary scaling governors.\\n\")\n\ndef root_check():\n    if not os.geteuid() == 0:\n        print(\"\\n\" + \"-\" * 33 + \" Root check \" + \"-\" * 34 + \"\\n\")\n        print(\"ERROR:\\n\\nMust be run root for this functionality to work, i.e: \\nsudo \" + app_name)\n        footer()\n        exit(1)\n\ndef countdown(s):\n    # Fix for wrong stats output and \"TERM environment variable not set\"\n    os.environ[\"TERM\"] = \"xterm\"\n\n    print(\"\\t\\t\\\"auto-cpufreq\\\" is about to refresh \", end = \"\")\n\n    # empty log file if size is larger then 10mb\n    if auto_cpufreq_stats_file is not None:\n        log_size = os.path.getsize(auto_cpufreq_stats_path)\n        if log_size >= 1e+7:\n            auto_cpufreq_stats_file.seek(0)\n            auto_cpufreq_stats_file.truncate(0)\n\n    # auto-refresh counter\n    for remaining in range(s, -1, -1):\n        if remaining <= 3 and remaining >= 0: print(\".\", end=\"\", flush=True)\n        sleep(s/3)\n\n    print(\"\\n\\t\\tExecuted on:\", getoutput('date'))\n\n# get cpu usage + system load for (last minute)\ndef get_load():    \n    cpuload = psutil.cpu_percent(interval=1) # get CPU utilization as a percentage\n    load1m, _, _ = os.getloadavg() # get system/CPU load\n\n    print(\"\\nTotal CPU usage:\", cpuload, \"%\")\n    print(\"Total system load: {:.2f}\".format(load1m))\n    from auto_cpufreq.modules.system_info import SystemInfo\n\n    print(\"Average temp. of all cores: {:.2f} °C \\n\".format(SystemInfo.avg_temp()))\n\n    return cpuload, load1m\n\ndef display_system_load_avg(): print(\" (load average: {:.2f}, {:.2f}, {:.2f})\".format(*os.getloadavg()))\n\n# set minimum and maximum CPU frequencies\ndef set_frequencies():\n    \"\"\"\n    Sets frequencies:\n     - if option is used in auto-cpufreq.conf: use configured value\n     - if option is disabled/no conf file used: set default frequencies\n    Frequency setting is performed only once on power supply change\n    \"\"\"\n    power_supply = \"charger\" if charging() else \"battery\"\n\n    # don't do anything if the power supply hasn't changed\n    if (\n        hasattr(set_frequencies, \"prev_power_supply\")\n        and power_supply == set_frequencies.prev_power_supply\n    ): return\n    else: set_frequencies.prev_power_supply = power_supply\n\n    frequency = {\n        \"scaling_max_freq\": {\n            \"cmdargs\": \"--frequency-max\",\n            \"minmax\": \"maximum\",\n        },\n        \"scaling_min_freq\": {\n            \"cmdargs\": \"--frequency-min\",\n            \"minmax\": \"minimum\",\n        },\n    }\n    if not hasattr(set_frequencies, \"max_limit\"):\n        set_frequencies.max_limit = int(getoutput(f\"cpufreqctl.auto-cpufreq --frequency-max-limit\"))\n    if not hasattr(set_frequencies, \"min_limit\"):\n        set_frequencies.min_limit = int(getoutput(f\"cpufreqctl.auto-cpufreq --frequency-min-limit\"))\n\n    conf = config.get_config()\n\n    for freq_type in frequency.keys():\n        value = None\n        if not conf.has_option(power_supply, freq_type):\n            # fetch and use default frequencies\n            if freq_type == \"scaling_max_freq\":\n                curr_freq = int(getoutput(f\"cpufreqctl.auto-cpufreq --frequency-max\"))\n                value = set_frequencies.max_limit\n            else:\n                curr_freq = int(getoutput(f\"cpufreqctl.auto-cpufreq --frequency-min\"))\n                value = set_frequencies.min_limit\n            if curr_freq == value: continue\n\n        try: frequency[freq_type][\"value\"] = value if value else int(conf[power_supply][freq_type].strip())\n        except ValueError:\n            print(f\"Invalid value for '{freq_type}': {frequency[freq_type]['value']}\")\n            exit(1)\n\n        if not set_frequencies.min_limit <= frequency[freq_type][\"value\"] <= set_frequencies.max_limit:\n            print(\n                f\"Given value for '{freq_type}' is not within the allowed frequencies {set_frequencies.min_limit}-{set_frequencies.max_limit} kHz\"\n            )\n            exit(1)\n\n        print(f'Setting {frequency[freq_type][\"minmax\"]} CPU frequency to {round(frequency[freq_type][\"value\"]/1000)} Mhz')\n        # set the frequency\n        run(f\"cpufreqctl.auto-cpufreq {frequency[freq_type]['cmdargs']} --set={frequency[freq_type]['value']}\", shell=True)\n\ndef set_platform_profile(conf, profile):\n    if conf.has_option(profile, \"platform_profile\"):\n        if not Path(\"/sys/firmware/acpi/platform_profile\").exists():\n            print('Not setting Platform Profile (not supported by system)')\n        else:\n            pp = conf[profile][\"platform_profile\"]\n            print(f'Setting to use: \"{pp}\" Platform Profile')\n            run(f\"cpufreqctl.auto-cpufreq --pp --set={pp}\", shell=True)\n\ndef set_energy_perf_bias(conf, profile):\n    if Path(\"/sys/devices/system/cpu/intel_pstate\").exists() is False:\n        print('Not setting EPB (not supported by system)')\n        return\n    epb = \"balance_performance\" if profile == \"charger\" else \"balance_power\"\n    if conf.has_option(profile, \"energy_perf_bias\"):\n        epb = conf[profile][\"energy_perf_bias\"]\n\n    run(f\"cpufreqctl.auto-cpufreq --epb --set={epb}\", shell=True)\n    print(f'Setting to use: \"{epb}\" EPB')\n\n\ndef set_powersave():\n    conf = config.get_config()\n    gov = conf[\"battery\"][\"governor\"] if conf.has_option(\"battery\", \"governor\") else AVAILABLE_GOVERNORS_SORTED[-1]\n    print(f'Setting to use: \"{gov}\" governor')\n    if get_override() != \"default\": print(\"Warning: governor overwritten using `--force` flag.\")\n    run(f\"cpufreqctl.auto-cpufreq --governor --set={gov}\", shell=True)\n\n    if Path(\"/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference\").exists() is False:\n        print('Not setting EPP (not supported by system)')\n    else:\n        dynboost_enabled = Path(\"/sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost\").exists()\n\n        if dynboost_enabled:\n            dynboost_enabled = bool(int(\n                os.popen(\"cat /sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost\").read()\n            ))\n\n        if dynboost_enabled: print('Not setting EPP (dynamic boosting is enabled)')\n        else:\n            if conf.has_option(\"battery\", \"energy_performance_preference\"):\n                epp = conf[\"battery\"][\"energy_performance_preference\"]\n                run(f\"cpufreqctl.auto-cpufreq --epp --set={epp}\", shell=True)\n                print(f'Setting to use: \"{epp}\" EPP')\n            else:\n                run(\"cpufreqctl.auto-cpufreq --epp --set=balance_power\", shell=True)\n                print('Setting to use: \"balance_power\" EPP')\n\n    set_energy_perf_bias(conf, \"battery\")\n    set_platform_profile(conf, \"battery\")\n    set_frequencies()\n\n    cpuload, load1m= get_load()\n\n    auto = conf[\"battery\"][\"turbo\"] if conf.has_option(\"battery\", \"turbo\") else \"auto\"\n    auto = get_turbo_override() if (get_turbo_override() != \"auto\") else auto # Override turbo if override file is present, otherwise stick to config.\n\n    if auto == \"always\":\n        print(\"Configuration file enforces turbo boost\")\n        set_turbo(True)\n    elif auto == \"never\":\n        print(\"Configuration file disables turbo boost\")\n        set_turbo(False)\n    else:\n        if psutil.cpu_percent(percpu=False, interval=0.01) >= 30.0 or isclose(\n            max(psutil.cpu_percent(percpu=True, interval=0.01)), 100\n        ): print(\"High CPU load\", end=\"\")\n        elif load1m > powersave_load_threshold: print(\"High system load\", end=\"\")\n        else: print(\"Load optimal\", end=\"\")\n        display_system_load_avg()\n\n        if cpuload >= 20: set_turbo(True) # high cpu usage trigger\n        else: # set turbo state based on average of all core temperatures\n            from auto_cpufreq.modules.system_info import SystemInfo\n\n            print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n            set_turbo(False)\n\n    footer()\n\ndef mon_powersave():\n    cpuload, load1m = get_load()\n\n    if psutil.cpu_percent(percpu=False, interval=0.01) >= 30.0 or isclose(\n        max(psutil.cpu_percent(percpu=True, interval=0.01)), 100\n    ): print(\"High CPU load\", end=\"\")\n    elif load1m > powersave_load_threshold: print(\"High system load\", end=\"\")\n    else: print(\"Load optimal\", end=\"\")\n    display_system_load_avg()\n\n    if cpuload >= 20: print(\"suggesting to set turbo boost: on\") # high cpu usage trigger\n    else: # set turbo state based on average of all core temperatures\n        from auto_cpufreq.modules.system_info import SystemInfo\n\n        print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n        print(\"suggesting to set turbo boost: off\")\n    get_turbo()\n\n    footer()\n\ndef set_performance():\n    conf = config.get_config()\n    gov = conf[\"charger\"][\"governor\"] if conf.has_option(\"charger\", \"governor\") else AVAILABLE_GOVERNORS_SORTED[0]\n\n    print(f'Setting to use: \"{gov}\" governor')\n    if get_override() != \"default\": print(\"Warning: governor overwritten using `--force` flag.\")\n    run(\"cpufreqctl.auto-cpufreq --governor --set=\"+gov, shell=True)\n\n    if not Path(\"/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference\").exists():\n        print('Not setting EPP (not supported by system)')\n    else:\n        if Path(\"/sys/devices/system/cpu/intel_pstate\").exists():\n            dynboost_enabled = Path(\"/sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost\").exists()\n\n            if dynboost_enabled:\n                dynboost_enabled = bool(int(\n                    os.popen(\"cat /sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost\").read()\n                ))\n\n            if dynboost_enabled: print('Not setting EPP (dynamic boosting is enabled)')\n            else:\n                intel_pstate_status_path = \"/sys/devices/system/cpu/intel_pstate/status\"\n\n                if conf.has_option(\"charger\", \"energy_performance_preference\"):\n                    epp = conf[\"charger\"][\"energy_performance_preference\"]\n\n                    if Path(intel_pstate_status_path).exists() and open(intel_pstate_status_path, 'r').read().strip() == \"active\" and epp != \"performance\" and gov == \"performance\":\n                        print(f'Warning \"{epp}\" EPP cannot be used in performance governor')\n                        print('Overriding EPP to \"performance\"')\n                        epp = \"performance\"\n\n                    run(f\"cpufreqctl.auto-cpufreq --epp --set={epp}\", shell=True)\n                    print(f'Setting to use: \"{epp}\" EPP')\n                else:\n                    if Path(intel_pstate_status_path).exists() and open(intel_pstate_status_path, 'r').read().strip() == \"active\":\n                        run(\"cpufreqctl.auto-cpufreq --epp --set=performance\", shell=True)\n                        print('Setting to use: \"performance\" EPP')\n                    else:\n                        run(\"cpufreqctl.auto-cpufreq --epp --set=balance_performance\", shell=True)\n                        print('Setting to use: \"balance_performance\" EPP')\n        elif Path(\"/sys/devices/system/cpu/amd_pstate\").exists():\n            amd_pstate_status_path = \"/sys/devices/system/cpu/amd_pstate/status\"\n\n            if conf.has_option(\"charger\", \"energy_performance_preference\"):\n                epp = conf[\"charger\"][\"energy_performance_preference\"]\n\n                if Path(amd_pstate_status_path).exists() and open(amd_pstate_status_path, 'r').read().strip() == \"active\" and epp != \"performance\" and gov == \"performance\":\n                    print(f'Warning \"{epp} EPP cannot be used in performance governor')\n                    print('Overriding EPP to \"performance\"')\n                    epp = \"performance\"\n\n                run(f\"cpufreqctl.auto-cpufreq --epp --set={epp}\", shell=True)\n                print(f'Setting to use: \"{epp}\" EPP')\n            else:\n                if Path(amd_pstate_status_path).exists() and open(amd_pstate_status_path, 'r').read().strip() == \"active\":\n                    run(\"cpufreqctl.auto-cpufreq --epp --set=performance\", shell=True)\n                    print('Setting to use: \"performance\" EPP')\n                else:\n                    run(\"cpufreqctl.auto-cpufreq --epp --set=balance_performance\", shell=True)\n                    print('Setting to use: \"balance_performance\" EPP')\n    \n    set_energy_perf_bias(conf, \"charger\")\n    set_platform_profile(conf, \"charger\")\n    set_frequencies()\n\n    cpuload, load1m = get_load()\n    auto = conf[\"charger\"][\"turbo\"] if conf.has_option(\"charger\", \"turbo\") else \"auto\"\n    auto = get_turbo_override() if (get_turbo_override() != \"auto\") else auto # Override turbo if override file is present, otherwise stick to config.\n\n    if auto == \"always\":\n        print(\"Configuration file enforces turbo boost\")\n        set_turbo(True)\n    elif auto == \"never\":\n        print(\"Configuration file disables turbo boost\")\n        set_turbo(False)\n    else:\n        from auto_cpufreq.modules.system_info import SystemInfo\n\n        if (\n            psutil.cpu_percent(percpu=False, interval=0.01) >= 20.0\n            or max(psutil.cpu_percent(percpu=True, interval=0.01)) >= 75\n        ):\n            print(\"High CPU load\", end=\"\"), display_system_load_avg()\n\n            if cpuload >= 20: set_turbo(True) # high cpu usage trigger\n            elif SystemInfo.avg_temp() >= 70: # set turbo state based on average of all core temperatures\n                print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n                set_turbo(False)\n            else: set_turbo(True)\n        elif load1m >= performance_load_threshold:\n\n            print(\"High system load\", end=\"\"), display_system_load_avg()\n            if cpuload >= 20: set_turbo(True) # high cpu usage trigger\n            elif SystemInfo.avg_temp() >= 65: # set turbo state based on average of all core temperatures\n                print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n                set_turbo(False)\n            else: set_turbo(True)\n        else:\n            print(\"Load optimal\", end=\"\"), display_system_load_avg()\n            if cpuload >= 20: set_turbo(True) # high cpu usage trigger\n            else: # set turbo state based on average of all core temperatures\n                print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n                set_turbo(False)\n    footer()\n\ndef mon_performance():\n    from auto_cpufreq.modules.system_info import SystemInfo\n    cpuload, load1m = get_load()\n\n    if (\n        psutil.cpu_percent(percpu=False, interval=0.01) >= 20.0\n        or max(psutil.cpu_percent(percpu=True, interval=0.01)) >= 75\n    ):\n        print(\"High CPU load\", end=\"\"), display_system_load_avg()\n        \n\n        if cpuload >= 20: # high cpu usage trigger\n            print(\"suggesting to set turbo boost: on\")\n            get_turbo()\n        # set turbo state based on average of all core temperatures\n        elif cpuload <= 25 and SystemInfo.avg_temp() >= 70:\n            print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n            print(\"suggesting to set turbo boost: off\")\n            get_turbo()\n        else:\n            print(\"suggesting to set turbo boost: on\")\n            get_turbo()\n    elif load1m > performance_load_threshold:\n        print(\"High system load\", end=\"\"), display_system_load_avg()\n        if cpuload >= 20: # high cpu usage trigger\n            print(\"suggesting to set turbo boost: on\")\n            get_turbo()\n        elif cpuload <= 25 and SystemInfo.avg_temp() >= 65: # set turbo state based on average of all core temperatures\n            print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n            print(\"suggesting to set turbo boost: off\")\n            get_turbo()\n        else:\n            print(\"suggesting to set turbo boost: on\")\n            get_turbo()\n    else:\n        print(\"Load optimal\", end=\"\"), display_system_load_avg()\n        if cpuload >= 20: # high cpu usage trigger\n            print(\"suggesting to set turbo boost: on\")\n            get_turbo()\n        elif cpuload <= 25 and SystemInfo.avg_temp() >= 60: # set turbo state based on average of all core temperatures\n            print(f\"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C\")\n            print(\"suggesting to set turbo boost: off\")\n            get_turbo()\n        else:\n            print(\"suggesting to set turbo boost: on\")\n            get_turbo()\n    footer()\n\ndef set_autofreq():\n    \"\"\"\n    set cpufreq governor based if device is charging\n    \"\"\"\n    print(\"\\n\" + \"-\" * 28 + \" CPU frequency scaling \" + \"-\" * 28 + \"\\n\")\n\n    # determine which governor should be used\n    override = get_override()\n    if override == \"powersave\": set_powersave()\n    elif override == \"performance\": set_performance()\n    elif charging():\n        print(\"Battery is: charging\\n\")\n        set_performance()\n    else:\n        print(\"Battery is: discharging\\n\")\n        set_powersave()\n\ndef mon_autofreq():\n    \"\"\"\n    make cpufreq suggestions\n    :return:\n    \"\"\"\n    print(\"\\n\" + \"-\" * 28 + \" CPU frequency scaling \" + \"-\" * 28 + \"\\n\")\n\n    # determine which governor should be used\n    if charging():\n        print(\"Battery is: charging\\n\")\n        get_current_gov()\n        print(f'Suggesting use of \"{AVAILABLE_GOVERNORS_SORTED[0]}\" governor')\n        mon_performance()\n    else:\n        print(\"Battery is: discharging\\n\")\n        get_current_gov()\n        print(f'Suggesting use of \"{AVAILABLE_GOVERNORS_SORTED[-1]}\" governor')\n        mon_powersave()\n\ndef python_info():\n    print(\"Python:\", platform.python_version())\n    print(\"psutil package:\", psutil.__version__)\n    print(\"platform package:\", platform.__version__)\n    print(\"click package:\", click.__version__)\n    print(\"distro package:\", distro.__version__)\n\ndef device_info(): print(\"Computer type:\", getoutput(\"dmidecode --string chassis-type\"))\n\ndef distro_info():\n    dist = \"UNKNOWN distro\"\n    version = \"UNKNOWN version\"\n    if IS_INSTALLED_WITH_SNAP:\n        try:\n            with open(\"/var/lib/snapd/hostfs/etc/os-release\", \"r\") as searchfile:\n                for line in searchfile:\n                    if line.startswith(\"NAME=\"):\n                        dist = line[5 : line.find(\"$\")].strip('\"')\n                        continue\n                    elif line.startswith(\"VERSION=\"):\n                        version = line[8 : line.find(\"$\")].strip('\"')\n                        continue\n        except PermissionError as e: print(repr(e))\n        dist = f\"{dist} {version}\"\n    else: # get distro information\n        fdist = distro.linux_distribution()\n        dist = \" \".join(x for x in fdist)\n\n    print(\"Linux distro: \" + dist)\n    print(\"Linux kernel: \" + platform.release())\n\ndef sysinfo():\n    \"\"\"\n    get system information\n    \"\"\"\n    # processor_info\n    model_name = getoutput(\"grep -E 'model name' /proc/cpuinfo -m 1\").split(\":\")[-1]\n    print(f\"Processor:{model_name}\")\n\n    # get core count\n    total_cpu_count = int(getoutput(\"nproc\"))\n    print(\"Cores:\", total_cpu_count)\n\n    # get architecture\n    cpu_arch = platform.machine()\n    print(\"Architecture:\", cpu_arch)\n\n    # get driver\n    driver = getoutput(\"cpufreqctl.auto-cpufreq --driver\")\n    print(\"Driver: \" + driver)\n\n    config_path = config.path if config.has_config() else None\n    if config_path is None:\n        from auto_cpufreq.config.config import find_config_file\n        config_path = find_config_file(None)\n    if os.path.isfile(config_path):\n        print(f\"\\nUsing settings defined in {config_path}\")\n\n    # get usage and freq info of cpus\n    usage_per_cpu = psutil.cpu_percent(interval=1, percpu=True)\n    # psutil current freq not used, gives wrong values with offline cpu's\n    minmax_freq_per_cpu = psutil.cpu_freq(percpu=True)\n\n    # max and min freqs, psutil reports wrong max/min freqs with offline cores with percpu=False\n    max_freq = max([freq.max for freq in minmax_freq_per_cpu])\n    min_freq = min([freq.min for freq in minmax_freq_per_cpu])\n    print(\"\\n\" + \"-\" * 30 + \" Current CPU stats \" + \"-\" * 30 + \"\\n\")\n    print(f\"CPU max frequency: {max_freq:.0f} MHz\")\n    print(f\"CPU min frequency: {min_freq:.0f} MHz\\n\")\n\n    # get coreid's and frequencies of online cpus by parsing /proc/cpuinfo\n    coreid_info = getoutput(\"grep -E 'processor|cpu MHz|core id' /proc/cpuinfo\").split(\"\\n\")\n    cpu_core = dict()\n    freq_per_cpu = []\n    for i in range(0, len(coreid_info), 3):\n        # ensure that indices are within the valid range, before accessing the corresponding elements\n        if i + 1 < len(coreid_info): freq_per_cpu.append(float(coreid_info[i + 1].split(\":\")[-1]))\n        else: continue # handle the case where the index is out of range\n        # ensure that indices are within the valid range, before accessing the corresponding elements\n        cpu = int(coreid_info[i].split(\":\")[-1])\n        if i + 2 < len(coreid_info):\n            core = int(coreid_info[i + 2].split(\":\")[-1])\n            cpu_core[cpu] = core\n        else: continue # handle the case where the index is out of range\n\n    online_cpu_count = len(cpu_core)\n    offline_cpus = [str(cpu) for cpu in range(total_cpu_count) if cpu not in cpu_core]\n\n    # temperatures\n    temp_sensors = psutil.sensors_temperatures()\n    temp_per_cpu = [float(\"nan\")] * online_cpu_count\n    try:\n        # the priority for CPU temp is as follows: coretemp sensor -> sensor with CPU in the label -> acpi -> k10temp\n        if \"coretemp\" in temp_sensors:\n            # list labels in 'coretemp'\n            core_temp_labels = [temp.label for temp in temp_sensors[\"coretemp\"]]\n            for i, cpu in enumerate(cpu_core):\n                # get correct index in temp_sensors\n                core = cpu_core[cpu]\n                cpu_temp_index = core_temp_labels.index(f\"Core {core}\")\n                temp_per_cpu[i] = temp_sensors[\"coretemp\"][cpu_temp_index].current\n        else:\n            # iterate over all sensors\n            for sensor in temp_sensors:\n                # iterate over all temperatures in the current sensor\n                for temp in temp_sensors[sensor]:\n                    if ('CPU' in temp.label or 'Tctl' in temp.label) and temp.current != 0:\n                        temp_per_cpu = [temp.current] * online_cpu_count\n                        break\n                else: continue\n                break\n            else:\n                for sensor in [\"acpitz\", \"k10temp\", \"zenpower\"]:\n                    if sensor in temp_sensors and temp_sensors[sensor][0].current != 0:\n                        temp_per_cpu = [temp_sensors[sensor][0].current] * online_cpu_count\n                        break\n    except Exception as e: print(repr(e))\n\n    print(\"Core\\tUsage\\tTemperature\\tFrequency\")\n    for (cpu, usage, freq, temp) in zip(cpu_core, usage_per_cpu, freq_per_cpu, temp_per_cpu):\n        print(f\"CPU{cpu}    {usage:>5.1f}%       {temp:>3.0f} °C     {freq:>5.0f} MHz\")\n\n    if offline_cpus: print(f\"\\nDisabled CPUs: {','.join(offline_cpus)}\")\n\n    # print current fan speed (only if > 0)\n    current_fans = list(psutil.sensors_fans())\n    for current_fan in current_fans:\n        fan_speed = psutil.sensors_fans()[current_fan][0].current\n        if fan_speed:\n            print(f\"\\nCPU fan speed: {fan_speed} RPM\")\n\ndef read_stats():\n    if os.path.isfile(auto_cpufreq_stats_path): call([\"tail\", \"-n 50\", \"-f\", str(auto_cpufreq_stats_path)], stderr=DEVNULL)\n    footer()\n\n# check if program (argument) is running\ndef is_running(program, argument):\n    # iterate over all processes found by psutil\n    # and find the one with name and args passed to the function\n    for p in psutil.process_iter():\n        try: cmd = p.cmdline()\n        except: continue\n        for s in filter(lambda x: program in x, cmd):\n            if argument in cmd: return True\n\ndef daemon_running_msg():\n    print(\"\\n\" + \"-\" * 24 + \" auto-cpufreq running \" + \"-\" * 30 + \"\\n\")\n    print(\n        \"ERROR: auto-cpufreq is running in daemon mode.\\n\\nMake sure to stop the daemon before running with --live or --monitor mode\"\n    )\n    footer()\n\ndef daemon_not_running_msg():\n    print(\"\\n\" + \"-\" * 24 + \" auto-cpufreq not running \" + \"-\" * 30 + \"\\n\")\n    print(\n        \"ERROR: auto-cpufreq is not running in daemon mode.\\n\\nMake sure to run \\\"sudo auto-cpufreq --install\\\" first\"\n    )\n    footer()\n\n# check if auto-cpufreq --daemon is running\ndef running_daemon_check():\n    if is_running(\"auto-cpufreq\", \"--daemon\"):\n        daemon_running_msg()\n        exit(1)\n    elif IS_INSTALLED_WITH_SNAP and SNAP_DAEMON_CHECK == \"enabled\":\n        daemon_running_msg()\n        exit(1)\n\n# check if auto-cpufreq --daemon is not running\ndef not_running_daemon_check():\n    if not is_running(\"auto-cpufreq\", \"--daemon\"):\n        daemon_not_running_msg()\n        exit(1)\n    elif IS_INSTALLED_WITH_SNAP and SNAP_DAEMON_CHECK == \"disabled\":\n        daemon_not_running_msg()\n        exit(1)\n"
  },
  {
    "path": "auto_cpufreq/globals.py",
    "content": "from os import getenv, path\nfrom subprocess import getoutput\n\nALL_GOVERNORS = ('performance', 'ondemand', 'conservative', 'schedutil', 'userspace', 'powersave') # from the highest performance to the lowest\nAVAILABLE_GOVERNORS = getoutput('cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors').strip().split(' ')\nAVAILABLE_GOVERNORS_SORTED = tuple(filter(lambda gov: gov in AVAILABLE_GOVERNORS, ALL_GOVERNORS))\n\nGITHUB = \"https://github.com/AdnanHodzic/auto-cpufreq\"\nIS_INSTALLED_WITH_AUR = path.isfile(\"/etc/arch-release\") and bool(getoutput(\"pacman -Qs auto-cpufreq\"))\nIS_INSTALLED_WITH_SNAP = getenv(\"PKG_MARKER\") == \"SNAP\"\nPOWER_SUPPLY_DIR = \"/sys/class/power_supply/\"\nSNAP_DAEMON_CHECK = getoutput(\"snapctl get daemon\")\n\nCPU_TEMP_SENSOR_PRIORITY = (\"coretemp\", \"acpitz\", \"k10temp\", \"zenpower\")\n"
  },
  {
    "path": "auto_cpufreq/gui/app.py",
    "content": "import gi\ngi.require_version(\"Gtk\", \"3.0\")\nfrom gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk\n\nfrom contextlib import redirect_stdout\nfrom io import StringIO\nfrom subprocess import PIPE, run\nfrom threading import Thread\n\nfrom auto_cpufreq.core import check_for_update, is_running\nfrom auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_SNAP\nfrom auto_cpufreq.gui.objects import BatteryInfoBox, BluetoothBootControl, CPUFreqScalingBox, CurrentGovernorBox, DaemonNotRunningView, DropDownMenu, MonitorModeView, RadioButtonView, CPUTurboOverride, SystemStatsLabel, SystemStatisticsBox, UpdateDialog\nfrom auto_cpufreq.gui.objects import get_stats\nfrom auto_cpufreq.power_helper import bluetoothctl_exists\n\nif IS_INSTALLED_WITH_SNAP:\n    CSS_FILE = \"/snap/auto-cpufreq/current/style.css\"\n    ICON_FILE = \"/snap/auto-cpufreq/current/icon.png\"\nelse:\n    CSS_FILE = \"/usr/local/share/auto-cpufreq/scripts/style.css\"\n    ICON_FILE = \"/usr/local/share/auto-cpufreq/images/icon.png\"\n\nHBOX_PADDING = 20\n\nclass ToolWindow(Gtk.Window):\n    def __init__(self):\n        super().__init__(title=\"auto-cpufreq\")\n        self.set_default_size(600, 480)\n        self.set_border_width(10)\n        self.set_resizable(False)\n        self.load_css()\n        pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=ICON_FILE, width=500, height=500, preserve_aspect_ratio=True)\n        self.set_icon(pixbuf)\n        self.build()\n\n    def main(self):\n        # Main HBOX\n        self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=HBOX_PADDING)\n       \n        self.systemstats = SystemStatsLabel()\n        self.hbox.pack_start(self.systemstats, False, False, 0)\n        self.add(self.hbox)\n\n        self.vbox_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=15)\n        \n        self.menu = DropDownMenu(self)\n        self.hbox.pack_end(self.menu, False, False, 0)\n        \n        self.currentgovernor = CurrentGovernorBox()\n        self.vbox_right.pack_start(self.currentgovernor, False, False, 0)\n        self.vbox_right.pack_start(RadioButtonView(), False, False, 0)\n        if \"Warning: CPU turbo is not available\" not in get_stats():\n            self.vbox_right.pack_start(CPUTurboOverride(), False, False, 0)\n\n        self.batteryinfo = BatteryInfoBox()\n        self.vbox_right.pack_start(self.batteryinfo, False, False, 0)\n\n        self.cpufreqscaling = CPUFreqScalingBox()\n        self.vbox_right.pack_start(self.cpufreqscaling, False, False, 0)\n\n        self.systemstats_box = SystemStatisticsBox()\n        self.vbox_right.pack_start(self.systemstats_box, False, False, 0)\n\n        if bluetoothctl_exists:\n            self.vbox_right.pack_start(BluetoothBootControl(), False, False, 0)\n\n        self.hbox.pack_start(self.vbox_right, False, False, 0)\n\n        GLib.timeout_add_seconds(5, self.refresh_in_thread)\n\n    def snap(self):\n        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER)\n        # reference: https://forum.snapcraft.io/t/pkexec-not-found-python-gtk-gnome-app/36579\n        label = Gtk.Label(label=\"GUI not available due to Snap package confinement limitations.\\nPlease install auto-cpufreq using auto-cpufreq-installer\\nVisit the GitHub repo for more info\")\n        label.set_justify(Gtk.Justification.CENTER)\n        button = Gtk.LinkButton.new_with_label(\n            uri=GITHUB,\n            label=\"GitHub Repo\"\n        )\n        \n        box.pack_start(label, False, False, 0)\n        box.pack_start(button, False, False, 0)\n        self.add(box)\n\n    def handle_update(self):\n        new_stdout = StringIO()\n        with redirect_stdout(new_stdout):\n            if not check_for_update(): return\n        captured_output = new_stdout.getvalue().splitlines()\n        dialog = UpdateDialog(self, captured_output[1], captured_output[2])\n        response = dialog.run()\n        dialog.destroy()\n        if response != Gtk.ResponseType.YES: return\n        updater = run([\"pkexec\", \"auto-cpufreq\", \"--update\"], input=\"y\\n\", encoding=\"utf-8\", stderr=PIPE)\n        if updater.returncode in (126, 127):\n            error = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, \"Error updating\")\n            error.format_secondary_text(\"Authorization Failed\")\n            error.run()\n            error.destroy()\n            return\n        success = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, \"Update successful\")\n        success.format_secondary_text(\"The app will now close. Please reopen to apply changes\")\n        success.run()\n        success.destroy()\n        exit(0)\n\n    def daemon_not_running(self):\n        self.box = DaemonNotRunningView(self)\n        self.add(self.box)\n\n    def monitor_mode(self):\n        self.monitor_view = MonitorModeView(self)\n        self.add(self.monitor_view)\n\n    def build(self):\n        if IS_INSTALLED_WITH_SNAP: self.snap()\n        elif is_running(\"auto-cpufreq\", \"--daemon\"): self.main()\n        else: self.daemon_not_running()\n\n    def load_css(self):\n        screen = Gdk.Screen.get_default()\n        self.gtk_provider = Gtk.CssProvider()\n        self.gtk_context = Gtk.StyleContext()\n        self.gtk_context.add_provider_for_screen(screen, self.gtk_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)\n        self.gtk_provider.load_from_file(Gio.File.new_for_path(CSS_FILE))\n\n    def refresh_in_thread(self):\n        Thread(target=self._refresh).start()\n        return True\n\n    def _refresh(self):\n        self.systemstats.refresh()\n        self.currentgovernor.refresh()\n        self.batteryinfo.refresh()\n        self.cpufreqscaling.refresh()\n        self.systemstats_box.refresh()\n"
  },
  {
    "path": "auto_cpufreq/gui/objects.py",
    "content": "import gi\ngi.require_version(\"Gtk\", \"3.0\")\nfrom gi.repository import Gdk, GdkPixbuf, GLib, Gtk\n\nimport sys\nfrom concurrent.futures import ThreadPoolExecutor\nfrom io import StringIO\nfrom os.path import isfile\nfrom platform import python_version\nfrom subprocess import getoutput, PIPE, run\nfrom threading import Thread\nimport time\n\nfrom auto_cpufreq.config.config import config, find_config_file\nfrom auto_cpufreq.core import distro_info, get_formatted_version, get_override, get_turbo_override, sysinfo\nfrom auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_AUR, IS_INSTALLED_WITH_SNAP\nfrom auto_cpufreq.modules.system_info import system_info\nfrom auto_cpufreq.power_helper import bluetoothctl_exists\n\nauto_cpufreq_stats_path = (\"/var/snap/auto-cpufreq/current\" if IS_INSTALLED_WITH_SNAP else \"/var/run\") + \"/auto-cpufreq.stats\"\n\ndef get_stats():\n    if isfile(auto_cpufreq_stats_path):\n        with open(auto_cpufreq_stats_path, \"r\") as file: stats = [line for line in (file.readlines() [-50:])]\n        return \"\".join(stats)\n\ndef get_version():\n    # snap package\n    if IS_INSTALLED_WITH_SNAP: return getoutput(r\"echo \\(Snap\\) $SNAP_VERSION\")\n    # aur package\n    elif IS_INSTALLED_WITH_AUR: return getoutput(\"pacman -Qi auto-cpufreq | grep Version\")\n    else:\n        # source code (auto-cpufreq-installer)\n        try: return get_formatted_version()\n        except Exception as e:\n            print(repr(e))\n            pass\n\ndef get_bluetooth_boot_status():\n    if not bluetoothctl_exists:\n        return None\n    btconf = \"/etc/bluetooth/main.conf\"\n    try:\n        with open(btconf, \"r\") as f:\n            in_policy_section = False\n            for line in f:\n                stripped = line.strip()\n                if stripped.startswith(\"[\"):\n                    in_policy_section = stripped.lower() == \"[policy]\"\n                    continue\n                if not in_policy_section:\n                    continue\n                if stripped.startswith(\"#\") or not stripped:\n                    continue\n                if stripped.startswith(\"AutoEnable=\"):\n                    value = stripped.split(\"=\", 1)[1].strip().lower()\n                    return \"on\" if value == \"true\" else \"off\"\n            return \"on\"\n    except Exception:\n        return None\n\nclass RadioButtonView(Gtk.Box):\n    def __init__(self):\n        super().__init__(orientation=Gtk.Orientation.HORIZONTAL)\n\n        self.set_hexpand(True)\n        self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)\n\n        self.label = Gtk.Label(\"Governor Override\", name=\"bold\")\n\n        self.default = Gtk.RadioButton.new_with_label_from_widget(None, \"Default\")\n        self.default.connect(\"toggled\", self.on_button_toggled, \"reset\")\n        self.default.set_halign(Gtk.Align.END)\n        self.powersave = Gtk.RadioButton.new_with_label_from_widget(self.default, \"Powersave\")\n        self.powersave.connect(\"toggled\", self.on_button_toggled, \"powersave\")\n        self.powersave.set_halign(Gtk.Align.END)\n        self.performance = Gtk.RadioButton.new_with_label_from_widget(self.default, \"Performance\")\n        self.performance.connect(\"toggled\", self.on_button_toggled, \"performance\")\n        self.performance.set_halign(Gtk.Align.END)\n\n        # this keeps track of whether or not the button was toggled by the app or the user to prompt for authorization\n        self.set_by_app = True\n        self.set_selected()\n\n        self.pack_start(self.label, False, False, 0)\n        self.pack_start(self.default, True, True, 0)\n        self.pack_start(self.powersave, True, True, 0)\n        self.pack_start(self.performance, True, True, 0)\n\n    def on_button_toggled(self, button, override):\n        if button.get_active():\n            if not self.set_by_app:\n                result = run(f\"pkexec auto-cpufreq --force={override}\", shell=True, stdout=PIPE, stderr=PIPE)\n                if result.returncode in (126, 127):\n                    self.set_by_app = True\n                    self.set_selected()\n            else: self.set_by_app = False\n\n    def set_selected(self):\n        override = get_override()\n        match override:\n            case \"powersave\": self.powersave.set_active(True)\n            case \"performance\": self.performance.set_active(True)\n            case \"default\":\n                # because this is the default button, it does not trigger the callback when set by the app\n                self.default.set_active(True)\n                if self.set_by_app: self.set_by_app = False\n\nclass CPUTurboOverride(Gtk.Box):\n    def __init__(self):\n        super().__init__(orientation=Gtk.Orientation.HORIZONTAL)\n\n        self.set_hexpand(True)\n        self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)\n\n        self.label = Gtk.Label(\"CPU Turbo Override\", name=\"bold\")\n\n        self.auto = Gtk.RadioButton.new_with_label_from_widget(None, \"Auto\")\n        self.auto.connect(\"toggled\", self.on_button_toggled,  \"auto\")\n        self.auto.set_halign(Gtk.Align.END)\n        self.never = Gtk.RadioButton.new_with_label_from_widget(self.auto, \"Never\")\n        self.never.connect(\"toggled\", self.on_button_toggled,  \"never\")\n        self.never.set_halign(Gtk.Align.END)\n        self.always = Gtk.RadioButton.new_with_label_from_widget(self.auto, \"Always\")\n        self.always.connect(\"toggled\", self.on_button_toggled, \"always\")\n        self.always.set_halign(Gtk.Align.END)\n\n        self.set_by_app = True\n        self.set_selected()\n\n        self.pack_start(self.label, False, False, 0)\n        self.pack_start(self.auto, True, True, 0)\n        self.pack_start(self.never, True, True, 0)\n        self.pack_start(self.always, True, True, 0)\n\n    def on_button_toggled(self, button, override):\n        if button.get_active():\n            if not self.set_by_app:\n                result = run(f\"pkexec auto-cpufreq --turbo={override}\", shell=True, stdout=PIPE, stderr=PIPE)\n                if result.returncode in (126, 127):\n                    self.set_by_app = True\n                    self.set_selected()\n            else: self.set_by_app = False\n\n    def set_selected(self):\n        override = get_turbo_override()\n        match override:\n            case \"never\": self.never.set_active(True)\n            case \"always\": self.always.set_active(True)\n            case \"auto\":\n                # because this is the default button, it does not trigger the callback when set by the app\n                self.auto.set_active(True)\n                if self.set_by_app: self.set_by_app = False\n\nclass BluetoothBootControl(Gtk.Box):\n    def __init__(self):\n        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=10)\n\n        self.set_hexpand(True)\n\n        self.advanced_btn = Gtk.Button(label=\"Advanced Settings\")\n        self.advanced_btn.connect(\"clicked\", self.on_advanced_clicked)\n        self.advanced_btn.set_halign(Gtk.Align.START)\n\n        self.revealer = Gtk.Revealer()\n        self.revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN)\n        self.revealer.set_transition_duration(200)\n\n        self.inner_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)\n        self.inner_box.set_hexpand(True)\n\n        self.label = Gtk.Label(\"Bluetooth on Boot\", name=\"bold\")\n\n        self.on_btn = Gtk.RadioButton.new_with_label_from_widget(None, \"On\")\n        self.on_btn.connect(\"toggled\", self.on_button_toggled, \"on\")\n        self.on_btn.set_halign(Gtk.Align.END)\n        self.off_btn = Gtk.RadioButton.new_with_label_from_widget(self.on_btn, \"Off\")\n        self.off_btn.connect(\"toggled\", self.on_button_toggled, \"off\")\n        self.off_btn.set_halign(Gtk.Align.END)\n\n        self.set_by_app = True\n        self.set_selected()\n\n        self.inner_box.pack_start(self.label, False, False, 0)\n        self.inner_box.pack_start(self.on_btn, True, True, 0)\n        self.inner_box.pack_start(self.off_btn, True, True, 0)\n\n        self.revealer.add(self.inner_box)\n\n        self.pack_start(self.advanced_btn, False, False, 0)\n        self.pack_start(self.revealer, False, False, 0)\n\n    def on_advanced_clicked(self, button):\n        revealed = self.revealer.get_reveal_child()\n        self.revealer.set_reveal_child(not revealed)\n        if revealed:\n            self.advanced_btn.set_label(\"Advanced Settings\")\n        else:\n            self.advanced_btn.set_label(\"Hide Advanced Settings\")\n\n    def on_button_toggled(self, button, action):\n        if button.get_active():\n            if not self.set_by_app:\n                if action == \"on\":\n                    result = run(\"pkexec auto-cpufreq --bluetooth_boot_on\", shell=True, stdout=PIPE, stderr=PIPE)\n                else:\n                    result = run(\"pkexec auto-cpufreq --bluetooth_boot_off\", shell=True, stdout=PIPE, stderr=PIPE)\n                if result.returncode in (126, 127):\n                    self.set_by_app = True\n                    self.set_selected()\n            else: self.set_by_app = False\n\n    def set_selected(self):\n        status = get_bluetooth_boot_status()\n        match status:\n            case \"off\": self.off_btn.set_active(True)\n            case \"on\" | _:\n                # because this is the default button, it does not trigger the callback when set by the app\n                self.on_btn.set_active(True)\n                if self.set_by_app: self.set_by_app = False\n\nclass CurrentGovernorBox(Gtk.Box):\n    def __init__(self):\n        super().__init__(spacing=25)\n        self.static = Gtk.Label(label=\"Current Governor\", name=\"bold\")\n        self.governor = Gtk.Label(label=getoutput(\"cpufreqctl.auto-cpufreq --governor\").strip().split(\" \")[0], halign=Gtk.Align.END)\n\n        self.pack_start(self.static, False, False, 0)\n        self.pack_start(self.governor, False, False, 0)\n\n    def refresh(self):\n        self.governor.set_label(getoutput(\"cpufreqctl.auto-cpufreq --governor\").strip().split(\" \")[0])\n\nclass BatteryInfoBox(Gtk.Box):\n    def __init__(self):\n        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=2)\n\n        self.header = Gtk.Label(label=\"-\" * 20 + \" Battery Stats \" + \"-\" * 20)\n        self.header.set_halign(Gtk.Align.START)\n\n        self.status_label = Gtk.Label(label=\"\")\n        self.status_label.set_halign(Gtk.Align.START)\n\n        self.percentage_label = Gtk.Label(label=\"\")\n        self.percentage_label.set_halign(Gtk.Align.START)\n\n        self.ac_label = Gtk.Label(label=\"\")\n        self.ac_label.set_halign(Gtk.Align.START)\n\n        self.start_threshold_label = Gtk.Label(label=\"\")\n        self.start_threshold_label.set_halign(Gtk.Align.START)\n\n        self.stop_threshold_label = Gtk.Label(label=\"\")\n        self.stop_threshold_label.set_halign(Gtk.Align.START)\n\n        self.pack_start(self.header, False, False, 0)\n        self.pack_start(self.status_label, False, False, 0)\n        self.pack_start(self.percentage_label, False, False, 0)\n        self.pack_start(self.ac_label, False, False, 0)\n        self.pack_start(self.start_threshold_label, False, False, 0)\n        self.pack_start(self.stop_threshold_label, False, False, 0)\n\n        self.refresh()\n\n    def refresh(self):\n        try:\n            battery_info = system_info.battery_info()\n\n            self.status_label.set_label(f\"Battery status: {str(battery_info)}\")\n\n            if battery_info.battery_level is not None:\n                percentage_text = f\"{battery_info.battery_level}%\"\n            else:\n                percentage_text = \"Unknown\"\n            self.percentage_label.set_label(f\"Battery percentage: {percentage_text}\")\n\n            if battery_info.is_ac_plugged is not None:\n                ac_text = \"Yes\" if battery_info.is_ac_plugged else \"No\"\n            else:\n                ac_text = \"Unknown\"\n            self.ac_label.set_label(f\"AC plugged: {ac_text}\")\n\n            if battery_info.is_ac_plugged is not None:\n                start_text = str(battery_info.charging_start_threshold) if battery_info.charging_start_threshold is not None else \"None\"\n            else:\n                start_text = \"Unknown\"\n            self.start_threshold_label.set_label(f\"Charging start threshold: {start_text}\")\n\n            if battery_info.is_ac_plugged is not None:\n                stop_text = str(battery_info.charging_stop_threshold) if battery_info.charging_stop_threshold is not None else \"None\"\n            else:\n                stop_text = \"Unknown\"\n            self.stop_threshold_label.set_label(f\"Charging stop threshold: {stop_text}\")\n\n        except Exception:\n            self.status_label.set_label(\"Battery status: Unknown\")\n            self.percentage_label.set_label(\"Battery percentage: Unknown\")\n            self.ac_label.set_label(\"AC plugged: Unknown\")\n            self.start_threshold_label.set_label(\"Charging start threshold: Unknown\")\n            self.stop_threshold_label.set_label(\"Charging stop threshold: Unknown\")\n\nclass CPUFreqScalingBox(Gtk.Box):\n    def __init__(self):\n        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=2)\n\n        self.header = Gtk.Label(label=\"-\" * 20 + \" CPU Frequency Scaling \" + \"-\" * 20)\n        self.header.set_halign(Gtk.Align.START)\n\n        self.governor_label = Gtk.Label(label=\"\")\n        self.governor_label.set_halign(Gtk.Align.START)\n\n        self.epp_label = Gtk.Label(label=\"\")\n        self.epp_label.set_halign(Gtk.Align.START)\n\n        self.epb_label = Gtk.Label(label=\"\")\n        self.epb_label.set_halign(Gtk.Align.START)\n        self.epb_label.set_no_show_all(True)\n\n        self.pack_start(self.header, False, False, 0)\n        self.pack_start(self.governor_label, False, False, 0)\n        self.pack_start(self.epp_label, False, False, 0)\n        self.pack_start(self.epb_label, False, False, 0)\n\n        self.refresh()\n\n    def refresh(self):\n        try:\n            report = system_info.generate_system_report()\n\n            gov = report.current_gov if report.current_gov else \"Unknown\"\n            self.governor_label.set_label(f'Setting to use: \"{gov}\" governor')\n\n            if report.current_epp:\n                self.epp_label.set_label(f\"EPP setting: {report.current_epp}\")\n                self.epp_label.show()\n            else:\n                self.epp_label.set_label(\"Not setting EPP (not supported by system)\")\n                self.epp_label.show()\n\n            if report.current_epb:\n                self.epb_label.set_label(f'Setting to use: \"{report.current_epb}\" EPB')\n                self.epb_label.show()\n            else:\n                self.epb_label.hide()\n\n        except Exception:\n            self.governor_label.set_label('Setting to use: \"Unknown\" governor')\n            self.epp_label.set_label(\"EPP setting: Unknown\")\n            self.epb_label.hide()\n\nclass SystemStatisticsBox(Gtk.Box):\n    def __init__(self):\n        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=2)\n\n        self.header = Gtk.Label(label=\"-\" * 20 + \" System Statistics \" + \"-\" * 20)\n        self.header.set_halign(Gtk.Align.START)\n\n        self.cpu_usage_label = Gtk.Label(label=\"\")\n        self.cpu_usage_label.set_halign(Gtk.Align.START)\n\n        self.load_label = Gtk.Label(label=\"\")\n        self.load_label.set_halign(Gtk.Align.START)\n\n        self.temp_label = Gtk.Label(label=\"\")\n        self.temp_label.set_halign(Gtk.Align.START)\n        self.temp_label.set_no_show_all(True)\n\n        self.load_status_label = Gtk.Label(label=\"\")\n        self.load_status_label.set_halign(Gtk.Align.START)\n        self.load_status_label.set_no_show_all(True)\n\n        self.usage_status_label = Gtk.Label(label=\"\")\n        self.usage_status_label.set_halign(Gtk.Align.START)\n        self.usage_status_label.set_no_show_all(True)\n\n        self.turbo_label = Gtk.Label(label=\"\")\n        self.turbo_label.set_halign(Gtk.Align.START)\n\n        self.fan_label = Gtk.Label(label=\"\")\n        self.fan_label.set_halign(Gtk.Align.START)\n        self.fan_label.set_no_show_all(True)\n\n        self.pack_start(self.header, False, False, 0)\n        self.pack_start(self.cpu_usage_label, False, False, 0)\n        self.pack_start(self.load_label, False, False, 0)\n        self.pack_start(self.temp_label, False, False, 0)\n        self.pack_start(self.fan_label, False, False, 0)\n        self.pack_start(self.load_status_label, False, False, 0)\n        self.pack_start(self.usage_status_label, False, False, 0)\n        self.pack_start(self.turbo_label, False, False, 0)\n\n        self.refresh()\n\n    def refresh(self):\n        try:\n            report = system_info.generate_system_report()\n\n            self.cpu_usage_label.set_label(f\"Total CPU usage: {report.cpu_usage:.1f} %\")\n\n            self.load_label.set_label(f\"Total system load: {report.load:.2f}\")\n\n            avg_temp = 0.0\n            if report.cores_info:\n                avg_temp = sum(core.temperature for core in report.cores_info) / len(report.cores_info)\n                self.temp_label.set_label(f\"Average temp. of all cores: {avg_temp:.2f} °C\")\n                self.temp_label.show()\n            else:\n                self.temp_label.hide()\n\n            if report.cpu_fan_speed:\n                self.fan_label.set_label(f\"CPU fan speed: {report.cpu_fan_speed} RPM\")\n                self.fan_label.show()\n            else:\n                self.fan_label.hide()\n\n            if report.avg_load:\n                load_status = \"Load optimal\" if report.load < 1.0 else \"Load high\"\n                self.load_status_label.set_label(\n                    f\"{load_status} (load average: {report.avg_load[0]:.2f}, {report.avg_load[1]:.2f}, {report.avg_load[2]:.2f})\"\n                )\n                self.load_status_label.show()\n            else:\n                self.load_status_label.hide()\n\n            if report.cores_info:\n                usage_status = \"Optimal\" if report.cpu_usage < 70 else \"High\"\n                temp_status = \"high\" if avg_temp > 75 else \"normal\"\n                self.usage_status_label.set_label(\n                    f\"{usage_status} total CPU usage: {report.cpu_usage:.1f}%, {temp_status} average core temp: {avg_temp:.1f}°C\"\n                )\n                self.usage_status_label.show()\n            else:\n                self.usage_status_label.hide()\n\n            if report.is_turbo_on[0] is not None:\n                turbo_status = \"On\" if report.is_turbo_on[0] else \"Off\"\n            elif report.is_turbo_on[1] is not None:\n                turbo_status = f\"Auto mode {'enabled' if report.is_turbo_on[1] else 'disabled'}\"\n            else:\n                turbo_status = \"Unknown\"\n            self.turbo_label.set_label(f\"Setting turbo boost: {turbo_status}\")\n\n        except Exception:\n            self.cpu_usage_label.set_label(\"Total CPU usage: Unknown\")\n            self.load_label.set_label(\"Total system load: Unknown\")\n            self.temp_label.hide()\n            self.fan_label.hide()\n            self.load_status_label.hide()\n            self.usage_status_label.hide()\n            self.turbo_label.set_label(\"Setting turbo boost: Unknown\")\n\nclass SystemStatsLabel(Gtk.Label):\n    def __init__(self):\n        super().__init__()\n        self.refresh()\n\n    def refresh(self):\n        # change stdout and store label text to file-like object\n        old_stdout = sys.stdout\n        text = StringIO()\n        sys.stdout = text\n        distro_info()\n        sysinfo()\n        self.set_label(text.getvalue())\n        sys.stdout = old_stdout\n    \nclass CPUFreqStatsLabel(Gtk.Label):\n    def __init__(self):\n        super().__init__()\n        self.refresh()\n  \n    def refresh(self):\n        stats = get_stats().split(\"\\n\")\n        start = None\n        for i, line in enumerate(stats):\n            if line == (\"-\" * 28 + \" CPU frequency scaling \" + \"-\" * 28):\n                start = i\n                break\n        if start is not None:\n            del stats[:i]\n            del stats[-4:]\n            self.set_label(\"\\n\".join(stats))\n \nclass DropDownMenu(Gtk.MenuButton):\n    def __init__(self, parent):\n        super().__init__()\n        self.set_halign(Gtk.Align.END)\n        self.set_valign(Gtk.Align.START)\n        self.image = Gtk.Image.new_from_icon_name(\"open-menu-symbolic\", Gtk.IconSize.LARGE_TOOLBAR)\n        self.add(self.image)\n        self.menu = self.build_menu(parent)\n        self.set_popup(self.menu)\n\n    def build_menu(self, parent):\n        menu = Gtk.Menu()\n\n        daemon = Gtk.MenuItem(label=\"Remove Daemon\")\n        daemon.connect(\"activate\", self._remove_daemon, parent)\n        menu.append(daemon)\n\n        about = Gtk.MenuItem(label=\"About\")\n        about.connect(\"activate\", self.about_dialog, parent)\n        menu.append(about)\n\n        menu.show_all()\n        return menu\n\n    def about_dialog(self, MenuItem, parent):\n        dialog = AboutDialog(parent)\n        response = dialog.run()\n        dialog.destroy()\n\n    def _remove_daemon(self, MenuItem, parent):\n        confirm = ConfirmDialog(parent, message=\"Are you sure you want to remove the daemon?\")\n        response = confirm.run()\n        confirm.destroy()\n        if response == Gtk.ResponseType.YES:\n            try:\n                # run in thread to prevent GUI from hanging\n                with ThreadPoolExecutor() as executor:\n                    kwargs = {\"shell\": True, \"stdout\": PIPE, \"stderr\": PIPE}\n                    future = executor.submit(run, \"pkexec auto-cpufreq --remove\", **kwargs)\n                    result = future.result()\n                assert result.returncode not in (126, 127), Exception(\"Authorization was cancelled\")\n                dialog = Gtk.MessageDialog(\n                    transient_for=parent,\n                    message_type=Gtk.MessageType.INFO,\n                    buttons=Gtk.ButtonsType.OK,\n                    text=\"Daemon successfully removed\"\n                )\n                dialog.format_secondary_text(\"The app will now close. Please reopen to apply changes\")\n                dialog.run()\n                dialog.destroy()\n                parent.destroy()\n            except Exception as e:\n                dialog = Gtk.MessageDialog(\n                    transient_for=parent,\n                    message_type=Gtk.MessageType.ERROR,\n                    buttons=Gtk.ButtonsType.OK,\n                    text=\"Daemon removal failed\"\n                )\n                dialog.format_secondary_text(f\"The following error occured:\\n{e}\")\n                dialog.run()\n                dialog.destroy()\n\nclass AboutDialog(Gtk.Dialog):\n    def __init__(self, parent):\n        super().__init__(title=\"About\", transient_for=parent)\n        app_version = get_version()\n        self.box = self.get_content_area()\n        self.box.set_spacing(10)\n        self.add_button(\"Close\", Gtk.ResponseType.CLOSE)\n        self.set_default_size(400, 350)\n        img_buffer = GdkPixbuf.Pixbuf.new_from_file_at_scale(\n            filename=\"/usr/local/share/auto-cpufreq/images/icon.png\",\n            width=150,\n            height=150,\n            preserve_aspect_ratio=True\n        )\n        self.image = Gtk.Image.new_from_pixbuf(img_buffer)\n        self.title = Gtk.Label(label=\"auto-cpufreq\", name=\"bold\")\n        self.version = Gtk.Label(label=app_version)\n        self.python = Gtk.Label(label=f\"Python {python_version()}\")\n        self.github = Gtk.Label(label=GITHUB)\n        self.license = Gtk.Label(label=\"Licensed under LGPL3\", name=\"small\")\n        self.love = Gtk.Label(label=\"Made with <3\", name=\"small\")\n\n        self.box.pack_start(self.image, False, False, 0)\n        self.box.pack_start(self.title, False, False, 0)\n        self.box.pack_start(self.version, False, False, 0)\n        self.box.pack_start(self.python, False, False, 0)\n        self.box.pack_start(self.github, False, False, 0)\n        self.box.pack_start(self.license, False, False, 0)\n        self.box.pack_start(self.love, False, False, 0)\n        self.show_all()\n\nclass UpdateDialog(Gtk.Dialog):\n    def __init__(self, parent, current_version: str, latest_version: str):\n        super().__init__(title=\"Update Available\", transient_for=parent)\n        self.box = self.get_content_area()\n        self.set_default_size(400, 100)\n        self.add_buttons(\"Update\", Gtk.ResponseType.YES, \"Cancel\", Gtk.ResponseType.NO)\n        self.label = Gtk.Label(label=\"An update is available\\n\")\n        self.current_version = Gtk.Label(label=current_version + \"\\n\")\n        self.latest_version = Gtk.Label(label=latest_version + \"\\n\")\n\n        self.box.pack_start(self.label, True, False, 0)\n        self.box.pack_start(self.current_version, True, False, 0)\n        self.box.pack_start(self.latest_version, True, False, 0)\n\n        self.show_all()\n\nclass ConfirmDialog(Gtk.Dialog):\n    def __init__(self, parent, message: str):\n        super().__init__(title=\"Confirmation\", transient_for=parent)\n        self.box = self.get_content_area()\n        self.set_default_size(400, 100)\n        self.add_buttons(\"Yes\", Gtk.ResponseType.YES, \"No\", Gtk.ResponseType.NO)\n        self.label = Gtk.Label(label=message)\n\n        self.box.pack_start(self.label, True, False, 0)\n\n        self.show_all()\n\n\nclass MonitorModeView(Gtk.Box):\n    def __init__(self, parent):\n        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=5)\n        self.parent = parent\n        self.running = True\n\n        self.header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)\n        self.header.set_margin_bottom(10)\n\n        self.title = Gtk.Label(label=\"Monitor Mode\", name=\"bold\")\n        self.title.set_halign(Gtk.Align.START)\n        self.header.pack_start(self.title, True, True, 0)\n\n        self.back_button = Gtk.Button.new_with_label(\"Back\")\n        self.back_button.connect(\"clicked\", self.on_back_clicked)\n        self.header.pack_end(self.back_button, False, False, 0)\n\n        self.pack_start(self.header, False, False, 0)\n\n        self.columns = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)\n        self.columns.set_vexpand(True)\n        self.columns.set_hexpand(True)\n\n        self.left_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)\n        self.left_box.set_valign(Gtk.Align.START)\n        self.columns.pack_start(self.left_box, True, True, 0)\n\n        self.separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL)\n        self.columns.pack_start(self.separator, False, False, 0)\n\n        self.right_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)\n        self.right_box.set_valign(Gtk.Align.START)\n        self.columns.pack_start(self.right_box, True, True, 0)\n\n        self.pack_start(self.columns, True, True, 0)\n\n        self.refresh()\n        self.refresh_id = GLib.timeout_add_seconds(5, self.refresh_in_thread)\n\n    def refresh_in_thread(self):\n        if not self.running:\n            return False\n        Thread(target=self._refresh, daemon=True).start()\n        return True\n\n    def _refresh(self):\n        try:\n            report = system_info.generate_system_report()\n            GLib.idle_add(self._update_display, report)\n        except Exception as e:\n            GLib.idle_add(self._show_error, str(e))\n\n    def refresh(self):\n        try:\n            report = system_info.generate_system_report()\n            self._update_display(report)\n        except Exception as e:\n            self._show_error(str(e))\n\n    def _show_error(self, error_msg):\n        self._clear_boxes()\n        self.left_box.pack_start(self._label(f\"Error: {error_msg}\"), False, False, 0)\n        self.left_box.show_all()\n\n    def _clear_boxes(self):\n        for child in self.left_box.get_children():\n            self.left_box.remove(child)\n        for child in self.right_box.get_children():\n            self.right_box.remove(child)\n\n    def _header(self, text):\n        label = Gtk.Label(label=text, name=\"bold\")\n        label.set_halign(Gtk.Align.START)\n        return label\n\n    def _label(self, text):\n        label = Gtk.Label(label=text)\n        label.set_halign(Gtk.Align.START)\n        return label\n\n    def _suggestion(self, text):\n        label = Gtk.Label(label=text)\n        label.set_halign(Gtk.Align.START)\n        label.override_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0.9, 0.7, 0.1, 1.0))\n        return label\n\n    def _separator(self, text):\n        label = Gtk.Label(label=\"-\" * 28 + f\" {text} \" + \"-\" * 28)\n        label.set_halign(Gtk.Align.START)\n        return label\n\n    def _update_display(self, report):\n        self._clear_boxes()\n\n        current_time = time.strftime(\"%H:%M:%S\")\n        self.title.set_text(f\"Monitor Mode - {current_time}\")\n\n\n        self.left_box.pack_start(self._separator(\"System Information\"), False, False, 5)\n        self.left_box.pack_start(self._label(f\"Linux distro: {report.distro_name} {report.distro_ver}\"), False, False, 0)\n        self.left_box.pack_start(self._label(f\"Linux kernel: {report.kernel_version}\"), False, False, 0)\n        self.left_box.pack_start(self._label(f\"Processor: {report.processor_model}\"), False, False, 0)\n        self.left_box.pack_start(self._label(f\"Cores: {report.total_core}\"), False, False, 0)\n        self.left_box.pack_start(self._label(f\"Architecture: {report.arch}\"), False, False, 0)\n        self.left_box.pack_start(self._label(f\"Driver: {report.cpu_driver}\"), False, False, 0)\n\n        config_path = config.path if config.has_config() else find_config_file(None)\n        if isfile(config_path):\n            self.left_box.pack_start(self._label(f\"\\nUsing settings defined in {config_path}\"), False, False, 0)\n\n        self.left_box.pack_start(self._label(\"\"), False, False, 0)\n\n        self.left_box.pack_start(self._separator(\"Current CPU Stats\"), False, False, 5)\n        self.left_box.pack_start(self._label(f\"CPU max frequency: {report.cpu_max_freq:.0f} MHz\" if report.cpu_max_freq else \"CPU max frequency: Unknown\"), False, False, 0)\n        self.left_box.pack_start(self._label(f\"CPU min frequency: {report.cpu_min_freq:.0f} MHz\" if report.cpu_min_freq else \"CPU min frequency: Unknown\"), False, False, 0)\n        self.left_box.pack_start(self._label(\"\"), False, False, 0)\n        self.left_box.pack_start(self._label(\"Core    Usage   Temperature     Frequency\"), False, False, 0)\n\n        for core in report.cores_info:\n            self.left_box.pack_start(\n                self._label(f\"CPU{core.id:<2}    {core.usage:>4.1f}%    {core.temperature:>6.0f} °C    {core.frequency:>6.0f} MHz\"),\n                False, False, 0\n            )\n\n        if report.cpu_fan_speed:\n            self.left_box.pack_start(self._label(\"\"), False, False, 0)\n            self.left_box.pack_start(self._label(f\"CPU fan speed: {report.cpu_fan_speed} RPM\"), False, False, 0)\n\n\n        if report.battery_info is not None:\n            self.right_box.pack_start(self._separator(\"Battery Stats\"), False, False, 5)\n            self.right_box.pack_start(self._label(f\"Battery status: {str(report.battery_info)}\"), False, False, 0)\n            battery_level = f\"{report.battery_info.battery_level}%\" if report.battery_info.battery_level is not None else \"Unknown\"\n            self.right_box.pack_start(self._label(f\"Battery percentage: {battery_level}\"), False, False, 0)\n            ac_status = \"Yes\" if report.battery_info.is_ac_plugged else \"No\" if report.battery_info.is_ac_plugged is not None else \"Unknown\"\n            self.right_box.pack_start(self._label(f\"AC plugged: {ac_status}\"), False, False, 0)\n            self.right_box.pack_start(self._label(f\"Charging start threshold: {report.battery_info.charging_start_threshold}\"), False, False, 0)\n            self.right_box.pack_start(self._label(f\"Charging stop threshold: {report.battery_info.charging_stop_threshold}\"), False, False, 0)\n            self.right_box.pack_start(self._label(\"\"), False, False, 0)\n\n        self.right_box.pack_start(self._separator(\"CPU Frequency Scaling\"), False, False, 5)\n        current_gov = report.current_gov if report.current_gov else \"Unknown\"\n        self.right_box.pack_start(self._label(f'Setting to use: \"{current_gov}\" governor'), False, False, 0)\n\n        suggested_gov = system_info.governor_suggestion()\n        if report.current_gov and suggested_gov != report.current_gov:\n            self.right_box.pack_start(self._suggestion(f'Suggesting use of: \"{suggested_gov}\" governor'), False, False, 0)\n\n        if report.current_epp:\n            self.right_box.pack_start(self._label(f\"EPP setting: {report.current_epp}\"), False, False, 0)\n        else:\n            self.right_box.pack_start(self._label(\"Not setting EPP (not supported by system)\"), False, False, 0)\n\n        if report.current_epb:\n            self.right_box.pack_start(self._label(f'Setting to use: \"{report.current_epb}\" EPB'), False, False, 0)\n\n        self.right_box.pack_start(self._label(\"\"), False, False, 0)\n\n        self.right_box.pack_start(self._separator(\"System Statistics\"), False, False, 5)\n        self.right_box.pack_start(self._label(f\"Total CPU usage: {report.cpu_usage:.1f} %\"), False, False, 0)\n        self.right_box.pack_start(self._label(f\"Total system load: {report.load:.2f}\"), False, False, 0)\n\n        avg_temp = 0.0\n        if report.cores_info:\n            avg_temp = sum(core.temperature for core in report.cores_info) / len(report.cores_info)\n            self.right_box.pack_start(self._label(f\"Average temp. of all cores: {avg_temp:.2f} °C\"), False, False, 0)\n\n        if report.avg_load:\n            load_status = \"Load optimal\" if report.load < 1.0 else \"Load high\"\n            self.right_box.pack_start(\n                self._label(f\"{load_status} (load average: {report.avg_load[0]:.2f}, {report.avg_load[1]:.2f}, {report.avg_load[2]:.2f})\"),\n                False, False, 0\n            )\n\n        if report.cores_info:\n            usage_status = \"Optimal\" if report.cpu_usage < 70 else \"High\"\n            temp_status = \"high\" if avg_temp > 75 else \"normal\"\n            self.right_box.pack_start(\n                self._label(f\"{usage_status} total CPU usage: {report.cpu_usage:.1f}%, {temp_status} average core temp: {avg_temp:.1f}°C\"),\n                False, False, 0\n            )\n\n        turbo_status = \"Unknown\"\n        if report.is_turbo_on[0] is not None:\n            turbo_status = \"On\" if report.is_turbo_on[0] else \"Off\"\n        elif report.is_turbo_on[1] is not None:\n            turbo_status = f\"Auto mode {'enabled' if report.is_turbo_on[1] else 'disabled'}\"\n        self.right_box.pack_start(self._label(f\"Setting turbo boost: {turbo_status}\"), False, False, 0)\n\n        if report.is_turbo_on[0] is not None:\n            suggested_turbo = system_info.turbo_on_suggestion()\n            if suggested_turbo != report.is_turbo_on[0]:\n                turbo_text = \"on\" if suggested_turbo else \"off\"\n                self.right_box.pack_start(self._suggestion(f\"Suggesting to set turbo boost: {turbo_text}\"), False, False, 0)\n\n        self.left_box.show_all()\n        self.right_box.show_all()\n        return False\n\n    def on_back_clicked(self, button):\n        self.cleanup()\n        self.parent.remove(self)\n        self.parent.daemon_not_running()\n        self.parent.show_all()\n\n    def cleanup(self):\n        self.running = False\n        if hasattr(self, 'refresh_id') and self.refresh_id:\n            GLib.source_remove(self.refresh_id)\n\n\nclass DaemonNotRunningView(Gtk.Box):\n    def __init__(self, parent):\n        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=10, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER)\n\n        self.label = Gtk.Label(label=\"auto-cpufreq daemon is not running\")\n        self.sublabel = Gtk.Label(label=\"Install the daemon for permanent optimization, or use Monitor mode to preview\")\n\n        self.button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10, halign=Gtk.Align.CENTER)\n        self.install_button = Gtk.Button.new_with_label(\"Install Daemon\")\n        self.monitor_button = Gtk.Button.new_with_label(\"Monitor Mode\")\n\n        self.install_button.connect(\"clicked\", self.install_daemon, parent)\n        self.monitor_button.connect(\"clicked\", self.start_monitor, parent)\n\n        self.button_box.pack_start(self.install_button, False, False, 0)\n        self.button_box.pack_start(self.monitor_button, False, False, 0)\n\n        self.pack_start(self.label, False, False, 0)\n        self.pack_start(self.sublabel, False, False, 0)\n        self.pack_start(self.button_box, False, False, 0)\n\n    def start_monitor(self, button, parent):\n        parent.remove(self)\n        parent.monitor_mode()\n        parent.show_all()\n\n    def install_daemon(self, button, parent):\n        try:\n            # run in thread to prevent GUI from hanging\n            with ThreadPoolExecutor() as executor:\n                kwargs = {\"shell\": True, \"stdout\": PIPE, \"stderr\": PIPE}\n                future = executor.submit(run, \"pkexec auto-cpufreq --install\", **kwargs)\n                result = future.result()\n            assert result.returncode not in (126, 127), Exception(\"Authorization was cancelled\")\n            # enable for debug. causes issues if kept\n            # elif result.stderr is not None:\n            #     raise Exception(result.stderr.decode())\n            dialog = Gtk.MessageDialog(\n                transient_for=parent,\n                message_type=Gtk.MessageType.INFO,\n                buttons=Gtk.ButtonsType.OK,\n                text=\"Daemon successfully installed\"\n            )\n            dialog.format_secondary_text(\"The app will now close. Please reopen to apply changes\")\n            dialog.run()\n            dialog.destroy()\n            parent.destroy()\n        except Exception as e:\n            dialog = Gtk.MessageDialog(\n                transient_for=parent,\n                message_type=Gtk.MessageType.ERROR,\n                buttons=Gtk.ButtonsType.OK,\n                text=\"Daemon install failed\"\n            )\n            dialog.format_secondary_text(f\"The following error occured:\\n{e}\")\n            dialog.run()\n            dialog.destroy()"
  },
  {
    "path": "auto_cpufreq/gui/tray.py",
    "content": "import gi\ngi.require_version(\"Gtk\", \"3.0\")\nfrom gi.repository import Gtk, AppIndicator3 as appindicator\n\nfrom subprocess import run\n\ndef main():\n    indicator = appindicator.Indicator.new(\"auto-cpufreq-tray\", \"network-idle-symbolic\", appindicator.IndicatorCategory.APPLICATION_STATUS)\n    indicator.set_status(appindicator.IndicatorStatus.ACTIVE)\n    indicator.set_menu(build_menu())\n    Gtk.main()\n\ndef build_menu():\n    menu = Gtk.Menu()\n\n    program = Gtk.MenuItem(\"auto-cpufreq\")\n    program.connect(\"activate\", open_app)\n    menu.append(program)\n\n    _quit = Gtk.MenuItem(\"Quit\")\n    _quit.connect(\"activate\", Gtk.main_quit)\n    menu.append(_quit)\n    menu.show_all()\n    return menu\n\ndef open_app(MenuItem): run(\"sudo -E python app.py\", shell=True)\n\nif __name__ == \"__main__\": main()"
  },
  {
    "path": "auto_cpufreq/modules/system_info.py",
    "content": "from dataclasses import dataclass\nimport os\nfrom pathlib import Path\nimport platform\nfrom subprocess import getoutput\nfrom typing import Tuple, List\nimport psutil\nimport distro\nfrom pathlib import Path\nfrom auto_cpufreq.config.config import config\nfrom auto_cpufreq.core import get_power_supply_ignore_list\nfrom auto_cpufreq.globals import (\n    AVAILABLE_GOVERNORS_SORTED,\n    CPU_TEMP_SENSOR_PRIORITY,\n    IS_INSTALLED_WITH_SNAP,\n    POWER_SUPPLY_DIR,\n)\nfrom typing import Optional\n\n\n@dataclass\nclass CoreInfo:\n    id: int\n    usage: float\n    temperature: float\n    frequency: float\n\n\n@dataclass\nclass BatteryInfo:\n    is_charging: bool | None\n    is_ac_plugged: bool | None\n    charging_start_threshold: int | None\n    charging_stop_threshold: int | None\n    battery_level: int | None\n    power_consumption: float | None\n\n    def __repr__(self) -> str:\n        if self.is_charging:\n            return \"charging\"\n        elif not self.is_ac_plugged:\n            return f\"discharging {('(' + '{:.2f}'.format(self.power_consumption) + ' W)') if self.power_consumption != None else ''}\"\n        return \"Not Charging\"\n\n\n@dataclass\nclass SystemReport:\n    distro_name: str\n    distro_ver: str\n    arch: str\n    processor_model: str\n    total_core: int | None\n    kernel_version: str\n    current_gov: str | None\n    current_epp: str | None\n    current_epb: str | None\n    cpu_driver: str\n    cpu_fan_speed: int | None\n    cpu_usage: float\n    cpu_max_freq: float | None\n    cpu_min_freq: float | None\n    load: float\n    avg_load: Tuple[float, float, float] | None\n    cores_info: list[CoreInfo]\n    battery_info: BatteryInfo\n    is_turbo_on: Tuple[bool | None, bool | None]\n\n\nclass SystemInfo:\n    \"\"\"\n    Provides system information related to CPU, distribution, and performance metrics.\n    \"\"\"\n\n    def __init__(self):\n        self.distro_name: str = (\n            distro.name(pretty=True) if not IS_INSTALLED_WITH_SNAP else \"UNKNOWN\"\n        )\n        self.distro_version: str = (\n            distro.version() if not IS_INSTALLED_WITH_SNAP else \"UNKNOWN\"\n        )\n        self.architecture: str = platform.machine()\n        self.processor_model: str = (\n            getoutput(\"grep -E 'model name' /proc/cpuinfo -m 1\").split(\":\")[-1].strip()\n        )\n        self.total_cores: int | None = psutil.cpu_count(logical=True)\n        self.cpu_driver: str = getoutput(\n            \"cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver\"\n        ).strip()\n        self.kernel_version: str = platform.release()\n\n    @staticmethod\n    def cpu_min_freq() -> float | None:\n        freqs = psutil.cpu_freq(percpu=True)\n        return min((freq.min for freq in freqs), default=None)\n\n    @staticmethod\n    def cpu_max_freq() -> float | None:\n        freqs = psutil.cpu_freq(percpu=True)\n        return max((freq.max for freq in freqs), default=None)\n\n    @staticmethod\n    def get_cpu_info() -> List[CoreInfo]:\n        \"\"\"Returns detailed CPU information for each core.\"\"\"\n        cpu_usage = psutil.cpu_percent(percpu=True)\n        cpu_freqs = psutil.cpu_freq(percpu=True)\n\n        try:\n            temps = psutil.sensors_temperatures()\n            temp_sensor = []\n            for sensor in CPU_TEMP_SENSOR_PRIORITY:\n                temp_sensor = temps.get(sensor, [])\n                if temp_sensor != []:\n                    break\n\n            core_temps = [temp.current for temp in temp_sensor]\n        except AttributeError:\n            core_temps = []\n\n        avg_temp = sum(core_temps) / len(core_temps) if core_temps else 0.0\n\n        return [\n            CoreInfo(\n                id=i,\n                usage=cpu_usage[i],\n                temperature=core_temps[i] if i < len(core_temps) else avg_temp,\n                frequency=cpu_freqs[i].current,\n            )\n            for i in range(len(cpu_usage))\n        ]\n\n    @staticmethod\n    def cpu_fan_speed() -> int | None:\n        fans = psutil.sensors_fans()\n        return next((fan[0].current for fan in fans.values() if fan), None)\n\n    @staticmethod\n    def current_gov() -> str | None:\n        try:\n            with open(\n                \"/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor\", \"r\"\n            ) as f:\n                return f.read().strip()\n        except:\n            return None\n\n    @staticmethod\n    def current_epp(is_ac_plugged: bool) -> str | None:\n        epp_path = \"/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference\"\n        if not Path(epp_path).exists():\n            return None\n            \n        return config.get_config().get( \n            \"charger\" if is_ac_plugged else \"battery\", \"energy_performance_preference\", fallback=\"balance_performance\" if is_ac_plugged else \"balance_power\"\n        )\n\n    @staticmethod\n    def current_epb(is_ac_plugged: bool) -> str | None:\n        epb_path = \"/sys/devices/system/cpu/intel_pstate\"\n        if not Path(epb_path).exists():\n            return None\n\n        return config.get_config().get(\n            \"charger\" if is_ac_plugged else \"battery\", \"energy_perf_bias\", fallback=\"balance_performance\" if is_ac_plugged else \"balance_power\"\n        )\n\n    @staticmethod\n    def cpu_usage() -> float:\n        return psutil.cpu_percent(\n            interval=0.5\n        )  # Reduced interval for better responsiveness\n\n    @staticmethod\n    def system_load() -> float:\n        return os.getloadavg()[0]\n\n    @staticmethod\n    def avg_load() -> Tuple[float, float, float]:\n        return os.getloadavg()\n\n    @staticmethod\n    def avg_temp() -> int:\n        temps: List[float] = [i.temperature for i in SystemInfo.get_cpu_info()]\n        return int(sum(temps) / len(temps))\n\n    @staticmethod\n    def turbo_on() -> Tuple[bool | None, bool | None]:\n        \"\"\"Get CPU turbo mode status.\n\n        Returns: Tuple[bool | None, bool | None]:\n\n        The first value indicates whether turbo mode is enabled, None if unknown\n\n        The second value indicates whether auto mode is enabled (amd_pstate only), None if unknown\n        \"\"\"\n        intel_pstate = Path(\"/sys/devices/system/cpu/intel_pstate/no_turbo\")\n        cpu_freq = Path(\"/sys/devices/system/cpu/cpufreq/boost\")\n        amd_pstate = Path(\"/sys/devices/system/cpu/amd_pstate/status\")\n\n        if intel_pstate.exists():\n            control_file: Path = intel_pstate\n            inverse_logic = True\n        elif cpu_freq.exists():\n            control_file = cpu_freq\n            inverse_logic = False\n        elif amd_pstate.exists():\n            amd_status: str = amd_pstate.read_text().strip()\n            if amd_status == \"active\":\n                return None, True\n            return None, False\n        else:\n            return None, None\n\n        try:\n            current_value = int(control_file.read_text().strip())\n            return bool(current_value) ^ inverse_logic, False\n        except Exception as e:\n            return None, None\n\n    @staticmethod\n    def read_file(path: str) -> Optional[str]:\n\n        try:\n            with open(path, \"r\") as f:\n                return f.read().strip()\n        except (FileNotFoundError, OSError):\n            return None\n\n    @staticmethod\n    def get_battery_path() -> Optional[str]:\n\n        # Check if user has specified a custom battery device in config\n        if config.has_config():\n            conf = config.get_config()\n            if conf.has_option(\"battery\", \"battery_device\"):\n                battery_device = conf.get(\"battery\", \"battery_device\").strip()\n                if battery_device:\n                    custom_path = os.path.join(POWER_SUPPLY_DIR, battery_device)\n                    type_path = os.path.join(custom_path, \"type\")\n                    # Validate that the specified device exists and is a battery\n                    if os.path.isfile(type_path):\n                        content = SystemInfo.read_file(type_path)\n                        if content and content.lower() == \"battery\":\n                            return custom_path\n\n        # Fall back to auto-detection if no custom device specified or if it's invalid\n        try:\n            for entry in os.listdir(POWER_SUPPLY_DIR):\n                path = os.path.join(POWER_SUPPLY_DIR, entry)\n                type_path = os.path.join(path, \"type\")\n                if os.path.isfile(type_path):\n                    content = SystemInfo.read_file(type_path)\n                    if content and content.lower() == \"battery\":\n                        return path\n        except Exception:\n            return None\n        return None\n\n    @staticmethod\n    def battery_info() -> BatteryInfo:\n\n        battery_path = SystemInfo.get_battery_path()\n\n        # By default, AC is considered connected if no battery is detected\n        is_ac_plugged = True\n        is_charging = None\n        battery_level = None\n        power_consumption = None\n        charging_start_threshold = None\n        charging_stop_threshold = None\n\n        if not battery_path:\n\n            # No battery detected\n            return BatteryInfo(\n                is_charging=None,\n                is_ac_plugged=is_ac_plugged,\n                charging_start_threshold=None,\n                charging_stop_threshold=None,\n                battery_level=None,\n                power_consumption=None,\n            )\n\n        # Reading AC info (Hands)\n        for supply in os.listdir(POWER_SUPPLY_DIR):\n            supply_path = os.path.join(POWER_SUPPLY_DIR, supply)\n            supply_type = SystemInfo.read_file(os.path.join(supply_path, \"type\"))\n            if supply_type == \"Mains\":\n                online = SystemInfo.read_file(os.path.join(supply_path, \"online\"))\n                is_ac_plugged = online == \"1\"\n\n        # Reading battery information\n        battery_status = SystemInfo.read_file(os.path.join(battery_path, \"status\"))\n        battery_capacity = SystemInfo.read_file(os.path.join(battery_path, \"capacity\"))\n\n        # first check for wattage in power_now\n        # this is not found on all laptops\n        energy_rate = (\n            SystemInfo.read_file(os.path.join(battery_path, \"power_now\"))\n        )\n\n        # if power_now wasn't found, try calculating wattage using current and voltage\n        if energy_rate is None:\n            current = SystemInfo.read_file(os.path.join(battery_path, \"current_now\"))\n            voltage = SystemInfo.read_file(os.path.join(battery_path, \"voltage_now\"))\n\n            if (current and current.isdigit()) and (voltage and voltage.isdigit()):\n                energy_rate = (int(current) * int(voltage)) / 1_000_000\n \n\n\n        charge_start_threshold = (\n            SystemInfo.read_file(os.path.join(battery_path, \"charge_start_threshold\"))\n            or SystemInfo.read_file(os.path.join(battery_path, \"charge_control_start_threshold\"))\n        )\n        charge_stop_threshold = (\n            SystemInfo.read_file(os.path.join(battery_path, \"charge_stop_threshold\"))\n            or SystemInfo.read_file(os.path.join(battery_path, \"charge_control_end_threshold\"))\n        )\n        is_charging = battery_status.lower() == \"charging\" if battery_status else None\n        battery_level = int(battery_capacity) if battery_capacity and battery_capacity.isdigit() else None\n        power_consumption = float(energy_rate) / 1_000_000 if energy_rate \\\n            and str(energy_rate).replace('.', '', 1).isdigit() else None\n        charging_start_threshold = int(charge_start_threshold) if charge_start_threshold \\\n            and charge_start_threshold.isdigit() else None\n        charging_stop_threshold = int(charge_stop_threshold) if charge_stop_threshold \\\n            and charge_stop_threshold.isdigit() else None\n\n        return BatteryInfo(\n            is_charging=is_charging,\n            is_ac_plugged=is_ac_plugged,\n            charging_start_threshold=charging_start_threshold,\n            charging_stop_threshold=charging_stop_threshold,\n            battery_level=battery_level,\n            power_consumption=power_consumption,\n        )\n\n    @staticmethod\n    def turbo_on_suggestion() -> bool:\n        usage = SystemInfo.cpu_usage()\n        if usage >= 20.0:\n            return True\n        elif usage <= 25 and SystemInfo.avg_temp() >= 70:\n            return False\n        return False\n\n    @staticmethod\n    def governor_suggestion() -> str:\n        if SystemInfo.battery_info().is_ac_plugged:\n            return AVAILABLE_GOVERNORS_SORTED[0]\n        return AVAILABLE_GOVERNORS_SORTED[-1]\n\n    def generate_system_report(self) -> SystemReport:\n        battery_info = self.battery_info()\n\n        return SystemReport(\n            distro_name=self.distro_name,\n            distro_ver=self.distro_version,\n            arch=self.architecture,\n            processor_model=self.processor_model,\n            total_core=self.total_cores,\n            cpu_driver=self.cpu_driver,\n            kernel_version=self.kernel_version,\n            current_gov=self.current_gov(),\n            current_epp=self.current_epp(battery_info.is_ac_plugged),\n            current_epb=self.current_epb(battery_info.is_ac_plugged),\n            cpu_fan_speed=self.cpu_fan_speed(),\n            cpu_usage=self.cpu_usage(),\n            cpu_max_freq=self.cpu_max_freq(),\n            cpu_min_freq=self.cpu_min_freq(),\n            load=self.system_load(),\n            avg_load=self.avg_load(),\n            cores_info=self.get_cpu_info(),\n            is_turbo_on=self.turbo_on(),\n            battery_info=battery_info,\n        )\n\n\nsystem_info = SystemInfo()\n"
  },
  {
    "path": "auto_cpufreq/modules/system_monitor.py",
    "content": "import sys\nfrom typing import Callable\nimport urwid\nimport time\nfrom .system_info import SystemReport, system_info\nfrom auto_cpufreq.config.config import config\nfrom enum import Enum\n\n\nclass ViewType(str, Enum):\n    STATS = \"Stats\"\n    MONITOR = \"Monitor\"\n    LIVE = \"Live\"\n\n    def __str__(self) -> str:\n        return self.value\n\n\nclass SystemMonitor:\n    def __init__(self, type: ViewType, suggestion: bool = False):\n        self.type: ViewType = type\n        self.title_header = urwid.Text(f\"{type} Mode\", align=\"center\")\n        self.header = urwid.Columns(\n            [\n                self.title_header,\n            ]\n        )\n\n        # Create separate content walkers for left and right columns\n        self.left_content = urwid.SimpleListWalker([])\n        self.right_content = urwid.SimpleListWalker([])\n\n        # Create listboxes for both columns\n        self.left_listbox = urwid.ListBox(self.left_content)\n        self.right_listbox = urwid.ListBox(self.right_content)\n\n        # Create a columns widget with a vertical line (using box drawing character)\n        self.columns = urwid.Columns(\n            [\n                (\"weight\", 1, self.left_listbox),\n                (\n                    \"fixed\",\n                    1,\n                    urwid.AttrMap(urwid.SolidFill(\"│\"), \"divider\"),\n                ),  # Vertical line # type: ignore\n                (\"weight\", 1, self.right_listbox),\n            ],\n            dividechars=0,\n        )\n\n        self.footer = urwid.AttrMap(\n            urwid.Text(\n                \"Press Q or Ctrl+C to quit | Use ↑↓ or PageUp/PageDown to scroll\",\n                align=\"center\",\n            ),\n            \"footer\",\n        )\n\n        self.frame = urwid.Frame(\n            body=self.columns,\n            header=urwid.AttrMap(self.header, \"header\"),\n            footer=self.footer,\n        )\n\n        palette = [\n            (\"header\", \"white\", \"dark blue\"),\n            (\"footer\", \"white\", \"dark green\"),\n            (\"body\", \"white\", \"default\"),\n            (\"divider\", \"light gray\", \"default\"),  # Style for the vertical line\n        ]\n\n        if suggestion:\n            palette.append((\"suggestion\", \"yellow\", \"default\"))\n\n        self.loop = urwid.MainLoop(\n            self.frame, palette=palette, unhandled_input=self.handle_input\n        )\n\n        self.last_focus_left = 0\n        self.last_focus_right = 0\n        self.on_quit: Callable[[], None] | None = None\n        self.suggestion = suggestion\n\n    def update(self, loop: urwid.MainLoop, user_data: dict) -> None:\n        # Store current focus positions\n        if len(self.left_content) > 0:\n            _, self.last_focus_left = self.left_listbox.get_focus()\n            if self.last_focus_left is None:\n                self.last_focus_left = 0\n        if len(self.right_content) > 0:\n            _, self.last_focus_right = self.right_listbox.get_focus()\n            if self.last_focus_right is None:\n                self.last_focus_right = 0\n\n        current_time = time.strftime(\"%H:%M:%S\")\n        self.title_header.set_text(f\"{self.type} Mode - {current_time}\")\n\n        report: SystemReport = system_info.generate_system_report()\n        self.format_system_info(report)\n\n        # Restore focus positions\n        if len(self.left_content) > 0:\n            self.left_listbox.set_focus(min(self.last_focus_left, len(self.left_content) - 1))  # type: ignore\n        if len(self.right_content) > 0:\n            self.right_listbox.set_focus(min(self.last_focus_right, len(self.right_content) - 1))  # type: ignore\n\n        self.loop.set_alarm_in(2, self.update)  # type: ignore\n\n    def handle_input(self, key):\n        if key in (\"q\", \"Q\"):\n            if self.on_quit:\n                self.on_quit()\n            raise urwid.ExitMainLoop()\n\n    def format_system_info(self, report: SystemReport):\n        self.left_content.clear()\n        self.right_content.clear()\n\n        # Helper function to create centered text\n        def aligned_text(text: str) -> urwid.Text:\n            return urwid.Text(text, align=\"left\")\n\n        # Left Column - System Info and CPU Stats\n        self.left_content.extend(\n            [\n                urwid.AttrMap(aligned_text(\"System Information\"), \"header\"),\n                aligned_text(\"\"),\n                aligned_text(f\"Linux distro: {report.distro_name} {report.distro_ver}\"),\n                aligned_text(f\"Linux kernel: {report.kernel_version}\"),\n                aligned_text(f\"Processor: {report.processor_model}\"),\n                aligned_text(f\"Cores: {report.total_core}\"),\n                aligned_text(f\"Architecture: {report.arch}\"),\n                aligned_text(f\"Driver: {report.cpu_driver}\"),\n                aligned_text(\"\"),\n            ]\n        )\n\n        if config.has_config():\n            self.left_content.append(\n                aligned_text(f\"Using settings defined in {config.path} file\")\n            )\n            self.left_content.append(aligned_text(\"\"))\n\n        # CPU Stats\n        self.left_content.extend(\n            [\n                urwid.AttrMap(aligned_text(\"Current CPU Stats\"), \"header\"),\n                aligned_text(\"\"),\n                aligned_text(f\"CPU max frequency: {report.cpu_max_freq} MHz\"),\n                aligned_text(f\"CPU min frequency: {report.cpu_min_freq} MHz\"),\n                aligned_text(\"\"),\n                aligned_text(\"Core    Usage   Temperature     Frequency\"),\n            ]\n        )\n\n        for core in report.cores_info:\n            self.left_content.append(\n                aligned_text(\n                    f\"CPU{core.id:<2}    {core.usage:>4.1f}%    {core.temperature:>6.0f} °C    {core.frequency:>6.0f} MHz\"\n                )\n            )\n\n        if report.cpu_fan_speed:\n            self.left_content.append(aligned_text(\"\"))\n            self.left_content.append(\n                aligned_text(f\"CPU fan speed: {report.cpu_fan_speed} RPM\")\n            )\n\n        # Right Column - Battery, Frequency Scaling, and System Stats\n        if report.battery_info != None:\n            self.right_content.extend(\n                [\n                    urwid.AttrMap(aligned_text(\"Battery Stats\"), \"header\"),\n                    aligned_text(\"\"),\n                    aligned_text(f\"Battery status: {str(report.battery_info)}\"),\n                    aligned_text(\n                        f\"Battery percentage: {(str(report.battery_info.battery_level) + '%') if report.battery_info.battery_level != None else 'Unknown'}\"\n                    ),\n                    aligned_text(\n                        f'AC plugged: {(\"Yes\" if report.battery_info.is_ac_plugged else \"No\") if report.battery_info.is_ac_plugged != None else \"Unknown\"}'\n                    ),\n                    aligned_text(\n                        f'Charging start threshold: {report.battery_info.charging_start_threshold if report.battery_info.is_ac_plugged != None else \"Unknown\"}'\n                    ),\n                    aligned_text(\n                        f'Charging stop threshold: {report.battery_info.charging_stop_threshold if report.battery_info.is_ac_plugged != None else \"Unknown\"}'\n                    ),\n                    aligned_text(\"\"),\n                ]\n            )\n\n        # CPU Frequency Scaling\n        self.right_content.extend(\n            [\n                urwid.AttrMap(aligned_text(\"CPU Frequency Scaling\"), \"header\"),\n                aligned_text(\"\"),\n                aligned_text(\n                    f'Setting to use: \"{report.current_gov if report.current_gov != None else \"Unknown\"}\" governor'\n                ),\n            ]\n        )\n\n        if (\n            self.suggestion\n            and report.current_gov != None\n            and system_info.governor_suggestion() != report.current_gov\n        ):\n            self.right_content.append(\n                urwid.AttrMap(\n                    aligned_text(\n                        f'Suggesting use of: \"{system_info.governor_suggestion()}\" governor'\n                    ),\n                    \"suggestion\",\n                )\n            )\n\n        if report.current_epp:\n            self.right_content.append(\n                aligned_text(f\"EPP setting: {report.current_epp}\")\n            )\n        else:\n            self.right_content.append(\n                aligned_text(\"Not setting EPP (not supported by system)\")\n            )\n\n        if report.current_epb:\n            self.right_content.append(\n                aligned_text(f'Setting to use: \"{report.current_epb}\" EPB')\n            )\n\n        self.right_content.append(aligned_text(\"\"))\n\n        # System Statistics\n        self.right_content.extend(\n            [\n                urwid.AttrMap(aligned_text(\"System Statistics\"), \"header\"),\n                aligned_text(\"\"),\n                aligned_text(f\"Total CPU usage: {report.cpu_usage:.1f} %\"),\n                aligned_text(f\"Total system load: {report.load:.2f}\"),\n            ]\n        )\n\n        if report.cores_info:\n            avg_temp = sum(core.temperature for core in report.cores_info) / len(\n                report.cores_info\n            )\n            self.right_content.append(\n                aligned_text(f\"Average temp. of all cores: {avg_temp:.2f} °C\")\n            )\n\n        if report.avg_load:\n            load_status = \"Load optimal\" if report.load < 1.0 else \"Load high\"\n            self.right_content.append(\n                aligned_text(\n                    f\"{load_status} (load average: {report.avg_load[0]:.2f}, {report.avg_load[1]:.2f}, {report.avg_load[2]:.2f})\"\n                )\n            )\n\n        if report.cores_info:\n            usage_status = \"Optimal\" if report.cpu_usage < 70 else \"High\"\n            temp_status = \"high\" if avg_temp > 75 else \"normal\"  # type: ignore\n            self.right_content.append(\n                aligned_text(\n                    f\"{usage_status} total CPU usage: {report.cpu_usage:.1f}%, {temp_status} average core temp: {avg_temp:.1f}°C\"  # type: ignore\n                )\n            )\n\n        turbo_status: str\n        if report.is_turbo_on[0] != None:\n            turbo_status = \"On\" if report.is_turbo_on[0] else \"Off\"\n        elif report.is_turbo_on[1] != None:\n            turbo_status = (\n                f\"Auto mode {'enabled' if report.is_turbo_on[1] else 'disabled'}\"\n            )\n        else:\n            turbo_status = \"Unknown\"\n        self.right_content.append(aligned_text(f\"Setting turbo boost: {turbo_status}\"))\n        if (\n            self.suggestion\n            and report.is_turbo_on[0] != None\n            and system_info.turbo_on_suggestion() != report.is_turbo_on[0]\n        ):\n            self.right_content.append(\n                urwid.AttrMap(\n                    aligned_text(\n                        f'Suggesting to set turbo boost: {\"on\" if system_info.turbo_on_suggestion() else \"off\"}'\n                    ),\n                    \"suggestion\",\n                )\n            )\n\n    def run(self, on_quit: Callable[[], None] | None = None):\n        try:\n            if on_quit:\n                self.on_quit = on_quit\n            self.loop.set_alarm_in(0, self.update)  # type: ignore\n            self.loop.run()\n        except KeyboardInterrupt:\n            if on_quit:\n                on_quit()\n            sys.exit(0)\n"
  },
  {
    "path": "auto_cpufreq/power_helper.py",
    "content": "# * add status as one of the available options\n# * alert user on snap if detected and how to remove first time live/stats message starts\n# * if daemon is disabled and auto-cpufreq is removed (snap) remind user to enable it back\nimport click\nfrom shutil import which\nfrom subprocess import call, DEVNULL, getoutput, STDOUT\nfrom sys import argv\n\n# ToDo: update README part how to run this script\nfrom auto_cpufreq.core import *\nfrom auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_SNAP\nfrom auto_cpufreq.tlp_stat_parser import TLPStatusParser\n\n# app_name var\napp_name = \"python3 power_helper.py\" if argv[0] == \"power_helper.py\" else \"auto-cpufreq\"\n\ndef header(): print(\"\\n------------------------- auto-cpufreq: Power helper -------------------------\\n\")\ndef warning(): print(\"\\n----------------------------------- Warning -----------------------------------\\n\")\n\ndef helper_opts(): print(\"\\nFor full list of options run: python3 -m auto_cpufreq.power_helper --help\")\n\n# used to check if binary exists on the system\ndef does_command_exists(cmd): return which(cmd) is not None\n\nbluetoothctl_exists = does_command_exists(\"bluetoothctl\")\npowerprofilesctl_exists = does_command_exists(\"powerprofilesctl\")\nsystemctl_exists = does_command_exists(\"systemctl\")\ntlp_stat_exists = does_command_exists(\"tlp-stat\")\ntuned_stat_exists = does_command_exists(\"tuned\")\n\n# detect if gnome power profile service is running\nif not IS_INSTALLED_WITH_SNAP:\n    if systemctl_exists:\n        try: gnome_power_status = call([\"systemctl\", \"is-active\", \"--quiet\", \"power-profiles-daemon\"])\n        except:\n            print(\"\\nUnable to determine init system\")\n            print(\"If this causes any problems, please submit an issue:\")\n            print(GITHUB+\"/issues\")\n\n# alert in case TLP service is running\ndef tlp_service_detect():\n    if tlp_stat_exists:\n        status_output = getoutput(\"tlp-stat -s\")\n        tlp_status = TLPStatusParser(status_output)\n        if tlp_status.is_enabled():\n            warning()\n            print(\"Detected you are running a TLP service!\")\n            print(\"This daemon might interfere with auto-cpufreq which can lead to unexpected results.\")\n            print(\"We strongly encourage you to remove TLP unless you really know what you are doing.\")\n\n# alert about TLP when using snap\ndef tlp_service_detect_snap():\n    warning()\n    print(\"Unable to detect if you are using a TLP service!\")\n    print(\"This daemon might interfere with auto-cpufreq which can lead to unexpected results.\")\n    print(\"We strongly encourage you not to use TLP unless you really know what you are doing.\")\n\n# alert in case gnome power profile service is running\ndef gnome_power_detect():\n    if systemctl_exists and not bool(gnome_power_status):\n        warning()\n        print(\"Detected running GNOME Power Profiles daemon service!\")\n        print(\"\\nThis daemon might interfere with auto-cpufreq and will be automatically\")\n        print(\"disabled when auto-cpufreq daemon is installed and\")\n        print(\"it will be re-enabled after auto-cpufreq is removed.\")\n        \n        print(\"\\nOnly necessary to be manually done on Snap package installs!\")\n        print(\"Steps to perform this action using auto-cpufreq: power_helper script:\")\n        print(f\"git clone {GITHUB}.git\")\n        print(\"python3 -m auto_cpufreq.power_helper --gnome_power_disable\")\n        print(f\"\\nReference: {GITHUB}#configuring-auto-cpufreq\")\n\n# automatically disable gnome power profile service in case it's running during install\ndef gnome_power_detect_install():\n    if systemctl_exists and not bool(gnome_power_status):\n        warning()\n        print(\"Detected running GNOME Power Profiles daemon service!\")\n        print(\"\\nThis daemon might interfere with auto-cpufreq and has been disabled.\\n\")\n        print('This daemon is not automatically disabled in \"monitor\" mode and')\n        print(\"will be enabled after auto-cpufreq daemon is removed.\")\n\n\n# notification on snap\ndef gnome_power_detect_snap():\n    warning()\n    print(\"Due to Snap package confinement limitations please consider installing auto-cpufreq using\")\n    print(f\"auto-cpufreq-installer: {GITHUB}#auto-cpufreq-installer\")\n    print()\n    print(\"Unable to detect state of GNOME Power Profiles daemon service!\")\n    print(\"This daemon might interfere with auto-cpufreq and should be disabled!\")\n    print(\"\\nSteps to perform this action using auto-cpufreq: power_helper script:\")\n    print(f\"git clone {GITHUB}.git\")\n    print(\"python3 -m auto_cpufreq.power_helper --gnome_power_disable\")\n    print(f\"\\nReference: {GITHUB}#configuring-auto-cpufreq\")\n\n# stops gnome >= 40 power profiles (live)\ndef gnome_power_stop_live():\n    if systemctl_exists and not bool(gnome_power_status) and powerprofilesctl_exists:\n        call([\"powerprofilesctl\", \"set\", \"balanced\"])\n        call([\"systemctl\", \"stop\", \"power-profiles-daemon\"])\n\n# stops tuned (live)\ndef tuned_stop_live():\n    if systemctl_exists and tuned_stat_exists:\n        call([\"systemctl\", \"stop\", \"tuned\"])\n\n# starts gnome >= 40 power profiles (live)\ndef gnome_power_start_live():\n    if systemctl_exists: call([\"systemctl\", \"start\", \"power-profiles-daemon\"])\n\ndef tuned_start_live():\n    if systemctl_exists and tuned_stat_exists: \n        call([\"systemctl\", \"start\", \"tuned\"])\n\n# enable gnome >= 40 power profiles (uninstall)\ndef gnome_power_svc_enable():\n    if systemctl_exists:\n        try:\n            print(\"* Enabling GNOME power profiles\\n\")\n            call([\"systemctl\", \"unmask\", \"power-profiles-daemon\"])\n            call([\"systemctl\", \"enable\", \"--now\", \"power-profiles-daemon\"])\n        except:\n            print(\"\\nUnable to enable GNOME power profiles\")\n            print(\"If this causes any problems, please submit an issue:\")\n            print(GITHUB+\"/issues\")\n\ndef tuned_svc_enable():\n    if systemctl_exists and tuned_stat_exists:\n        try:\n            print(\"* Enabling TuneD\\n\")\n            call([\"systemctl\", \"unmask\", \"tuned\"])\n            call([\"systemctl\", \"enable\", \"--now\", \"tuned\"])\n        except:\n            print(\"\\nUnable to enable TuneD daemon\")\n            print(\"If this causes any problems, please submit an issue:\")\n            print(GITHUB+\"/issues\")\n\n# gnome power profiles current status\ndef gnome_power_svc_status():\n    if systemctl_exists:\n        try:\n            print(\"* GNOME power profiles status\")\n            call([\"systemctl\", \"status\", \"power-profiles-daemon\"])\n        except:\n            print(\"\\nUnable to see GNOME power profiles status\")\n            print(\"If this causes any problems, please submit an issue:\")\n            print(GITHUB+\"/issues\")\n\ndef set_bluetooth_auto_enable(value: bool) -> bool:\n    \"\"\"Set AutoEnable in [Policy] section of /etc/bluetooth/main.conf.\n    Returns True on success, False on failure.\"\"\"\n    btconf = Path(\"/etc/bluetooth/main.conf\")\n    setting = f\"AutoEnable={'true' if value else 'false'}\"\n\n    try:\n        lines = btconf.read_text().splitlines(keepends=True)\n    except Exception:\n        return False\n\n    new_lines = []\n    in_policy_section = False\n    found_and_set = False\n\n    for line in lines:\n        stripped = line.strip()\n\n        if stripped.startswith(\"[\"):\n            if in_policy_section and not found_and_set:\n                new_lines.append(f\"{setting}\\n\")\n                found_and_set = True\n            in_policy_section = stripped.lower() == \"[policy]\"\n            new_lines.append(line)\n            continue\n\n        if in_policy_section:\n            if not stripped.startswith(\"#\") and stripped.startswith(\"AutoEnable=\"):\n                new_lines.append(f\"{setting}\\n\")\n                found_and_set = True\n                continue\n            if stripped.startswith(\"#\"):\n                uncommented = stripped.lstrip(\"#\").strip()\n                if uncommented.startswith(\"AutoEnable=\"):\n                    new_lines.append(f\"{setting}\\n\")\n                    found_and_set = True\n                    continue\n\n        new_lines.append(line)\n\n    if in_policy_section and not found_and_set:\n        new_lines.append(f\"{setting}\\n\")\n        found_and_set = True\n\n    if not found_and_set:\n        new_lines.append(\"\\n[Policy]\\n\")\n        new_lines.append(f\"{setting}\\n\")\n\n    try:\n        btconf.write_text(\"\".join(new_lines))\n        return True\n    except Exception:\n        return False\n\n# disable bluetooth on boot\ndef bluetooth_disable():\n    if IS_INSTALLED_WITH_SNAP: bluetooth_notif_snap()\n    elif bluetoothctl_exists:\n        print(\"* Turn off Bluetooth on boot (only)!\")\n        print(\"  If you want bluetooth enabled on boot run: auto-cpufreq --bluetooth_boot_on\")\n        if not set_bluetooth_auto_enable(False):\n            print(\"\\nERROR:\\nWas unable to turn off bluetooth on boot\")\n    else: print(\"* Turn off bluetooth on boot [skipping] (package providing bluetooth access is not present)\")\n\n# enable bluetooth on boot\ndef bluetooth_enable():\n    if IS_INSTALLED_WITH_SNAP: bluetooth_on_notif_snap()\n    elif bluetoothctl_exists:\n        print(\"* Turn on bluetooth on boot\")\n        if not set_bluetooth_auto_enable(True):\n            print(\"\\nERROR:\\nWas unable to turn on bluetooth on boot\")\n    else: print(\"* Turn on bluetooth on boot [skipping] (package providing bluetooth access is not present)\")\n\n# turn off bluetooth on snap message\ndef bluetooth_notif_snap():\n    print(\"\\n* Unable to turn off bluetooth on boot due to Snap package restrictions!\")\n    print(\"\\nSteps to perform this action using auto-cpufreq: power_helper script:\")\n    print(\"python3 -m auto_cpufreq.power_helper --bluetooth_boot_off\")\n    print(\"\\nFor help see: https://github.com/AdnanHodzic/auto-cpufreq/#1-power_helperpy-script-snap-package-install-only\")\n\n# turn off bluetooth on snap message\ndef bluetooth_on_notif_snap():\n    print(\"\\n* Unable to turn on bluetooth on boot due to Snap package restrictions!\")\n    print(\"\\nSteps to perform this action using auto-cpufreq: power_helper script:\")\n    print(\"python3 -m auto_cpufreq.power_helper --bluetooth_boot_on\")\n    print(\"\\nFor help see: https://github.com/AdnanHodzic/auto-cpufreq/#1-power_helperpy-script-snap-package-install-only\")\n\n# gnome power removal reminder\ndef gnome_power_rm_reminder():\n    if systemctl_exists and bool(gnome_power_status):\n        warning()\n        print(\"Detected GNOME Power Profiles daemon service is stopped!\")\n        print(\"This service will now be enabled and started again.\\n\")\n\n\ndef gnome_power_rm_reminder_snap():\n    warning()\n    print(\"Unable to detect state of GNOME Power Profiles daemon service!\")\n    print(\"Now it's recommended to enable this service.\")\n    print(\"\\nSteps to perform this action using auto-cpufreq: power_helper script:\")\n    print(f\"git clone {GITHUB}.git\")\n    print(\"python3 -m auto_cpufreq.power_helper --gnome_power_enable\")\n    print(f\"\\nReference: {GITHUB}#configuring-auto-cpufreq\")\n\ndef valid_options():\n    print(\"--gnome_power_enable\\t\\tEnable GNOME Power Profiles daemon\")\n    print(\"--gnome_power_disable\\t\\tDisable GNOME Power Profiles daemon\\n\")\n\ndef disable_power_profiles_daemon():\n    # always disable power-profiles-daemon\n    try:\n        print(\"\\n* Disabling GNOME power profiles\")\n        call([\"systemctl\", \"disable\", \"--now\", \"power-profiles-daemon\"])\n        call([\"systemctl\", \"mask\", \"power-profiles-daemon\"])\n    except:\n        print(\"\\nUnable to disable GNOME power profiles\")\n        print(\"If this causes any problems, please submit an issue:\")\n        print(GITHUB+\"/issues\")\n\ndef disable_tuned_daemon():\n    # always disable TuneD daemon\n    try:\n        print(\"\\n* Disabling TuneD daemon\")\n        call([\"systemctl\", \"disable\", \"--now\", \"tuned\"])\n        call([\"systemctl\", \"mask\", \"tuned\"])\n    except:\n        print(\"\\nUnable to disable TuneD daemon\")\n        print(\"If this causes any problems, please submit an issue:\")\n        print(GITHUB+\"/issues\")\n\n# default gnome_power_svc_disable func (balanced)\ndef gnome_power_svc_disable():\n    snap_pkg_check = 0\n    if systemctl_exists:\n        if bool(gnome_power_status):\n            try:\n                # check if snap package installed\n                snap_pkg_check = call(['snap', 'list', '|', 'grep', 'auto-cpufreq'], \n                stdout=DEVNULL,\n                stderr=STDOUT)\n                # check if snapd is present and if snap package is installed | 0 is success\n                if not bool(snap_pkg_check):\n                    print(\"GNOME Power Profiles Daemon is already disabled, it can be re-enabled by running:\\n\"\n                        \"sudo python3 -m auto_cpufreq.power_helper --gnome_power_enable\\n\"\n                    )\n                elif snap_pkg_check == 1:\n                    print(\"auto-cpufreq snap package not installed\\nGNOME Power Profiles Daemon should be enabled. run:\\n\\n\"\n                        \"sudo python3 -m auto_cpufreq.power_helper --gnome_power_enable\"\n                    )\n            except:\n                # snapd not found on the system\n                print(\"There was a problem, couldn't determine GNOME Power Profiles Daemon\")\n                snap_pkg_check = 0\n\n        if not bool(gnome_power_status) and powerprofilesctl_exists:\n            if snap_pkg_check == 1:\n                print(\"auto-cpufreq snap package not installed.\\nGNOME Power Profiles Daemon should be enabled, run:\\n\\n\"\n                    \"sudo python3 -m auto_cpufreq.power_helper --gnome_power_enable\"\n                )\n            else:\n                print(\"auto-cpufreq snap package installed, GNOME Power Profiles Daemon should be disabled.\\n\")\n                print(\"Using profile: \", \"balanced\")\n                call([\"powerprofilesctl\", \"set\", \"balanced\"])\n\n                disable_power_profiles_daemon()\n\ndef tuned_svc_disable():\n    if systemctl_exists and tuned_stat_exists:\n        disable_tuned_daemon()\n\n# cli\n@click.command()\n#@click.option(\"--gnome_power_disable\", help=\"Disable GNOME Power profiles service (default: balanced), reference:\\n https://bit.ly/3bjVZW1\", type=click.Choice(['balanced', 'performance'], case_sensitive=False))\n@click.option(\"--gnome_power_disable\", is_flag=True, help=\"Disable GNOME Power profiles service\")\n# ToDo:\n# * update readme/docs\n@click.option(\"--gnome_power_enable\", is_flag=True, help=\"Enable GNOME Power profiles service\")\n\n@click.option(\"--gnome_power_status\", is_flag=True, help=\"Get status of GNOME Power profiles service\")\n@click.option(\"--bluetooth_boot_on\", is_flag=True, help=\"Turn on Bluetooth on boot\")\n@click.option(\"--bluetooth_boot_off\", is_flag=True, help=\"Turn off Bluetooth on boot\")\ndef main(\n    gnome_power_enable,\n    gnome_power_disable,\n    gnome_power_status,\n    bluetooth_boot_off,\n    bluetooth_boot_on,\n):\n    root_check()\n    header()\n\n    if len(argv) == 1: print('Unrecognized option!\\n\\nRun: \"' + app_name + ' --help\" for list of available options.')\n    else:\n        if gnome_power_enable: gnome_power_svc_enable()\n        elif gnome_power_disable: gnome_power_svc_disable()\n        elif gnome_power_status: gnome_power_svc_status()\n        elif bluetooth_boot_off: bluetooth_disable()\n        elif bluetooth_boot_on: bluetooth_enable()\n        helper_opts()\n\n    footer()\n\nif __name__ == \"__main__\": main()"
  },
  {
    "path": "auto_cpufreq/tlp_stat_parser.py",
    "content": "class TLPStatusParser:\n    def __init__(self, tlp_stat_output):\n        self.data = {}\n        self._parse(tlp_stat_output)\n\n    def _parse(self, data):\n        for line in data.split(\"\\n\"):\n            key_val = line.split(\"=\", 1)\n            if len(key_val) > 1: self.data[key_val[0].strip().lower()] = key_val[1].strip()\n\n    def _get_key(self, key): return self.data[key] if key in self.data else \"\"\n\n    def is_enabled(self): return self._get_key(\"state\") == \"enabled\"\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\n  };\n\n  outputs = {nixpkgs, ...} @ inputs: let\n    forAllSystems = nixpkgs.lib.genAttrs [\"x86_64-linux\" \"i686-linux\" \"aarch64-linux\"];\n    pkgsForEach = nixpkgs.legacyPackages;\n  in {\n    packages = forAllSystems (system: {\n      default = pkgsForEach.${system}.callPackage ./nix/default.nix {};\n    });\n\n    devShells = forAllSystems (system: {\n      default = pkgsForEach.${system}.callPackage ./nix/shell.nix {};\n    });\n\n    overlays.default = final: _: {\n      auto-cpufreq = final.callPackage ./nix/default.nix {};\n    };\n\n    nixosModules.default = import ./nix/module.nix inputs;\n  };\n}\n"
  },
  {
    "path": "nix/default.nix",
    "content": "{\n  lib,\n  python3Packages,\n  pkgs,\n  fetchFromGitHub,\n  fetchPypi,\n}:\nlet\n\n  pyinotify = python3Packages.pyinotify.overrideAttrs (oldAttrs: {\n    src = fetchFromGitHub {\n      owner = \"shadeyg56\";\n      repo = \"pyinotify-3.12\";\n      rev = \"923cebec3a2a84c7e38c9e68171eb93f5d07ce5d\";\n      hash = \"sha256-714CximEK4YhIqDmvqJYOUGs39gvDkWGrkNrXwxT8iM=\";\n    };\n    patches = [];\n  });\n\n  requests = python3Packages.requests.overrideAttrs (oldAttrs: rec {\n    version = \"2.32.4\";\n    src = fetchPypi {\n      pname = \"requests\";\n      inherit version;\n      hash = \"sha256-J9AxZoLIopg00yZIIAJLYqNpQgg9Usry8UwFkTNtNCI=\";\n    };\n    patches = [];\n  });\n\nin\npython3Packages.buildPythonPackage {\n  # use pyproject.toml instead of setup.py\n  format = \"pyproject\";\n\n  pname = \"auto-cpufreq\";\n  version = \"3.0.0\";\n  src = ../.;\n\n  nativeBuildInputs = with pkgs; [wrapGAppsHook3 gobject-introspection];\n\n  buildInputs = with pkgs; [gtk3 python3Packages.poetry-core];\n\n  propagatedBuildInputs = with python3Packages; [requests pygobject3 click distro psutil setuptools poetry-dynamic-versioning pyinotify urwid pyasyncore pkgs.getent\n    pkgs.kmod pkgs.dmidecode \n  ];\n\n  doCheck = false;\n  pythonImportsCheck = [\"auto_cpufreq\"];\n\n  patches = [\n    # patch to prevent script copying and to disable install\n    ./patches/prevent-install-and-copy.patch\n  ];\n\n  postPatch = ''\n    substituteInPlace auto_cpufreq/core.py --replace-fail '/opt/auto-cpufreq/override.pickle' /var/run/override.pickle\n    substituteInPlace scripts/org.auto-cpufreq.pkexec.policy --replace-fail \"/opt/auto-cpufreq/venv/bin/auto-cpufreq\" $out/bin/auto-cpufreq\n\n    substituteInPlace auto_cpufreq/gui/app.py auto_cpufreq/gui/objects.py --replace-fail \"/usr/local/share/auto-cpufreq/images/icon.png\" $out/share/pixmaps/auto-cpufreq.png\n    substituteInPlace auto_cpufreq/gui/app.py --replace-fail \"/usr/local/share/auto-cpufreq/scripts/style.css\" $out/share/auto-cpufreq/scripts/style.css\n  '';\n\n  postInstall = ''\n    # copy script manually\n    cp scripts/cpufreqctl.sh $out/bin/cpufreqctl.auto-cpufreq\n\n    # move the css to the right place\n    mkdir -p $out/share/auto-cpufreq/scripts\n    cp scripts/style.css $out/share/auto-cpufreq/scripts/style.css\n\n    # systemd service\n    mkdir -p $out/lib/systemd/system\n    cp scripts/auto-cpufreq.service $out/lib/systemd/system\n\n    # desktop icon\n    mkdir -p $out/share/applications\n    mkdir $out/share/pixmaps\n    cp scripts/auto-cpufreq-gtk.desktop $out/share/applications\n    cp images/icon.png $out/share/pixmaps/auto-cpufreq.png\n\n    # polkit policy\n    mkdir -p $out/share/polkit-1/actions\n    cp scripts/org.auto-cpufreq.pkexec.policy $out/share/polkit-1/actions\n  '';\n\n  meta = {\n    homepage = \"https://github.com/AdnanHodzic/auto-cpufreq\";\n    description = \"Automatic CPU speed & power optimizer for Linux\";\n    license = lib.licenses.lgpl3Plus;\n    platforms = lib.platforms.linux;\n    maintainers = with lib.maintainers; [Technical27];\n    mainProgram = \"auto-cpufreq\";\n  };\n}\n"
  },
  {
    "path": "nix/module.nix",
    "content": "inputs: {\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.programs.auto-cpufreq;\n  inherit (pkgs.stdenv.hostPlatform) system;\n  defaultPackage = inputs.self.packages.${system}.default;\n  cfgFilename = \"auto-cpufreq.conf\";\n  cfgFile = format.generate cfgFilename cfg.settings;\n\n  inherit (lib) types;\n  inherit (lib.modules) mkIf mkForce;\n  inherit (lib.options) mkOption mkEnableOption;\n\n  format = pkgs.formats.ini {};\nin {\n  options.programs.auto-cpufreq = {\n    enable = mkEnableOption \"Automatic CPU speed & power optimizer for Linux\";\n\n    settings = mkOption {\n      description = ''\n        Configuration for `auto-cpufreq`.\n\n        See its [example configuration file] for supported settings.\n        [example configuration file]: https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto-cpufreq.conf-example\n      '';\n\n      default = {};\n      type = types.submodule {freeformType = format.type;};\n    };\n  };\n\n  config = mkIf cfg.enable {\n    environment.systemPackages = [ defaultPackage ];\n\n    systemd = {\n      packages =  [ defaultPackage ];\n      services.auto-cpufreq = {\n        wantedBy = [ \"multi-user.target\" ];\n        path = with pkgs; [ bash coreutils gawk ];\n        overrideStrategy = \"asDropin\";\n\n        serviceConfig.WorkingDirectory = \"\";\n        serviceConfig.ExecStart = mkForce [\n          \"\"\n          \"${defaultPackage}/bin/auto-cpufreq --daemon --config ${cfgFile}\"\n        ];\n      };\n    };\n\n    assertions = [\n      {\n        assertion = !config.services.power-profiles-daemon.enable;\n        message = ''\n          You have set services.power-profiles-daemon.enable = true;\n          which conflicts with auto-cpufreq\n        '';\n      }\n    ];\n  };\n}\n"
  },
  {
    "path": "nix/patches/prevent-install-and-copy.patch",
    "content": "diff --git a/auto_cpufreq/core.py b/auto_cpufreq/core.py\nindex f03e7de..2dff5fb 100755\n--- a/auto_cpufreq/core.py\n+++ b/auto_cpufreq/core.py\n@@ -277,19 +277,12 @@ def get_current_gov():\n     )\n \n def cpufreqctl():\n-    \"\"\"\n-    deploy cpufreqctl.auto-cpufreq script\n-    \"\"\"\n-    if not (IS_INSTALLED_WITH_SNAP or os.path.isfile(\"/usr/local/bin/cpufreqctl.auto-cpufreq\")):\n-        copy(SCRIPTS_DIR / \"cpufreqctl.sh\", \"/usr/local/bin/cpufreqctl.auto-cpufreq\")\n-        call([\"chmod\", \"a+x\", \"/usr/local/bin/cpufreqctl.auto-cpufreq\"])\n+    # scripts are already in the correct place\n+    pass\n \n def cpufreqctl_restore():\n-    \"\"\"\n-    remove cpufreqctl.auto-cpufreq script\n-    \"\"\"\n-    if not IS_INSTALLED_WITH_SNAP and os.path.isfile(\"/usr/local/bin/cpufreqctl.auto-cpufreq\"):\n-        os.remove(\"/usr/local/bin/cpufreqctl.auto-cpufreq\")\n+    #no need to restore\n+    pass\n \n def footer(l=79): print(\"\\n\" + \"-\" * l + \"\\n\")\n \n@@ -307,31 +300,8 @@ def remove_complete_msg():\n     footer()\n \n def deploy_daemon():\n-    print(\"\\n\" + \"-\" * 21 + \" Deploying auto-cpufreq as a daemon \" + \"-\" * 22 + \"\\n\")\n-\n-    cpufreqctl() # deploy cpufreqctl script func call\n-\n-    bluetooth_disable() # turn off bluetooth on boot\n-\n-    auto_cpufreq_stats_path.touch(exist_ok=True)\n-\n-    print(\"\\n* Deploy auto-cpufreq install script\")\n-    copy(SCRIPTS_DIR / \"auto-cpufreq-install.sh\", \"/usr/local/bin/auto-cpufreq-install\")\n-    call([\"chmod\", \"a+x\", \"/usr/local/bin/auto-cpufreq-install\"])\n-\n-    print(\"\\n* Deploy auto-cpufreq remove script\")\n-    copy(SCRIPTS_DIR / \"auto-cpufreq-remove.sh\", \"/usr/local/bin/auto-cpufreq-remove\")\n-    call([\"chmod\", \"a+x\", \"/usr/local/bin/auto-cpufreq-remove\"])\n-\n-    # output warning if gnome power profile is running\n-    gnome_power_detect_install()\n-    gnome_power_svc_disable()\n-\n-    tuned_svc_disable()\n-\n-    tlp_service_detect() # output warning if TLP service is detected\n-\n-    call(\"/usr/local/bin/auto-cpufreq-install\", shell=True)\n+    # prevent needless copying and system changes\n+    pass\n \n def deploy_daemon_performance():\n     print(\"\\n\" + \"-\" * 21 + \" Deploying auto-cpufreq as a daemon (performance) \" + \"-\" * 22 + \"\\n\")\n@@ -363,37 +333,7 @@ def deploy_daemon_performance():\n \n     call(\"/usr/local/bin/auto-cpufreq-install\", shell=True)\n \n-def remove_daemon():\n-    # check if auto-cpufreq is installed\n-    if not os.path.exists(\"/usr/local/bin/auto-cpufreq-remove\"):\n-        print(\"\\nauto-cpufreq daemon is not installed.\\n\")\n-        sys.exit(1)\n-\n-    print(\"\\n\" + \"-\" * 21 + \" Removing auto-cpufreq daemon \" + \"-\" * 22 + \"\\n\")\n-\n-    bluetooth_enable() # turn on bluetooth on boot\n-\n-    # output warning if gnome power profile is stopped\n-    gnome_power_rm_reminder()\n-    gnome_power_svc_enable()\n-\n-    tuned_svc_enable()\n-\n-    # run auto-cpufreq daemon remove script\n-    call(\"/usr/local/bin/auto-cpufreq-remove\", shell=True)\n-\n-    # remove auto-cpufreq-remove\n-    os.remove(\"/usr/local/bin/auto-cpufreq-remove\")\n-\n-    # delete override pickle if it exists\n-    if os.path.exists(governor_override_state):  os.remove(governor_override_state)\n-\n-    # delete stats file\n-    if auto_cpufreq_stats_path.exists():\n-        if auto_cpufreq_stats_file is not None: auto_cpufreq_stats_file.close()\n-        auto_cpufreq_stats_path.unlink()\n-\n-    cpufreqctl_restore() # restore original cpufrectl script\n+def remove_daemon(): pass\n \n def gov_check():\n     for gov in AVAILABLE_GOVERNORS:\n"
  },
  {
    "path": "nix/shell.nix",
    "content": "{\n  python3Packages,\n  pkgs,\n  ...\n}: let\n  mainPkg = python3Packages.callPackage ./default.nix {};\nin\n  mainPkg.overrideAttrs (oa: {\n    nativeBuildInputs =\n      [\n        python3Packages.pip\n        pkgs.poetry\n      ]\n      ++ (oa.nativeBuildInputs or []);\n  })\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"auto-cpufreq\"\nversion = \"3.0.0\"\ndescription = \"Automatic CPU speed & power optimizer for Linux\"\nauthors = [\"Adnan Hodzic <adnan@hodzic.org>\"]\nlicense = \"GPL-3.0-or-later\"\nreadme = \"README.md\"\nclassifiers=[\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Operating System :: POSIX :: Linux\",\n    \"Environment :: Console\",\n    \"Natural Language :: English\"\n]\nkeywords=[\"linux\", \"cpu\", \"speed\", \"power\", \"frequency\", \"turbo\", \"optimzier\", \"auto\", \"cpufreq\"]\nrepository = \"https://github.com/AdnanHodzic/auto-cpufreq\"\ndocumentation = \"https://github.com/AdnanHodzic/auto-cpufreq#readme\"\npackages = [\n    { include = \"./auto_cpufreq\" },\n    { include = \"./auto_cpufreq/gui\" },\n]\n\n[tool.poetry.dependencies]\npython = \">=3.9, <4.0\"\npsutil = \">=6.0.0,<8.0.0\"\nclick = \"^8.1.0\"\ndistro = \"^1.8.0\"\nrequests = \"^2.32.4\"\n# PyObject version will be updated automatically for Debian based distro based on libgirepository \n# apt package version: https://github.com/AdnanHodzic/auto-cpufreq/pull/826#issuecomment-2794549837\nPyGObject = {version=\"^3.50.0\", optional=true}\nurwid = \"^3.0.2\"\npyinotify = {git = \"https://github.com/shadeyg56/pyinotify-3.12\"}\npyasyncore = \"^1.0.4\"\n\n[tool.poetry.extras]\ngui = [\"PyGObject\"]\n\n[tool.poetry.group.dev.dependencies]\npoetry = \"^1.6.1\"\n\n[build-system]\nrequires = [\"poetry-core>=1.0.0\", \"poetry-dynamic-versioning>=1.0.0,<2.0.0\"]\nbuild-backend = \"poetry_dynamic_versioning.backend\"\n\n[tool.poetry.scripts]\nauto-cpufreq = \"auto_cpufreq.bin.auto_cpufreq:main\"\nauto-cpufreq-gtk = \"auto_cpufreq.bin.auto_cpufreq_gtk:main\"\n\n# https://github.com/mtkennerly/poetry-dynamic-versioning\n[tool.poetry-dynamic-versioning]\nenable = true\nvcs = \"git\"\nformat = \"v{base}+{commit}\"\n\n# SideNote\n# Regarding zip_safe = https://setuptools.pypa.io/en/latest/deprecated/zip_safe.html\n"
  },
  {
    "path": "scripts/auto-cpufreq-dinit",
    "content": "type = scripted\ncommand = /usr/local/bin/auto-cpufreq --daemon\nrun-as = root"
  },
  {
    "path": "scripts/auto-cpufreq-gtk.desktop",
    "content": "[Desktop Entry]\nName=auto-cpufreq\nExec=auto-cpufreq-gtk\nType=Application\nTerminal=false\nIcon=auto-cpufreq\nStartupWMClass=app.py\nCategories=System;"
  },
  {
    "path": "scripts/auto-cpufreq-install.sh",
    "content": "#!/usr/bin/env bash\n#\n# auto-cpufreq daemon install script\n# reference: https://github.com/AdnanHodzic/auto-cpufreq\n# Thanks to https://github.com/errornonamer for openrc fix\n\nMID=\"$((`tput cols` / 2))\"\n\necho\nprintf \"%0.s─\" $(seq $(( (MID-(${#1}/2)-2) / 2 )))\nprintf \" Running auto-cpufreq daemon install script \"\nprintf \"%0.s─\" $(seq $(( (MID-(${#1}/2)-2) / 2 )))\necho; echo\n\n# root check\nif ((EUID != 0)); then\n  echo; echo \"Must be run as root (i.e: 'sudo $0').\"; echo\n  exit 1\nfi\n\n# First argument is the init name, second argument is the start command, third argument is the enable command\nfunction auto_cpufreq_install {\n    echo -e \"\\n* Starting auto-cpufreq daemon ($1) service\"\n    $2\n    echo -e \"\\n* Enabling auto-cpufreq daemon ($1) at boot\"\n    $3\n}\n\ncase \"$(ps h -o comm 1)\" in\n  dinit) \n    echo -e \"\\n* Deploying auto-cpufreq (dinit) unit file\"\n    cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-dinit /etc/dinit.d/auto-cpufreq\n\n    auto_cpufreq_install \"dinit\" \"dinitctl start auto-cpufreq\" \"dinitctl enable auto-cpufreq\"\n  ;;\n  init) \n    echo -e \"\\n* Deploying auto-cpufreq openrc unit file\"\n    cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-openrc /etc/init.d/auto-cpufreq\n    chmod +x /etc/init.d/auto-cpufreq\n\n    auto_cpufreq_install \"openrc\" \"rc-service auto-cpufreq start\" \"rc-update add auto-cpufreq\"\n  ;;\n  runit)\n    # First argument is the \"sv\" path, second argument is the \"service\" path\n    runit_ln() {\n      echo -e \"\\n* Deploying auto-cpufreq (runit) unit file\"\n      mkdir \"$1\"/sv/auto-cpufreq\n      cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-runit \"$1\"/sv/auto-cpufreq/run\n      chmod +x \"$1\"/sv/auto-cpufreq/run\n\n      echo -e \"\\n* Creating symbolic link ($2/service/auto-cpufreq -> $1/sv/auto-cpufreq)\"\n      ln -s \"$1\"/sv/auto-cpufreq \"$2\"/service\n\n      auto_cpufreq_install \"runit\"\n\n      sv start auto-cpufreq\n      sv up auto-cpufreq\n    }\n\n    if [ -f /etc/os-release ];then\n      eval \"$(cat /etc/os-release)\"\n      case $ID in\n        void) runit_ln /etc /var;;\n        artix) runit_ln /etc/runit /run/runit;;\n        *)\n          echo -e \"\\n* Runit init detected but your distro is not supported\\n\"\n          echo -e \"\\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\\n\"\n      esac\n    fi\n  ;;\n  systemd)\n    echo -e \"Deploying auto-cpufreq systemd unit file\"\n    cp /usr/local/share/auto-cpufreq/scripts/auto-cpufreq.service /etc/systemd/system/auto-cpufreq.service\n\n    echo -e \"\\n* Reloading systemd manager configuration\"\n    systemctl daemon-reload\n\n    auto_cpufreq_install \"systemd\" \"systemctl start auto-cpufreq\" \"systemctl enable auto-cpufreq\"\n  ;;\n  s6-svscan)\n\t  echo -e \"\\n* Deploying auto-cpufreq (s6) unit file\"\n    cp -r /usr/local/share/auto-cpufreq/scripts/auto-cpufreq-s6 /etc/s6/sv/auto-cpufreq\n\n    echo -e \"\\n* Add auto-cpufreq service (s6) to default bundle\"\n    s6-service add default auto-cpufreq\n\n    auto_cpufreq_install \"s6\" \"s6-rc -u change auto-cpufreq default\"\n\n    echo -e \"\\n* Update daemon service bundle (s6)\"\n    s6-db-reload\n  ;;\n  *)\n    echo -e \"\\n* Unsupported init system detected, could not install the daemon\\n\"\n    echo -e \"\\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\\n\"\n  ;;\nesac\n"
  },
  {
    "path": "scripts/auto-cpufreq-openrc",
    "content": "#!/sbin/openrc-run\n\nname=$RC_SVCNAME\ndescription=\"auto-cpufreq - Automatic CPU speed & power optimizer for Linux\"\nsupervisor=\"supervise-daemon\"\ncommand=\"/usr/local/bin/auto-cpufreq\"\ncommand_args=\"--daemon\"\ncommand_user=\"root\"\n\ndepend() {\n\tafter net\n}\n"
  },
  {
    "path": "scripts/auto-cpufreq-remove.sh",
    "content": "#!/usr/bin/env bash\n#\n# auto-cpufreq daemon removal script\n# reference: https://github.com/AdnanHodzic/auto-cpufreq\n# Thanks to https://github.com/errornonamer for openrc fix\n\nMID=\"$((`tput cols` / 2))\"\n\necho\nprintf \"%0.s─\" $(seq $(( (MID-(${#1}/2)-2) / 2 )))\nprintf \" Running auto-cpufreq daemon removal script \"\nprintf \"%0.s─\" $(seq $(( (MID-(${#1}/2)-2) / 2 )))\necho; echo\n\n# root check\nif ((EUID != 0)); then\n  echo; echo \"Must be run as root (i.e: 'sudo $0').\"; echo\n  exit 1\nfi\n\n# First argument is the init name, second argument is the stop command, third argument is the disable command and the fourth is the \"service\" path\nfunction auto_cpufreq_remove {\n    echo -e \"\\n* Stopping auto-cpufreq daemon ($1) service\"\n    $2\n    echo -e \"\\n* Disabling auto-cpufreq daemon ($1) at boot\"\n    $3\n    echo -e \"\\n* Removing auto-cpufreq daemon ($1) unit file\"\n    rm $4\n}\n\ncase \"$(ps h -o comm 1)\" in\n  dinit) auto_cpufreq_remove \"dinit\" \"dinitctl stop auto-cpufreq\" \"dinitctl disable auto-cpufreq\" \"/etc/dinit.d/auto-cpufreq\";;\n  init) auto_cpufreq_remove \"openrc\" \"rc-service auto-cpufreq stop\" \"rc-update del auto-cpufreq\" \"/etc/init.d/auto-cpufreq\";;\n  runit)\n    # First argument is the \"sv\" path, second argument is the \"service\" path\n    rm_sv() {\n      auto_cpufreq_remove \"runit\" \"sv stop auto-cpufreq\" \"\" \"-rf $1/sv/auto-cpufreq $2/service/auto-cpufreq\"\n    }\n\n    if [ -f /etc/os-release ]; then\n      . /etc/os-release\n      case $ID in\n        void) rm_sv /etc /var;;\n        artix) rm_sv /etc/runit /run/runit;;\n        *)\n          echo -e \"\\n* Runit init detected but your distro is not supported\\n\"\n          echo -e \"\\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\\n\"\n        ;;\n      esac\n    fi\n  ;;\n  systemd)\n    auto_cpufreq_remove \"systemd\" \"systemctl stop auto-cpufreq\" \"systemctl disable auto-cpufreq\" \"/etc/systemd/system/auto-cpufreq.service\"\n\n    echo -e \"\\n* Reloading systemd manager configuration\"\n    systemctl daemon-reload\n\n    echo \"reset failed\"\n    systemctl reset-failed\n  ;;\n  s6-svscan)\n    auto_cpufreq_remove \"s6\" \"\" \"s6-service delete default auto-cpufreq\" \"-rf /etc/s6/sv/auto-cpufreq\"\n    \n    echo -e \"\\n* Update daemon service bundle (s6)\"\n    s6-db-reload\n  ;;\n  *)\n    echo -e \"\\n* Unsupported init system detected, could not remove the daemon\"\n    echo -e \"\\n* Please open an issue on https://github.com/AdnanHodzic/auto-cpufreq\\n\"\n  ;;\nesac\n"
  },
  {
    "path": "scripts/auto-cpufreq-runit",
    "content": "#!/bin/bash\nexport PATH=\"$PATH:/usr/local/bin\"\nexec /usr/local/bin/auto-cpufreq --daemon\n"
  },
  {
    "path": "scripts/auto-cpufreq-s6/run",
    "content": "#!/bin/sh\n\nexec /usr/local/bin/auto-cpufreq --daemon\n\n"
  },
  {
    "path": "scripts/auto-cpufreq-s6/type",
    "content": "longrun\n"
  },
  {
    "path": "scripts/auto-cpufreq-venv-wrapper",
    "content": "#!/bin/bash\n# Wrapper script around auto-cpufreq using the python virtual environment\n\nset -eu\n\n# bailout function\nerr_exit() {\n  echo \"$(basename $0): ${1:-wrong invocation. try --help for help.}\" 1>&2\n  exit 1\n}\n\n# load python virtual environment\nopt_path=/opt/auto-cpufreq\nvenv_bin_dir=$opt_path/venv/bin\n. \"$venv_bin_dir/activate\"\n\nPYTHONPATH=$opt_path $venv_bin_dir/python $venv_bin_dir/auto-cpufreq \"$@\"\n"
  },
  {
    "path": "scripts/auto-cpufreq.service",
    "content": "[Unit]\nDescription=auto-cpufreq - Automatic CPU speed & power optimizer for Linux\n\n[Service]\nType=simple\nUser=root\nWorkingDirectory=/opt/auto-cpufreq/venv\nEnvironment=PYTHONPATH=/opt/auto-cpufreq\nExecStart=/opt/auto-cpufreq/venv/bin/python /opt/auto-cpufreq/venv/bin/auto-cpufreq --daemon\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "scripts/cpufreqctl.sh",
    "content": "#!/usr/bin/env bash\n\nVERSION='20'\ncpucount=`cat /proc/cpuinfo | grep processor | wc -l`\nFLROOT=/sys/devices/system/cpu\nFWROOT=/sys/firmware\nDRIVER=auto\nVERBOSE=0\n\n## parse special options\nfor i in \"$@\"; do\n  case $i in\n    -v|--verbose)\n      VERBOSE=1\n      shift\n    ;;\n    -s=*|--set=*)\n      VALUE=\"${i#*=}\"\n      shift\n    ;;\n    -c=*|--core=*)\n      CORE=\"${i#*=}\"\n      shift\n    ;;\n    -a|--available)\n      AVAILABLE=1\n      shift\n    ;;\n    -*)\n      OPTION=$i\n      shift\n    ;;\n    *) exit 1;;\n  esac\ndone\n\nfunction help () {\n  echo \"Package version: \"$VERSION\n  echo \"Usage: cpufreqctl [OPTION[=VALUE]...]\"\n  echo\n  echo \"  -h, --help                  Show help options\"\n  echo \"      --version               Package version\"\n  echo \"  -v, --verbose               Verbose output\"\n  echo\n  echo \"  -s, --set       =VALUE      Set VALUE for selected option\"\n  echo \"  -c, --core      =NUMBER     Apply selected option just for the core NUMBER (0 ~ N - 1)\"\n  echo \"  -a, --available             Get available values instand of default: current\"\n  echo\n  echo \"  -d, --driver                Current processor driver\"\n  echo \"  -g, --governor              Scaling governor's options\"\n  echo \"  -e, --epp                   Governor's energy_performance_preference options\"\n  echo \"  -f, --frequency             Frequency options\"\n  echo \"      --on                    Turn on --core=NUMBER\"\n  echo \"      --off                   Turn off --core=NUMBER\"\n  echo \"      --frequency-min         Minimal frequency options\"\n  echo \"      --frequency-max         Maximum frequency options\"\n  echo \"      --frequency-min-limit   Get minimal frequency limit\"\n  echo \"      --frequency-max-limit   Get maximum frequency limit\"\n  echo \"  -b, --boost                 Current cpu boost value\"\n  echo\n  echo \"intel_pstate options\"\n  echo \"      --no-turbo              Current no_turbo value\"\n  echo \"      --min-perf              Current min_perf_pct options\"\n  echo \"      --max-perf              Current max_perf_pct options\"\n  echo\n  echo \"Events options\"\n  echo \"      --throttle              Get thermal throttle counter\"\n  echo \"      --throttle-event        Get kernel thermal throttle events counter\"\n  echo \"      --irqbalance            Get irqbalance presence\"\n}\n\nfunction info () {\n  echo \"CPU driver: \"`driver`\n  echo \"Governors: \"`cat $FLROOT/cpu0/cpufreq/scaling_available_governors`\n  echo \"Frequencies: \"`cat $FLROOT/cpu0/cpufreq/scaling_available_frequencies`\n  echo\n  echo \"Usage:\"\n  echo \"## list scaling governors:\"\n  echo \"cpufreqctl --governor\"\n  echo\n  echo \"## Set all active cpu cores to the 'performance' scaling governor:\"\n  echo \"cpufreqctl --governor --set=performance\"\n  echo\n  echo \"## Set 'performance' scaling governor for the selected core:\"\n  echo \"cpufreqctl --governor --set=performance --core=0\"\n  echo\n  echo \"Use --help argument to see available options\"\n}\n\nverbose () {\n  if [ $VERBOSE = 1 ]; then echo $1; fi\n}\n\nfunction driver () {\n  cat $FLROOT/cpu0/cpufreq/scaling_driver\n}\n\nfunction write_value () {\n  if [ -w $FLNM ]; then echo $VALUE > $FLNM; fi\n}\n\nfunction set_driver () {\n  DRIVER=`driver`\n  case $DRIVER in\n    intel*|*pstate*) DRIVER=pstate;;\n    *)DRIVER=acpi;;\n  esac\n}\n\nfunction get_governor () {\n  if [ -z $CORE ]\n  then\n    i=0\n    ag=''\n    while [ $i -ne $cpucount ]; do\n      if [ $i = 0 ]; then ag=`cat $FLROOT/cpu0/cpufreq/scaling_governor`\n      else ag=$ag' '`cat $FLROOT/cpu$i/cpufreq/scaling_governor`\n      fi\n      i=`expr $i + 1`\n    done\n    echo $ag\n  else cat $FLROOT/cpu$CORE/cpufreq/scaling_governor\n  fi\n}\n\nfunction set_governor () {\n  if [ -z $CORE ]; then\n    i=0\n    while [ $i -ne $cpucount ]; do\n      FLNM=\"$FLROOT/cpu\"$i\"/cpufreq/scaling_governor\"\n      write_value\n      i=`expr $i + 1`\n    done\n  else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_governor\n  fi\n}\n\nfunction get_frequency () {\n  if [ -z $CORE ]; then\n    i=0\n    V=0\n    M=$(cat \"$FLROOT/cpu0/cpufreq/scaling_cur_freq\")\n    while [ $i -ne $cpucount ]; do\n      V=$(cat \"$FLROOT/cpu\"$i\"/cpufreq/scaling_cur_freq\")\n      if [[ $V > $M ]]; then M=$V; fi\n      i=`expr $i + 1`\n    done\n    echo \"$M\"\n  else cat $FLROOT/cpu$CORE/cpufreq/scaling_cur_freq\n  fi\n}\n\nfunction set_frequency () {\n  set_driver\n  if [ $DRIVER = 'pstate' ]; then\n    echo \"Unavailable function for intel_pstate\"\n    return\n  fi\n  if [ -z $CORE ]; then\n    i=0\n    while [ $i -ne $cpucount ]; do\n      FLNM=\"$FLROOT/cpu\"$i\"/cpufreq/scaling_setspeed\"\n      write_value\n      i=`expr $i + 1`\n    done\n  else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_setspeed\n  fi\n}\n\nfunction get_frequency_min () {\n  if [ -z $CORE ]; then CORE=0; fi\n  cat $FLROOT/cpu$CORE/cpufreq/scaling_min_freq\n}\n\nfunction set_frequency_min () {\n  if [ -z $CORE ]; then\n    i=0\n    while [ $i -ne $cpucount ]; do\n      FLNM=\"$FLROOT/cpu\"$i\"/cpufreq/scaling_min_freq\"\n      write_value\n      i=`expr $i + 1`\n    done\n  else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_min_freq\n  fi\n}\n\nfunction get_frequency_max () {\n  if [ -z $CORE ]; then CORE=0; fi\n  cat $FLROOT/cpu$CORE/cpufreq/scaling_max_freq\n}\n\nfunction set_frequency_max () {\n  if [ -z $CORE ]; then\n    i=0\n    while [ $i -ne $cpucount ]; do\n      FLNM=\"$FLROOT/cpu\"$i\"/cpufreq/scaling_max_freq\"\n      write_value\n      i=`expr $i + 1`\n    done\n  else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/scaling_max_freq\n  fi\n}\n\nfunction get_frequency_min_limit () {\n  if [ -z $CORE ]; then CORE=0; fi\n  echo $(awk '{a[NR]=$1} END{if(a[1]<a[2]) print a[1]; else print a[2]}' $FLROOT/cpu$CORE/cpufreq/cpuinfo_min_freq $FLROOT/cpu$CORE/cpufreq/scaling_min_freq)\n}\n\nfunction get_frequency_max_limit () {\n  if [ -z $CORE ]; then CORE=0; fi\n  echo $(awk '{a[NR]=$1} END{if(a[1]>a[2]) print a[1]; else print a[2]}' $FLROOT/cpu$CORE/cpufreq/cpuinfo_max_freq $FLROOT/cpu$CORE/cpufreq/scaling_max_freq)\n}\n\nfunction get_energy_performance_preference () {\n  if [ -z $CORE ]; then\n    i=0\n    ag=''\n    while [ $i -ne $cpucount ]; do\n      if [ $i = 0 ]; then\n        ag=`cat $FLROOT/cpu0/cpufreq/energy_performance_preference`\n      else\n        ag=$ag' '`cat $FLROOT/cpu$i/cpufreq/energy_performance_preference`\n      fi\n      i=`expr $i + 1`\n    done\n    echo $ag\n  else cat $FLROOT/cpu$CORE/cpufreq/energy_performance_preference\n  fi\n}\n\nfunction set_energy_performance_preference () {\n  if [ -z $CORE ]; then\n    i=0\n    while [ $i -ne $cpucount ]; do\n      FLNM=\"$FLROOT/cpu\"$i\"/cpufreq/energy_performance_preference\"\n      write_value\n      i=`expr $i + 1`\n    done\n  else echo $VALUE > $FLROOT/cpu$CORE/cpufreq/energy_performance_preference\n  fi\n}\n\n\nfunction get_energy_performance_bias () {\n  if [ -z $CORE ]; then\n    i=0\n    ag=''\n    while [ $i -ne $cpucount ]; do\n      if [ $i = 0 ]; then\n        ag=`cat $FLROOT/cpu0/power/energy_perf_bias`\n      else\n        ag=$ag' '`cat $FLROOT/cpu$i/power/energy_perf_bias`\n      fi\n      i=`expr $i + 1`\n    done\n    echo $ag\n  else cat $FLROOT/cpu$CORE/power/energy_perf_bias\n  fi\n}\n\nfunction set_energy_performance_bias () {\n  if [ `driver` != 'intel_pstate' ]; then\n    verbose \"EPB is not supported by a driver other than intel_pstate\"\n    return\n  fi\n  local EPB_VALUE=6 # default value\n  if [[ \"$VALUE\" =~ ^[0-9]+$ && $VALUE -ge 0 && $VALUE -le 15 ]]; then\n    EPB_VALUE=$VALUE\n  else\n    case $VALUE in\n      performance) EPB_VALUE=0;;\n      balance_performance) EPB_VALUE=4;;\n      default) EPB_VALUE=6;;\n      balance_power) EPB_VALUE=8;;\n      power) EPB_VALUE=15;;\n      *)\n        verbose \"Invalid value provided for EPB\"\n        verbose \"Acceptable values: performance|balance-power|default|balance-power|power or a number in the range [0-15]\"\n        return\n      ;;\n    esac\n  fi\n\n  if [ -z $CORE ]; then\n    i=0\n    while [ $i -ne $cpucount ]; do\n      FLNM=\"$FLROOT/cpu\"$i\"/power/energy_perf_bias\"\n      if [ -w $FLNM ]; then echo $EPB_VALUE > $FLNM; fi\n      i=`expr $i + 1`\n    done\n  else echo $EPB_VALUE > $FLROOT/cpu$CORE/power/energy_perf_bias\n  fi\n}\n\ncase $OPTION in\n  -h|--help) help;;\n  --version) echo $VERSION;;\n  -d|--driver) driver;;\n  -g|--governor)\n    if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/cpufreq/scaling_available_governors\n    elif [ -z $VALUE ]; then\n      verbose \"Getting CPU\"$CORE\" governors\"\n      get_governor\n    else\n      verbose \"Setting CPU\"$CORE\" governors to \"$VALUE\n      set_governor\n    fi\n  ;;\n  -e|--epp)\n    if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/cpufreq/energy_performance_available_preferences\n    elif [ -z $VALUE ]; then\n      verbose \"Getting CPU\"$CORE\" EPPs\"\n      get_energy_performance_preference\n    else\n      verbose \"Setting CPU\"$CORE\" EPPs to \"$VALUE\n      set_energy_performance_preference\n    fi\n  ;;\n  --epb)\n    if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/power/energy_perf_bias\n    elif [ -z $VALUE ]; then \n      verbose \"Getting CPU\"$CORE\" EPBs\"\n      get_energy_performance_bias\n    else\n      verbose \"Setting CPU\"$CORE\" EPBs to \"$VALUE\n      set_energy_performance_bias\n    fi\n  ;;\n  -p|--pp)\n    if [ ! -z $AVAILABLE ]; then cat $FWROOT/acpi/platform_profile_choices\n    elif [ -z $VALUE ]; then\n      verbose \"Getting Platform Profile\"\n      cat $FWROOT/acpi/platform_profile\n    else\n      verbose \"Getting Platform Profile to \"$VALUE\n      echo $VALUE > $FWROOT/acpi/platform_profile\n    fi\n  ;;\n  -f|--frequency)\n    if [ ! -z $AVAILABLE ]; then cat $FLROOT/cpu0/cpufreq/scaling_available_frequencies\n    elif [ -z $VALUE ]; then\n      verbose \"Getting CPU\"$CORE\" frequency\"\n      get_frequency\n    else\n      verbose \"Setting CPU\"$CORE\" frequency to \"$VALUE\n      set_frequency\n    fi\n  ;;\n  --no-turbo)\n    if [ -z $VALUE ]; then\n      verbose \"Getting no_turbo value\"\n      cat $FLROOT/intel_pstate/no_turbo\n    else\n      verbose \"Setting no_turbo value \"$VALUE\n      echo $VALUE > $FLROOT/intel_pstate/no_turbo\n    fi\n  ;;\n  -b|--boost)\n    if [ -z $VALUE ]; then\n      verbose \"Getting boost value\"\n      cat $FLROOT/cpufreq/boost\n    else\n      verbose \"Setting boost value \"$VALUE\n      echo $VALUE > $FLROOT/cpufreq/boost\n    fi\n  ;;\n  --frequency-min)\n    if [ -z $VALUE ]; then\n      verbose \"Getting CPU\"$CORE\" minimal frequency\"\n      get_frequency_min\n    else\n      verbose \"Setting CPU\"$CORE\" minimal frequency to \"$VALUE\n      set_frequency_min\n    fi\n  ;;\n  --frequency-max)\n    if [ -z $VALUE ]; then\n      verbose \"Getting CPU\"$CORE\" maximal frequency\"\n      get_frequency_max\n    else\n      verbose \"Setting CPU\"$CORE\" maximal frequency to \"$VALUE\n      set_frequency_max\n    fi\n  ;;\n  --frequency-min-limit)\n    verbose \"Getting CPU\"$CORE\" minimal frequency limit\"\n    get_frequency_min_limit\n  ;;\n  --frequency-max-limit)\n    verbose \"Getting CPU\"$CORE\" maximum frequency limit\"\n    get_frequency_max_limit\n  ;;\n  --min-perf)\n    if [ -z $VALUE ]; then\n      verbose \"Getting min_perf_pct value\"\n      cat $FLROOT/intel_pstate/min_perf_pct\n    else\n      verbose \"Setting min_perf_pct value \"$VALUE\n      echo $VALUE > $FLROOT/intel_pstate/min_perf_pct\n    fi\n  ;;\n  --max-perf)\n    if [ -z $VALUE ]; then\n      verbose \"Getting max_perf_pct value\"\n      cat $FLROOT/intel_pstate/max_perf_pct\n    else\n      verbose \"Setting max_perf_pct value \"$VALUE\n      echo $VALUE > $FLROOT/intel_pstate/max_perf_pct\n    fi\n  ;;\n  --on)\n    if [ -z $CORE ]; then verbose \"Should be specify --core=NUMBER\"\n    else\n      verbose \"Power on CPU Core\"$CORE\n      echo \"1\" > $FLROOT/cpu\"$CORE\"/online\n    fi\n  ;;\n  --off)\n    if [ -z $CORE ]; then verbose \"Should be specify --core=NUMBER\"\n    else\n      verbose \"Power off CPU Core$CORE\"\n      echo \"0\" > $FLROOT/cpu\"$CORE\"/online\n    fi\n  ;;\n  --throttle)\n    i=1\n    V=0\n    M=$(cat \"$FLROOT/cpu0/thermal_throttle/core_throttle_count\")\n    while [ $i -ne $cpucount ]; do\n      V=$(cat \"$FLROOT/cpu$i/thermal_throttle/core_throttle_count\")\n      M=`expr $M + $V`\n      i=`expr $i + 1`\n    done\n    echo \"$M\"\n  ;;\n  --throttle-events)\n    M=$(journalctl --dmesg --boot --since=yesterday | grep \"cpu clock throttled\" | wc -l)\n    echo \"$M\"\n  ;;\n  --irqbalance)\n    M=$(ps -A | grep irqbalance)\n    echo \"$M\"\n  ;;\n  *)\n    info\n    exit 1\n  ;;\nesac\n\nexit 0\n"
  },
  {
    "path": "scripts/org.auto-cpufreq.pkexec.policy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE policyconfig PUBLIC\n \"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN\"\n \"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd\">\n<policyconfig>\n    <action id=\"org.auto-cpufreq.pkexec\">\n    <description>Run auto-cpufreq command</description>\n    <message>Authentication is required to run auto-cpufreq</message>\n    <icon_name>auto-cpufreq</icon_name>\n    <defaults>\n        <allow_any>auth_admin</allow_any>\n        <allow_inactive>auth_admin</allow_inactive>\n        <allow_active>auth_admin</allow_active>\n    </defaults>\n    <annotate key=\"org.freedesktop.policykit.exec.path\">/opt/auto-cpufreq/venv/bin/auto-cpufreq</annotate>\n    <!-- <annotate key=\"org.freedesktop.policykit.exec.argv1\">/opt/auto-cpufreq/venv/bin/auto-cpufreq</annotate>  -->\n    <!-- <annotate key=\"org.freedesktop.policykit.exec.allow_gui\">true</annotate> -->\n    </action>\n</policyconfig>"
  },
  {
    "path": "scripts/snapdaemon.sh",
    "content": "#!/usr/bin/env bash\n#\n# workaround for running Daemon without polluting syslog (#53, #82)\n$SNAP/bin/auto-cpufreq --daemon 2>&1 >> $SNAP_DATA/auto-cpufreq.stats\n"
  },
  {
    "path": "scripts/start_app",
    "content": "#!/usr/bin/sh\n\n# load python virtual environment\nvenv_dir=/opt/auto-cpufreq/venv\n. \"$venv_dir/bin/activate\"\npython_command=\"$venv_dir/bin/auto-cpufreq-gtk\"\n\n# if [ \"$XDG_SESSION_TYPE\" = \"wayland\" ] ; then\n#     # necessary for running on wayland\n#     xhost +SI:localuser:root\n#     pkexec $python_command\n#     xhost -SI:localuser:root\n#     xhost\n# else\n#     pkexec $python_command\n# fi\n\n$python_command"
  },
  {
    "path": "scripts/style.css",
    "content": "label {\n    /*font-family: Noto Sans;*/\n    font-size: 15px;\n}\n\n#bold { font-weight: bold; }\n\n#small { font-size: 12px; }\n"
  },
  {
    "path": "snap/gui/auto-cpufreq_auto-cpufreq.desktop",
    "content": "[Desktop Entry]\nType=Application\nEncoding=UTF-8\nName=auto-cpufreq\nComment=Automatic CPU speed & power optimizer for Linux\nExec=auto-cpufreq.auto-cpufreq-gtk\nStartupWMClass=app.py\nTerminal=false\nIcon=${SNAP}/meta/gui/auto-cpufreq.png\nCategories=System;"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "name: auto-cpufreq\nbase: core22\nsummary: Automatic CPU speed & power optimizer for Linux\ndescription: |\n  Automatic CPU speed & power optimizer for Linux based on active\n  monitoring of laptop's battery state, CPU usage and system load.\n  Ultimately allowing you to improve battery life without making\n  any compromises.\n\nlicense: LGPL-3.0\ngrade: stable\nconfinement: strict\nadopt-info: auto-cpufreq\n\ncompression: lzo\n\narchitectures:\n  - build-on: [amd64]\n    build-for: [amd64]\n  - build-on: [amd64]\n    build-for: [arm64]\n\nparts:\n  auto-cpufreq:\n    plugin: python\n    python-packages:\n       - setuptools\n       - wheel\n       - requests\n    build-packages:\n       - gcc\n       - python3-dev\n    stage-packages:\n       - coreutils\n       - dmidecode\n       - pkexec\n    source: .\n    override-pull: |\n      snapcraftctl pull\n      snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/pyproject.toml | sed 's/.*\"\\(.*\\)\"/\\1/'`\n\n  deploy-scripts:\n    plugin: dump\n    source: scripts\n    organize:\n      cpufreqctl.sh: usr/bin/cpufreqctl.auto-cpufreq\n      snapdaemon.sh: usr/bin/snapdaemon\n\n  copy-image:\n    plugin: dump\n    source: images\n\nplugs:\n   etc-auto-cpufreq-conf:\n    interface: system-files\n    write:\n    - /etc/auto-cpufreq.conf\n\napps:\n  auto-cpufreq:\n    command: bin/auto-cpufreq\n    environment:\n      PYTHONPATH: $SNAP/usr/lib/python3/site-packages:$SNAP/usr/lib/python3/dist-packages:$PYTHONPATH\n      LC_ALL: C.UTF-8\n      LANG: C.UTF-8\n      PKG_MARKER: SNAP\n    plugs:\n      - cpu-control\n      - system-observe\n      - hardware-observe\n      - etc-auto-cpufreq-conf\n\n  auto-cpufreq-gtk:\n    command: bin/auto-cpufreq-gtk\n    extensions: [gnome]\n    environment:\n      PYTHONPATH: $SNAP/usr/lib/python3/site-packages:$SNAP/usr/lib/python3/dist-packages:$PYTHONPATH\n      LC_ALL: C.UTF-8\n      LANG: C.UTF-8\n      PKG_MARKER: SNAP\n    plugs:\n      - cpu-control\n      - system-observe\n      - hardware-observe\n      - desktop\n      - desktop-legacy\n      - wayland\n      - x11\n  service:\n    command: usr/bin/snapdaemon\n    plugs:\n      - cpu-control\n      - system-observe\n      - hardware-observe\n    environment:\n      LC_ALL: C.UTF-8\n      LANG: C.UTF-8\n      PKG_MARKER: SNAP\n    daemon: simple\n"
  }
]