[
  {
    "path": ".coveragerc",
    "content": "[run]\nsource = st7789\nomit =\n    .tox/*\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "Thank you for opening an issue on an Pimoroni Python library repository.  To\nimprove the speed of resolution please review the following guidelines and\ncommon troubleshooting steps below before creating the issue:\n\n- **Do not use GitHub issues for troubleshooting projects and issues.**  Instead use\n  the forums at http://forums.pimoroni.com to ask questions and troubleshoot why\n  something isn't working as expected.  In many cases the problem is a common issue\n  that you will more quickly receive help from the forum community.  GitHub issues\n  are meant for known defects in the code.  If you don't know if there is a defect\n  in the code then start with troubleshooting on the forum first.\n\n- **If following a tutorial or guide be sure you didn't miss a step.** Carefully\n  check all of the steps and commands to run have been followed.  Consult the\n  forum if you're unsure or have questions about steps in a guide/tutorial.\n\n- **For Python/Raspberry Pi projects check these very common issues to ensure they don't apply**:\n\n  - If you are receiving an **ImportError: No module named...** error then a\n    library the code depends on is not installed.  Check the tutorial/guide or\n    README to ensure you have installed the necessary libraries.  Usually the\n    missing library can be installed with the `pip` tool, but check the tutorial/guide\n    for the exact command.\n\n  - **Be sure you are supplying adequate power to the board.**  Check the specs of\n    your board and power in an external power supply.  In many cases just\n    plugging a board into your computer is not enough to power it and other\n    peripherals.\n\n  - **Double check all soldering joints and connections.**  Flakey connections\n    cause many mysterious problems.  See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints.\n\nIf you're sure this issue is a defect in the code and checked the steps above\nplease fill in the following fields to provide enough troubleshooting information.\nYou may delete the guideline and text above to just leave the following details:\n\n- Platform/operating system (i.e. Raspberry Pi with Raspbian operating system,\n  Windows 32-bit, Windows 64-bit, Mac OSX 64-bit, etc.):  **INSERT PLATFORM/OPERATING\n  SYSTEM HERE**\n\n- Python version (run `python -version` or `python3 -version`):  **INSERT PYTHON\n  VERSION HERE**\n\n- Error message you are receiving, including any Python exception traces:  **INSERT\n  ERROR MESAGE/EXCEPTION TRACES HERE***\n\n- List the steps to reproduce the problem below (if possible attach code or commands\n  to run): **LIST REPRO STEPS BELOW**\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "Thank you for creating a pull request to contribute to Pimoroni's GitHub code!\nBefore you open the request please review the following guidelines and tips to\nhelp it be more easily integrated:\n\n- **Describe the scope of your change--i.e. what the change does and what parts\n  of the code were modified.**  This will help us understand any risks of integrating\n  the code.\n\n- **Describe any known limitations with your change.**  For example if the change\n  doesn't apply to a supported platform of the library please mention it.\n\n- **Please run any tests or examples that can exercise your modified code.**  We\n  strive to not break users of the code and running tests/examples helps with this\n  process. You should install tox (`pip install tox`) and run it in the `library`\n  folder to execute the tests and run Python linting.\n\nThank you again for contributing!  We will try to test and integrate the change\nas soon as we can, but be aware we have many GitHub repositories to manage and\ncan't immediately respond to every request.  There is no need to bump or check in\non a pull request (it will clutter the discussion of the request).\n\nAlso don't be worried if the request is closed or not integrated--sometimes the\npriorities of Pimoroni's GitHub code (education, ease of use) might not match the\npriorities of the pull request.  Don't fret, the open source community thrives on\nforks and GitHub makes it easy to keep your changes in a forked repo.\n\nAfter reviewing the guidelines above you can delete this text from the pull request.\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  test:\n    name: Python ${{ matrix.python }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python: ['3.9', '3.10', '3.11']\n\n    env:\n      RELEASE_FILE: ${{ github.event.repository.name }}-${{ github.event.release.tag_name || github.sha }}-py${{ matrix.python }}\n\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v4\n\n      - name: Set up Python ${{ matrix.python }}\n        uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python }}\n\n      - name: Install Dependencies\n        run: |\n          make dev-deps\n\n      - name: Build Packages\n        run: |\n          make build\n\n      - name: Upload Packages\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ env.RELEASE_FILE }}\n          path: dist/\n"
  },
  {
    "path": ".github/workflows/qa.yml",
    "content": "name: QA\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  test:\n    name: linting & spelling\n    runs-on: ubuntu-latest\n    env:\n      TERM: xterm-256color\n\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v4\n\n      - name: Set up Python '3,11'\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.11'\n\n      - name: Install Dependencies\n        run: |\n          make dev-deps\n\n      - name: Run Quality Assurance\n        run: |\n          make qa\n\n      - name: Run Code Checks\n        run: |\n          make check\n\n      - name: Run Bash Code Checks\n        run: |\n          make shellcheck\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  test:\n    name: Python ${{ matrix.python }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python: ['3.9', '3.10', '3.11']\n\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v3\n\n      - name: Set up Python ${{ matrix.python }}\n        uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python }}\n\n      - name: Install Dependencies\n        run: |\n          make dev-deps\n\n      - name: Run Tests\n        run: |\n          make pytest\n\n      - name: Coverage\n        if: ${{ matrix.python == '3.9' }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          python -m pip install coveralls\n          coveralls --service=github\n\n"
  },
  {
    "path": ".gitignore",
    "content": "build/\n_build/\n*.o\n*.so\n*.a\n*.py[cod]\n*.egg-info\ndist/\n__pycache__\n.DS_Store\n*.deb\n*.dsc\n*.build\n*.changes\n*.orig.*\npackaging/*tar.xz\nlibrary/debian/\n.coverage\n.pytest_cache\n.tox\n"
  },
  {
    "path": ".stickler.yml",
    "content": "---\nlinters:\n    flake8:\n        python: 3\n        max-line-length: 160\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "1.0.1\n-----\n\n* Add spidev and numpy dependencies.\n\n1.0.0\n-----\n\n* Repackage to hatch/pyproject.toml\n* Port to gpiod/gpiodevice\n\n0.0.4\n-----\n\n* Add support for 320x240 2.0\" LCD (Display HAT Mini)\n* Add support for 240x135 1.14\" LCD (@slabua)\n* Rework numpy RGB888 to RGB565\n* Support displaying numpy arrays (@zecktos)\n\n0.0.3\n-----\n\n* Add support for RLCD\n* Brought back `offset_left` and `offset_top` parameters\n\n0.0.2\n-----\n\n* Fix for image retention\n* Drop defunct parameters\n\n0.0.1\n-----\n\n* Initial Release\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Adafruit Industries\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "LIBRARY_NAME := $(shell hatch project metadata name 2> /dev/null)\nLIBRARY_VERSION := $(shell hatch version 2> /dev/null)\n\n.PHONY: usage install uninstall check pytest qa build-deps check tag wheel sdist clean dist testdeploy deploy\nusage:\nifdef LIBRARY_NAME\n\t@echo \"Library: ${LIBRARY_NAME}\"\n\t@echo \"Version: ${LIBRARY_VERSION}\\n\"\nelse\n\t@echo \"WARNING: You should 'make dev-deps'\\n\"\nendif\n\t@echo \"Usage: make <target>, where target is one of:\\n\"\n\t@echo \"install:      install the library locally from source\"\n\t@echo \"uninstall:    uninstall the local library\"\n\t@echo \"dev-deps:     install Python dev dependencies\"\n\t@echo \"check:        perform basic integrity checks on the codebase\"\n\t@echo \"qa:           run linting and package QA\"\n\t@echo \"pytest:       run Python test fixtures\"\n\t@echo \"clean:        clean Python build and dist directories\"\n\t@echo \"build:        build Python distribution files\"\n\t@echo \"testdeploy:   build and upload to test PyPi\"\n\t@echo \"deploy:       build and upload to PyPi\"\n\t@echo \"tag:          tag the repository with the current version\\n\"\n\nversion:\n\t@hatch version\n\ninstall:\n\t./install.sh --unstable\n\nuninstall:\n\t./uninstall.sh\n\ndev-deps:\n\tpython3 -m pip install -r requirements-dev.txt\n\tsudo apt install dos2unix shellcheck\n\ncheck:\n\t@bash check.sh\n\nshellcheck:\n\tshellcheck *.sh\n\nqa:\n\ttox -e qa\n\npytest:\n\ttox -e py\n\nnopost:\n\t@bash check.sh --nopost\n\ntag: version\n\tgit tag -a \"v${LIBRARY_VERSION}\" -m \"Version ${LIBRARY_VERSION}\"\n\nbuild: check\n\t@hatch build\n\nclean:\n\t-rm -r dist\n\ntestdeploy: build\n\ttwine upload --repository testpypi dist/*\n\ndeploy: nopost build\n\ttwine upload dist/*\n"
  },
  {
    "path": "README.md",
    "content": "# Python ST7789\n\n[![Build Status](https://img.shields.io/github/actions/workflow/status/pimoroni/st7789-python/test.yml?branch=main)](https://github.com/pimoroni/st7789-python/actions/workflows/test.yml)\n[![Coverage Status](https://coveralls.io/repos/github/pimoroni/st7789-python/badge.svg?branch=main)](https://coveralls.io/github/pimoroni/st7789-python?branch=main)\n[![PyPi Package](https://img.shields.io/pypi/v/st7789.svg)](https://pypi.python.org/pypi/st7789)\n[![Python Versions](https://img.shields.io/pypi/pyversions/st7789.svg)](https://pypi.python.org/pypi/st7789)\n\nPython library to control an ST7789 TFT LCD display\n\nDesigned specifically to work with a ST7789 based 240x240 pixel TFT SPI display. (Specifically the [1.3\" SPI LCD from Pimoroni](https://shop.pimoroni.com/products/1-3-spi-colour-lcd-240x240-breakout)).\n\n![Animated GIF showing the ST7789 SPI LCD displaying Deploy/Rainbows in alternating frames](https://raw.githubusercontent.com/pimoroni/st7789-python/master/square-lcd-breakout-1.gif)\n\n# Installation\n\nMake sure you have the following dependencies:\n\n````\nsudo apt-get update\nsudo apt-get install python-rpi.gpio python-spidev python-pip python-pil python-numpy\n````\n\nInstall this library by running:\n\n````\nsudo pip install st7789\n````\n\nYou might also need to enable I2C and SPI in raspi-config. See example of usage in the examples folder.\n\n\n# Licensing & History\n\nThis library is a modification of a modification of code originally written by Tony DiCola for Adafruit Industries, and modified to work with the ST7735 by Clement Skau.\n\nTo create this ST7789 driver, it has been hard-forked from st7735-python which was originally modified by Pimoroni to include support for their 160x80 SPI LCD breakout.\n\n## Modifications include:\n\n* PIL/Pillow has been removed from the underlying display driver to separate concerns- you should create your own PIL image and display it using `display(image)`\n* `width`, `height`, `rotation`, `invert`, `offset_left` and `offset_top` parameters can be passed into `__init__` for alternate displays\n* `Adafruit_GPIO` has been replaced with `RPi.GPIO` and `spidev` to closely align with our other software (IE: Raspberry Pi only)\n* Test fixtures have been added to keep this library stable\n\nPimoroni invests time and resources forking and modifying this open source code, please support Pimoroni and open-source software by purchasing products from us, too!\n\nAdafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!\n\nModified from 'Modified from 'Adafruit Python ILI9341' written by Tony DiCola for Adafruit Industries.' written by Clement Skau.\n\nMIT license, all text above must be included in any redistribution\n"
  },
  {
    "path": "ST7789.py",
    "content": "from warnings import warn\n\nfrom st7789 import *  # noqa F403\n\nwarn(\n    'Using \"import ST7789\" is deprecated. Please \"import st7789\" (all lowercase)!',\n    DeprecationWarning,\n    stacklevel=2,\n)\n"
  },
  {
    "path": "check.sh",
    "content": "#!/bin/bash\n\n# This script handles some basic QA checks on the source\n\nNOPOST=$1\nLIBRARY_NAME=$(hatch project metadata name)\nLIBRARY_VERSION=$(hatch version | awk -F \".\" '{print $1\".\"$2\".\"$3}')\nPOST_VERSION=$(hatch version | awk -F \".\" '{print substr($4,0,length($4))}')\nTERM=${TERM:=\"xterm-256color\"}\n\nsuccess() {\n\techo -e \"$(tput setaf 2)$1$(tput sgr0)\"\n}\n\ninform() {\n\techo -e \"$(tput setaf 6)$1$(tput sgr0)\"\n}\n\nwarning() {\n\techo -e \"$(tput setaf 1)$1$(tput sgr0)\"\n}\n\nwhile [[ $# -gt 0 ]]; do\n\tK=\"$1\"\n\tcase $K in\n\t-p|--nopost)\n\t\tNOPOST=true\n\t\tshift\n\t\t;;\n\t*)\n\t\tif [[ $1 == -* ]]; then\n\t\t\tprintf \"Unrecognised option: %s\\n\" \"$1\";\n\t\t\texit 1\n\t\tfi\n\t\tPOSITIONAL_ARGS+=(\"$1\")\n\t\tshift\n\tesac\ndone\n\ninform \"Checking $LIBRARY_NAME $LIBRARY_VERSION\\n\"\n\ninform \"Checking for trailing whitespace...\"\nif grep -IUrn --color \"[[:blank:]]$\" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO; then\n    warning \"Trailing whitespace found!\"\n    exit 1\nelse\n    success \"No trailing whitespace found.\"\nfi\nprintf \"\\n\"\n\ninform \"Checking for DOS line-endings...\"\nif grep -lIUrn --color $'\\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile; then\n    warning \"DOS line-endings found!\"\n    exit 1\nelse\n    success \"No DOS line-endings found.\"\nfi\nprintf \"\\n\"\n\ninform \"Checking CHANGELOG.md...\"\nif ! grep \"^${LIBRARY_VERSION}\" CHANGELOG.md > /dev/null 2>&1; then\n    warning \"Changes missing for version ${LIBRARY_VERSION}! Please update CHANGELOG.md.\"\n    exit 1\nelse\n    success \"Changes found for version ${LIBRARY_VERSION}.\"\nfi\nprintf \"\\n\"\n\ninform \"Checking for git tag ${LIBRARY_VERSION}...\"\nif ! git tag -l | grep -E \"${LIBRARY_VERSION}$\"; then\n    warning \"Missing git tag for version ${LIBRARY_VERSION}\"\nfi\nprintf \"\\n\"\n\nif [[ $NOPOST ]]; then\n    inform \"Checking for .postN on library version...\"\n    if [[ \"$POST_VERSION\" != \"\" ]]; then\n        warning \"Found .$POST_VERSION on library version.\"\n        inform \"Please only use these for testpypi releases.\"\n        exit 1\n    else\n        success \"OK\"\n    fi\nfi\n"
  },
  {
    "path": "examples/320x240.py",
    "content": "#!/usr/bin/env python3\nimport time\n\nfrom PIL import Image, ImageDraw\n\nfrom st7789 import ST7789\n\n# Buttons\nBUTTON_A = 5\nBUTTON_B = 6\nBUTTON_X = 16\nBUTTON_Y = 24\n\n# Onboard RGB LED\nLED_R = 17\nLED_G = 27\nLED_B = 22\n\n# General\nSPI_PORT = 0\nSPI_CS = 1\nSPI_DC = 9\nBACKLIGHT = 13\n\n# Screen dimensions\nWIDTH = 320\nHEIGHT = 240\n\nbuffer = Image.new(\"RGB\", (WIDTH, HEIGHT))\ndraw = ImageDraw.Draw(buffer)\n\ndraw.rectangle((0, 0, 50, 50), (255, 0, 0))\ndraw.rectangle((320 - 50, 0, 320, 50), (0, 255, 0))\ndraw.rectangle((0, 240 - 50, 50, 240), (0, 0, 255))\ndraw.rectangle((320 - 50, 240 - 50, 320, 240), (255, 255, 0))\n\ndisplay = ST7789(\n    port=SPI_PORT,\n    cs=SPI_CS,\n    dc=SPI_DC,\n    backlight=BACKLIGHT,\n    width=WIDTH,\n    height=HEIGHT,\n    rotation=180,\n    spi_speed_hz=60 * 1000 * 1000,\n)\n\nwhile True:\n    display.display(buffer)\n    time.sleep(1.0 / 60)\n"
  },
  {
    "path": "examples/LICENSE.txt",
    "content": "Examples originally modified from Adafruit_Python_ILI9341\nhttps://github.com/adafruit/Adafruit_Python_ILI9341\n\nCopyright (c 2014 Adafruit Industries\nAuthor: Tony DiCola\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "examples/framerate.py",
    "content": "#!/usr/bin/env python3\nimport math\nimport sys\nimport time\n\nfrom PIL import Image, ImageDraw\n\nimport st7789\n\n# Higher SPI bus speed = higher framerate\ntry:\n    SPI_SPEED_MHZ = int(sys.argv[1])\nexcept ValueError:\n    sys.exit(1)\nexcept IndexError:\n    SPI_SPEED_MHZ = 80\n\ntry:\n    display_type = sys.argv[2]\nexcept IndexError:\n    display_type = \"square\"\n\nprint(\n    f\"\"\"\nframerate.py - Test LCD framerate.\n\nIf you're using Breakout Garden, plug the 1.3\" LCD (SPI)\nbreakout into the front slot.\n\nUsage: {sys.argv[0]} <spi_speed_mhz> <display_type>\n\nWhere <display_type> is one of:\n  * square - 240x240 1.3\" Square LCD\n  * round  - 240x240 1.3\" Round LCD (applies an offset)\n  * rect   - 240x135 1.14\" Rectangular LCD (applies an offset)\n  * dhmini - 320x240 2.0\" Rectangular LCD\n\nRunning at: {SPI_SPEED_MHZ}MHz on a {display_type} display.\n\"\"\"\n)\n\ntry:\n    width, height, rotation, backlight, offset_left, offset_top = {\n        \"square\": (240, 240, 90, 19, 0, 0),\n        \"round\": (240, 240, 90, 19, 40, 0),\n        \"rect\": (240, 135, 0, 19, 40, 53),\n        \"dhmini\": (320, 240, 180, 13, 0, 0),\n    }[display_type]\nexcept IndexError:\n    raise RuntimeError(f\"Unsupported display type: {display_type}\")\n\n# Create ST7789 LCD display class.\ndisp = st7789.ST7789(\n    width=width,\n    height=height,\n    rotation=rotation,\n    port=0,\n    cs=st7789.BG_SPI_CS_FRONT,  # BG_SPI_CS_BACK or BG_SPI_CS_FRONT\n    dc=9,\n    backlight=19,  # Breakout Garden: 18 for back slot, 19 for front slot.\n                   # NOTE: Change this to 13 for Pirate Audio boards\n    spi_speed_hz=SPI_SPEED_MHZ * 1000000,\n    offset_left=offset_left,\n    offset_top=offset_top,\n)\n\nWIDTH = disp.width\nHEIGHT = disp.height\nSTEPS = WIDTH * 2\nimages = []\n\nfor step in range(STEPS):\n    image = Image.new(\"RGB\", (WIDTH, HEIGHT), (0, 0, 128))\n    draw = ImageDraw.Draw(image)\n\n    if step % 2 == 0:\n        draw.rectangle((WIDTH / 2, int(HEIGHT / 2), WIDTH, HEIGHT), (0, 128, 0))\n    else:\n        draw.rectangle((0, 0, WIDTH / 2 - 1, int(HEIGHT / 2) - 1), (0, 128, 0))\n\n    f = math.sin((float(step) / STEPS) * math.pi)\n    offset_left = int(f * WIDTH)\n    draw.ellipse((offset_left, 35, offset_left + 10, 45), (255, 0, 0))\n\n    images.append(image)\n\ncount = 0\ntime_start = time.time()\n\nwhile True:\n    disp.display(images[count % len(images)])\n    count += 1\n    time_current = time.time() - time_start\n    if count % 120 == 0:\n        print(\n            f\"Time: {time_current:8.3f},      Frames: {count:6d},      FPS: {count / time_current:8.3f}\"\n        )\n"
  },
  {
    "path": "examples/gif.py",
    "content": "#!/usr/bin/env python3\nimport sys\nimport time\n\nfrom PIL import Image\n\nimport st7789\n\nprint(\n    \"\"\"\ngif.py - Display a gif on the LCD.\n\nIf you're using Breakout Garden, plug the 1.3\" LCD (SPI)\nbreakout into the front slot.\n\n\"\"\"\n)\n\nif len(sys.argv) < 2:\n    print(\n        f\"\"\"Usage: {sys.argv[0]} <gif_file> <display_type>\n\nWhere <gif_file> is a .gif file.\n  Hint: {sys.argv[0]} deployrainbows.gif\n\nAnd <display_type> is one of:\n  * square - 240x240 1.3\" Square LCD\n  * round  - 240x240 1.3\" Round LCD (applies an offset)\n  * rect   - 240x135 1.14\" Rectangular LCD (applies an offset)\n  * dhmini - 320x240 2.0\" Display HAT Mini\n\"\"\"\n    )\n    sys.exit(1)\n\nimage_file = sys.argv[1]\n\ntry:\n    display_type = sys.argv[2]\nexcept IndexError:\n    display_type = \"square\"\n\n# Create ST7789 LCD display class.\n\nif display_type in (\"square\", \"rect\", \"round\"):\n    disp = st7789.ST7789(\n        height=135 if display_type == \"rect\" else 240,\n        rotation=0 if display_type == \"rect\" else 90,\n        port=0,\n        cs=st7789.BG_SPI_CS_FRONT,  # BG_SPI_CS_BACK or BG_SPI_CS_FRONT\n        dc=9,\n        backlight=19,  # Breakout Garden: 18 for back slot, 19 for front slot.\n                       # NOTE: Change this to 13 for Pirate Audio boards\n        spi_speed_hz=80 * 1000 * 1000,\n        offset_left=0 if display_type == \"square\" else 40,\n        offset_top=53 if display_type == \"rect\" else 0,\n    )\n\nelif display_type == \"dhmini\":\n    disp = st7789.ST7789(\n        height=240,\n        width=320,\n        rotation=180,\n        port=0,\n        cs=1,\n        dc=9,\n        backlight=13,\n        spi_speed_hz=60 * 1000 * 1000,\n        offset_left=0,\n        offset_top=0,\n    )\n\nelse:\n    print(\"Invalid display type!\")\n\n# Initialize display.\ndisp.begin()\n\nwidth = disp.width\nheight = disp.height\n\n# Load an image.\nprint(f\"Loading gif: {image_file}...\")\nimage = Image.open(image_file)\n\nprint(\"Drawing gif, press Ctrl+C to exit!\")\n\nframe = 0\n\nwhile True:\n    try:\n        image.seek(frame)\n        disp.display(image.resize((width, height)))\n        frame += 1\n        time.sleep(0.05)\n\n    except EOFError:\n        frame = 0\n"
  },
  {
    "path": "examples/image.py",
    "content": "#!/usr/bin/env python3\nimport sys\n\nfrom PIL import Image\n\nimport st7789\n\nprint(\n    \"\"\"\nimage.py - Display an image on the LCD.\n\nIf you're using Breakout Garden, plug the 1.3\" LCD (SPI)\nbreakout into the front slot.\n\n\"\"\"\n)\n\nif len(sys.argv) < 2:\n    print(\n        f\"\"\"Usage: {sys.argv[0]} <image_file> <display_type>\n\nWhere <display_type> is one of:\n  * square - 240x240 1.3\" Square LCD\n  * round  - 240x240 1.3\" Round LCD (applies an offset)\n  * rect   - 240x135 1.14\" Rectangular LCD (applies an offset)\n  * dhmini - 320x240 2.0\" Display HAT Mini\n\"\"\"\n    )\n    sys.exit(1)\n\nimage_file = sys.argv[1]\n\ntry:\n    display_type = sys.argv[2]\nexcept IndexError:\n    display_type = \"square\"\n\n# Create ST7789 LCD display class.\n\nif display_type in (\"square\", \"rect\", \"round\"):\n    disp = st7789.ST7789(\n        height=135 if display_type == \"rect\" else 240,\n        rotation=0 if display_type == \"rect\" else 90,\n        port=0,\n        cs=st7789.BG_SPI_CS_FRONT,  # BG_SPI_CS_BACK or BG_SPI_CS_FRONT\n        dc=9,\n        backlight=19,  # Breakout Garden: 18 for back slot, 19 for front slot.\n                       # NOTE: Change this to 13 for Pirate Audio boards\n        spi_speed_hz=80 * 1000 * 1000,\n        offset_left=0 if display_type == \"square\" else 40,\n        offset_top=53 if display_type == \"rect\" else 0,\n    )\n\nelif display_type == \"dhmini\":\n    disp = st7789.ST7789(\n        height=240,\n        width=320,\n        rotation=180,\n        port=0,\n        cs=1,\n        dc=9,\n        backlight=13,\n        spi_speed_hz=60 * 1000 * 1000,\n        offset_left=0,\n        offset_top=0,\n    )\n\nelse:\n    print(\"Invalid display type!\")\n\nWIDTH = disp.width\nHEIGHT = disp.height\n\n# Initialize display.\ndisp.begin()\n\n# Load an image.\nprint(f\"Loading image: {image_file}...\")\nimage = Image.open(image_file)\n\n# Resize the image\nimage = image.resize((WIDTH, HEIGHT))\n\n# Draw the image on the display hardware.\nprint(\"Drawing image\")\n\ndisp.display(image)\n"
  },
  {
    "path": "examples/round.py",
    "content": "#!/usr/bin/env python3\nimport colorsys\nimport math\nimport sys\nimport time\n\nfrom PIL import Image, ImageDraw\n\nimport st7789\n\nprint(\n    f\"\"\"\nround.py - Shiny shiny round LCD!\n\nIf you're using Breakout Garden, plug a 1.3\" ROUND LCD\n(SPI) breakout into the front slot.\n\nUsage: {sys.argv[0]} <style>\n\nWhere style is one of:\n\n * dots - swirly swooshy dots\n * lines - 3D depth effect lines\n\n\"\"\"\n)\n\ntry:\n    style = sys.argv[1]\nexcept IndexError:\n    style = \"dots\"\n\n# Create ST7789 LCD display class.\ndisp = st7789.ST7789(\n    port=0,\n    cs=st7789.BG_SPI_CS_FRONT,  # BG_SPI_CS_BACK or BG_SPI_CS_FRONT\n    dc=9,\n    backlight=19,  # Breakout Garden: 18 for back slot, 19 for front slot.\n                   # NOTE: Change this to 13 for Pirate Audio boards\n    rotation=90,\n    spi_speed_hz=80 * 1000 * 1000,\n    offset_left=40,\n)\n\n# Initialize display.\ndisp.begin()\n\nRADIUS = disp.width // 2\n\nimg = Image.new(\"RGB\", (disp.width, disp.height), color=(0, 0, 0))\ndraw = ImageDraw.Draw(img)\n\n\nwhile True:\n    t = time.time()\n    draw.rectangle((0, 0, disp.width, disp.height), (0, 0, 0))\n    angle = t % (math.pi * 2)\n\n    prev_x = RADIUS\n    prev_y = RADIUS\n\n    steps = 100.0\n    angle_step = 1.0\n\n    if style == \"lines\":\n        steps *= 5\n        angle_step = 0.1\n\n    for step in range(int(steps)):\n        angle += angle_step\n\n        distance = RADIUS / steps * step\n        distance += step * 0.2\n\n        r, g, b = [\n            int(c * 255)\n            for c in colorsys.hsv_to_rgb((t / 10.0) + distance / 120.0, 1.0, 1.0)\n        ]\n\n        x = RADIUS + int(distance * math.cos(angle))\n        y = RADIUS + int(distance * math.sin(angle))\n\n        line = ((math.sin(t + angle) + 1) / 2.0) * 10\n        if style == \"lines\":\n            draw.line(\n                (prev_x + line, prev_y + line, x - line, y - line), fill=(r, g, b)\n            )\n        else:\n            line += 1\n            draw.ellipse(\n                (x - line, y - line, x + (line * 2), y + (line * 2)), fill=(r, g, b)\n            )\n\n        prev_x = x\n        prev_y = y\n\n    disp.display(img)\n"
  },
  {
    "path": "examples/scrolling-text.py",
    "content": "#!/usr/bin/env python3\nimport sys\nimport time\n\nfrom PIL import Image, ImageDraw, ImageFont\n\nimport st7789\n\nMESSAGE = \"Hello World! How are you today?\"\n\nprint(\n    f\"\"\"\nscrolling-test.py - Display scrolling text.\n\nIf you're using Breakout Garden, plug the 1.3\" LCD (SPI)\nbreakout into the front slot.\n\nUsage: {sys.argv[0]} \"<message>\" <display_type>\n\nWhere <display_type> is one of:\n\n  * square - 240x240 1.3\" Square LCD\n  * round  - 240x240 1.3\" Round LCD (applies an offset)\n  * rect   - 240x135 1.14\" Rectangular LCD (applies an offset)\n  * dhmini - 320x240 2.0\" Display HAT Mini\n\"\"\"\n)\n\ntry:\n    MESSAGE = sys.argv[1]\nexcept IndexError:\n    pass\n\ntry:\n    display_type = sys.argv[2]\nexcept IndexError:\n    display_type = \"square\"\n\n\n# Create ST7789 LCD display class.\n\nif display_type in (\"square\", \"rect\", \"round\"):\n    disp = st7789.ST7789(\n        height=135 if display_type == \"rect\" else 240,\n        rotation=0 if display_type == \"rect\" else 90,\n        port=0,\n        cs=st7789.BG_SPI_CS_FRONT,  # BG_SPI_CS_BACK or BG_SPI_CS_FRONT\n        dc=9,\n        backlight=19,  # Breakout Garden: 18 for back slot, 19 for front slot.\n                       # NOTE: Change this to 13 for Pirate Audio boards\n        spi_speed_hz=80 * 1000 * 1000,\n        offset_left=0 if display_type == \"square\" else 40,\n        offset_top=53 if display_type == \"rect\" else 0,\n    )\n\nelif display_type == \"dhmini\":\n    disp = st7789.ST7789(\n        height=240,\n        width=320,\n        rotation=180,\n        port=0,\n        cs=1,\n        dc=9,\n        backlight=13,\n        spi_speed_hz=60 * 1000 * 1000,\n        offset_left=0,\n        offset_top=0,\n    )\n\nelse:\n    print(\"Invalid display type!\")\n\n# Initialize display.\ndisp.begin()\n\nWIDTH = disp.width\nHEIGHT = disp.height\n\n\nimg = Image.new(\"RGB\", (WIDTH, HEIGHT), color=(0, 0, 0))\n\ndraw = ImageDraw.Draw(img)\n\nfont = ImageFont.truetype(\"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf\", 30)\n\nsize_x, size_y = draw.textsize(MESSAGE, font)\n\ntext_x = disp.width\ntext_y = (disp.height - size_y) // 2\n\nt_start = time.time()\n\nwhile True:\n    x = (time.time() - t_start) * 100\n    x %= size_x + disp.width\n    draw.rectangle((0, 0, disp.width, disp.height), (0, 0, 0))\n    draw.text((int(text_x - x), text_y), MESSAGE, font=font, fill=(255, 255, 255))\n    disp.display(img)\n"
  },
  {
    "path": "examples/shapes.py",
    "content": "#!/usr/bin/env python3\nimport sys\n\nfrom PIL import Image, ImageDraw, ImageFont\n\nimport st7789\n\nprint(\n    f\"\"\"\nshapes.py - Display test shapes on the LCD using PIL.\n\nIf you're using Breakout Garden, plug the 1.3\" LCD (SPI)\nbreakout into the front slot.\n\nUsage: {sys.argv[0]} <display_type>\n\nWhere <display_type> is one of:\n\n  * square - 240x240 1.3\" Square LCD\n  * round  - 240x240 1.3\" Round LCD (applies an offset)\n  * rect   - 240x135 1.14\" Rectangular LCD (applies an offset)\n  * dhmini - 320x240 2.0\" Display HAT Mini\n\"\"\"\n)\n\ntry:\n    display_type = sys.argv[1]\nexcept IndexError:\n    display_type = \"square\"\n\n# Create ST7789 LCD display class.\n\nif display_type in (\"square\", \"rect\", \"round\"):\n    disp = st7789.ST7789(\n        height=135 if display_type == \"rect\" else 240,\n        rotation=0 if display_type == \"rect\" else 90,\n        port=0,\n        cs=st7789.BG_SPI_CS_FRONT,  # BG_SPI_CS_BACK or BG_SPI_CS_FRONT\n        dc=9,\n        backlight=19,  # Breakout Garden: 18 for back slot, 19 for front slot.\n                       # NOTE: Change this to 13 for Pirate Audio boards\n        spi_speed_hz=80 * 1000 * 1000,\n        offset_left=0 if display_type == \"square\" else 40,\n        offset_top=53 if display_type == \"rect\" else 0,\n    )\n\nelif display_type == \"dhmini\":\n    disp = st7789.ST7789(\n        height=240,\n        width=320,\n        rotation=180,\n        port=0,\n        cs=1,\n        dc=9,\n        backlight=13,\n        spi_speed_hz=60 * 1000 * 1000,\n        offset_left=0,\n        offset_top=0,\n    )\n\nelse:\n    print(\"Invalid display type!\")\n\n# Initialize display.\ndisp.begin()\n\nWIDTH = disp.width\nHEIGHT = disp.height\n\n\n# Clear the display to a red background.\n# Can pass any tuple of red, green, blue values (from 0 to 255 each).\n# Get a PIL Draw object to start drawing on the display buffer.\nimg = Image.new(\"RGB\", (WIDTH, HEIGHT), color=(255, 0, 0))\n\ndraw = ImageDraw.Draw(img)\n\n# Draw a purple rectangle with yellow outline.\ndraw.rectangle(\n    (10, 10, WIDTH - 10, HEIGHT - 10), outline=(255, 255, 0), fill=(255, 0, 255)\n)\n\n# Draw some shapes.\n# Draw a blue ellipse with a green outline.\ndraw.ellipse((10, 10, WIDTH - 10, HEIGHT - 10), outline=(0, 255, 0), fill=(0, 0, 255))\n\n# Draw a white X.\ndraw.line((10, 10, WIDTH - 10, HEIGHT - 10), fill=(255, 255, 255))\ndraw.line((10, HEIGHT - 10, WIDTH - 10, 10), fill=(255, 255, 255))\n\n# Draw a cyan triangle with a black outline.\ndraw.polygon(\n    [(WIDTH / 2, 10), (WIDTH - 10, HEIGHT - 10), (10, HEIGHT - 10)],\n    outline=(0, 0, 0),\n    fill=(0, 255, 255),\n)\n\n# Load default font.\nfont = ImageFont.load_default()\n\n# Alternatively load a TTF font.\n# Some other nice fonts to try: http://www.dafont.com/bitmap.php\n# font = ImageFont.truetype('Minecraftia.ttf', 16)\n\n\n# Define a function to create rotated text.  Unfortunately PIL doesn't have good\n# native support for rotated fonts, but this function can be used to make a\n# text image and rotate it so it's easy to paste in the buffer.\ndef draw_rotated_text(image, text, position, angle, font, fill=(255, 255, 255)):\n    # Get rendered font width and height.\n    draw = ImageDraw.Draw(image)\n    width, height = draw.textsize(text, font=font)\n    # Create a new image with transparent background to store the text.\n    textimage = Image.new(\"RGBA\", (width, height), (0, 0, 0, 0))\n    # Render the text.\n    textdraw = ImageDraw.Draw(textimage)\n    textdraw.text((0, 0), text, font=font, fill=fill)\n    # Rotate the text image.\n    rotated = textimage.rotate(angle, expand=1)\n    # Paste the text into the image, using it as a mask for transparency.\n    image.paste(rotated, position, rotated)\n\n\n# Write two lines of white text on the buffer, rotated 90 degrees counter clockwise.\ndraw_rotated_text(img, \"Hello World!\", (0, 0), 90, font, fill=(255, 255, 255))\ndraw_rotated_text(\n    img, \"This is a line of text.\", (10, HEIGHT - 10), 0, font, fill=(255, 255, 255)\n)\n\n# Write buffer to display hardware, must be called to make things visible on the\n# display!\ndisp.display(img)\n"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/bash\nLIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F\" = \" '{print substr($2,2,length($2)-2)}')\nCONFIG_FILE=config.txt\nCONFIG_DIR=\"/boot/firmware\"\nDATESTAMP=$(date \"+%Y-%m-%d-%H-%M-%S\")\nCONFIG_BACKUP=false\nAPT_HAS_UPDATED=false\nRESOURCES_TOP_DIR=\"$HOME/Pimoroni\"\nVENV_BASH_SNIPPET=\"$RESOURCES_TOP_DIR/auto_venv.sh\"\nVENV_DIR=\"$HOME/.virtualenvs/pimoroni\"\nUSAGE=\"./install.sh (--unstable)\"\nPOSITIONAL_ARGS=()\nFORCE=false\nUNSTABLE=false\nPYTHON=\"python\"\nCMD_ERRORS=false\n\n\nuser_check() {\n\tif [ \"$(id -u)\" -eq 0 ]; then\n\t\tfatal \"Script should not be run as root. Try './install.sh'\\n\"\n\tfi\n}\n\nconfirm() {\n\tif $FORCE; then\n\t\ttrue\n\telse\n\t\tread -r -p \"$1 [y/N] \" response < /dev/tty\n\t\tif [[ $response =~ ^(yes|y|Y)$ ]]; then\n\t\t\ttrue\n\t\telse\n\t\t\tfalse\n\t\tfi\n\tfi\n}\n\nsuccess() {\n\techo -e \"$(tput setaf 2)$1$(tput sgr0)\"\n}\n\ninform() {\n\techo -e \"$(tput setaf 6)$1$(tput sgr0)\"\n}\n\nwarning() {\n\techo -e \"$(tput setaf 1)⚠ WARNING:$(tput sgr0) $1\"\n}\n\nfatal() {\n\techo -e \"$(tput setaf 1)⚠ FATAL:$(tput sgr0) $1\"\n\texit 1\n}\n\nfind_config() {\n\tif [ ! -f \"$CONFIG_DIR/$CONFIG_FILE\" ]; then\n\t\tCONFIG_DIR=\"/boot\"\n\t\tif [ ! -f \"$CONFIG_DIR/$CONFIG_FILE\" ]; then\n\t\t\tfatal \"Could not find $CONFIG_FILE!\"\n\t\tfi\n\tfi\n\tinform \"Using $CONFIG_FILE in $CONFIG_DIR\"\n}\n\nvenv_bash_snippet() {\n\tinform \"Checking for $VENV_BASH_SNIPPET\\n\"\n\tif [ ! -f \"$VENV_BASH_SNIPPET\" ]; then\n\t\tinform \"Creating $VENV_BASH_SNIPPET\\n\"\n\t\tmkdir -p \"$RESOURCES_TOP_DIR\"\n\t\tcat << EOF > \"$VENV_BASH_SNIPPET\"\n# Add \"source $VENV_BASH_SNIPPET\" to your ~/.bashrc to activate\n# the Pimoroni virtual environment automagically!\nVENV_DIR=\"$VENV_DIR\"\nif [ ! -f \\$VENV_DIR/bin/activate ]; then\n  printf \"Creating user Python environment in \\$VENV_DIR, please wait...\\n\"\n  mkdir -p \\$VENV_DIR\n  python3 -m venv --system-site-packages \\$VENV_DIR\nfi\nprintf \" ↓ ↓ ↓ ↓   Hello, we've activated a Python venv for you. To exit, type \\\"deactivate\\\".\\n\"\nsource \\$VENV_DIR/bin/activate\nEOF\n\tfi\n}\n\nvenv_check() {\n\tPYTHON_BIN=$(which \"$PYTHON\")\n\tif [[ $VIRTUAL_ENV == \"\" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then\n\t\tprintf \"This script should be run in a virtual Python environment.\\n\"\n\t\tif confirm \"Would you like us to create and/or use a default one?\"; then\n\t\t\tprintf \"\\n\"\n\t\t\tif [ ! -f \"$VENV_DIR/bin/activate\" ]; then\n\t\t\t\tinform \"Creating a new virtual Python environment in $VENV_DIR, please wait...\\n\"\n\t\t\t\tmkdir -p \"$VENV_DIR\"\n\t\t\t\t/usr/bin/python3 -m venv \"$VENV_DIR\" --system-site-packages\n\t\t\t\tvenv_bash_snippet\n\t\t\t\t# shellcheck disable=SC1091\n\t\t\t\tsource \"$VENV_DIR/bin/activate\"\n\t\t\telse\n\t\t\t\tinform \"Activating existing virtual Python environment in $VENV_DIR\\n\"\n\t\t\t\tprintf \"source \\\"%s/bin/activate\\\"\\n\" \"$VENV_DIR\"\n\t\t\t\t# shellcheck disable=SC1091\n\t\t\t\tsource \"$VENV_DIR/bin/activate\"\n\t\t\tfi\n\t\telse\n\t\t\tprintf \"\\n\"\n\t\t\tfatal \"Please create and/or activate a virtual Python environment and try again!\\n\"\n\t\tfi\n\tfi\n\tprintf \"\\n\"\n}\n\ncheck_for_error() {\n\tif [ $? -ne 0 ]; then\n\t\tCMD_ERRORS=true\n\t\twarning \"^^^ 😬 previous command did not exit cleanly!\"\n\tfi\n}\n\nfunction do_config_backup {\n\tif [ ! $CONFIG_BACKUP == true ]; then\n\t\tCONFIG_BACKUP=true\n\t\tFILENAME=\"config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt\"\n\t\tinform \"Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\\n\"\n\t\tsudo cp \"$CONFIG_DIR/$CONFIG_FILE\" \"$CONFIG_DIR/$FILENAME\"\n\t\tmkdir -p \"$RESOURCES_TOP_DIR/config-backups/\"\n\t\tcp $CONFIG_DIR/$CONFIG_FILE \"$RESOURCES_TOP_DIR/config-backups/$FILENAME\"\n\t\tif [ -f \"$UNINSTALLER\" ]; then\n\t\t\techo \"cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE\" >> \"$UNINSTALLER\"\n\t\tfi\n\tfi\n}\n\nfunction apt_pkg_install {\n\tPACKAGES_NEEDED=()\n\tPACKAGES_IN=(\"$@\")\n\t# Check the list of packages and only run update/install if we need to\n\tfor ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do\n\t\tPACKAGE=\"${PACKAGES_IN[$i]}\"\n\t\tif [ \"$PACKAGE\" == \"\" ]; then continue; fi\n\t\tprintf \"Checking for %s\\n\" \"$PACKAGE\"\n\t\tdpkg -L \"$PACKAGE\" > /dev/null 2>&1\n\t\tif [ \"$?\" == \"1\" ]; then\n\t\t\tPACKAGES_NEEDED+=(\"$PACKAGE\")\n\t\tfi\n\tdone\n\tPACKAGES=\"${PACKAGES_NEEDED[*]}\"\n\tif ! [ \"$PACKAGES\" == \"\" ]; then\n\t\tprintf \"\\n\"\n\t\tinform \"Installing missing packages: $PACKAGES\"\n\t\tif [ ! $APT_HAS_UPDATED ]; then\n\t\t\tsudo apt update\n\t\t\tAPT_HAS_UPDATED=true\n\t\tfi\n\t\t# shellcheck disable=SC2086\n\t\tsudo apt install -y $PACKAGES\n\t\tcheck_for_error\n\t\tif [ -f \"$UNINSTALLER\" ]; then\n\t\t\techo \"apt uninstall -y $PACKAGES\" >> \"$UNINSTALLER\"\n\t\tfi\n\tfi\n}\n\nfunction pip_pkg_install {\n\t# A null Keyring prevents pip stalling in the background\n\tPYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade \"$@\"\n\tcheck_for_error\n}\n\nwhile [[ $# -gt 0 ]]; do\n\tK=\"$1\"\n\tcase $K in\n\t-u|--unstable)\n\t\tUNSTABLE=true\n\t\tshift\n\t\t;;\n\t-f|--force)\n\t\tFORCE=true\n\t\tshift\n\t\t;;\n\t-p|--python)\n\t\tPYTHON=$2\n\t\tshift\n\t\tshift\n\t\t;;\n\t*)\n\t\tif [[ $1 == -* ]]; then\n\t\t\tprintf \"Unrecognised option: %s\\n\" \"$1\";\n\t\t\tprintf \"Usage: %s\\n\" \"$USAGE\";\n\t\t\texit 1\n\t\tfi\n\t\tPOSITIONAL_ARGS+=(\"$1\")\n\t\tshift\n\tesac\ndone\n\nprintf \"Installing %s...\\n\\n\" \"$LIBRARY_NAME\"\n\nuser_check\nvenv_check\n\nif [ ! -f \"$(which \"$PYTHON\")\" ]; then\n\tfatal \"Python path %s not found!\\n\" \"$PYTHON\"\nfi\n\nPYTHON_VER=$($PYTHON --version)\n\ninform \"Checking Dependencies. Please wait...\"\n\n# Install toml and try to read pyproject.toml into bash variables\n\npip_pkg_install toml\n\nCONFIG_VARS=$(\n\t$PYTHON - <<EOF\nimport toml\nconfig = toml.load(\"pyproject.toml\")\ngithub_url = config['project']['urls']['GitHub']\np = dict(config['tool']['pimoroni'])\n# Convert list config entries into bash arrays\nfor k, v in p.items():\n    v = \"'\\n\\t'\".join(v)\n    p[k] = f\"('{v}')\"\nprint(f'GITHUB_URL=\"{github_url}\"')\nprint(\"\"\"\nAPT_PACKAGES={apt_packages}\nSETUP_CMDS={commands}\nCONFIG_TXT={configtxt}\n\"\"\".format(**p))\nEOF\n)\n\n# shellcheck disable=SC2181 # Inlining the above command would be messy\nif [ $? -ne 0 ]; then\n\t# This is bad, this should not happen in production!\n\tfatal \"Error parsing configuration...\\n\"\nfi\n\neval \"$CONFIG_VARS\"\n\nRESOURCES_DIR=$RESOURCES_TOP_DIR/$LIBRARY_NAME\nUNINSTALLER=$RESOURCES_DIR/uninstall.sh\n\nRES_DIR_OWNER=$(stat -c \"%U\" \"$RESOURCES_TOP_DIR\")\n\n# Previous install.sh scripts were run as root with sudo, which caused\n# the ~/Pimoroni dir to be created with root ownership. Try and fix it.\nif [[ \"$RES_DIR_OWNER\" == \"root\" ]]; then\n\twarning \"\\n\\nFixing $RESOURCES_TOP_DIR permissions!\\n\\n\"\n\tsudo chown -R \"$USER:$USER\" \"$RESOURCES_TOP_DIR\"\nfi\n\nmkdir -p \"$RESOURCES_DIR\"\n\n# Create a stub uninstaller file, we'll try to add the inverse of every\n# install command run to here, though it's not complete.\ncat << EOF > \"$UNINSTALLER\"\nprintf \"It's recommended you run these steps manually.\\n\"\nprintf \"If you want to run the full script, open it in\\n\"\nprintf \"an editor and remove 'exit 1' from below.\\n\"\nexit 1\nsource $VIRTUAL_ENV/bin/activate\nEOF\n\nprintf \"\\n\"\n\ninform \"Installing for $PYTHON_VER...\\n\"\n\n# Install apt packages from pyproject.toml / tool.pimoroni.apt_packages\napt_pkg_install \"${APT_PACKAGES[@]}\"\n\nprintf \"\\n\"\n\nif $UNSTABLE; then\n\twarning \"Installing unstable library from source.\\n\"\n\tpip_pkg_install .\nelse\n\tinform \"Installing stable library from pypi.\\n\"\n\tpip_pkg_install \"$LIBRARY_NAME\"\nfi\n\n# shellcheck disable=SC2181 # One of two commands run, depending on --unstable flag\nif [ $? -eq 0 ]; then\n\tsuccess \"Done!\\n\"\n\techo \"$PYTHON -m pip uninstall $LIBRARY_NAME\" >> \"$UNINSTALLER\"\nfi\n\nfind_config\n\nprintf \"\\n\"\n\n# Run the setup commands from pyproject.toml / tool.pimoroni.commands\n\ninform \"Running setup commands...\\n\"\nfor ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do\n\tCMD=\"${SETUP_CMDS[$i]}\"\n\t# Attempt to catch anything that touches config.txt and trigger a backup\n\tif [[ \"$CMD\" == *\"raspi-config\"* ]] || [[ \"$CMD\" == *\"$CONFIG_DIR/$CONFIG_FILE\"* ]] || [[ \"$CMD\" == *\"\\$CONFIG_DIR/\\$CONFIG_FILE\"* ]]; then\n\t\tdo_config_backup\n\tfi\n\tif [[ ! \"$CMD\" == printf* ]]; then\n\t\tprintf \"Running: \\\"%s\\\"\\n\" \"$CMD\"\n\tfi\n\teval \"$CMD\"\n\tcheck_for_error\ndone\n\nprintf \"\\n\"\n\n# Add the config.txt entries from pyproject.toml / tool.pimoroni.configtxt\n\nfor ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do\n\tCONFIG_LINE=\"${CONFIG_TXT[$i]}\"\n\tif ! [ \"$CONFIG_LINE\" == \"\" ]; then\n\t\tdo_config_backup\n\t\tinform \"Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE\"\n\t\tsudo sed -i \"s/^#$CONFIG_LINE/$CONFIG_LINE/\" $CONFIG_DIR/$CONFIG_FILE\n\t\tif ! grep -q \"^$CONFIG_LINE\" $CONFIG_DIR/$CONFIG_FILE; then\n\t\t\tprintf \"%s \\n\" \"$CONFIG_LINE\" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE\n\t\tfi\n\tfi\ndone\n\nprintf \"\\n\"\n\n# Just a straight copy of the examples/ dir into ~/Pimoroni/board/examples\n\nif [ -d \"examples\" ]; then\n\tif confirm \"Would you like to copy examples to $RESOURCES_DIR?\"; then\n\t\tinform \"Copying examples to $RESOURCES_DIR\"\n\t\tcp -r examples/ \"$RESOURCES_DIR\"\n\t\techo \"rm -r $RESOURCES_DIR\" >> \"$UNINSTALLER\"\n\t\tsuccess \"Done!\"\n\tfi\nfi\n\nprintf \"\\n\"\n\n# Use pdoc to generate basic documentation from the installed module\n\nif confirm \"Would you like to generate documentation?\"; then\n\tinform \"Installing pdoc. Please wait...\"\n\tpip_pkg_install pdoc\n\tinform \"Generating documentation.\\n\"\n\tif $PYTHON -m pdoc \"$LIBRARY_NAME\" -o \"$RESOURCES_DIR/docs\" > /dev/null; then\n\t\tinform \"Documentation saved to $RESOURCES_DIR/docs\"\n\t\tsuccess \"Done!\"\n\telse\n\t\twarning \"Error: Failed to generate documentation.\"\n\tfi\nfi\n\nprintf \"\\n\"\n\nif [ \"$CMD_ERRORS\" = true ]; then\n\twarning \"One or more setup commands appear to have failed.\"\n\tprintf \"This might prevent things from working properly.\\n\"\n\tprintf \"Make sure your OS is up to date and try re-running this installer.\\n\"\n\tprintf \"If things still don't work, report this or find help at %s.\\n\\n\" \"$GITHUB_URL\"\nelse\n\tsuccess \"\\nAll done!\"\nfi\n\nprintf \"If this is your first time installing you should reboot for hardware changes to take effect.\\n\"\nprintf \"Find uninstall steps in %s\\n\\n\" \"$UNINSTALLER\"\n\nif [ \"$CMD_ERRORS\" = true ]; then\n\texit 1\nelse\n\texit 0\nfi\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\", \"hatch-fancy-pypi-readme\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"st7789\"\ndynamic = [\"version\", \"readme\"]\ndescription = \"Driver for ST7789-based TFT LCD displays.\"\nlicense = {file = \"LICENSE\"}\nrequires-python = \">= 3.7\"\nauthors = [\n    { name = \"Philip Howard\", email = \"phil@pimoroni.com\" },\n]\nmaintainers = [\n    { name = \"Philip Howard\", email = \"phil@pimoroni.com\" },\n]\nkeywords = [\n    \"Pi\",\n    \"Raspberry\",\n]\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Operating System :: POSIX :: Linux\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.7\",\n    \"Programming Language :: Python :: 3.8\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Topic :: Software Development\",\n    \"Topic :: Software Development :: Libraries\",\n    \"Topic :: System :: Hardware\",\n]\ndependencies = [\n    \"gpiod\",\n    \"gpiodevice >= 0.0.4\",\n    \"numpy >= 1.26.4\",\n    \"spidev >= 3.6\"\n]\n\n[project.urls]\nGitHub = \"https://www.github.com/pimoroni/st7789-python\"\nHomepage = \"https://www.pimoroni.com\"\n\n[tool.hatch.version]\npath = \"st7789/__init__.py\"\n\n[tool.hatch.build]\ninclude = [\n    \"st7789\",\n    \"ST7789.py\",\n    \"README.md\",\n    \"CHANGELOG.md\",\n    \"LICENSE\"\n]\n\n[tool.hatch.build.targets.sdist]\ninclude = [\n    \"*\"\n]\nexclude = [\n    \".*\",\n    \"dist\"\n]\n\n[tool.hatch.metadata.hooks.fancy-pypi-readme]\ncontent-type = \"text/markdown\"\nfragments = [\n  { path = \"README.md\" },\n  { text = \"\\n\" },\n  { path = \"CHANGELOG.md\" }\n]\n\n[tool.ruff]\nexclude = [\n    '.tox',\n    '.egg',\n    '.git',\n    '__pycache__',\n    'build',\n    'dist'\n]\nline-length = 200\n\n[tool.codespell]\nskip = \"\"\"\n./.tox,\\\n./.egg,\\\n./.git,\\\n./__pycache__,\\\n./build,\\\n./dist.\\\n\"\"\"\n\n[tool.isort]\nline_length = 200\n\n[tool.check-manifest]\nignore = [\n    '.stickler.yml',\n    'boilerplate.md',\n    'check.sh',\n    'install.sh',\n    'uninstall.sh',\n    'Makefile',\n    'tox.ini',\n    'tests/*',\n    'examples/*',\n    '.coveragerc',\n    'requirements-dev.txt'\n]\n\n[tool.pimoroni]\napt_packages = []\nconfigtxt = []\ncommands = []\n"
  },
  {
    "path": "requirements-dev.txt",
    "content": "check-manifest\nruff\ncodespell\nisort\ntwine\nhatch\nhatch-fancy-pypi-readme\ntox\npdoc\n"
  },
  {
    "path": "st7789/__init__.py",
    "content": "# Copyright (c) 2014 Adafruit Industries\n# Author: Tony DiCola\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\nimport numbers\nimport time\n\nimport gpiod\nimport gpiodevice\nimport numpy\nimport spidev\nfrom gpiod.line import Direction, Value\n\n__version__ = \"1.0.1\"\n\nOUTL = gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE)\n\nBG_SPI_CS_BACK = 0\nBG_SPI_CS_FRONT = 1\n\nSPI_CLOCK_HZ = 16000000\n\nST7789_NOP = 0x00\nST7789_SWRESET = 0x01\nST7789_RDDID = 0x04\nST7789_RDDST = 0x09\n\nST7789_SLPIN = 0x10\nST7789_SLPOUT = 0x11\nST7789_PTLON = 0x12\nST7789_NORON = 0x13\n\nST7789_INVOFF = 0x20\nST7789_INVON = 0x21\nST7789_DISPOFF = 0x28\nST7789_DISPON = 0x29\n\nST7789_CASET = 0x2A\nST7789_RASET = 0x2B\nST7789_RAMWR = 0x2C\nST7789_RAMRD = 0x2E\n\nST7789_PTLAR = 0x30\nST7789_MADCTL = 0x36\nST7789_COLMOD = 0x3A\n\nST7789_FRMCTR1 = 0xB1\nST7789_FRMCTR2 = 0xB2\nST7789_FRMCTR3 = 0xB3\nST7789_INVCTR = 0xB4\nST7789_DISSET5 = 0xB6\n\nST7789_GCTRL = 0xB7\nST7789_GTADJ = 0xB8\nST7789_VCOMS = 0xBB\n\nST7789_LCMCTRL = 0xC0\nST7789_IDSET = 0xC1\nST7789_VDVVRHEN = 0xC2\nST7789_VRHS = 0xC3\nST7789_VDVS = 0xC4\nST7789_VMCTR1 = 0xC5\nST7789_FRCTRL2 = 0xC6\nST7789_CABCCTRL = 0xC7\n\nST7789_RDID1 = 0xDA\nST7789_RDID2 = 0xDB\nST7789_RDID3 = 0xDC\nST7789_RDID4 = 0xDD\n\nST7789_GMCTRP1 = 0xE0\nST7789_GMCTRN1 = 0xE1\n\nST7789_PWCTR6 = 0xFC\n\n\nclass ST7789(object):\n    \"\"\"Representation of an ST7789 TFT LCD.\"\"\"\n\n    def __init__(\n        self,\n        port,\n        cs,\n        dc,\n        backlight=None,\n        rst=None,\n        width=240,\n        height=240,\n        rotation=90,\n        invert=True,\n        spi_speed_hz=4000000,\n        offset_left=0,\n        offset_top=0,\n    ):\n        \"\"\"Create an instance of the display using SPI communication.\n\n        Must provide the GPIO pin number for the D/C pin and the SPI driver.\n\n        Can optionally provide the GPIO pin number for the reset pin as the rst parameter.\n\n        :param port: SPI port number\n        :param cs: SPI chip-select number (0 or 1 for BCM\n        :param backlight: Pin for controlling backlight\n        :param rst: Reset pin for ST7789\n        :param width: Width of display connected to ST7789\n        :param height: Height of display connected to ST7789\n        :param rotation: Rotation of display connected to ST7789\n        :param invert: Invert display\n        :param spi_speed_hz: SPI speed (in Hz)\n\n        \"\"\"\n        if rotation not in [0, 90, 180, 270]:\n            raise ValueError(f\"Invalid rotation {rotation}\")\n\n        if width != height and rotation in [90, 270]:\n            raise ValueError(\n                f\"Invalid rotation {rotation} for {width}x{height} resolution\"\n            )\n\n        gpiodevice.friendly_errors = True\n\n        self._spi = spidev.SpiDev(port, cs)\n        self._spi.mode = 0\n        self._spi.lsbfirst = False\n        self._spi.max_speed_hz = spi_speed_hz\n\n        self._dc = dc\n        self._rst = rst\n        self._width = width\n        self._height = height\n        self._rotation = rotation\n        self._invert = invert\n\n        self._offset_left = offset_left\n        self._offset_top = offset_top\n\n        # Set up DC pin if a lines/offset tuple is not supplied\n        if isinstance(dc, int):\n            self._dc = ST7789.get_dc_pin(dc)\n\n        # Setup backlight as output (if provided).\n        if backlight is not None:\n            if isinstance(backlight, int):\n                self._bl = ST7789.get_bl_pin(backlight)\n            else:\n                self._bl = backlight\n            self.set_pin(self._bl, False)\n            time.sleep(0.1)\n            self.set_pin(self._bl, True)\n\n        # Set up and call reset (if provided)\n        if rst is not None:\n            # Set up RESET pin if a lines/offset tuple is not supplied\n            if isinstance(rst, int):\n                self._rst = ST7789.get_rst_pin(rst)\n            self.reset()\n\n        self._init()\n\n    def set_pin(self, pin, state):\n        lines, offset = pin\n        lines.set_value(offset, Value.ACTIVE if state else Value.INACTIVE)\n\n    def send(self, data, is_data=True, chunk_size=4096):\n        \"\"\"Write a byte or array of bytes to the display. Is_data parameter\n        controls if byte should be interpreted as display data (True) or command\n        data (False).  Chunk_size is an optional size of bytes to write in a\n        single SPI transaction, with a default of 4096.\n        \"\"\"\n        # Set DC low for command, high for data.\n        self.set_pin(self._dc, is_data)\n        # Convert scalar argument to list so either can be passed as parameter.\n        if isinstance(data, numbers.Number):\n            data = [data & 0xFF]\n        # Write data a chunk at a time.\n        for start in range(0, len(data), chunk_size):\n            end = min(start + chunk_size, len(data))\n            self._spi.xfer(data[start:end])\n\n    def set_backlight(self, value):\n        \"\"\"Set the backlight on/off.\"\"\"\n        if self._bl is not None:\n            self.set_pin(self._bl, value)\n\n    @property\n    def width(self):\n        return (\n            self._width\n            if self._rotation == 0 or self._rotation == 180\n            else self._height\n        )\n\n    @property\n    def height(self):\n        return (\n            self._height\n            if self._rotation == 0 or self._rotation == 180\n            else self._width\n        )\n\n    def command(self, data):\n        \"\"\"Write a byte or array of bytes to the display as command data.\"\"\"\n        self.send(data, False)\n\n    def data(self, data):\n        \"\"\"Write a byte or array of bytes to the display as display data.\"\"\"\n        self.send(data, True)\n\n    def reset(self):\n        \"\"\"Reset the display, if reset pin is connected.\"\"\"\n        if self._rst is not None:\n            self.set_pin(self._rst, True)\n            time.sleep(0.500)\n            self.set_pin(self._rst, False)\n            time.sleep(0.500)\n            self.set_pin(self._rst, True)\n            time.sleep(0.500)\n\n    def _init(self):\n        # Initialize the display.\n\n        self.command(ST7789_SWRESET)  # Software reset\n        time.sleep(0.150)  # delay 150 ms\n\n        self.command(ST7789_MADCTL)\n        self.data(0x70)\n\n        self.command(ST7789_FRMCTR2)  # Frame rate ctrl - idle mode\n        self.data(0x0C)\n        self.data(0x0C)\n        self.data(0x00)\n        self.data(0x33)\n        self.data(0x33)\n\n        self.command(ST7789_COLMOD)\n        self.data(0x05)\n\n        self.command(ST7789_GCTRL)\n        self.data(0x14)\n\n        self.command(ST7789_VCOMS)\n        self.data(0x37)\n\n        self.command(ST7789_LCMCTRL)  # Power control\n        self.data(0x2C)\n\n        self.command(ST7789_VDVVRHEN)  # Power control\n        self.data(0x01)\n\n        self.command(ST7789_VRHS)  # Power control\n        self.data(0x12)\n\n        self.command(ST7789_VDVS)  # Power control\n        self.data(0x20)\n\n        self.command(0xD0)\n        self.data(0xA4)\n        self.data(0xA1)\n\n        self.command(ST7789_FRCTRL2)\n        self.data(0x0F)\n\n        self.command(ST7789_GMCTRP1)  # Set Gamma\n        self.data(0xD0)\n        self.data(0x04)\n        self.data(0x0D)\n        self.data(0x11)\n        self.data(0x13)\n        self.data(0x2B)\n        self.data(0x3F)\n        self.data(0x54)\n        self.data(0x4C)\n        self.data(0x18)\n        self.data(0x0D)\n        self.data(0x0B)\n        self.data(0x1F)\n        self.data(0x23)\n\n        self.command(ST7789_GMCTRN1)  # Set Gamma\n        self.data(0xD0)\n        self.data(0x04)\n        self.data(0x0C)\n        self.data(0x11)\n        self.data(0x13)\n        self.data(0x2C)\n        self.data(0x3F)\n        self.data(0x44)\n        self.data(0x51)\n        self.data(0x2F)\n        self.data(0x1F)\n        self.data(0x1F)\n        self.data(0x20)\n        self.data(0x23)\n\n        if self._invert:\n            self.command(ST7789_INVON)  # Invert display\n        else:\n            self.command(ST7789_INVOFF)  # Don't invert display\n\n        self.command(ST7789_SLPOUT)\n\n        self.command(ST7789_DISPON)  # Display on\n        time.sleep(0.100)  # 100 ms\n\n    def begin(self):\n        \"\"\"Set up the display\n\n        Deprecated. Included in __init__.\n\n        \"\"\"\n        pass\n\n    def set_window(self, x0=0, y0=0, x1=None, y1=None):\n        \"\"\"Set the pixel address window for proceeding drawing commands. x0 and\n        x1 should define the minimum and maximum x pixel bounds.  y0 and y1\n        should define the minimum and maximum y pixel bound.  If no parameters\n        are specified the default will be to update the entire display from 0,0\n        to width-1,height-1.\n        \"\"\"\n        if x1 is None:\n            x1 = self._width - 1\n\n        if y1 is None:\n            y1 = self._height - 1\n\n        y0 += self._offset_top\n        y1 += self._offset_top\n\n        x0 += self._offset_left\n        x1 += self._offset_left\n\n        self.command(ST7789_CASET)  # Column addr set\n        self.data(x0 >> 8)\n        self.data(x0 & 0xFF)  # XSTART\n        self.data(x1 >> 8)\n        self.data(x1 & 0xFF)  # XEND\n        self.command(ST7789_RASET)  # Row addr set\n        self.data(y0 >> 8)\n        self.data(y0 & 0xFF)  # YSTART\n        self.data(y1 >> 8)\n        self.data(y1 & 0xFF)  # YEND\n        self.command(ST7789_RAMWR)  # write to RAM\n\n    def display(self, image):\n        \"\"\"Write the provided image to the hardware.\n\n        :param image: Should be RGB format and the same dimensions as the display hardware.\n\n        \"\"\"\n        # Set address bounds to entire display.\n        self.set_window()\n\n        # Convert image to 16bit RGB565 format and\n        # flatten into bytes.\n        pixelbytes = self.image_to_data(image, self._rotation)\n\n        # Write data to hardware.\n        for i in range(0, len(pixelbytes), 4096):\n            self.data(pixelbytes[i : i + 4096])\n\n    def image_to_data(self, image, rotation=0):\n        if not isinstance(image, numpy.ndarray):\n            image = numpy.array(image.convert(\"RGB\"))\n\n        # Rotate the image\n        pb = numpy.rot90(image, rotation // 90).astype(\"uint16\")\n\n        # Mask and shift the 888 RGB into 565 RGB\n        red = (pb[..., [0]] & 0xF8) << 8\n        green = (pb[..., [1]] & 0xFC) << 3\n        blue = (pb[..., [2]] & 0xF8) >> 3\n\n        # Stick 'em together\n        result = red | green | blue\n\n        # Output the raw bytes\n        return result.byteswap().tobytes()\n\n    @staticmethod\n    def get_bl_pin(pin):\n        return gpiodevice.get_pin(pin, \"st7789-bl\", OUTL)\n\n    @staticmethod\n    def get_rst_pin(pin):\n        return gpiodevice.get_pin(pin, \"st7789-rst\", OUTL)\n\n    @staticmethod\n    def get_dc_pin(pin):\n        return gpiodevice.get_pin(pin, \"st7789-dc\", OUTL)\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import sys\n\nimport mock\nimport pytest\n\n\n@pytest.fixture(scope=\"function\", autouse=False)\ndef st7789():\n    import st7789\n\n    yield st7789\n    del sys.modules[\"st7789\"]\n\n\n@pytest.fixture(scope=\"function\", autouse=False)\ndef gpiod():\n    \"\"\"Mock gpiod module.\"\"\"\n    sys.modules[\"gpiod\"] = mock.MagicMock()\n    sys.modules[\"gpiod.line\"] = mock.MagicMock()\n    yield sys.modules[\"gpiod\"]\n    del sys.modules[\"gpiod\"]\n\n\n@pytest.fixture(scope=\"function\", autouse=False)\ndef gpiodevice():\n    \"\"\"Mock gpiodevice module.\"\"\"\n    sys.modules[\"gpiodevice\"] = mock.MagicMock()\n    sys.modules[\"gpiodevice\"].get_pin.return_value = (mock.Mock(), 0)\n    yield sys.modules[\"gpiodevice\"]\n    del sys.modules[\"gpiodevice\"]\n\n\n@pytest.fixture(scope=\"function\", autouse=False)\ndef spidev():\n    \"\"\"Mock spidev module.\"\"\"\n    spidev = mock.MagicMock()\n    sys.modules[\"spidev\"] = spidev\n    yield spidev\n    del sys.modules[\"spidev\"]\n\n\n@pytest.fixture(scope=\"function\", autouse=False)\ndef numpy():\n    \"\"\"Mock numpy module.\"\"\"\n    numpy = mock.MagicMock()\n    sys.modules[\"numpy\"] = numpy\n    yield numpy\n    del sys.modules[\"numpy\"]\n"
  },
  {
    "path": "tests/test_dimensions.py",
    "content": "def test_240_240(gpiodevice, gpiod, spidev, numpy, st7789):\n    display = st7789.ST7789(port=0, cs=0, dc=24, width=240, height=240, rotation=0)\n    assert display.width == 240\n    assert display.height == 240\n\n\ndef test_240_135(gpiodevice, gpiod, spidev, numpy, st7789):\n    display = st7789.ST7789(port=0, cs=0, dc=24, width=240, height=135, rotation=0)\n    assert display.width == 240\n    assert display.height == 135\n\n\ndef test_320_240(gpiodevice, gpiod, spidev, numpy, st7789):\n    display = st7789.ST7789(port=0, cs=0, dc=24, width=320, height=240, rotation=0)\n    assert display.width == 320\n    assert display.height == 240\n"
  },
  {
    "path": "tests/test_display.py",
    "content": "def test_display_pil_image(gpiodevice, gpiod, spidev, st7789):\n    from PIL import Image\n\n    display = st7789.ST7789(port=0, cs=0, dc=24)\n\n    image = Image.new(\"RGB\", (display.width, display.width))\n    display.display(image)\n\n\ndef test_display_numpy_array(gpiodevice, gpiod, spidev, st7789):\n    import numpy\n\n    display = st7789.ST7789(port=0, cs=0, dc=24)\n\n    image = numpy.empty((display.width, display.height, 3))\n    display.display(image)\n"
  },
  {
    "path": "tests/test_setup.py",
    "content": "import pytest\n\n\ndef test_setup(gpiodevice, gpiod, spidev, numpy, st7789):\n    display = st7789.ST7789(port=0, cs=0, dc=24)\n    del display\n\n\ndef test_backlight(gpiodevice, gpiod, spidev, numpy, st7789):\n    display = st7789.ST7789(port=0, cs=0, dc=24, backlight=19)\n    display.set_backlight(1)\n\n\ndef test_reset(gpiodevice, gpiod, spidev, numpy, st7789):\n    display = st7789.ST7789(port=0, cs=0, dc=24, rst=19)\n    display.reset()\n\n\ndef test_unsupported_rotation_320_x_240_90(gpiodevice, gpiod, spidev, numpy, st7789):\n    with pytest.raises(ValueError):\n        display = st7789.ST7789(port=0, cs=0, dc=24, width=320, height=240, rotation=90)\n        del display\n\n\ndef test_unsupported_rotation_320_x_240_270(gpiodevice, gpiod, spidev, numpy, st7789):\n    with pytest.raises(ValueError):\n        display = st7789.ST7789(\n            port=0, cs=0, dc=24, width=320, height=240, rotation=270\n        )\n        del display\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py,qa\nskip_missing_interpreters = True\nisolated_build = true\nminversion = 4.0.0\n\n[testenv]\ncommands =\n\tcoverage run -m pytest -v -r wsx\n\tcoverage report\ndeps =\n\tmock\n\tpytest>=3.1\n\tpytest-cov\n\tbuild\n\tnumpy\n\tpillow\n\n[testenv:qa]\ncommands =\n\tcheck-manifest\n\tpython -m build --no-isolation\n\tpython -m twine check dist/*\n\tisort --check .\n\truff check .\n\tcodespell .\ndeps =\n\tcheck-manifest\n\truff\n\tcodespell\n\tisort\n\ttwine\n\tbuild\n\thatch\n\thatch-fancy-pypi-readme\n\n"
  },
  {
    "path": "uninstall.sh",
    "content": "#!/bin/bash\n\nFORCE=false\nLIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F\" = \" '{print substr($2,2,length($2)-2)}')\nRESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME\nPYTHON=\"python\"\n\n\nvenv_check() {\n\tPYTHON_BIN=$(which $PYTHON)\n\tif [[ $VIRTUAL_ENV == \"\" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then\n\t\tprintf \"This script should be run in a virtual Python environment.\\n\"\n\t\texit 1\n\tfi\n}\n\nuser_check() {\n\tif [ \"$(id -u)\" -eq 0 ]; then\n\t\tprintf \"Script should not be run as root. Try './uninstall.sh'\\n\"\n\t\texit 1\n\tfi\n}\n\nconfirm() {\n\tif $FORCE; then\n\t\ttrue\n\telse\n\t\tread -r -p \"$1 [y/N] \" response < /dev/tty\n\t\tif [[ $response =~ ^(yes|y|Y)$ ]]; then\n\t\t\ttrue\n\t\telse\n\t\t\tfalse\n\t\tfi\n\tfi\n}\n\nprompt() {\n\tread -r -p \"$1 [y/N] \" response < /dev/tty\n\tif [[ $response =~ ^(yes|y|Y)$ ]]; then\n\t\ttrue\n\telse\n\t\tfalse\n\tfi\n}\n\nsuccess() {\n\techo -e \"$(tput setaf 2)$1$(tput sgr0)\"\n}\n\ninform() {\n\techo -e \"$(tput setaf 6)$1$(tput sgr0)\"\n}\n\nwarning() {\n\techo -e \"$(tput setaf 1)$1$(tput sgr0)\"\n}\n\nprintf \"%s Python Library: Uninstaller\\n\\n\" \"$LIBRARY_NAME\"\n\nuser_check\nvenv_check\n\nprintf \"Uninstalling for Python 3...\\n\"\n$PYTHON -m pip uninstall \"$LIBRARY_NAME\"\n\nif [ -d \"$RESOURCES_DIR\" ]; then\n\tif confirm \"Would you like to delete $RESOURCES_DIR?\"; then\n\t\trm -r \"$RESOURCES_DIR\"\n\tfi\nfi\n\nprintf \"Done!\\n\"\n"
  }
]