Full Code of devksingh4/thinkpad-tools for AI

master 437585f8fbff cached
23 files
43.4 KB
10.4k tokens
36 symbols
1 requests
Download .txt
Repository: devksingh4/thinkpad-tools
Branch: master
Commit: 437585f8fbff
Files: 23
Total size: 43.4 KB

Directory structure:
gitextract_l20ul__j/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── .vscode/
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── requirements.txt
├── setup.py
├── stdeb.cfg
├── thinkpad-tools
├── thinkpad_tools_assets/
│   ├── __init__.py
│   ├── __main__.py
│   ├── battery.py
│   ├── classes.py
│   ├── cmd.py
│   ├── persistence.py
│   ├── thinkpad-tools.ini
│   ├── thinkpad-tools.service
│   ├── trackpoint.py
│   ├── undervolt.py
│   └── utils.py
└── upload-pypi.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. Debian]
 - Version: [e.g. 0.9.1-hotfix1]
 - Python Version [e.g. 3.8.6 64-bit]
 - Thinkpad Model [e.g. T480]
**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Don't upload snap files
*.snap
snap/.snapcraft
prime/
stage/
deb_dist/

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

# IntelliJ IDEA
.idea


================================================
FILE: .vscode/settings.json
================================================
{
    "python.linting.pylintEnabled": false,
    "python.linting.enabled": true,
    "python.linting.pycodestyleEnabled": true
}

================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
 advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
 address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
 professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at dev@devksingh.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


================================================
FILE: LICENSE
================================================
BSD 3-Clause License

Copyright (c) 2020-2021, Dev Singh
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
## Update 02/10/2021

There also exists a somewhat-functioning GUI for this utility, which relies on this utility being installed. You may find it [here](https://github.com/devksingh4/thinkpad-tools-gui). Beware questionable design choices, I am *definitely* not a frontend person!

## Update 07/30/2020

My primary machine is now not a ThinkPad anymore, but rather a desktop computer. I still have my ThinkPad and use it frequently, but not much development is occuring on it. As a result, this tool may not recieve many updates other than to fix bugs brought up by others, or ones I notice during my use. 

Feel free to open PRs with new features or bugfixes!

---
# Thinkpad Tools
Tools created to manage thinkpad properties

## Currently Supported Properties
* Adjusting Trackpoint Speed and Sensitivity
* Managing battery/batteries
  * Setting Charge Stop and Start thresholds
  * Checking battery health
* Undervolting CPU (Can write values but cannot read them)

## Planned Features
None right now, but feel free to suggest one in issues!

While most of these tools exist seperately, it would be nice to have a first-class linux tool that allows all of the above to be managed all in one place. This is why I started development on thinkpad-tools. 

## Installing Utility
### Debian/Ubuntu
`.deb` files are available for Debian/Ubuntu on the releases page.
### Fedora/CentOS
A COPR repository has been created for Fedora/CentOS at `https://copr.fedorainfracloud.org/coprs/dsingh/thinkpad-tools/`.
### Other distros
Run `python3 setup.py install` after cloning the repository (`git clone https://github.com/devksingh4/thinkpad-tools`). 

## Supported Devices
While this tool should work for any Core-i (xx10 series and onwards) ThinkPad, the following devices have been tested to work with this tool: 
* T480
* X1 Carbon Gen 7
* T470
* X260

Undervolting is only supported on Skylake or newer Intel CPUs. 

If you have tested this tool to work on more machines, please open a pull request and add it to this list!

## Contribution Copyright Assignment
By contributing to this codebase, you hereby assign copyright in this code to the project, to be licensed under the same terms as the rest of the code.

## Persistence of Settings
Run `thinkpad-tools persistence enable` to enable persistence and see the instructions to set the persistent settings.


[![Copr build status](https://copr.fedorainfracloud.org/coprs/dsingh/thinkpad-tools/package/python-thinkpad-tools/status_image/last_build.png)](https://copr.fedorainfracloud.org/a/dsingh/thinkpad-tools/package/python-thinkpad-tools/)


================================================
FILE: requirements.txt
================================================
setuptools


================================================
FILE: setup.py
================================================
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

"""
Setup tools wrapper
"""

from setuptools import find_packages, setup
from shutil import copyfile

setup(
    name='thinkpad-tools',
    maintainer="Dev Singh",
    maintainer_email="dev@devksingh.com",
    version='0.14',
    zip_safe=False,
    description='Tools for ThinkPads',
    long_description="Tools created to manage thinkpad properties such as TrackPoint, Undervolt, and Battery",
    platforms=['Linux'],
    include_package_data=True,
    keywords='thinkpad trackpoint battery undervolt',
    packages=find_packages(),
    project_urls={
        "Bug Tracker": "https://github.com/devksingh4/thinkpad-tools/issues",
        "Documentation": "https://github.com/devksingh4/thinkpad-tools/blob/master/README.md",
        "Source Code": "https://github.com/devksingh4/thinkpad-tools/",
    },
    license='GPLv3',
    scripts=['thinkpad-tools'],
    data_files=[
        ('/etc/', ["thinkpad_tools_assets/thinkpad-tools.ini"]),
        ('/usr/lib/systemd/system/', ["thinkpad_tools_assets/thinkpad-tools.service"]),
        ('/usr/share/licenses/python-thinkpad-tools/', ["LICENSE"])

    ],
)


================================================
FILE: stdeb.cfg
================================================
[DEFAULT]
Suite: focal
XS-Python-Version: >= 3.6
Copyright-File: LICENSE


================================================
FILE: thinkpad-tools
================================================
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

"""
ThinkPad-tools commandline wrapper
"""
import os
import sys
from thinkpad_tools_assets.cmd import commandline_parser

if __name__ == '__main__':
    commandline_parser(sys.argv[1:])


================================================
FILE: thinkpad_tools_assets/__init__.py
================================================
# __init__.py

__version__ = '0.13'


================================================
FILE: thinkpad_tools_assets/__main__.py
================================================
# __main__.py

import sys

from .cmd import commandline_parser

if __name__ == '__main__':
    commandline_parser(sys.argv[1:])


================================================
FILE: thinkpad_tools_assets/battery.py
================================================
"""
Battery related stuff
"""


import os
import re
import sys
import pathlib
import argparse
from thinkpad_tools_assets.utils import ApplyValueFailedException, NotSudo

BASE_DIR = pathlib.PurePath('/sys/class/power_supply/')

PROPERTIES: dict = {
    'alarm': 0,
    'capacity': 100, 'capacity_level': 'Unknown',
    'charge_start_threshold': 0, 'charge_stop_threshold': 100,
    'cycle_count': 0,
    'energy_full': 0, 'energy_full_design': 0, 'energy_now': 0,
    'manufacturer': 'Unknown', 'model_name': 'Unknown',
    'power_now': False, 'present': True,
    'serial_number': 0,
    'status': 'Unknown',
    'technology': 'Unknown', 'type': 'Unknown',
    'voltage_min_design': 0, 'voltage_now': 0
}

STRING_PROPERTIES: list = [
    'capacity_level',
    'manufacturer', 'model_name',
    'status',
    'technology', 'type'
]

BOOLEAN_PROPERTIES: list = [
    'power_now', 'present'
]

EDITABLE_PROPERTIES: list = [
    'charge_start_threshold', 'charge_stop_threshold'
]

STATUS_STR_TEMPLATE: str = '''\
Status of battery "{name}":
  Alarm:                    {alarm} Wh
  Capacity level:           {capacity_level}
  Charge start threshold:   {charge_start_threshold}%
  Charge stop threshold:    {charge_stop_threshold}%
  Cycle count:              {cycle_count}
  Current capacity:         {energy_full} Wh
  Design capacity:          {energy_full_design} Wh
  Battery health:           {battery_health}%
  Current energy:           {energy_now} Wh
  Manufacturer:             {manufacturer}
  Model name:               {model_name}
  In use:                   {power_now}
  Present:                  {present}
  Serial number:            {serial_number}
  Status:                   {status}
  Technology:               {technology}
  Type:                     {type}
  Minimum design voltage:   {voltage_min_design}
  Current voltage:          {voltage_now}\
'''

USAGE_HEAD: str = '''\
thinkpad-tools battery <verb> <battery> [argument]

Regex can be used in <battery> to match multiple batteries

Supported verbs are:
    list            List available batteries
    status          Print all properties
    set-<property>  Set value
    get-<property>  Get property
Readable properties: {properties}
Editable properties: {editable_properties}
'''.format(
    properties=', '.join(PROPERTIES.keys()),
    editable_properties=', '.join(EDITABLE_PROPERTIES)
)

USAGE_EXAMPLES: str = '''\
Examples:

thinkpad-tools battery list
thinkpad-tools battery set-charge-start-threshold all 80
thinkpad-tools battery set-stop-start-threshold BAT0 90
thinkpad-tools battery get-battery-health
'''


class Battery(object):
    """
    Class to handle requests related to Batteries
    """

    def __init__(self, name: str = 'BAT0', **kwargs):
        self.name: str = name
        self.path: pathlib.PurePath = BASE_DIR / self.name
        for prop, default_value in PROPERTIES.items():
            if prop in kwargs.keys():
                if type(kwargs[prop]) == type(default_value):
                    self.__dict__[prop] = kwargs[prop]
            self.__dict__[prop] = default_value
        self.battery_health: int = 100

    def read_values(self):
        """
        Read values from the system
        :return: Nothing
        """
        for prop in self.__dict__.keys():
            path = str(self.path / prop)
            if os.path.isfile(path):
                with open(path) as file:
                    content = file.readline()
                if prop in STRING_PROPERTIES:
                    self.__dict__[prop] = str(content).strip()
                elif prop in BOOLEAN_PROPERTIES:
                    self.__dict__[prop] = bool(content)
                else:
                    self.__dict__[prop] = int(content)
        self.battery_health: int = int(
            self.energy_full / self.energy_full_design * 100)

    def set_values(self):
        """
        Set values to the system
        :return: Nothing
        """
        success: bool = True
        failures: list = list()
        for prop in EDITABLE_PROPERTIES:
            if prop not in self.__dict__.keys():
                success = False
                failures.append(
                    'Property "%s" not found in current object' % prop)
                continue
            path = str(self.path / prop)
            if os.path.isfile(path):
                try:
                    with open(path, 'w') as file:
                        # TODO: Handle different types of properties
                        file.write(str(self.__dict__[prop]))
                except Exception as e:
                    success = False
                    failures.append(str(e))
        if not success:
            raise ApplyValueFailedException(', '.join(failures))

    def get_status_str(self) -> str:
        """
        Return status string
        :return: str: status string
        """
        return STATUS_STR_TEMPLATE.format(
            name=str(self.name),
            alarm=str(self.alarm / 1000000),
            capacity=str(self.capacity),
            capacity_level=str(self.capacity_level),
            charge_start_threshold=str(self.charge_start_threshold),
            charge_stop_threshold=str(self.charge_stop_threshold),
            cycle_count=str(self.cycle_count),
            energy_full=str(self.energy_full / 1000000),
            energy_full_design=str(self.energy_full_design / 1000000),
            battery_health=str(self.battery_health),
            energy_now=str(self.energy_now / 1000000),
            manufacturer=str(self.manufacturer),
            model_name=str(self.model_name),
            power_now='Yes' if self.power_now else 'No',
            present='Yes' if self.present else 'No',
            serial_number=str(self.serial_number),
            status=str(self.status),
            technology=str(self.technology),
            type=str(self.type),
            voltage_min_design=str(self.voltage_min_design),
            voltage_now=str(self.voltage_now)
        )


class BatteryHandler(object):
    """
    Handler for battery related commands
    """

    def __init__(self):
        self.parser: argparse.ArgumentParser = argparse.ArgumentParser(
            prog='thinkpad-tools battery',
            description='Battery related commands',
            usage=USAGE_HEAD,
            epilog=USAGE_EXAMPLES,
            formatter_class=argparse.RawDescriptionHelpFormatter
        )
        self.parser.add_argument(
            'verb', type=str, help='The action going to take')
        self.parser.add_argument(
            'battery', nargs='?', type=str, help='The battery')
        self.parser.add_argument(
            'arguments', nargs='*', help='Arguments of the action')
        self.inner: dict = dict()
        for name in os.listdir(str(BASE_DIR)):
            if not name.startswith('BAT'):
                continue
            self.inner[name]: Battery = Battery(name)

    def run(self, unparsed_args: list):
        """
        Parse and execute the commands
        :param unparsed_args: Unparsed arguments
        :return: Nothing
        """
        def find_match(battery_name: str) -> list:
            """
            Find matched batteries
            :param battery_name: name(regex) of the battery
            :return: list: List of matched battery/batteries
            """
            if battery_name.lower() == 'all':
                return list(self.inner.keys())
            try:
                pattern: re.Pattern = re.compile(battery_name)
            except re.error as e:
                print(
                    'Invalid matching pattern "%s", %s' % (
                        battery_name, str(e)),
                    file=sys.stderr
                )
                exit(1)
            return list(filter(pattern.match, self.inner.keys()))

        def invalid_battery(battery_name: str):
            """
            No battery found for the given pattern
            :param battery_name: pattern of the battery
            :return: Nothing, the program exits with status code 1
            """
            print(
                'No battery found for pattern"%s", \
                available battery(ies): ' % battery_name +
                ', '.join(self.inner.keys()),
                file=sys.stderr
            )
            exit(1)

        def invalid_property(
                prop_name: str, battery_name: str, exit_code: int):
            """
            Invalid property
            :param prop_name: Name of the property
            :param battery_name: Name of the battery
            :param exit_code: Exit code going to be used
            :return: Nothing, the program exits with the given status code
            """
            print(
                'Invalid property "%s", available properties: ' % prop_name +
                ', '.join(self.inner[battery_name].__dict__.keys()),
                file=sys.stderr
            )
            exit(exit_code)

        # Parse arguments
        args: argparse.Namespace = self.parser.parse_args(unparsed_args)
        verb: str = str(args.verb).lower()
        if not args.battery:
            battery: str = 'all'
        else:
            battery: str = str(args.battery)
        names: list = find_match(battery)

        # Read values from the system
        for name in names:
            self.inner[name].read_values()

        # Commands
        if verb == 'list':
            print(' '.join(self.inner.keys()))
            return

        if verb == 'status':
            result: list = list()
            for name in names:
                result.append(self.inner[name].get_status_str())
            if len(result) == 0:
                invalid_battery(battery)
            print('\n'.join(result))
            return

        if verb.startswith('set-'):
            if os.getuid() != 0:
                raise NotSudo("Script must be run as superuser/sudo")
            try:
                prop: str = verb.split('-', maxsplit=1)[1].replace('-', '_')
            except IndexError:
                print('Invalid command', file=sys.stderr)
                exit(1)
            for name in names:
                if (prop not in EDITABLE_PROPERTIES) or\
                        (prop not in self.inner[name].__dict__.keys()):
                    invalid_property(prop, name, 1)
                value: str = ''.join(args.arguments)
                if not value:
                    print('Value is needed', file=sys.stderr)
                    exit(1)
                    return
                self.inner[name].__dict__[prop] = int(value)
                try:
                    self.inner[name].set_values()
                except ApplyValueFailedException as e:
                    print(str(e), file=sys.stderr)
                    exit(1)
                print(value)
            return

        if verb.startswith('get-'):
            try:
                prop: str = verb.split('-', maxsplit=1)[1].replace('-', '_')
            except IndexError:
                print('Invalid command', file=sys.stderr)
                exit(1)
            result: list = list()
            for name in names:
                if prop not in self.inner[name].__dict__.keys():
                    invalid_property(prop, name, 1)
                result.append(str(self.inner[name].__dict__[prop]))
            print(' '.join(result))
            return

        # No match found
        print('Command "%s" not found' % verb, file=sys.stderr)
        exit(1)


================================================
FILE: thinkpad_tools_assets/classes.py
================================================
import os
import glob
import sys
import struct
import subprocess
from struct import pack, unpack


class UndervoltSystem(object):
    def __init__(self):
        pass

    def applyUndervolt(self, mv, plane):
        """
        Apply undervolt to system MSR for Intel-based systems
        :return: int error: error code to pass
        """
        error = 0
        uv_value = format(
            0xFFE00000 & ((round(mv*1.024) & 0xFFF) << 21), '08x').upper()
        final_val = int(("0x80000" + str(plane) + "11" + uv_value), 16)
        n: list = glob.glob('/dev/cpu/[0-9]*/msr')
        for c in n:
            f: int = os.open(c, os.O_WRONLY)
            os.lseek(f, 0x150, os.SEEK_SET)  # MSR register 0x150
            os.write(f, struct.pack('Q', final_val))  # Write final val
            os.close(f)
        if not n:
            raise OSError("MSR not available. Is Secure Boot Disabled? \
                If not, it must be disabled for this to work.")
        return error

    def parseReadUndervolt(self, offset):
        plane = int(offset / (1 << 40))
        unpack_val_unround = offset ^ (plane << 40)
        temp = unpack_val_unround >> 21
        unpack_val = temp if temp <= 1024 else - (2048-temp)
        unpack_val_round = unpack_val / 1.024
        return f"{str(round(unpack_val_round))}"

    def readUndervolt(self, plane):
        """
        Read undervolt offset on given plane
        :return: str val: offset on plane in hex
        """
        # write read to register for cpu0
        final_val = ((1 << 63) | (plane << 40) | (1 << 36) | 0)
        f: int = os.open('/dev/cpu/0/msr', os.O_WRONLY)
        os.lseek(f, 0x150, os.SEEK_SET)  # MSR register 0x150
        os.write(f, struct.pack('Q', final_val))  # Write final val
        os.close(f)
        # now read offset
        f: int = os.open('/dev/cpu/0/msr', os.O_RDONLY)
        os.lseek(f, 0x150, os.SEEK_SET)
        offset, *_ = unpack('Q', os.read(f, 8))
        return self.parseReadUndervolt(offset)


================================================
FILE: thinkpad_tools_assets/cmd.py
================================================
# cmd.py

"""
Commandline parser
"""


import logging
import pathlib
import argparse

# Setup logger
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

BASE_DIR = pathlib.Path('/etc/thinkpad-tool/')
DEFAULT_CONFIG_PATH = BASE_DIR / 'config.ini'

USAGE_HEAD = '''\
thinkpad-tools <property> <action> [<args>]

Supported properties are:
    trackpoint      Things related to TrackPoints
    battery         Things related to batteries
    undervolt       Things related to undervolting
    persistence     Things related to editing persistence
'''

USAGE_EXAMPLES = '''\
Examples:

thinkpad-tools trackpoint status
thinkpad-tools trackpoint set-sensitivity 20
thinkpad-tools battery list
thinkpad-tools battery status all
thinkpad-tools undervolt set-core -20
thinkpad-tools undervolt status
thinkpad-tools persistence edit
'''


def commandline_parser(unparsed_args: None or list = None):
    """
    Parse the first argument and call the right handler
    :param unparsed_args: Unparsed arguments
    :return: Nothing
    """
    parser = argparse.ArgumentParser(
        prog='thinkpad-tools',
        description='Tool for ThinkPads',
        usage=USAGE_HEAD,
        epilog=USAGE_EXAMPLES,
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument(
        'property', type=str, help='Property going to take action')
    prop = str(parser.parse_args(unparsed_args[0:1]).property).lower()
    if prop == 'trackpoint':
        from .trackpoint import TrackPointHandler
        handler = TrackPointHandler()
        handler.run(unparsed_args[1:])
    if prop == 'battery':
        from .battery import BatteryHandler
        handler = BatteryHandler()
        handler.run(unparsed_args[1:])
    if prop == 'undervolt':
        from .undervolt import UndervoltHandler
        handler = UndervoltHandler()
        handler.run(unparsed_args[1:])
    if prop == 'persistence':
        from .persistence import PersistenceHandler
        handler = PersistenceHandler()
        handler.run(unparsed_args[1:])


================================================
FILE: thinkpad_tools_assets/persistence.py
================================================
# persistence.py

"""
Wrapper to edit the persistent settings
"""

import os
import sys
import pathlib
import argparse
import configparser
import thinkpad_tools_assets.classes
from thinkpad_tools_assets.cmd import commandline_parser
from thinkpad_tools_assets.utils import NotSudo

try:
    if os.getuid() != 0:
        raise NotSudo("Script must be run as superuser/sudo")
except NotSudo:
    print("ERROR: This script must be run as superuser/sudo")
    sys.exit(1)

USAGE_HEAD: str = '''\
thinkpad-tools persistence <verb>

Supported verbs are:
    edit    Edit the persistent settings
    enable  Enable persistent settings
    disable Disable persistent settings
    apply   Apply the persistent settings
'''

USAGE_EXAMPLES: str = '''\
Examples:

thinkpad-tools persistence edit
thinkpad-tools persistence disable
thinkpad-tools persistence enable
thinkpad-tools persistence apply
'''


class PersistenceHandler(object):
    """
    Handler for Undervolt related commands
    """
    def __init__(self):
        self.parser: argparse.ArgumentParser = argparse.ArgumentParser(
            prog='thinkpad-tools persistence',
            description='Edit persistence settings',
            usage=USAGE_HEAD,
            epilog=USAGE_EXAMPLES,
            formatter_class=argparse.RawDescriptionHelpFormatter
        )
        self.parser.add_argument('verb', type=str, help='The action going to \
            take')

    def run(self, unparsed_args: list):
        """
        Parse and execute the command
        :param unparsed_args: Unparsed arguments for this property
        :return: Nothing
        """
        def invalid_property(prop_name: str, exit_code: int):
            """
            Print error message and exit with exit code 1
            :param prop_name: Name of the property
            :param exit_code: Exit code
            :return: Nothing, the problem exits with the given exit code
            """
            print(
                'Invalid command "%s", available properties: ' % prop_name +
                ', '.join(self.inner.__dict__.keys()),
                file=sys.stderr
            )
            exit(exit_code)

        # Parse arguments
        args: argparse.Namespace = self.parser.parse_args(unparsed_args)
        verb: str = str(args.verb).lower()

        # Commands
        if verb == 'edit':
            try:
                editor: str = os.environ['EDITOR']
            except KeyError:
                editor: str = "/usr/bin/nano"
            os.system('sudo {editor} /etc/thinkpad-tools.ini'
                      .format(editor=editor))
            return
        if verb == "enable":
            os.system('systemctl daemon-reload')
            os.system('systemctl enable thinkpad-tools.service')
            print("""To set persistent settings, please edit the file
                     '/etc/thinkpad-tools.ini'""")
            print("Persistence enabled")
            return
        if verb == "disable":
            os.system('systemctl daemon-reload')
            os.system('systemctl disable thinkpad-tools.service')
            print("Persistence disabled")
            return
        if verb == "apply":
            config: configparser.ConfigParser = configparser.ConfigParser()
            config.read('/etc/thinkpad-tools.ini')
            for section in config.sections():
                for (command, val) in config.items(section):
                    commandline_parser([section, "set-"+command, val])
            return

        # No match found
        print('Command "%s" not found' % verb, file=sys.stderr)
        exit(1)


================================================
FILE: thinkpad_tools_assets/thinkpad-tools.ini
================================================
# [trackpoint]
# speed = 255
# sensitivity = 255
# [undervolt]
# [battery]

================================================
FILE: thinkpad_tools_assets/thinkpad-tools.service
================================================
[Unit]
Description=Thinkpad Tools Persistence Service
After=multi-user.target

[Service]
Type=oneshot
User=root
Group=root
ExecStart=thinkpad-tools persistence apply

[Install]
WantedBy=multi-user.target

================================================
FILE: thinkpad_tools_assets/trackpoint.py
================================================
# trackpoint.py

"""
Trackpoint related stuff
"""

from thinkpad_tools_assets.utils import ApplyValueFailedException, NotSudo
import os
import sys
import pathlib
import argparse

try:
    if os.getuid() != 0:
        raise NotSudo("Script must be run as superuser/sudo")
except NotSudo:
    print("ERROR: This script must be run as superuser/sudo")
    sys.exit(1)

if os.path.exists("/sys/devices/rmi4-00/rmi4-00.fn03/serio2"):
    BASE_PATH = pathlib.PurePath('/sys/devices/rmi4-00/rmi4-00.fn03/serio2')
elif os.path.exists("/sys/devices/rmi4-00/rmi4-00.fn03/serio3"):
    BASE_PATH = pathlib.PurePath('/sys/devices/rmi4-00/rmi4-00.fn03/serio3')
else:
    BASE_PATH = pathlib.PurePath('/sys/devices/platform/i8042/serio1/serio2')

STATUS_TEXT = '''\
Current status:
  Sensitivity:             {sensitivity}
  Speed:                   {speed}\
'''

USAGE_HEAD: str = '''\
thinkpad-tools trackpoint <verb> [argument]

Supported verbs are:
    status              Print all properties
    set-<property>      Set value
    get-<property>      Get property
    disable             Disable trackpoint
Available properties: sensitivity, speed
'''

USAGE_EXAMPLES: str = '''\
Examples:

thinkpad-tools trackpoint status
thinkpad-tools trackpoint set-sensitivity 20
thinkpad-tools trackpoint get-speed
thinkpad-tools trackpoint disable
'''


class TrackPoint(object):
    """
    Class to handle requests related to TrackPoints
    """

    def __init__(
            self,
            sensitivity: int or None = None,
            speed: int or None = None
    ):
        self.sensitivity = sensitivity
        self.speed = speed

    def read_values(self):
        """
        Read values from the system
        :return: Nothing
        """
        for prop in self.__dict__.keys():
            file_path: str = str(BASE_PATH / prop)
            if os.path.isfile(file_path):
                with open(file_path) as file:
                    self.__dict__[prop] = file.readline()
            else:
                self.__dict__[prop] = None

    def set_values(self):
        """
        Set values to the system
        :return: Nothing
        """
        success: bool = True
        failures: list = list()
        for prop in self.__dict__.keys():
            file_path: str = str(BASE_PATH / prop)
            if os.path.isfile(file_path):
                try:
                    with open(file_path, 'w') as file:
                        file.write(self.__dict__[prop])
                except Exception as e:
                    success = False
                    failures.append(str(e))
        if not success:
            raise ApplyValueFailedException(', '.join(failures))

    def disableTrackpoint(self):
        """
        Disable the trackpoint
        :return: Nothing
        """
        success: bool = True
        failures: list = list()
        for prop in self.__dict__.keys():
            file_path: str = str(BASE_PATH / prop)
            if os.path.isfile(file_path):
                try:
                    with open(file_path, 'w') as file:
                        file.write('0')
                except Exception as e:
                    success = False
                    failures.append(str(e))
        if not success:
            raise ApplyValueFailedException(', '.join(failures))

    def get_status_str(self) -> str:
        """
        Return status string
        :return: str: status string
        """
        return STATUS_TEXT.format(
            sensitivity=self.sensitivity or 'Unknown',
            speed=self.speed or 'Unknown'
        )


class TrackPointHandler(object):
    """
    Handler for TrackPoint related commands
    """

    def __init__(self):
        self.parser: argparse.ArgumentParser = argparse.ArgumentParser(
            prog='thinkpad-tools trackpoint',
            description='TrackPoint related commands',
            usage=USAGE_HEAD,
            epilog=USAGE_EXAMPLES,
            formatter_class=argparse.RawDescriptionHelpFormatter
        )
        self.parser.add_argument(
            'verb', type=str, help='The action going to take')
        self.parser.add_argument(
            'arguments', nargs='*', help='Arguments of the action')
        self.inner: TrackPoint = TrackPoint()

    def run(self, unparsed_args: list):
        """
        Parse and execute the command
        :param unparsed_args: Unparsed arguments for this property
        :return: Nothing
        """
        def invalid_property(prop_name: str, exit_code: int):
            """
            Print error message and exit with exit code 1
            :param prop_name: Name of the property
            :param exit_code: Exit code
            :return: Nothing, the problem exits with the given exit code
            """
            print(
                'Invalid command "%s", available properties: ' % prop_name +
                ', '.join(self.inner.__dict__.keys()),
                file=sys.stderr
            )
            exit(exit_code)

        # Parse arguments
        args: argparse.Namespace = self.parser.parse_args(unparsed_args)
        verb: str = str(args.verb).lower()

        # Read values from the system
        self.inner.read_values()

        # Commands
        if verb == 'status':
            print(self.inner.get_status_str())
            return

        if verb.startswith('set-'):
            try:
                prop: str = verb.split('-', maxsplit=1)[1]
            except IndexError:
                invalid_property(verb, 1)
                return
            if prop not in self.inner.__dict__.keys():
                invalid_property(prop, 1)
            self.inner.__dict__[prop] = str(''.join(args.arguments))
            self.inner.set_values()
            print(self.inner.get_status_str())
            return

        if verb.startswith('get-'):
            try:
                prop: str = verb.split('-', maxsplit=1)[1]
            except IndexError:
                invalid_property(verb, 1)
                return
            if not hasattr(self.inner, prop):
                invalid_property(prop, 1)
            if not self.inner.__dict__[prop]:
                print('Unable to read %s' % prop)
                exit(1)
            print(self.inner.__dict__[prop])
            return
        if verb == 'disable':
            self.inner.disableTrackpoint()
            print(self.inner.get_status_str())
            return
        # No match found
        print('Command "%s" not found' % verb, file=sys.stderr)
        exit(1)


================================================
FILE: thinkpad_tools_assets/undervolt.py
================================================
# undervolt.py

"""
Undervolt related stuff
"""

import os
import sys
import pathlib
import argparse
import thinkpad_tools_assets.classes
from thinkpad_tools_assets.utils import ApplyValueFailedException, NotSudo


try:
    if os.getuid() != 0:
        raise NotSudo("Script must be run as superuser/sudo")
except NotSudo:
    print("ERROR: This script must be run as superuser/sudo")
    sys.exit(1)

# PLANE KEY:
# Plane 0: Core
# Plane 1: GPU
# Plane 2: Cache
# Plane 3: Uncore
# Plane 4: Analogio

STATUS_TEXT = '''\
Current status:
  Core:                    {core}\n
  GPU:                     {gpu}\n
  Cache:                   {cache}\n
  Uncore:                  {uncore}\n
  Analogio:                {analogio}\n
'''
USAGE_HEAD: str = '''\
thinkpad-tools undervolt <verb> [argument]

Supported verbs are:
    status          Print all properties
    set-<property>  Set value
    get-<property>  Get property
Available properties: core, gpu, cache, uncore, analogio
'''

USAGE_EXAMPLES: str = '''\
Examples:

thinkpad-tools trackpoint status
thinkpad-tools trackpoint set-core -20
thinkpad-tools trackpoint get-gpu
'''


class Undervolt(object):
    """
    Class to handle requests related to Undervolting
    """

    def __init__(
            self,
            core: float or None = None,
            gpu: float or None = None,
            cache: float or None = None,
            uncore: float or None = None,
            analogio: float or None = None,
    ):
        # self.__register: str = "0x150"
        # self.__undervolt_value: str = "0x80000"
        self.core = core
        self.gpu = gpu
        self.cache = cache
        self.uncore = uncore
        self.analogio = analogio

    def read_values(self):
        """
        Read values from the system
        :return: Nothing
        """
        success = True
        failures: list = list()
        system = thinkpad_tools_assets.classes.UndervoltSystem()
        for prop in self.__dict__.keys():
            plane_hashmap = {"core": 0, "gpu": 1, "cache": 2, "uncore": 3, "analogio": 4}
            h: str = ''
            try:
                plane = plane_hashmap[prop]
                h = system.readUndervolt(plane)
            except Exception as e:
                success = False
                failures.append(str(e))
            self.__dict__[prop] = h
        if not success:
            raise ApplyValueFailedException(', '.join(failures))

    def set_values(self):
        """
        Set values to the system MSR using undervolt function
        :return: Nothing
        """
        system = thinkpad_tools_assets.classes.UndervoltSystem()
        success: bool = True
        failures: list = list()
        plane_hashmap = {"core": 0, "gpu": 1, "cache": 2, "uncore": 3, "analogio": 4}
        for prop in self.__dict__.keys():
            if self.__dict__[prop] is None:
                continue
            try:
                plane: int = plane_hashmap[prop]
                system.applyUndervolt(int(self.__dict__[prop]), plane)
            except Exception as e:
                success = False
                failures.append(str(e))
        if not success:
            raise ApplyValueFailedException(', '.join(failures))

    def get_status_str(self) -> str:
        """
        Return status string
        :return: str: status string
        """
        return STATUS_TEXT.format(
            core=self.core,
            gpu=self.gpu,
            cache=self.cache,
            uncore=self.uncore,
            analogio=self.analogio
        )


class UndervoltHandler(object):
    """
    Handler for Undervolt related commands
    """
    def __init__(self):
        self.parser: argparse.ArgumentParser = argparse.ArgumentParser(
            prog='thinkpad-tools undervolt',
            description='Undervolt related commands',
            usage=USAGE_HEAD,
            epilog=USAGE_EXAMPLES,
            formatter_class=argparse.RawDescriptionHelpFormatter
        )
        self.parser.add_argument('verb', type=str, help='The action going to \
            take')
        self.parser.add_argument(
            'arguments', nargs='*', help='Arguments of the action')
        self.inner: Undervolt = Undervolt()

    def run(self, unparsed_args: list):
        """
        Parse and execute the command
        :param unparsed_args: Unparsed arguments for this property
        :return: Nothing
        """
        def invalid_property(prop_name: str, exit_code: int):
            """
            Print error message and exit with exit code 1
            :param prop_name: Name of the property
            :param exit_code: Exit code
            :return: Nothing, the problem exits with the given exit code
            """
            print(
                'Invalid command "%s", available properties: ' % prop_name +
                ', '.join(self.inner.__dict__.keys()),
                file=sys.stderr
            )
            exit(exit_code)

        # Parse arguments
        args: argparse.Namespace = self.parser.parse_args(unparsed_args)
        verb: str = str(args.verb).lower()

        # Read values from the system
        self.inner.read_values()

        # Commands
        if verb == 'status':
            print(self.inner.get_status_str())
            return

        if verb.startswith('set-'):
            try:
                prop: str = verb.split('-', maxsplit=1)[1]
            except IndexError:
                invalid_property(verb, 1)
                return
            if prop not in self.inner.__dict__.keys():
                invalid_property(prop, 1)
            self.inner.__dict__[prop] = str(''.join(args.arguments))
            self.inner.set_values()
            print(self.inner.get_status_str())
            return

        if verb.startswith('get-'):
            try:
                prop: str = verb.split('-', maxsplit=1)[1]
            except IndexError:
                invalid_property(verb, 1)
            if not hasattr(self.inner, prop):
                invalid_property(prop, 1)
            if not self.inner.__dict__[prop]:
                print('Unable to read %s' % prop)
                exit(1)
            print(self.inner.__dict__[prop])
            return

        # No match found
        print('Command "%s" not found' % verb, file=sys.stderr)
        exit(1)


================================================
FILE: thinkpad_tools_assets/utils.py
================================================
# thinkpad_tools_assets.utils.py


class ApplyValueFailedException(Exception):
    """
    Exception raised when failed to apply settings
    """
    pass


class NotSudo(Exception):
    pass


================================================
FILE: upload-pypi.sh
================================================
#!/bin/bash
sudo rm -rf dist && sudo python3 setup.py sdist && twine upload dist/*
Download .txt
gitextract_l20ul__j/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── .vscode/
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── requirements.txt
├── setup.py
├── stdeb.cfg
├── thinkpad-tools
├── thinkpad_tools_assets/
│   ├── __init__.py
│   ├── __main__.py
│   ├── battery.py
│   ├── classes.py
│   ├── cmd.py
│   ├── persistence.py
│   ├── thinkpad-tools.ini
│   ├── thinkpad-tools.service
│   ├── trackpoint.py
│   ├── undervolt.py
│   └── utils.py
└── upload-pypi.sh
Download .txt
SYMBOL INDEX (36 symbols across 7 files)

FILE: thinkpad_tools_assets/battery.py
  class Battery (line 94) | class Battery(object):
    method __init__ (line 99) | def __init__(self, name: str = 'BAT0', **kwargs):
    method read_values (line 109) | def read_values(self):
    method set_values (line 128) | def set_values(self):
    method get_status_str (line 153) | def get_status_str(self) -> str:
  class BatteryHandler (line 183) | class BatteryHandler(object):
    method __init__ (line 188) | def __init__(self):
    method run (line 208) | def run(self, unparsed_args: list):

FILE: thinkpad_tools_assets/classes.py
  class UndervoltSystem (line 9) | class UndervoltSystem(object):
    method __init__ (line 10) | def __init__(self):
    method applyUndervolt (line 13) | def applyUndervolt(self, mv, plane):
    method parseReadUndervolt (line 33) | def parseReadUndervolt(self, offset):
    method readUndervolt (line 41) | def readUndervolt(self, plane):

FILE: thinkpad_tools_assets/cmd.py
  function commandline_parser (line 42) | def commandline_parser(unparsed_args: None or list = None):

FILE: thinkpad_tools_assets/persistence.py
  class PersistenceHandler (line 43) | class PersistenceHandler(object):
    method __init__ (line 47) | def __init__(self):
    method run (line 58) | def run(self, unparsed_args: list):

FILE: thinkpad_tools_assets/trackpoint.py
  class TrackPoint (line 54) | class TrackPoint(object):
    method __init__ (line 59) | def __init__(
    method read_values (line 67) | def read_values(self):
    method set_values (line 80) | def set_values(self):
    method disableTrackpoint (line 99) | def disableTrackpoint(self):
    method get_status_str (line 118) | def get_status_str(self) -> str:
  class TrackPointHandler (line 129) | class TrackPointHandler(object):
    method __init__ (line 134) | def __init__(self):
    method run (line 148) | def run(self, unparsed_args: list):

FILE: thinkpad_tools_assets/undervolt.py
  class Undervolt (line 56) | class Undervolt(object):
    method __init__ (line 61) | def __init__(
    method read_values (line 77) | def read_values(self):
    method set_values (line 98) | def set_values(self):
    method get_status_str (line 119) | def get_status_str(self) -> str:
  class UndervoltHandler (line 133) | class UndervoltHandler(object):
    method __init__ (line 137) | def __init__(self):
    method run (line 151) | def run(self, unparsed_args: list):

FILE: thinkpad_tools_assets/utils.py
  class ApplyValueFailedException (line 4) | class ApplyValueFailedException(Exception):
  class NotSudo (line 11) | class NotSudo(Exception):
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (47K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 714,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".gitignore",
    "chars": 1300,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 128,
    "preview": "{\n    \"python.linting.pylintEnabled\": false,\n    \"python.linting.enabled\": true,\n    \"python.linting.pycodestyleEnabled\""
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3349,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "LICENSE",
    "chars": 1522,
    "preview": "BSD 3-Clause License\n\nCopyright (c) 2020-2021, Dev Singh\nAll rights reserved.\n\nRedistribution and use in source and bina"
  },
  {
    "path": "README.md",
    "chars": 2593,
    "preview": "## Update 02/10/2021\n\nThere also exists a somewhat-functioning GUI for this utility, which relies on this utility being "
  },
  {
    "path": "requirements.txt",
    "chars": 11,
    "preview": "setuptools\n"
  },
  {
    "path": "setup.py",
    "chars": 1158,
    "preview": "#!/usr/bin/env python3\n# -*- encoding: utf-8 -*-\n\n\"\"\"\nSetup tools wrapper\n\"\"\"\n\nfrom setuptools import find_packages, set"
  },
  {
    "path": "stdeb.cfg",
    "chars": 73,
    "preview": "[DEFAULT]\nSuite: focal\nXS-Python-Version: >= 3.6\nCopyright-File: LICENSE\n"
  },
  {
    "path": "thinkpad-tools",
    "chars": 236,
    "preview": "#!/usr/bin/env python3\n# -*- encoding: utf-8 -*-\n\n\"\"\"\nThinkPad-tools commandline wrapper\n\"\"\"\nimport os\nimport sys\nfrom t"
  },
  {
    "path": "thinkpad_tools_assets/__init__.py",
    "chars": 36,
    "preview": "# __init__.py\n\n__version__ = '0.13'\n"
  },
  {
    "path": "thinkpad_tools_assets/__main__.py",
    "chars": 128,
    "preview": "# __main__.py\n\nimport sys\n\nfrom .cmd import commandline_parser\n\nif __name__ == '__main__':\n    commandline_parser(sys.ar"
  },
  {
    "path": "thinkpad_tools_assets/battery.py",
    "chars": 11497,
    "preview": "\"\"\"\nBattery related stuff\n\"\"\"\n\n\nimport os\nimport re\nimport sys\nimport pathlib\nimport argparse\nfrom thinkpad_tools_assets"
  },
  {
    "path": "thinkpad_tools_assets/classes.py",
    "chars": 2003,
    "preview": "import os\nimport glob\nimport sys\nimport struct\nimport subprocess\nfrom struct import pack, unpack\n\n\nclass UndervoltSystem"
  },
  {
    "path": "thinkpad_tools_assets/cmd.py",
    "chars": 2064,
    "preview": "# cmd.py\n\n\"\"\"\nCommandline parser\n\"\"\"\n\n\nimport logging\nimport pathlib\nimport argparse\n\n# Setup logger\nlogging.basicConfig"
  },
  {
    "path": "thinkpad_tools_assets/persistence.py",
    "chars": 3604,
    "preview": "# persistence.py\n\n\"\"\"\nWrapper to edit the persistent settings\n\"\"\"\n\nimport os\nimport sys\nimport pathlib\nimport argparse\ni"
  },
  {
    "path": "thinkpad_tools_assets/thinkpad-tools.ini",
    "chars": 74,
    "preview": "# [trackpoint]\n# speed = 255\n# sensitivity = 255\n# [undervolt]\n# [battery]"
  },
  {
    "path": "thinkpad_tools_assets/thinkpad-tools.service",
    "chars": 203,
    "preview": "[Unit]\nDescription=Thinkpad Tools Persistence Service\nAfter=multi-user.target\n\n[Service]\nType=oneshot\nUser=root\nGroup=ro"
  },
  {
    "path": "thinkpad_tools_assets/trackpoint.py",
    "chars": 6530,
    "preview": "# trackpoint.py\n\n\"\"\"\nTrackpoint related stuff\n\"\"\"\n\nfrom thinkpad_tools_assets.utils import ApplyValueFailedException, No"
  },
  {
    "path": "thinkpad_tools_assets/undervolt.py",
    "chars": 6329,
    "preview": "# undervolt.py\n\n\"\"\"\nUndervolt related stuff\n\"\"\"\n\nimport os\nimport sys\nimport pathlib\nimport argparse\nimport thinkpad_too"
  },
  {
    "path": "thinkpad_tools_assets/utils.py",
    "chars": 192,
    "preview": "# thinkpad_tools_assets.utils.py\n\n\nclass ApplyValueFailedException(Exception):\n    \"\"\"\n    Exception raised when failed "
  },
  {
    "path": "upload-pypi.sh",
    "chars": 82,
    "preview": "#!/bin/bash\nsudo rm -rf dist && sudo python3 setup.py sdist && twine upload dist/*"
  }
]

About this extraction

This page contains the full source code of the devksingh4/thinkpad-tools GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (43.4 KB), approximately 10.4k tokens, and a symbol index with 36 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!