From 160bf98cff0406191448e4c2027f88e2cde8c962 Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Wed, 9 Jul 2025 14:52:51 -0700 Subject: [PATCH 1/6] Added PiTFT V2 and stripping out old stuff --- adafruit-pitft-old.py | 1007 +++++++++++++++++ adafruit-pitft.py | 215 ++-- con2fbmap-helper.sh | 25 + con2fbmap.service | 14 + .../adafruit-pitft-mipi.py | 0 overlays/pitft24v2-tsc2007-overlay.dts | 114 ++ templates/95-ftcaptouch.rules | 3 + templates/95-stmpe.rules | 2 + templates/95-touchmouse.rules | 1 + templates/99-tsc2007-touchscreen-drm.rules | 14 + templates/99-tsc2007-touchscreen.rules | 5 + templates/config_text_base.txt | 8 + templates/fbcp.service | 11 + 13 files changed, 1335 insertions(+), 84 deletions(-) create mode 100644 adafruit-pitft-old.py create mode 100644 con2fbmap-helper.sh create mode 100644 con2fbmap.service rename adafruit-pitft-mipi.py => old_scripts/adafruit-pitft-mipi.py (100%) create mode 100644 overlays/pitft24v2-tsc2007-overlay.dts create mode 100644 templates/95-ftcaptouch.rules create mode 100644 templates/95-stmpe.rules create mode 100644 templates/95-touchmouse.rules create mode 100644 templates/99-tsc2007-touchscreen-drm.rules create mode 100644 templates/99-tsc2007-touchscreen.rules create mode 100644 templates/config_text_base.txt create mode 100644 templates/fbcp.service diff --git a/adafruit-pitft-old.py b/adafruit-pitft-old.py new file mode 100644 index 0000000..d857c84 --- /dev/null +++ b/adafruit-pitft-old.py @@ -0,0 +1,1007 @@ +""" +Adafruit PiTFT Installer Script +(C) Adafruit Industries, Creative Commons 3.0 - Attribution Share Alike + +Written in Python by Melissa LeBlanc-Williams for Adafruit Industries +""" + +import time +import os +try: + import click +except ImportError: + raise RuntimeError("The library 'Click' was not found. To install, try typing: pip3 install Click") +try: + from adafruit_shell import Shell +except ImportError: + raise RuntimeError("The library 'adafruit_shell' was not found. To install, try typing: pip3 install adafruit-python-shell") + +shell = Shell() +shell.group = 'PITFT' + +__version__ = "3.9.0" + +""" +This is the main configuration. Displays should be placed in the order +they are to appear in the menu. + +Config Parameters: +REQUIRED fields: +type: Unique identifier for the display type. +menulabel: The label to display in the menu. +product: The product name for the display. +kernel_upgrade: Whether the kernel needs to be upgraded for this display. +overlay: The overlay string to apply for the display. +width: Width of the display in pixels. +height: Height of the display in pixels. + +OPTIONAL fields: +touchscreen: Dictionary containing touchscreen calibration data. + identifier: Name of the touchscreen calibration. + product: Product name for the touchscreen. + transforms: Calibration transforms for different rotations. + overlay_params: Overlay parameters for different rotations. +overlay_drm_option: Optional parameter to add for DRM support. +force_x11: Whether to force X11 for the display. +calibrations: Calibration data for the touchscreen. +overlay_src: Source path for the overlay file (if applicable). +overlay_dest: Destination path for the compiled overlay file (if applicable). +mipi_data (Optional): Dictionary containing MIPI display data. + command_bin: Name of the command binary for MIPI. + gpio: GPIO settings for the display. + viewport: Viewport settings for different rotations. +use_kms: Whether to use KMS for the display. +""" +config = [ + { + "type": "28r", + "menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)", + "product": "2.8\" resistive, PID 1601", + "kernel_upgrade": False, + "touchscreen": { + "identifier": "STMPE Touchscreen Calibration", + "product": "stmpe", + "transforms": { + "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", + "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", + "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", + "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", + }, + "overlay_params": { + "0": None, + "90": "touch-swapxy,touch-invx", + "180": "touch-invx,touch-invy", + "270": "touch-swapxy,touch-invy", + }, + }, + "overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, + "width": 320, + "height": 240, + }, + { + "type": "22", + "menulabel": "PiTFT 2.2\" no touch", + "product": "2.2\" no touch", + "kernel_upgrade": False, + "overlay": "dtoverlay=pitft22,rotate={pitftrot},speed=64000000,fps=30", + "overlay_drm_option": "drm", + "width": 320, + "height": 240, + }, + { + "type": "28c", + "menulabel": "PiTFT 2.8\" capacitive touch", + "product": "2.8\" capacitive, PID 1983", + "kernel_upgrade": False, + "touchscreen": { + "identifier": "FocalTech Touchscreen Calibration", + "product": "EP0110M09", + "transforms": { + "0": "-1 0 1 0 -1 1 0 0 1", + "90": "0 1 0 -1 0 1 0 0 1", + "180": "1 0 0 0 1 0 0 0 1", + "270": "0 -1 1 1 0 0 0 0 1", + }, + "overlay_params": { + "0": "touch-invx,touch-invy", + "90": "touch-swapxy,touch-invy", + "180": None, + "270": "touch-swapxy,touch-invx", + }, + }, + "overlay": "dtoverlay=pitft28-capacitive,rotate={pitftrot},speed=64000000,fps=30", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": "320 65536 0 -65536 0 15728640 65536", + "width": 320, + "height": 240, + }, + { + "type": "35r", + "menulabel": "PiTFT 3.5\" resistive touch", + "product": "3.5\" Resistive", + "kernel_upgrade": False, + "touchscreen": { + "identifier": "STMPE Touchscreen Calibration", + "product": "stmpe", + "transforms": { + "0": "-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1", + "90": "-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1", + "180": "1.102807 0.000030 -0.066352 0.001374 1.085417 -0.027208 0 0 1", + "270": "0.003893 -1.087542 1.025913 1.084281 0.008762 -0.060700 0 0 1", + }, + "overlay_params": { + "0": None, + "90": "touch-swapxy,touch-invx", + "180": "touch-invx,touch-invy", + "270": "touch-swapxy,touch-invy", + }, + }, + "overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": { + "0": "5724 -6 -1330074 26 8427 -1034528 65536", + "90": "5 8425 -978304 -5747 61 22119468 65536", + "180": "-5682 -1 22069150 13 -8452 32437698 65536", + "270": "3 -8466 32440206 5703 -1 -1308696 65536", + }, + "width": 480, + "height": 320, + "x11_scale": 1.5, + }, + { + "type": "st7789_240x240", + "menulabel": "PiTFT Mini 1.3\" or 1.54\" display", + "product": "1.54\" or 1.3\" no touch", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/minipitft13-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-minipitft13.dtbo", + "overlay": "dtoverlay=drm-minipitft13,rotate={pitftrot},fps=60", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=22", + "viewport": { + "0": "width=240,height=240", + "90": "width=240,height=240,x-offset=80", + "180": "width=240,height=240,y-offset=80", + "270": "width=240,height=240", + }, + }, + "width": 240, + "height": 240, + "fbcp_rotations": { + "0": "0", + "90": "1", + "180": "2", + "270": "3", + }, + }, + { + "type": "st7789_240x320", + "menulabel": "ST7789V 2.0\" no touch", + "product": "2.0\" no touch", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/st7789v_240x320-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-st7789v_240x320.dtbo", + "overlay": "dtoverlay=drm-st7789v_240x320,rotate={pitftrot},fps=30", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=22", + "viewport": { + "0": "width=320,height=240", + "90": "width=240,height=320", + "180": "width=320,height=240", + "270": "width=240,height=320", + }, + }, + "width": 320, + "height": 240, + }, + { + "type": "st7789_240x135", + "menulabel": "MiniPiTFT 1.14\" display", + "product": "1.14\" no touch", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/minipitft114-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-minipitft114.dtbo", + "overlay": "dtoverlay=drm-minipitft114,rotate={pitftrot},fps=60", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=22", + "viewport": { + "0": "width=136,height=240,x-offset=52,y-offset=40", + "90": "width=240,height=136,y-offset=52,x-offset=40", + "180": "width=136,height=240,x-offset=52,y-offset=40", + "270": "width=240,height=136,y-offset=52,x-offset=40", + }, + }, + "rotations": { + "0": None, + "90": "90", + "180": None, + "270": "270", + }, + "width": 240, + "height": 135, + "fbcp_rotations": { + "0": "3", + "90": "2", + "180": "1", + "270": "0", + }, + }, + { + "type": "st7789v_bonnet_240x240", + "menulabel": "BrainCraft HAT or 1.3\" TFT Bonnet + Joystick", + "product": "1.3\" Joystick", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/tftbonnet13-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-tftbonnet13.dtbo", + "overlay": "dtoverlay=drm-tftbonnet13,rotate={pitftrot},fps=60", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=26", + "viewport": { + "0": "width=240,height=240", + "90": "width=240,height=240,x-offset=80", + "180": "width=240,height=240,y-offset=80", + "270": "width=240,height=240", + }, + }, + "width": 240, + "height": 240, + "fbcp_rotations": { + "0": "0", + "90": "1", + "180": "2", + "270": "3", + }, + }, + { + "type": "24v2", + "menulabel": "PiTFT 2.4\" V2 resistive (240x320)", + "product": "2.4\" V2 resistive", + "kernel_upgrade": False, + "overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/pitft24v2.dtbo", + "touchscreen": { + "identifier": "STMPE Touchscreen Calibration", + "product": "stmpe", + "transforms": { + "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", + "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", + "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", + "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", + }, + }, + "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, + "width": 320, + "height": 240, + }, +] + +# default rotations +fbcp_rotations = { + "0": "1", + "90": "0", + "180": "3", + "270": "2", +} + +# default mipi data +mipi_data = { + "speed": 40000000, + "spi": "spi0-0", +} + +PITFT_ROTATIONS = ("90", "180", "270", "0") +UPDATE_DB = False +SYSTEMD = None +REMOVE_KERNEL_PINNING = False +pitft_config = None +pitftrot = None +auto_reboot = None +wayland = False +is_bullseye = False + +def warn_exit(message): + shell.warn(message) + shell.exit(1) + +def uninstall_cb(ctx, param, value): + if not value or ctx.resilient_parsing: + return + uninstall() + +def print_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + print("Adafruit PiTFT Helper v{}".format(__version__)) + shell.exit(1) + +def progress(ellipsis_count): + for i in range(ellipsis_count): + print("...", end='') + time.sleep(1) + print("") + +def sysupdate(): + global UPDATE_DB + if not UPDATE_DB: + print("Updating apt indexes...", end='') + progress(3) + if not shell.run_command('sudo apt update', suppress_message=True): + warn_exit("Apt failed to update indexes! Try running 'sudo apt update' manually.") + if not shell.run_command('sudo apt-get update', suppress_message=True): + warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.") + print("Reading package lists...") + progress(3) + UPDATE_DB = True + return True + +############################ Sub-Scripts ############################ + +def is_wayland(): + username = os.environ["SUDO_USER"] + output = shell.run_command("loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | grep wayland", suppress_message=True, return_output=True, run_as_user=username).strip() + return len(output) > 0 + +def softwareinstall(): + print("Installing Pre-requisite Software...This may take a few minutes!") + if not shell.run_command("apt-get install -y libts0", suppress_message=True): + if not shell.run_command("apt-get install -y tslib"): + if not shell.run_command("apt-get install -y libts-dev"): + warn_exit("Apt failed to install TSLIB!") + if not shell.run_command("apt-get install -y bc fbi git python3-dev python3-pip python3-smbus python3-spidev evtest libts-bin device-tree-compiler libraspberrypi-dev build-essential python3-evdev"): + warn_exit("Apt failed to install software!") + return True + +def uninstall_bootconfigtxt(): + """Remove any old flexfb/fbtft stuff""" + if shell.pattern_search(f"{boot_dir}/config.txt", "adafruit-pitft-helper"): + print(f"Already have an adafruit-pitft-helper section in {boot_dir}/config.txt.") + print("Removing old section...") + shell.run_command(f"cp {boot_dir}/config.txt {boot_dir}/configtxt.bak") + shell.pattern_replace(f"{boot_dir}/config.txt", '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) + return True + +def uninstall_etc_modules(): + """Remove any old flexfb/fbtft stuff""" + shell.run_command('rm -f /etc/modprobe.d/fbtft.conf') + shell.pattern_replace("/etc/modules", 'spi-bcm2835') + shell.pattern_replace("/etc/modules", 'flexfb') + shell.pattern_replace("/etc/modules", 'fbtft_device') + return True + +def use_mipi_driver(config = None): + """Check if MIPI Overlay is Present""" + if not config: + config = pitft_config + if not "mipi_data" in config: + return False + if not shell.exists(f"{boot_dir}/overlays/mipi-dbi-spi.dtbo"): + return False + return True + +def is_kernel_upgrade_required(config = None): + """Check if kernel upgrade is required""" + if not config: + config = pitft_config + if not config['kernel_upgrade']: + return False + # Only upgrade Kernel if MIPI Driver is not an option + if use_mipi_driver(config): + return False + + return True + +def install_drivers(): + """Compile display driver and overlay if required""" + if "overlay_src" in pitft_config and "overlay_dest" in pitft_config: + print("Compiling Device Tree Overlay") + destination = pitft_config['overlay_dest'].format(boot_dir=boot_dir) + shell.run_command("dtc --warning no-unit_address_vs_reg -I dts -O dtb -o {dest} {src}".format(dest=destination, src=pitft_config['overlay_src'])) + + if use_mipi_driver(): + mipi_data.update(pitft_config['mipi_data']) + if not compile_display_fw(): + shell.bail("Unable to compile MIPI firmware") + + if is_kernel_upgrade_required(): + print("############# UPGRADING KERNEL ###############") + print("Updating packages...") + if not shell.run_command("sudo apt-get update", suppress_message=True): + warn_exit("Apt failed to update itself!") + print("Upgrading packages...") + if not shell.run_command("sudo apt-get -y upgrade"): + warn_exit("Apt failed to install software!") + print("Installing Kernel Headers. This may take a few minutes...") + if not shell.run_command("apt-get install -y raspberrypi-kernel-headers", suppress_message=True): + warn_exit("Apt failed to install software!") + # If the kernel was upgraded, a build folder should exist once it has been loaded + module = pitft_config['kernel_module'] + if not shell.isdir(f"/lib/modules/{shell.release()}/build"): + warn_exit(f"Kernel headers build folder for {shell.release()}, not found. Please reboot now and re-run script!") + print("Compiling and installing display driver...") + shell.pushd("st7789_module") + if not shell.run_command("make"): + warn_exit("Apt failed to compile ST7789V drivers!") + shell.run_command(f"mv /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.ko.xz /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.BACK.xz") + shell.run_command(f"xz -v2 {module}.ko") + shell.run_command(f"mv {module}.ko.xz /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.ko.xz") + shell.popd() + return True + +def update_configtxt(rotation_override=None, tinydrm_install=False): + """update /boot/firmware/config.txt (or equivalent folder) with appropriate values""" + uninstall_bootconfigtxt() + uninstall_etc_modules() + overlay_key = "overlay" + overlay = pitft_config[overlay_key] + if "{pitftrot}" in overlay: + rotation = str(rotation_override) if rotation_override is not None else pitftrot + overlay = overlay.format(pitftrot=rotation) + if use_mipi_driver(): + # Mipi Driver does not work if hdmi_force_hotplug=1 is present + shell.pattern_replace(f"{boot_dir}/config.txt", "hdmi_force_hotplug=1", "hdmi_force_hotplug=0") + # Use the MIPI Overlay instead + overlay = f"dtoverlay=mipi-dbi-spi,{mipi_data['spi']},speed=40000000" + overlay += f"\ndtparam=compatible={mipi_data['command_bin']}\\0panel-mipi-dbi-spi" + viewport = "" + if mipi_data['viewport'][pitftrot] is not None: + viewport = mipi_data['viewport'][pitftrot] + overlay += f"\ndtparam={viewport}" + if "gpio" in mipi_data: + overlay += f"\ndtparam={mipi_data['gpio']}" + + if tinydrm_install and "overlay_drm_option" in pitft_config: + overlay += "," + pitft_config["overlay_drm_option"] + if tinydrm_install: # Wayland ignores X11 Transformations, so use params instead + if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: + overlay += "," + pitft_config["overlay_params"][pitftrot] + shell.write_text_file(f"{boot_dir}/config.txt", """ +# --- added by adafruit-pitft-helper {date} --- +[all] +hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! +dtparam=spi=on +dtparam=i2c1=on +dtparam=i2c_arm=on +{overlay} +# --- end adafruit-pitft-helper {date} --- +""".format(date=shell.date(), overlay=overlay)) + return True + +def update_udev(): + shell.write_text_file("/etc/udev/rules.d/95-touchmouse.rules", """ +SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +""", append=False) + shell.write_text_file("/etc/udev/rules.d/95-ftcaptouch.rules", """ +SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +""", append=False) + shell.write_text_file("/etc/udev/rules.d/95-stmpe.rules", """ +SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +""", append=False) + return True + +def compile_display_fw(): + command_src = "mipi/panel.txt" + + # We could just copy the file to panel.txt, edit that, then remove it after + shell.copy(f"mipi/{mipi_data['command_bin']}.txt", command_src) + + # Make sure each of the lines with "# rotation" starts with exactly 1 # and no more + shell.pattern_replace(command_src, "^#*(.*?# rotation.*?$)", "#\\1") + # Uncomment the one for the rotation we are going for + shell.pattern_replace(command_src, "^#(.*?# rotation " + pitftrot + ".*?$)", "\\1") + # Download the mipi-dbi-cmd script if it doesn't exist + if not shell.exists("mipi-dbi-cmd"): + shell.run_command("wget https://raw.githubusercontent.com/notro/panel-mipi-dbi/main/mipi-dbi-cmd") + shell.run_command("chmod +x mipi-dbi-cmd") + # Run the mipi-dbi-script and output directly to the /lib/firmware folder + shell.run_command(f"./mipi-dbi-cmd /lib/firmware/{mipi_data['command_bin']}.bin mipi/panel.txt") + shell.remove(command_src) + return True + +def update_pointercal(): + if "calibrations" in pitft_config: + if isinstance(pitft_config["calibrations"], dict): + shell.write_text_file("/etc/pointercal", pitft_config["calibrations"][pitftrot]) + else: + shell.write_text_file("/etc/pointercal", pitft_config["calibrations"]) + return True + +def install_console(): + print("Set up main console turn on") + if not shell.pattern_search(f"{boot_dir}/cmdline.txt", 'fbcon=map:10 fbcon=font:VGA8x8'): + print(f"Updating {boot_dir}/cmdline.txt") + shell.pattern_replace(f"{boot_dir}/cmdline.txt", "rootwait", "rootwait fbcon=map:10 fbcon=font:VGA8x8") + else: + print(f"{boot_dir}/cmdline.txt already updated") + + print("Turning off console blanking") + + # pre-stretch this is what you'd do: + if shell.exists("/etc/kbd/config"): + shell.pattern_replace("/etc/kbd/config", "BLANK_TIME=.*", "BLANK_TIME=0") + + # as of stretch.... + # removing any old version + shell.pattern_replace("/etc/rc.local", '# disable console blanking.*') + shell.pattern_replace("/etc/rc.local", 'sudo sh -c "TERM=linux setterm -blank.*') + shell.pattern_replace("/etc/rc.local", '^exit 0', "# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0") + + shell.reconfig("/etc/default/console-setup", "^.*FONTFACE.*$", "FONTFACE=\"Terminus\"") + shell.reconfig("/etc/default/console-setup", "^.*FONTSIZE.*$", "FONTSIZE=\"6x12\"") + + print("Setting raspi-config to boot to console w/o login...") + shell.chdir(target_homedir) + shell.run_command("raspi-config nonint do_boot_behaviour B2") + + # remove fbcp + shell.pattern_replace("/etc/rc.local", "^.*fbcp.*$") + return True + +def uninstall_console(): + print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") + shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') + if shell.exists("/etc/kbd/config"): + print("Screen blanking time reset to 10 minutes") + shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'BLANK_TIME=0', 'BLANK_TIME=10') + shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') + shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') + return True + +def install_fbcp(): + global fbcp_rotations + print("Installing cmake...") + if not shell.run_command("apt-get --yes --allow-downgrades --allow-remove-essential --allow-change-held-packages install cmake", suppress_message=True): + warn_exit("Apt failed to install software!") + print("Downloading rpi-fbcp...") + shell.pushd("/tmp") + shell.run_command("curl -sLO https://github.com/adafruit/rpi-fbcp/archive/master.zip") + print("Uncompressing rpi-fbcp...") + shell.run_command("rm -rf /tmp/rpi-fbcp-master") + if not shell.run_command("unzip master.zip", suppress_message=True): + warn_exit("Failed to uncompress fbcp!") + shell.chdir("rpi-fbcp-master") + shell.run_command("mkdir build") + shell.chdir("build") + print("Building rpi-fbcp...") + shell.write_text_file("../CMakeLists.txt", "\nset (CMAKE_C_FLAGS \"-std=gnu99 ${CMAKE_C_FLAGS}\")") + if not shell.run_command("cmake ..", suppress_message=True): + warn_exit("Failed to cmake fbcp!") + if not shell.run_command("make", suppress_message=True): + warn_exit("Failed to make fbcp!") + print("Installing rpi-fbcp...") + shell.run_command("install fbcp /usr/local/bin/fbcp") + shell.popd() + shell.run_command("rm -rf /tmp/rpi-fbcp-master") + + if "fbcp_rotations" in pitft_config: + fbcp_rotations = pitft_config['fbcp_rotations'] + + # Start fbcp in the appropriate place, depending on init system: + if SYSTEMD: + # Add fbcp to /etc/rc.local: + print("We have sysvinit, so add fbcp to /etc/rc.local...") + if shell.pattern_search("/etc/rc.local", "fbcp"): + # fbcp already in rc.local, but make sure correct: + shell.pattern_replace("/etc/rc.local", "^.*fbcp.*$", "/usr/local/bin/fbcp \&") + else: + # Insert fbcp into rc.local before final 'exit 0': + shell.pattern_replace("/etc/rc.local", "^exit 0", "/usr/local/bin/fbcp \&\\nexit 0") + else: + # Install fbcp systemd unit, first making sure it's not in rc.local: + uninstall_fbcp_rclocal() + print("We have systemd, so install fbcp systemd unit...") + if not install_fbcp_unit(): + shell.bail("Unable to install fbcp unit file") + shell.run_command("sudo systemctl enable fbcp.service") + + # if there's X11 installed... + if shell.exists("/etc/lightdm"): + print("Setting raspi-config to boot to desktop w/o login...") + shell.run_command("raspi-config nonint do_boot_behaviour B4") + + # Disable overscan compensation (use full screen): + shell.run_command("raspi-config nonint do_overscan 1") + # Set up HDMI parameters: + print("Configuring boot/config.txt for forced HDMI") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_force_hotplug.*$", "hdmi_force_hotplug=1") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_group.*$", "hdmi_group=2") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") + + # Don't use the 3D driver if we're on X11, unless the display specifically uses kms + if (is_bullseye or not wayland) and ("use_kms" not in pitft_config or not pitft_config["use_kms"]): + shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-kms-v3d.*$", "#dtoverlay=vc4-kms-v3d") + shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-fkms-v3d.*$", "#dtoverlay=vc4-fkms-v3d") + + # if there's X11 installed... + scale = 1 + if shell.exists("/etc/lightdm"): + if "x11_scale" in pitft_config: + scale = pitft_config["x11_scale"] + else: + scale = 2 + WIDTH = int(pitft_config['width'] * scale) + HEIGHT = int(pitft_config['height'] * scale) + + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_cvt.*$", "hdmi_cvt={} {} 60 1 0 0 0".format(WIDTH, HEIGHT)) + + try: + default_orientation = int(list(fbcp_rotations.keys())[list(fbcp_rotations.values()).index("0")]) + except ValueError: + default_orientation = 90 + + if fbcp_rotations[pitftrot] == "0": + # dont rotate HDMI on default orientation + shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "") + else: + display_rotate = fbcp_rotations[pitftrot] + shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "display_hdmi_rotate={}".format(display_rotate)) + # Because we rotate HDMI we have to 'unrotate' the TFT by overriding pitftrot! + if not update_configtxt(default_orientation): + shell.bail(f"Unable to update {boot_dir}/config.txt") + return True + +def update_wayfire_settings(): + # Set the scale factor for Wayland, which is the reciprocal of the X11 scale factor + if "x11_scale" in pitft_config: + scale = 1/pitft_config["x11_scale"] + else: + scale = 0.5 + device_name = "SPI-1" + wayfire_config = f"{target_homedir}/.config/wayfire.ini" + # Remove any existing settings previously added by this script + shell.pattern_replace(wayfire_config, '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) + date = shell.date() + shell.write_text_file(wayfire_config, f""" +# --- added by adafruit-pitft-helper {date} --- +[output:{device_name}] +scale = {scale} +# --- end adafruit-pitft-helper {date} --- +""") + + shell.pattern_replace(target_homedir, "^.*[output:SPI-1].*$", "hdmi_force_hotplug=0") + + +def install_fbcp_unit(): + shell.write_text_file("/etc/systemd/system/fbcp.service", + """[Unit] +Description=Framebuffer copy utility for PiTFT +After=network.target + +[Service] +Type=simple +ExecStartPre=/bin/sleep 10 +ExecStart=/usr/local/bin/fbcp + +[Install] +WantedBy=multi-user.target +""", append=False) + return True + +def uninstall_fbcp(): + uninstall_fbcp_rclocal() + # Enable overscan compensation + if shell.exists("/etc/systemd/system/fbcp.service"): + shell.run_command("sudo systemctl disable fbcp.service") + # Set up HDMI parameters: + shell.run_command("raspi-config nonint do_overscan 0") + print("Configuring boot/config.txt for default HDMI") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_force_hotplug.*$", "hdmi_force_hotplug=0") + shell.pattern_replace(f"{boot_dir}/config.txt", "^.*#.*dtoverlay=vc4-kms-v3d.*$", "dtoverlay=vc4-kms-v3d") + shell.pattern_replace(f"{boot_dir}/config.txt", "^.*#.*dtoverlay=vc4-fkms-v3d.*$", "dtoverlay=vc4-fkms-v3d") + shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_group=2.*$') + shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_mode=87.*$') + shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_cvt=.*$') + + if not wayland and not is_bullseye and shell.exists("/etc/lightdm"): + print("Restoring Wayland as default display manager...") + shell.set_window_manager("wayland") + + return True + +def uninstall_fbcp_rclocal(): + """Remove fbcp from /etc/rc.local:""" + print("Remove fbcp from /etc/rc.local, if it's there...") + shell.pattern_replace("/etc/rc.local", '^.*fbcp.*$') + return True + +def update_xorg(tinydrm_install=False): + if "touchscreen" in pitft_config: + transform_setting = pitft_config["touchscreen"]["transforms"][pitftrot] + if not tinydrm_install and "old_transforms" in pitft_config["touchscreen"]: + transform_setting = pitft_config["touchscreen"]["old_transforms"][pitftrot] + transform = f"Option \"TransformationMatrix\" \"{transform_setting}\"" + shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ +Section "InputClass" + Identifier "{identifier}" + MatchProduct "{product}" + MatchDevicePath "/dev/input/event*" + Driver "libinput" + {transform} +EndSection +""".format( + identifier=pitft_config["touchscreen"]["identifier"], + product=pitft_config["touchscreen"]["product"], + transform=transform + ), + append=False, + ) + return True + +def get_config_types(): + types = [] + for item in config: + types.append(item["type"]) + return types + +def get_config(type): + for item in config: + if item["type"] == type: + return item + return None + +def uninstall(): + shell.info("Uninstalling PiTFT") + uninstall_bootconfigtxt() + uninstall_console() + uninstall_fbcp() + uninstall_fbcp_rclocal() + uninstall_etc_modules() + success() + +def success(): + global auto_reboot + shell.info("Success!") + print(""" +Settings take effect on next boot. +""") + if auto_reboot is None: + auto_reboot = shell.prompt("REBOOT NOW?", default="y") + if not auto_reboot: + print("Exiting without reboot.") + shell.exit() + print("Reboot started...") + shell.reboot() + shell.exit() + +####################################################### MAIN +target_homedir = "/home/pi" +username = os.environ["SUDO_USER"] +user_homedir = os.path.expanduser(f"~{username}") +if shell.isdir(user_homedir): + target_homedir = user_homedir + +boot_dir = "/boot/firmware" if shell.isdir("/boot/firmware") else "/boot" + +# If neither directory is valid, use shell.get_boot_config() +if not shell.isdir(boot_dir): + boot_dir = os.path.dirname(shell.get_boot_config()) + +# Final safeguard: Ensure boot_dir is actually a directory and not a file path +if boot_dir is None or not shell.isdir(boot_dir): + shell.bail("Unable to find boot directory") + +if shell.get_raspbian_version() == "bullseye": + is_bullseye = True + +@click.command() +@click.option('-v', '--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="Print version information") +@click.option('-u', '--user', nargs=1, default=target_homedir, type=str, help="Specify path of primary user's home directory", show_default=True) +@click.option('--display', nargs=1, default=None, help="Specify a display option (1-{}) or type {}".format(len(config), get_config_types())) +@click.option('--rotation', nargs=1, default=None, type=int, help="Specify a rotation option (1-4) or degrees {}".format(tuple(sorted([int(x) for x in PITFT_ROTATIONS])))) +@click.option('--install-type', nargs=1, default=None, type=click.Choice(['mirror', 'fbcp', 'console', 'uninstall']), help="Installation Type") +@click.option('--reboot', nargs=1, default=None, type=click.Choice(['yes', 'no']), help="Specify whether to reboot after the script is finished") +@click.option('--boot', nargs=1, default=boot_dir, type=str, help="Specify the boot directory", show_default=True) +def main(user, display, rotation, install_type, reboot, boot): + global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, wayland + shell.clear() + if user != target_homedir: + target_homedir = user + print(f"Homedir = {target_homedir}") + if boot != boot_dir: + if shell.isdir(boot): + boot_dir = boot + print(f"Boot dir = {boot_dir}") + else: + print(f"{boot} not found or not a directory. Using {boot_dir} instead.") + wayland = is_wayland() + print(("Wayland" if wayland else "X11") + " Detected") + if is_bullseye: + print("Bullseye Detected") + print() + # "mirror" will be the new install type, but keep fbcp for backwards compatibility + if install_type == "fbcp": + install_type = "mirror" + + print("""This script downloads and installs +PiTFT Support using userspace touch +controls and a DTO for display drawing. +one of several configuration files. +Run time of up to 5 minutes. Reboot required! +""") + if reboot is not None: + auto_reboot = reboot.lower() == 'yes' + + if install_type == "uninstall": + uninstall() + + def select_display(config, interactive=False): + global pitft_config, wayland + pitft_config = config + print("Display Type: {}".format(pitft_config["menulabel"])) + if is_kernel_upgrade_required(): + print("WARNING! WILL UPGRADE YOUR KERNEL TO LATEST") + if "force_x11" in pitft_config and pitft_config["force_x11"] and wayland: + if not interactive or shell.prompt("This display works better with X11, but Wayland is currently running. Use X11 instead? (Recommended)", default="y"): + shell.set_window_manager("x11") + wayland = False + + if display in [str(x) for x in range(1, len(config) + 1)]: + select_display(config[int(display) - 1]) + elif display in get_config_types(): + select_display(get_config(display)) + else: + # Build menu from config + selections = [] + for item in config: + option = "{} ({}x{})".format(item['menulabel'], item['width'], item['height']) + if is_kernel_upgrade_required(item): + option += " - WARNING! WILL UPGRADE YOUR KERNEL TO LATEST" + selections.append(option) + selections.append("Uninstall PiTFT") + selections.append("Quit without installing") + + PITFT_SELECT = shell.select_n("Select configuration:", selections) + if PITFT_SELECT == len(config) + 2: + shell.exit(1) + if PITFT_SELECT == len(config) + 1: + uninstall() + select_display(config[PITFT_SELECT - 1], True) + + if rotation is not None and 1 <= rotation <= 4: + pitftrot = PITFT_ROTATIONS[rotation - 1] + shell.info("Rotation: {}".format(pitftrot)) + elif str(rotation) in PITFT_ROTATIONS: + pitftrot = str(rotation) + shell.info("Rotation: {}".format(pitftrot)) + else: + PITFT_ROTATE = shell.select_n( + "Select rotation:", ( + "90 degrees (landscape)", + "180 degrees (portrait)", + "270 degrees (landscape)", + "0 degrees (portrait)" + )) + pitftrot = PITFT_ROTATIONS[PITFT_ROTATE - 1] + + if 'rotations' in pitft_config and isinstance(pitft_config['rotations'], dict) and pitftrot in pitft_config['rotations'] and pitft_config['rotations'][pitftrot] is None: + shell.bail("""Unfortunately {rotation} degrees for the {display} is not working at this time. Please +restart the script and choose a different orientation.""".format(rotation=pitftrot, display=pitft_config["menulabel"])) + + if REMOVE_KERNEL_PINNING: + # Checking if kernel is pinned + if shell.exists('/etc/apt/preferences.d/99-adafruit-pin-kernel'): + shell.warn("WARNING! Script detected your system is currently pinned to an older kernel. The pin will be removed and your system will be updated.") + if not shell.prompt("Continue?"): + shell.exit() + shell.remove('/etc/apt/preferences.d/99-adafruit-pin-kernel') + + # check init system (technique borrowed from raspi-config): + shell.info('Checking init system...') + if shell.run_command("which systemctl", suppress_message=True) and shell.run_command("systemctl | grep '\-\.mount'", suppress_message=True): + SYSTEMD = True + print("Found systemd") + elif os.path.isfile("/etc/init.d/cron") and not os.path.islink("/etc/init.d/cron"): + SYSTEMD = False + print("Found sysvinit") + else: + shell.bail("Unrecognised init system") + + if shell.grep("boot", "/proc/mounts"): + print("/boot is mounted") + else: + print("/boot must be mounted. if you think it's not, quit here and try: sudo mount /dev/mmcblk0p1 /boot") + if shell.prompt("Continue?"): + print("Proceeding.") + else: + shell.bail("Aborting.") + + if not shell.isdir(target_homedir): + shell.bail("{} must be an existing directory (use -u /home/foo to specify)".format(target_homedir)) + + shell.info("System update") + if not sysupdate(): + shell.bail("Unable to apt-get update") + + shell.info("Installing Python libraries & Software...") + if not softwareinstall(): + shell.bail("Unable to install software") + + if "overlay_src" in pitft_config and "overlay_dest" in pitft_config: + shell.info("Installing display drivers and device tree overlay...") + if not install_drivers(): + shell.bail("Unable to install display drivers") + + shell.info(f"Updating {boot_dir}/config.txt...") + if not update_configtxt(tinydrm_install=(not is_bullseye)): + shell.bail(f"Unable to update {boot_dir}/config.txt") + + if "touchscreen" in pitft_config: + shell.info("Updating SysFS rules for Touchscreen...") + if not update_udev(): + shell.bail("Unable to update /etc/udev/rules.d") + + shell.info("Updating TSLib default calibration...") + if not update_pointercal(): + shell.bail("Unable to update /etc/pointercal") + + # ask for console access + if install_type == "console" or (install_type is None and shell.prompt("Would you like the console to appear on the PiTFT display?")): + shell.info("Updating console to PiTFT...") + if not uninstall_fbcp(): + shell.bail("Unable to uninstall fbcp") + if not install_console(): + shell.bail("Unable to configure console") + else: + shell.info("Making sure console doesn't use PiTFT") + if not uninstall_console(): + shell.bail("Unable to configure console") + + mirror_prompt = "Would you like the HDMI display to mirror to the PiTFT display?" + if wayland: + # With wayland, PiTFT shows up as an additional display rather than a mirror + mirror_prompt = "Would you like the to use the PiTFT as a desktop display?" + if install_type == "mirror" or (install_type is None and shell.prompt(mirror_prompt)): + if wayland: + shell.info("Updating Wayland desktop settings...") + update_wayfire_settings() + else: + shell.info("Adding FBCP support...") + if not install_fbcp(): + shell.bail("Unable to configure fbcp") + + if shell.exists("/etc/lightdm"): + shell.info("Updating Desktop Touch calibration...") + if not update_xorg(tinydrm_install=wayland): + shell.bail("Unable to update calibration") + else: + if not uninstall_fbcp(): + shell.bail("Unable to uninstall fbcp") + success() + +# Main function +if __name__ == "__main__": + shell.require_root() + if shell.is_raspberry_pi_os() and shell.is_kernel_userspace_mismatched() and shell.is_pi5_or_newer(): + shell.bail("Unable to proceed on Pi 5 or newer boards with a with a 32-bit OS. Please reinstall with a 64-bit OS.") + shell.check_kernel_userspace_mismatch() + main() diff --git a/adafruit-pitft.py b/adafruit-pitft.py index c6c9d04..3274b16 100644 --- a/adafruit-pitft.py +++ b/adafruit-pitft.py @@ -19,12 +19,44 @@ except ImportError: shell = Shell() shell.group = 'PITFT' -__version__ = "3.9.0" +__version__ = "4.0.0" """ This is the main configuration. Displays should be placed in the order they are to appear in the menu. + +Config Parameters: +REQUIRED fields: +type: Unique identifier for the display type. +menulabel: The label to display in the menu. +product: The product name for the display. +kernel_upgrade: Whether the kernel needs to be upgraded for this display. +overlay: The overlay string to apply for the display. +width: Width of the display in pixels. +height: Height of the display in pixels. + +OPTIONAL fields: +touchscreen: Dictionary containing touchscreen settings for displays with touchscreens. + identifier: Name of the touchscreen calibration. + product: Product name for the touchscreen. + transforms: X11 transforms for different rotations. + overlay_params: Overlay parameters for different rotations. + calibrations: Calibration data for the touchscreen. Can be overall or per rotation. +overlay_drm_option: Optional parameter to add for DRM support. +overlay_src: Source path for the overlay file (if applicable). +overlay_dest: Destination path for the compiled overlay file (if applicable). +mipi_data (Optional): Dictionary containing MIPI display data. + command_bin: Name of the command binary for MIPI. + gpio: GPIO settings for the display. + viewport: Viewport settings for different rotations. +use_kms: Whether to use KMS for the display. """ + +# Touchscreen Products +TS_STMPE = "stmpe" +TS_TSC2007 = "tsc2007" +TS_FOCALTOUCH = "EP0110M09" + config = [ { "type": "28r", @@ -33,7 +65,8 @@ config = [ "kernel_upgrade": False, "touchscreen": { "identifier": "STMPE Touchscreen Calibration", - "product": "stmpe", + "product": TS_STMPE, + # X11 Transforms "transforms": { "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", @@ -46,16 +79,15 @@ config = [ "180": "touch-invx,touch-invy", "270": "touch-swapxy,touch-invy", }, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, }, "overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30", "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": { - "0": "4232 11 -879396 1 5786 -752768 65536", - "90": "33 -5782 21364572 4221 35 -1006432 65536", - "180": "-4273 61 16441290 4 -5772 21627524 65536", - "270": "-9 5786 -784608 -4302 19 16620508 65536", - }, "width": 320, "height": 240, }, @@ -76,7 +108,8 @@ config = [ "kernel_upgrade": False, "touchscreen": { "identifier": "FocalTech Touchscreen Calibration", - "product": "EP0110M09", + "product": TS_FOCALTOUCH, + # X11 Transforms "transforms": { "0": "-1 0 1 0 -1 1 0 0 1", "90": "0 1 0 -1 0 1 0 0 1", @@ -89,11 +122,10 @@ config = [ "180": None, "270": "touch-swapxy,touch-invx", }, + "calibrations": "320 65536 0 -65536 0 15728640 65536", }, "overlay": "dtoverlay=pitft28-capacitive,rotate={pitftrot},speed=64000000,fps=30", "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": "320 65536 0 -65536 0 15728640 65536", "width": 320, "height": 240, }, @@ -104,7 +136,8 @@ config = [ "kernel_upgrade": False, "touchscreen": { "identifier": "STMPE Touchscreen Calibration", - "product": "stmpe", + "product": TS_STMPE, + # X11 Transforms "transforms": { "0": "-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1", "90": "-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1", @@ -117,16 +150,15 @@ config = [ "180": "touch-invx,touch-invy", "270": "touch-swapxy,touch-invy", }, + "calibrations": { + "0": "5724 -6 -1330074 26 8427 -1034528 65536", + "90": "5 8425 -978304 -5747 61 22119468 65536", + "180": "-5682 -1 22069150 13 -8452 32437698 65536", + "270": "3 -8466 32440206 5703 -1 -1308696 65536", + }, }, "overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20", "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": { - "0": "5724 -6 -1330074 26 8427 -1034528 65536", - "90": "5 8425 -978304 -5747 61 22119468 65536", - "180": "-5682 -1 22069150 13 -8452 32437698 65536", - "270": "3 -8466 32440206 5703 -1 -1308696 65536", - }, "width": 480, "height": 320, "x11_scale": 1.5, @@ -247,6 +279,35 @@ config = [ "270": "3", }, }, + { + "type": "24v2", + "menulabel": "PiTFT 2.4\" V2 resistive (240x320)", + "product": "2.4\" V2 resistive", + "kernel_upgrade": False, + "overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/pitft24v2.dtbo", + "touchscreen": { + "identifier": "TSC2007 Touchscreen Calibration", + "product": TS_TSC2007, + "transforms": { + # TODO: Test because 0 and 180 may be reversed + "0": "-1 0 1 0 -1 1", + "90": "0 1 0 -1 0 1", + "180": "1 0 0 0 1 0", + "270": "0 -1 1 1 0 0", + }, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, + }, + "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", + "overlay_drm_option": "drm", + "width": 320, + "height": 240, + }, ] # default rotations @@ -277,12 +338,12 @@ def warn_exit(message): shell.warn(message) shell.exit(1) -def uninstall_cb(ctx, param, value): +def uninstall_cb(ctx, _param, value): if not value or ctx.resilient_parsing: return uninstall() -def print_version(ctx, param, value): +def print_version(ctx, _param, value): if not value or ctx.resilient_parsing: return print("Adafruit PiTFT Helper v{}".format(__version__)) @@ -311,6 +372,7 @@ def sysupdate(): ############################ Sub-Scripts ############################ def is_wayland(): + "Check if the current session is Wayland" username = os.environ["SUDO_USER"] output = shell.run_command("loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | grep wayland", suppress_message=True, return_output=True, run_as_user=username).strip() return len(output) > 0 @@ -423,34 +485,29 @@ def update_configtxt(rotation_override=None, tinydrm_install=False): if "gpio" in mipi_data: overlay += f"\ndtparam={mipi_data['gpio']}" - if tinydrm_install and "overlay_drm_option" in pitft_config: - overlay += "," + pitft_config["overlay_drm_option"] - if tinydrm_install: # Wayland ignores X11 Transformations, so use params instead + if tinydrm_install: # Wayland ignores X11 Transformations, so use overlay params instead + overlay += ",drm" if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: overlay += "," + pitft_config["overlay_params"][pitftrot] - shell.write_text_file(f"{boot_dir}/config.txt", """ -# --- added by adafruit-pitft-helper {date} --- -[all] -hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! -dtparam=spi=on -dtparam=i2c1=on -dtparam=i2c_arm=on -{overlay} -# --- end adafruit-pitft-helper {date} --- -""".format(date=shell.date(), overlay=overlay)) + config_text_base = shell.load_template("templates/config_text_base.txt", date=shell.date(), overlay=overlay) + shell.write_text_file(f"{boot_dir}/config.txt", config_text_base) return True def update_udev(): - shell.write_text_file("/etc/udev/rules.d/95-touchmouse.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) - shell.write_text_file("/etc/udev/rules.d/95-ftcaptouch.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) - shell.write_text_file("/etc/udev/rules.d/95-stmpe.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) + product = None + if "touchscreen" in pitft_config and "product" in pitft_config["touchscreen"]: + product = pitft_config["touchscreen"]["product"] + shell.write_templated_file("/etc/udev/rules.d/", "templates/95-touchmouse.rules") + if product == TS_FOCALTOUCH: + shell.write_templated_file("/etc/udev/rules.d/", "templates/95-ftcaptouch.rules") + elif product == TS_STMPE: + shell.write_templated_file("/etc/udev/rules.d/", "templates/95-stmpe.rules") + elif product == TS_TSC2007: + calibration_matrix = "0 1 0 -1 0 1" # Default calibration matrix + if "transforms" in pitft_config["touchscreen"] and pitftrot in pitft_config["touchscreen"]["transforms"]: + calibration_matrix = pitft_config["touchscreen"]["transforms"][pitftrot] + shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen.rules", calibration_matrix=calibration_matrix) + shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen-drm.rules") return True def compile_display_fw(): @@ -473,33 +530,32 @@ def compile_display_fw(): return True def update_pointercal(): - if "calibrations" in pitft_config: - if isinstance(pitft_config["calibrations"], dict): - shell.write_text_file("/etc/pointercal", pitft_config["calibrations"][pitftrot]) + if "touchscreen" in pitft_config and "calibrations" in pitft_config["touchscreen"]: + if isinstance(pitft_config["touchscreen"]["calibrations"], dict): + shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"][pitftrot]) else: - shell.write_text_file("/etc/pointercal", pitft_config["calibrations"]) + shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"]) return True def install_console(): - print("Set up main console turn on") - if not shell.pattern_search(f"{boot_dir}/cmdline.txt", 'fbcon=map:10 fbcon=font:VGA8x8'): - print(f"Updating {boot_dir}/cmdline.txt") - shell.pattern_replace(f"{boot_dir}/cmdline.txt", "rootwait", "rootwait fbcon=map:10 fbcon=font:VGA8x8") - else: - print(f"{boot_dir}/cmdline.txt already updated") + print("Installing console fbcon map helper...") + shell.copy("con2fbmap-helper.sh", "/usr/local/bin/") + shell.chmod("/usr/local/bin/con2fbmap-helper.sh", "+x") + + print("Installing console fbcon map service...") + shell.copy("con2fbmap.service", "/etc/systemd/system/") + shell.run_command("systemctl daemon-reload") + shell.run_command("systemctl restart con2fbmap.service") print("Turning off console blanking") - - # pre-stretch this is what you'd do: - if shell.exists("/etc/kbd/config"): - shell.pattern_replace("/etc/kbd/config", "BLANK_TIME=.*", "BLANK_TIME=0") - - # as of stretch.... - # removing any old version + # removing old versions shell.pattern_replace("/etc/rc.local", '# disable console blanking.*') shell.pattern_replace("/etc/rc.local", 'sudo sh -c "TERM=linux setterm -blank.*') + + # adding new version shell.pattern_replace("/etc/rc.local", '^exit 0', "# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0") + # Set the console font to Terminus 6x12 shell.reconfig("/etc/default/console-setup", "^.*FONTFACE.*$", "FONTFACE=\"Terminus\"") shell.reconfig("/etc/default/console-setup", "^.*FONTSIZE.*$", "FONTSIZE=\"6x12\"") @@ -514,9 +570,15 @@ def install_console(): def uninstall_console(): print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') - print("Screen blanking time reset to 10 minutes") - if shell.exists("/etc/kbd/config"): - shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'BLANK_TIME=0', 'BLANK_TIME=10') + + print("Removing console fbcon map service...") + shell.run_command("systemctl stop con2fbmap.service") + shell.run_command("systemctl disable con2fbmap.service") + shell.remove("/etc/systemd/system/con2fbmap.service") + + print("Removing console fbcon map helper...") + shell.remove("/usr/local/bin/con2fbmap-helper.sh") + shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') return True @@ -568,7 +630,7 @@ def install_fbcp(): shell.bail("Unable to install fbcp unit file") shell.run_command("sudo systemctl enable fbcp.service") - # if there's X11 installed... + # if desktop environment is installed... if shell.exists("/etc/lightdm"): print("Setting raspi-config to boot to desktop w/o login...") shell.run_command("raspi-config nonint do_boot_behaviour B4") @@ -636,19 +698,7 @@ scale = {scale} def install_fbcp_unit(): - shell.write_text_file("/etc/systemd/system/fbcp.service", - """[Unit] -Description=Framebuffer copy utility for PiTFT -After=network.target - -[Service] -Type=simple -ExecStartPre=/bin/sleep 10 -ExecStart=/usr/local/bin/fbcp - -[Install] -WantedBy=multi-user.target -""", append=False) + shell.write_templated_file("/etc/systemd/system/", "templates/fbcp.service") return True def uninstall_fbcp(): @@ -684,7 +734,8 @@ def update_xorg(tinydrm_install=False): if not tinydrm_install and "old_transforms" in pitft_config["touchscreen"]: transform_setting = pitft_config["touchscreen"]["old_transforms"][pitftrot] transform = f"Option \"TransformationMatrix\" \"{transform_setting}\"" - shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ + if shell.exists("/usr/share/X11/xorg.conf.d"): + shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ Section "InputClass" Identifier "{identifier}" MatchProduct "{product}" @@ -804,10 +855,6 @@ Run time of up to 5 minutes. Reboot required! print("Display Type: {}".format(pitft_config["menulabel"])) if is_kernel_upgrade_required(): print("WARNING! WILL UPGRADE YOUR KERNEL TO LATEST") - if "force_x11" in pitft_config and pitft_config["force_x11"] and wayland: - if not interactive or shell.prompt("This display works better with X11, but Wayland is currently running. Use X11 instead? (Recommended)", default="y"): - shell.set_window_manager("x11") - wayland = False if display in [str(x) for x in range(1, len(config) + 1)]: select_display(config[int(display) - 1]) diff --git a/con2fbmap-helper.sh b/con2fbmap-helper.sh new file mode 100644 index 0000000..52a1da6 --- /dev/null +++ b/con2fbmap-helper.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Wait for TFT framebuffer to be ready +echo "Waiting for SPI TFT framebuffer..." + +# Wait up to 30 seconds for /dev/fb0 or /dev/fb1 to appear +for i in {1..300}; do + for fbdev in 0 1; do + if [ -e /dev/fb$fbdev ]; then + echo "Found /dev/fb$fbdev, checking if it's ili9341..." + + # Check if it's actually the ili9341 device + if dmesg | grep -q "ili9341.*fb$fbdev"; then + echo "ili9341 framebuffer ready, mapping console..." + con2fbmap 1 $fbdev + echo "Console mapped to framebuffer $fbdev" + exit 0 + fi + fi + done + sleep 0.1 +done + +echo "Timeout waiting for SPI TFT framebuffer" +exit 1 diff --git a/con2fbmap.service b/con2fbmap.service new file mode 100644 index 0000000..7605709 --- /dev/null +++ b/con2fbmap.service @@ -0,0 +1,14 @@ +[Unit] +Description=Map console to framebuffer for SPI TFT +After=multi-user.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/con2fbmap-helper.sh +RemainAfterExit=yes +StandardOutput=journal +StandardError=journal +TimeoutStartSec=60 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/adafruit-pitft-mipi.py b/old_scripts/adafruit-pitft-mipi.py similarity index 100% rename from adafruit-pitft-mipi.py rename to old_scripts/adafruit-pitft-mipi.py diff --git a/overlays/pitft24v2-tsc2007-overlay.dts b/overlays/pitft24v2-tsc2007-overlay.dts new file mode 100644 index 0000000..7934017 --- /dev/null +++ b/overlays/pitft24v2-tsc2007-overlay.dts @@ -0,0 +1,114 @@ +/* + * Device Tree overlay for Adafruit PiTFT 2.4" resistive touch screen with tsc2007 + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@4 { + target = <&gpio>; + __overlay__ { + adafruit_2_4_tft_pins: adafruit_2_4_tft_pins { + brcm,pins = <25 24 18>; + brcm,function = <1 0 1>; /* out in out*/ + brcm,pull = <0 2 2>; /* none up up */ + }; + }; + }; + + fragment@5 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + cs-gpios = <&gpio 8 1>; + + adafruit_2_4_tft: adafruit_2_4_tft@0{ + compatible = "adafruit,yx240qv29"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&adafruit_2_4_tft_pins>; + + spi-max-frequency = <32000000>; + rotation = <90>; + reset-gpios = <&gpio 23 0>; + dc-gpios = <&gpio 25 0>; + }; + }; + }; + + fragment@7 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + tsc2007: tsc2007@48 { + compatible = "ti,tsc2007"; + reg = <0x48>; + ti,fuzzx = <4>; + ti,fuzzy = <4>; + ti,max-rt = <3700>; + ti,fuzzz = <4>; + ti,x-plate-ohms = <180>; + + panel = <&adafruit_2_4_tft>; + pinctrl-0 = <&adafruit_2_4_tft_pins>; + interrupt-parent = <&gpio>; + interrupts = <24 2>; + gpios = <&gpio 24 1>; + + }; + }; + }; + + fragment@8 { + target-path = "/soc"; + __overlay__ { + backlight: backlight { + compatible = "gpio-backlight"; + pinctrl-0 = <&adafruit_2_4_tft_pins>; + gpios = <&gpio 18 2>; + default-on; + }; + }; + }; + + __overrides__ { + speed = <&adafruit_2_4_tft>,"spi-max-frequency:0"; + rotate = <&adafruit_2_4_tft>,"rotation:0"; + fps = <&adafruit_2_4_tft>,"fps:0"; + debug = <&adafruit_2_4_tft>,"debug:0"; + // xohms = <&tsc2007>,"touchscreen-x-plate-ohms;0"; + // swapxy = <&tsc2007>,"touchscreen-swapped-x-y?"; + }; + +}; diff --git a/templates/95-ftcaptouch.rules b/templates/95-ftcaptouch.rules new file mode 100644 index 0000000..3685669 --- /dev/null +++ b/templates/95-ftcaptouch.rules @@ -0,0 +1,3 @@ +# udev rule for Focaltouch Capactive Touchscreen +SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" diff --git a/templates/95-stmpe.rules b/templates/95-stmpe.rules new file mode 100644 index 0000000..d92b2ef --- /dev/null +++ b/templates/95-stmpe.rules @@ -0,0 +1,2 @@ +udev rule for STMPE Resistive Touchscreen Controller +SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" diff --git a/templates/95-touchmouse.rules b/templates/95-touchmouse.rules new file mode 100644 index 0000000..24cf4a4 --- /dev/null +++ b/templates/95-touchmouse.rules @@ -0,0 +1 @@ +SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" \ No newline at end of file diff --git a/templates/99-tsc2007-touchscreen-drm.rules b/templates/99-tsc2007-touchscreen-drm.rules new file mode 100644 index 0000000..d90e8b9 --- /dev/null +++ b/templates/99-tsc2007-touchscreen-drm.rules @@ -0,0 +1,14 @@ +# udev rules for SPI TFT DRM devices to create reliable symlinks +# Supports ili9341, st7789, and other SPI TFT displays + +# Rule for ili9341 DRM device (and other SPI TFT displays) +SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", DRIVERS=="ili9341", SYMLINK+="dri/spitft" + +# Generic rule for any SPI TFT display on spi0.0 +SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", SUBSYSTEMS=="spi", SYMLINK+="dri/spitft" + +# Rule for st7789 displays (add when needed) +SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", DRIVERS=="st7789", SYMLINK+="dri/spitft" + +# Alternative rule using device path pattern +SUBSYSTEM=="drm", KERNEL=="card*", DEVPATH=="*/spi0/spi0.0/drm/card*", SYMLINK+="dri/spitft" \ No newline at end of file diff --git a/templates/99-tsc2007-touchscreen.rules b/templates/99-tsc2007-touchscreen.rules new file mode 100644 index 0000000..609643c --- /dev/null +++ b/templates/99-tsc2007-touchscreen.rules @@ -0,0 +1,5 @@ +# udev rule for TSC2007 touchscreen to create /dev/input/touchscreen symlink and apply transformation +SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="TSC2007 Touchscreen", SYMLINK+="input/touchscreen", ENV{LIBINPUT_CALIBRATION_MATRIX}="{calibration_matrix}" + +# Backup rule using touchscreen capability and TSC2007 name pattern +SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_TOUCHSCREEN}=="1", ATTRS{name}=="*TSC2007*", SYMLINK+="input/touchscreen", ENV{LIBINPUT_CALIBRATION_MATRIX}="{calibration_matrix}" diff --git a/templates/config_text_base.txt b/templates/config_text_base.txt new file mode 100644 index 0000000..6136bf7 --- /dev/null +++ b/templates/config_text_base.txt @@ -0,0 +1,8 @@ +# --- added by adafruit-pitft-helper {date} --- +[all] +hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! +dtparam=spi=on +dtparam=i2c1=on +dtparam=i2c_arm=on +{overlay} +# --- end adafruit-pitft-helper {date} --- diff --git a/templates/fbcp.service b/templates/fbcp.service new file mode 100644 index 0000000..c39105b --- /dev/null +++ b/templates/fbcp.service @@ -0,0 +1,11 @@ +[Unit] +Description=Framebuffer copy utility for PiTFT +After=network.target + +[Service] +Type=simple +ExecStartPre=/bin/sleep 10 +ExecStart=/usr/local/bin/fbcp + +[Install] +WantedBy=multi-user.target \ No newline at end of file From 292185d7b9f9c4d9e900e2e5939fda3ec2801c05 Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Fri, 18 Jul 2025 15:22:46 -0700 Subject: [PATCH 2/6] Removed old stuff and updated so it's now in a working state --- adafruit-pitft.py | 80 ++++++++++--------- .../con2fbmap-helper.sh | 0 .../con2fbmap.service | 0 3 files changed, 42 insertions(+), 38 deletions(-) rename con2fbmap-helper.sh => templates/con2fbmap-helper.sh (100%) rename con2fbmap.service => templates/con2fbmap.service (100%) diff --git a/adafruit-pitft.py b/adafruit-pitft.py index 3274b16..77877b6 100644 --- a/adafruit-pitft.py +++ b/adafruit-pitft.py @@ -50,6 +50,7 @@ mipi_data (Optional): Dictionary containing MIPI display data. gpio: GPIO settings for the display. viewport: Viewport settings for different rotations. use_kms: Whether to use KMS for the display. +mirror_rotations: Dictionary mapping pitftrot values to DRM rotation values. """ # Touchscreen Products @@ -185,7 +186,7 @@ config = [ }, "width": 240, "height": 240, - "fbcp_rotations": { + "mirror_rotations": { "0": "0", "90": "1", "180": "2", @@ -243,7 +244,7 @@ config = [ }, "width": 240, "height": 135, - "fbcp_rotations": { + "mirror_rotations": { "0": "3", "90": "2", "180": "1", @@ -272,7 +273,7 @@ config = [ }, "width": 240, "height": 240, - "fbcp_rotations": { + "mirror_rotations": { "0": "0", "90": "1", "180": "2", @@ -304,14 +305,14 @@ config = [ }, }, "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", - "overlay_drm_option": "drm", + #"overlay_drm_option": "drm", "width": 320, "height": 240, }, ] # default rotations -fbcp_rotations = { +mirror_rotations = { "0": "1", "90": "0", "180": "3", @@ -332,7 +333,6 @@ pitft_config = None pitftrot = None auto_reboot = None wayland = False -is_bullseye = False def warn_exit(message): shell.warn(message) @@ -343,7 +343,7 @@ def uninstall_cb(ctx, _param, value): return uninstall() -def print_version(ctx, _param, value): +def print_version_cb(ctx, _param, value): if not value or ctx.resilient_parsing: return print("Adafruit PiTFT Helper v{}".format(__version__)) @@ -385,6 +385,8 @@ def softwareinstall(): warn_exit("Apt failed to install TSLIB!") if not shell.run_command("apt-get install -y bc fbi git python3-dev python3-pip python3-smbus python3-spidev evtest libts-bin device-tree-compiler libraspberrypi-dev build-essential python3-evdev"): warn_exit("Apt failed to install software!") + if not shell.run_command("apt-get install -y raspi-config"): + warn_exit("Apt failed to install raspi-config!") return True def uninstall_bootconfigtxt(): @@ -435,7 +437,7 @@ def install_drivers(): if use_mipi_driver(): mipi_data.update(pitft_config['mipi_data']) - if not compile_display_fw(): + if not compile_mipi_fw(): shell.bail("Unable to compile MIPI firmware") if is_kernel_upgrade_required(): @@ -490,6 +492,8 @@ def update_configtxt(rotation_override=None, tinydrm_install=False): if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: overlay += "," + pitft_config["overlay_params"][pitftrot] config_text_base = shell.load_template("templates/config_text_base.txt", date=shell.date(), overlay=overlay) + if config_text_base is None: + shell.bail("Unable to load config_text_base template!") shell.write_text_file(f"{boot_dir}/config.txt", config_text_base) return True @@ -510,7 +514,7 @@ def update_udev(): shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen-drm.rules") return True -def compile_display_fw(): +def compile_mipi_fw(): command_src = "mipi/panel.txt" # We could just copy the file to panel.txt, edit that, then remove it after @@ -539,11 +543,11 @@ def update_pointercal(): def install_console(): print("Installing console fbcon map helper...") - shell.copy("con2fbmap-helper.sh", "/usr/local/bin/") + shell.write_templated_file("/usr/local/bin/", "templates/con2fbmap-helper.sh") shell.chmod("/usr/local/bin/con2fbmap-helper.sh", "+x") print("Installing console fbcon map service...") - shell.copy("con2fbmap.service", "/etc/systemd/system/") + shell.write_templated_file("/etc/systemd/system/", "templates/con2fbmap.service") shell.run_command("systemctl daemon-reload") shell.run_command("systemctl restart con2fbmap.service") @@ -571,20 +575,22 @@ def uninstall_console(): print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') - print("Removing console fbcon map service...") - shell.run_command("systemctl stop con2fbmap.service") - shell.run_command("systemctl disable con2fbmap.service") - shell.remove("/etc/systemd/system/con2fbmap.service") + if shell.exists("/etc/systemd/system/con2fbmap.service"): + print("Removing console fbcon map service...") + shell.run_command("systemctl stop con2fbmap.service") + shell.run_command("systemctl disable con2fbmap.service") + shell.remove("/etc/systemd/system/con2fbmap.service") - print("Removing console fbcon map helper...") - shell.remove("/usr/local/bin/con2fbmap-helper.sh") + if shell.exists("/usr/local/bin/con2fbmap-helper.sh"): + print("Removing console fbcon map helper...") + shell.remove("/usr/local/bin/con2fbmap-helper.sh") shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') return True -def install_fbcp(): - global fbcp_rotations +def install_mirror(): + global mirror_rotations print("Installing cmake...") if not shell.run_command("apt-get --yes --allow-downgrades --allow-remove-essential --allow-change-held-packages install cmake", suppress_message=True): warn_exit("Apt failed to install software!") @@ -609,8 +615,8 @@ def install_fbcp(): shell.popd() shell.run_command("rm -rf /tmp/rpi-fbcp-master") - if "fbcp_rotations" in pitft_config: - fbcp_rotations = pitft_config['fbcp_rotations'] + if "mirror_rotations" in pitft_config: + mirror_rotations = pitft_config['mirror_rotations'] # Start fbcp in the appropriate place, depending on init system: if SYSTEMD: @@ -623,11 +629,11 @@ def install_fbcp(): # Insert fbcp into rc.local before final 'exit 0': shell.pattern_replace("/etc/rc.local", "^exit 0", "/usr/local/bin/fbcp \&\\nexit 0") else: - # Install fbcp systemd unit, first making sure it's not in rc.local: + # Install fbcp systemd service, first making sure it's not in rc.local: uninstall_fbcp_rclocal() - print("We have systemd, so install fbcp systemd unit...") - if not install_fbcp_unit(): - shell.bail("Unable to install fbcp unit file") + print("We have systemd, so install fbcp systemd service...") + if not install_fbcp_service(): + shell.bail("Unable to install fbcp service file") shell.run_command("sudo systemctl enable fbcp.service") # if desktop environment is installed... @@ -644,7 +650,7 @@ def install_fbcp(): shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") # Don't use the 3D driver if we're on X11, unless the display specifically uses kms - if (is_bullseye or not wayland) and ("use_kms" not in pitft_config or not pitft_config["use_kms"]): + if (not wayland) and ("use_kms" not in pitft_config or not pitft_config["use_kms"]): shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-kms-v3d.*$", "#dtoverlay=vc4-kms-v3d") shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-fkms-v3d.*$", "#dtoverlay=vc4-fkms-v3d") @@ -661,18 +667,18 @@ def install_fbcp(): shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_cvt.*$", "hdmi_cvt={} {} 60 1 0 0 0".format(WIDTH, HEIGHT)) try: - default_orientation = int(list(fbcp_rotations.keys())[list(fbcp_rotations.values()).index("0")]) + default_orientation = int(list(mirror_rotations.keys())[list(mirror_rotations.values()).index("0")]) except ValueError: default_orientation = 90 - if fbcp_rotations[pitftrot] == "0": + if mirror_rotations[pitftrot] == "0": # dont rotate HDMI on default orientation shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "") else: - display_rotate = fbcp_rotations[pitftrot] + display_rotate = mirror_rotations[pitftrot] shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "display_hdmi_rotate={}".format(display_rotate)) # Because we rotate HDMI we have to 'unrotate' the TFT by overriding pitftrot! - if not update_configtxt(default_orientation): + if not update_configtxt(rotation=default_orientation): shell.bail(f"Unable to update {boot_dir}/config.txt") return True @@ -697,7 +703,7 @@ scale = {scale} shell.pattern_replace(target_homedir, "^.*[output:SPI-1].*$", "hdmi_force_hotplug=0") -def install_fbcp_unit(): +def install_fbcp_service(): shell.write_templated_file("/etc/systemd/system/", "templates/fbcp.service") return True @@ -716,7 +722,7 @@ def uninstall_fbcp(): shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_mode=87.*$') shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_cvt=.*$') - if not wayland and not is_bullseye and shell.exists("/etc/lightdm"): + if not wayland and shell.exists("/etc/lightdm"): print("Restoring Wayland as default display manager...") shell.set_window_manager("wayland") @@ -806,10 +812,10 @@ if boot_dir is None or not shell.isdir(boot_dir): shell.bail("Unable to find boot directory") if shell.get_raspbian_version() == "bullseye": - is_bullseye = True + shell.bail("Bullseye is not supported by this script. Please update to Bookworm first.") @click.command() -@click.option('-v', '--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="Print version information") +@click.option('-v', '--version', is_flag=True, callback=print_version_cb, expose_value=False, is_eager=True, help="Print version information") @click.option('-u', '--user', nargs=1, default=target_homedir, type=str, help="Specify path of primary user's home directory", show_default=True) @click.option('--display', nargs=1, default=None, help="Specify a display option (1-{}) or type {}".format(len(config), get_config_types())) @click.option('--rotation', nargs=1, default=None, type=int, help="Specify a rotation option (1-4) or degrees {}".format(tuple(sorted([int(x) for x in PITFT_ROTATIONS])))) @@ -830,8 +836,6 @@ def main(user, display, rotation, install_type, reboot, boot): print(f"{boot} not found or not a directory. Using {boot_dir} instead.") wayland = is_wayland() print(("Wayland" if wayland else "X11") + " Detected") - if is_bullseye: - print("Bullseye Detected") print() # "mirror" will be the new install type, but keep fbcp for backwards compatibility if install_type == "fbcp": @@ -943,7 +947,7 @@ restart the script and choose a different orientation.""".format(rotation=pitftr shell.bail("Unable to install display drivers") shell.info(f"Updating {boot_dir}/config.txt...") - if not update_configtxt(tinydrm_install=(not is_bullseye)): + if not update_configtxt(tinydrm_install=True): shell.bail(f"Unable to update {boot_dir}/config.txt") if "touchscreen" in pitft_config: @@ -977,7 +981,7 @@ restart the script and choose a different orientation.""".format(rotation=pitftr update_wayfire_settings() else: shell.info("Adding FBCP support...") - if not install_fbcp(): + if not install_mirror(): shell.bail("Unable to configure fbcp") if shell.exists("/etc/lightdm"): diff --git a/con2fbmap-helper.sh b/templates/con2fbmap-helper.sh similarity index 100% rename from con2fbmap-helper.sh rename to templates/con2fbmap-helper.sh diff --git a/con2fbmap.service b/templates/con2fbmap.service similarity index 100% rename from con2fbmap.service rename to templates/con2fbmap.service From d14339d748a250029c43802fe2158c27d467c13f Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Sat, 26 Jul 2025 12:18:38 -0700 Subject: [PATCH 3/6] Fixed console mode, touch now working --- adafruit-pitft.py | 111 +++++++----------- ...hscreen-drm.rules => 99-spi-tft-drm.rules} | 0 2 files changed, 41 insertions(+), 70 deletions(-) rename templates/{99-tsc2007-touchscreen-drm.rules => 99-spi-tft-drm.rules} (100%) diff --git a/adafruit-pitft.py b/adafruit-pitft.py index 77877b6..a0b8b66 100644 --- a/adafruit-pitft.py +++ b/adafruit-pitft.py @@ -49,7 +49,6 @@ mipi_data (Optional): Dictionary containing MIPI display data. command_bin: Name of the command binary for MIPI. gpio: GPIO settings for the display. viewport: Viewport settings for different rotations. -use_kms: Whether to use KMS for the display. mirror_rotations: Dictionary mapping pitftrot values to DRM rotation values. """ @@ -74,18 +73,18 @@ config = [ "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", }, + "calibrations": { + "0": "5724 -6 -1330074 26 8427 -1034528 65536", + "90": "5 8425 -978304 -5747 61 22119468 65536", + "180": "-5682 -1 22069150 13 -8452 32437698 65536", + "270": "3 -8466 32440206 5703 -1 -1308696 65536", + }, "overlay_params": { "0": None, "90": "touch-swapxy,touch-invx", "180": "touch-invx,touch-invy", "270": "touch-swapxy,touch-invy", }, - "calibrations": { - "0": "4232 11 -879396 1 5786 -752768 65536", - "90": "33 -5782 21364572 4221 35 -1006432 65536", - "180": "-4273 61 16441290 4 -5772 21627524 65536", - "270": "-9 5786 -784608 -4302 19 16620508 65536", - }, }, "overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30", "overlay_drm_option": "drm", @@ -151,12 +150,6 @@ config = [ "180": "touch-invx,touch-invy", "270": "touch-swapxy,touch-invy", }, - "calibrations": { - "0": "5724 -6 -1330074 26 8427 -1034528 65536", - "90": "5 8425 -978304 -5747 61 22119468 65536", - "180": "-5682 -1 22069150 13 -8452 32437698 65536", - "270": "3 -8466 32440206 5703 -1 -1308696 65536", - }, }, "overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20", "overlay_drm_option": "drm", @@ -173,7 +166,6 @@ config = [ "overlay_src": "overlays/minipitft13-overlay.dts", "overlay_dest": "{boot_dir}/overlays/drm-minipitft13.dtbo", "overlay": "dtoverlay=drm-minipitft13,rotate={pitftrot},fps=60", - "use_kms": True, "mipi_data": { "command_bin": "adafruit_st7789_drm", "gpio": "dc-gpio=25,backlight-gpio=22", @@ -202,7 +194,6 @@ config = [ "overlay_src": "overlays/st7789v_240x320-overlay.dts", "overlay_dest": "{boot_dir}/overlays/drm-st7789v_240x320.dtbo", "overlay": "dtoverlay=drm-st7789v_240x320,rotate={pitftrot},fps=30", - "use_kms": True, "mipi_data": { "command_bin": "adafruit_st7789_drm", "gpio": "dc-gpio=25,backlight-gpio=22", @@ -225,7 +216,6 @@ config = [ "overlay_src": "overlays/minipitft114-overlay.dts", "overlay_dest": "{boot_dir}/overlays/drm-minipitft114.dtbo", "overlay": "dtoverlay=drm-minipitft114,rotate={pitftrot},fps=60", - "use_kms": True, "mipi_data": { "command_bin": "adafruit_st7789_drm", "gpio": "dc-gpio=25,backlight-gpio=22", @@ -260,7 +250,6 @@ config = [ "overlay_src": "overlays/tftbonnet13-overlay.dts", "overlay_dest": "{boot_dir}/overlays/drm-tftbonnet13.dtbo", "overlay": "dtoverlay=drm-tftbonnet13,rotate={pitftrot},fps=60", - "use_kms": True, "mipi_data": { "command_bin": "adafruit_st7789_drm", "gpio": "dc-gpio=25,backlight-gpio=26", @@ -297,12 +286,6 @@ config = [ "180": "1 0 0 0 1 0", "270": "0 -1 1 1 0 0", }, - "calibrations": { - "0": "4232 11 -879396 1 5786 -752768 65536", - "90": "33 -5782 21364572 4221 35 -1006432 65536", - "180": "-4273 61 16441290 4 -5772 21627524 65536", - "270": "-9 5786 -784608 -4302 19 16620508 65536", - }, }, "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", #"overlay_drm_option": "drm", @@ -332,7 +315,7 @@ REMOVE_KERNEL_PINNING = False pitft_config = None pitftrot = None auto_reboot = None -wayland = False +is_desktop = False def warn_exit(message): shell.warn(message) @@ -360,8 +343,8 @@ def sysupdate(): if not UPDATE_DB: print("Updating apt indexes...", end='') progress(3) - if not shell.run_command('sudo apt update', suppress_message=True): - warn_exit("Apt failed to update indexes! Try running 'sudo apt update' manually.") + if not shell.run_command('sudo apt-get update', suppress_message=True): + warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.") if not shell.run_command('sudo apt-get update', suppress_message=True): warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.") print("Reading package lists...") @@ -371,12 +354,6 @@ def sysupdate(): ############################ Sub-Scripts ############################ -def is_wayland(): - "Check if the current session is Wayland" - username = os.environ["SUDO_USER"] - output = shell.run_command("loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | grep wayland", suppress_message=True, return_output=True, run_as_user=username).strip() - return len(output) > 0 - def softwareinstall(): print("Installing Pre-requisite Software...This may take a few minutes!") if not shell.run_command("apt-get install -y libts0", suppress_message=True): @@ -487,7 +464,7 @@ def update_configtxt(rotation_override=None, tinydrm_install=False): if "gpio" in mipi_data: overlay += f"\ndtparam={mipi_data['gpio']}" - if tinydrm_install: # Wayland ignores X11 Transformations, so use overlay params instead + if tinydrm_install: # Use overlay params for Wayland overlay += ",drm" if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: overlay += "," + pitft_config["overlay_params"][pitftrot] @@ -511,7 +488,7 @@ def update_udev(): if "transforms" in pitft_config["touchscreen"] and pitftrot in pitft_config["touchscreen"]["transforms"]: calibration_matrix = pitft_config["touchscreen"]["transforms"][pitftrot] shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen.rules", calibration_matrix=calibration_matrix) - shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen-drm.rules") + shell.write_templated_file("/etc/udev/rules.d/", "templates/99-spi-tft-drm.rules") return True def compile_mipi_fw(): @@ -536,9 +513,11 @@ def compile_mipi_fw(): def update_pointercal(): if "touchscreen" in pitft_config and "calibrations" in pitft_config["touchscreen"]: if isinstance(pitft_config["touchscreen"]["calibrations"], dict): - shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"][pitftrot]) + shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"][pitftrot], append=False) else: - shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"]) + shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"], append=False) + else: + shell.remove("/etc/pointercal") return True def install_console(): @@ -572,9 +551,18 @@ def install_console(): return True def uninstall_console(): + global is_desktop + print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') + if is_desktop: + print("Restoring Desktop Environment...") + shell.run_command("apt-get -y install rpd-plym-splash") # Install Splash Screen + shell.run_command("raspi-config nonint do_boot_splash 0") # Enable Splash Screen + shell.run_command("apt-get -y install raspberrypi-ui-mods") # Reinstall Raspberry Pi OS UI mods + shell.run_command("raspi-config nonint do_boot_target B2") # Boot to Desktop + if shell.exists("/etc/systemd/system/con2fbmap.service"): print("Removing console fbcon map service...") shell.run_command("systemctl stop con2fbmap.service") @@ -585,8 +573,9 @@ def uninstall_console(): print("Removing console fbcon map helper...") shell.remove("/usr/local/bin/con2fbmap-helper.sh") - shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') - shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') + if shell.exists("/etc/rc.local"): + shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') + shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') return True def install_mirror(): @@ -637,7 +626,7 @@ def install_mirror(): shell.run_command("sudo systemctl enable fbcp.service") # if desktop environment is installed... - if shell.exists("/etc/lightdm"): + if is_desktop: print("Setting raspi-config to boot to desktop w/o login...") shell.run_command("raspi-config nonint do_boot_behaviour B4") @@ -649,14 +638,9 @@ def install_mirror(): shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_group.*$", "hdmi_group=2") shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") - # Don't use the 3D driver if we're on X11, unless the display specifically uses kms - if (not wayland) and ("use_kms" not in pitft_config or not pitft_config["use_kms"]): - shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-kms-v3d.*$", "#dtoverlay=vc4-kms-v3d") - shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-fkms-v3d.*$", "#dtoverlay=vc4-fkms-v3d") - # if there's X11 installed... scale = 1 - if shell.exists("/etc/lightdm"): + if is_desktop: if "x11_scale" in pitft_config: scale = pitft_config["x11_scale"] else: @@ -722,10 +706,6 @@ def uninstall_fbcp(): shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_mode=87.*$') shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_cvt=.*$') - if not wayland and shell.exists("/etc/lightdm"): - print("Restoring Wayland as default display manager...") - shell.set_window_manager("wayland") - return True def uninstall_fbcp_rclocal(): @@ -734,11 +714,9 @@ def uninstall_fbcp_rclocal(): shell.pattern_replace("/etc/rc.local", '^.*fbcp.*$') return True -def update_xorg(tinydrm_install=False): +def update_xorg(): if "touchscreen" in pitft_config: transform_setting = pitft_config["touchscreen"]["transforms"][pitftrot] - if not tinydrm_install and "old_transforms" in pitft_config["touchscreen"]: - transform_setting = pitft_config["touchscreen"]["old_transforms"][pitftrot] transform = f"Option \"TransformationMatrix\" \"{transform_setting}\"" if shell.exists("/usr/share/X11/xorg.conf.d"): shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ @@ -775,7 +753,6 @@ def uninstall(): uninstall_bootconfigtxt() uninstall_console() uninstall_fbcp() - uninstall_fbcp_rclocal() uninstall_etc_modules() success() @@ -823,7 +800,7 @@ if shell.get_raspbian_version() == "bullseye": @click.option('--reboot', nargs=1, default=None, type=click.Choice(['yes', 'no']), help="Specify whether to reboot after the script is finished") @click.option('--boot', nargs=1, default=boot_dir, type=str, help="Specify the boot directory", show_default=True) def main(user, display, rotation, install_type, reboot, boot): - global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, wayland + global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, is_desktop shell.clear() if user != target_homedir: target_homedir = user @@ -834,8 +811,9 @@ def main(user, display, rotation, install_type, reboot, boot): print(f"Boot dir = {boot_dir}") else: print(f"{boot} not found or not a directory. Using {boot_dir} instead.") - wayland = is_wayland() - print(("Wayland" if wayland else "X11") + " Detected") + # Check if we are running on a desktop environment or lite + is_desktop = shell.exists("/etc/lightdm") + print("Running on a {} environment".format("desktop" if is_desktop else "lite")) print() # "mirror" will be the new install type, but keep fbcp for backwards compatibility if install_type == "fbcp": @@ -854,7 +832,7 @@ Run time of up to 5 minutes. Reboot required! uninstall() def select_display(config, interactive=False): - global pitft_config, wayland + global pitft_config pitft_config = config print("Display Type: {}".format(pitft_config["menulabel"])) if is_kernel_upgrade_required(): @@ -971,22 +949,15 @@ restart the script and choose a different orientation.""".format(rotation=pitftr if not uninstall_console(): shell.bail("Unable to configure console") - mirror_prompt = "Would you like the HDMI display to mirror to the PiTFT display?" - if wayland: - # With wayland, PiTFT shows up as an additional display rather than a mirror - mirror_prompt = "Would you like the to use the PiTFT as a desktop display?" + # With wayland, PiTFT shows up as an additional display rather than a mirror + mirror_prompt = "Would you like the to use the PiTFT as a desktop display?" if install_type == "mirror" or (install_type is None and shell.prompt(mirror_prompt)): - if wayland: - shell.info("Updating Wayland desktop settings...") - update_wayfire_settings() - else: - shell.info("Adding FBCP support...") - if not install_mirror(): - shell.bail("Unable to configure fbcp") + shell.info("Updating Wayland desktop settings...") + update_wayfire_settings() - if shell.exists("/etc/lightdm"): + if is_desktop: shell.info("Updating Desktop Touch calibration...") - if not update_xorg(tinydrm_install=wayland): + if not update_xorg(): shell.bail("Unable to update calibration") else: if not uninstall_fbcp(): diff --git a/templates/99-tsc2007-touchscreen-drm.rules b/templates/99-spi-tft-drm.rules similarity index 100% rename from templates/99-tsc2007-touchscreen-drm.rules rename to templates/99-spi-tft-drm.rules From 7ba3f50d36e6db650192d242c985d2a8846acd3f Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Wed, 6 Aug 2025 14:59:03 -0700 Subject: [PATCH 4/6] Add labwc support --- adafruit-pitft.py | 170 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 120 insertions(+), 50 deletions(-) diff --git a/adafruit-pitft.py b/adafruit-pitft.py index a0b8b66..857f845 100644 --- a/adafruit-pitft.py +++ b/adafruit-pitft.py @@ -7,6 +7,9 @@ Written in Python by Melissa LeBlanc-Williams for Adafruit Industries import time import os +import glob +import re + try: import click except ImportError: @@ -58,6 +61,27 @@ TS_TSC2007 = "tsc2007" TS_FOCALTOUCH = "EP0110M09" config = [ + { + "type": "24rv2", + "menulabel": "PiTFT 2.4\" V2 resistive (240x320)", + "product": "2.4\" V2 resistive", + "kernel_upgrade": False, + "overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/pitft24rv2.dtbo", + "touchscreen": { + "identifier": "TSC2007 Touchscreen Calibration", + "product": TS_TSC2007, + "transforms": { + "0": "-1 0 1 0 -1 1", + "90": "0 1 0 -1 0 1", + "180": "1 0 0 0 1 0", + "270": "0 -1 1 1 0 0", + }, + }, + "overlay": "dtoverlay=pitft24rv2,rotate={pitftrot},fps=60", + "width": 320, + "height": 240, + }, { "type": "28r", "menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)", @@ -155,7 +179,7 @@ config = [ "overlay_drm_option": "drm", "width": 480, "height": 320, - "x11_scale": 1.5, + "display_scale": 1.5, }, { "type": "st7789_240x240", @@ -269,29 +293,6 @@ config = [ "270": "3", }, }, - { - "type": "24v2", - "menulabel": "PiTFT 2.4\" V2 resistive (240x320)", - "product": "2.4\" V2 resistive", - "kernel_upgrade": False, - "overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts", - "overlay_dest": "{boot_dir}/overlays/pitft24v2.dtbo", - "touchscreen": { - "identifier": "TSC2007 Touchscreen Calibration", - "product": TS_TSC2007, - "transforms": { - # TODO: Test because 0 and 180 may be reversed - "0": "-1 0 1 0 -1 1", - "90": "0 1 0 -1 0 1", - "180": "1 0 0 0 1 0", - "270": "0 -1 1 1 0 0", - }, - }, - "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", - #"overlay_drm_option": "drm", - "width": 320, - "height": 240, - }, ] # default rotations @@ -308,6 +309,12 @@ mipi_data = { "spi": "spi0-0", } +install_types = { + "mirror": "Setup PiTFT as desktop display (mirror)", + "console": "Display console on PiTFT (console)", + "drivers": "Install Drivers only" +} + PITFT_ROTATIONS = ("90", "180", "270", "0") UPDATE_DB = False SYSTEMD = None @@ -316,6 +323,7 @@ pitft_config = None pitftrot = None auto_reboot = None is_desktop = False +manager = None def warn_exit(message): shell.warn(message) @@ -551,8 +559,6 @@ def install_console(): return True def uninstall_console(): - global is_desktop - print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') @@ -638,11 +644,11 @@ def install_mirror(): shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_group.*$", "hdmi_group=2") shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") - # if there's X11 installed... + # if using the desktop version, update driver scale... scale = 1 if is_desktop: - if "x11_scale" in pitft_config: - scale = pitft_config["x11_scale"] + if "display_scale" in pitft_config: + scale = pitft_config["display_scale"] else: scale = 2 WIDTH = int(pitft_config['width'] * scale) @@ -666,26 +672,68 @@ def install_mirror(): shell.bail(f"Unable to update {boot_dir}/config.txt") return True -def update_wayfire_settings(): +def update_wayland_settings(): # Set the scale factor for Wayland, which is the reciprocal of the X11 scale factor - if "x11_scale" in pitft_config: - scale = 1/pitft_config["x11_scale"] + if "display_scale" in pitft_config: + scale = 1/pitft_config["display_scale"] else: - scale = 0.5 + scale = 0.5 # Default Scale device_name = "SPI-1" - wayfire_config = f"{target_homedir}/.config/wayfire.ini" - # Remove any existing settings previously added by this script - shell.pattern_replace(wayfire_config, '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) date = shell.date() - shell.write_text_file(wayfire_config, f""" + + if manager == "wayfire": + ### WAYFIRE ### + wayfire_config = f"{target_homedir}/.config/wayfire.ini" + # Remove any existing settings previously added by this script + shell.pattern_replace(wayfire_config, '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) + shell.write_text_file(wayfire_config, f""" # --- added by adafruit-pitft-helper {date} --- [output:{device_name}] scale = {scale} # --- end adafruit-pitft-helper {date} --- """) - shell.pattern_replace(target_homedir, "^.*[output:SPI-1].*$", "hdmi_force_hotplug=0") + elif manager == "labwc": + ### LABWC (Using Kanshi) ### + # See https://man.archlinux.org/man/kanshi.5.en for more information on kanshi config files + labwc_config = f"{target_homedir}/.config/kanshi/config" + # If the config file doesn't exist or doesn't contain profile, enumerate the devices and create a new profile + if not shell.exists(labwc_config) or not shell.pattern_search(labwc_config, "^profile"): + drm_devices = get_drm_devices(connected_only=True) + profile_content = "profile {" + for device in drm_devices: + device_name = device["name"] + mode = device["modes"][0] + device_refresh = "@60.000" + device_scale = "" + if device_name == "SPI-1": + device_refresh = "" # For SPI displays, we don't specify refresh rate + device_scale = f" scale {scale}" + + profile_content += f"\n\t\toutput {device_name} enable mode {mode}{device_refresh} transform normal{device_scale}" + profile_content += "\n}\n" + shell.write_text_file(labwc_config, profile_content, append=True) + else: + profiles = shell.pattern_search(labwc_config, r"(profile(?: .*)?) {([\S\s]*?)}", multi_line=True, return_match=True, find_all=True) + if profiles: + for profile in profiles: + profile_name = profile[0].strip() + profile_content = profile[1].strip() + # Check if the profile already has an output section for the device + if not shell.pattern_search(profile_content, f"(output {device_name}.*)"): + # If not, add the output section for the device + width, height = pitft_config["width"], pitft_config["height"] + new_output_device = f"\t\toutput {device_name} enable mode {width}x{height} transform normal scale {scale}\n" + # insert the new output device line into the profile content + profile_content += new_output_device + # Replace the profile with the updated content + shell.pattern_replace(labwc_config, f"^{profile_name} {{([\S\s]*?)}}", f"{profile_name}: {{{profile_content}}}", multi_line=True) + + # Remove scaling from any existing scale settings for the device + shell.pattern_replace(labwc_config, f"(output {device_name}.*) scale [0-9](?:\.[0-9]+)?(.*)", r"\1\2") + # Add new scale to the end of the line + shell.pattern_replace(labwc_config, f"(output {device_name}.*)", r"\1 scale " + f"{scale}") def install_fbcp_service(): shell.write_templated_file("/etc/systemd/system/", "templates/fbcp.service") @@ -771,6 +819,24 @@ Settings take effect on next boot. shell.reboot() shell.exit() +def get_drm_devices(connected_only=False): + # get all drm devices from /sys/class/drm e.g. card1-HDMI-A-1, card2-SPI-1, etc. + devices = [] + for item in glob.glob("/sys/class/drm/card?-*"): + status = shell.read_text_file(f"{item}/status").strip() + if connected_only and status != "connected": + continue + modes = shell.read_text_file(f"{item}/modes").strip().splitlines() + # remove duplicate modes + modes = list(dict.fromkeys(modes)) + info = { + "name": item.split("-", 1)[1], + "status": status, + "modes": modes, + } + devices.append(info) + return devices + ####################################################### MAIN target_homedir = "/home/pi" username = os.environ["SUDO_USER"] @@ -800,7 +866,7 @@ if shell.get_raspbian_version() == "bullseye": @click.option('--reboot', nargs=1, default=None, type=click.Choice(['yes', 'no']), help="Specify whether to reboot after the script is finished") @click.option('--boot', nargs=1, default=boot_dir, type=str, help="Specify the boot directory", show_default=True) def main(user, display, rotation, install_type, reboot, boot): - global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, is_desktop + global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, is_desktop, manager, SYSTEMD shell.clear() if user != target_homedir: target_homedir = user @@ -814,10 +880,12 @@ def main(user, display, rotation, install_type, reboot, boot): # Check if we are running on a desktop environment or lite is_desktop = shell.exists("/etc/lightdm") print("Running on a {} environment".format("desktop" if is_desktop else "lite")) + if is_desktop: + manager = shell.get_window_manager() + if manager is None: + shell.bail("Unable to determine desktop manager. Please run this script in a terminal emulator, not from the desktop environment.") + print("Desktop manager: {}".format(manager)) print() - # "mirror" will be the new install type, but keep fbcp for backwards compatibility - if install_type == "fbcp": - install_type = "mirror" print("""This script downloads and installs PiTFT Support using userspace touch @@ -830,7 +898,6 @@ Run time of up to 5 minutes. Reboot required! if install_type == "uninstall": uninstall() - def select_display(config, interactive=False): global pitft_config pitft_config = config @@ -880,6 +947,11 @@ Run time of up to 5 minutes. Reboot required! shell.bail("""Unfortunately {rotation} degrees for the {display} is not working at this time. Please restart the script and choose a different orientation.""".format(rotation=pitftrot, display=pitft_config["menulabel"])) + if install_type is None: + # Show a selection menu for install_types using shell.select_n and setting the selection to the key of install_types + install_selection = shell.select_n("Select install type:", install_types.values()) + install_type = list(install_types.keys())[install_selection - 1] + update_wayland_settings() if REMOVE_KERNEL_PINNING: # Checking if kernel is pinned if shell.exists('/etc/apt/preferences.d/99-adafruit-pin-kernel'): @@ -938,7 +1010,7 @@ restart the script and choose a different orientation.""".format(rotation=pitftr shell.bail("Unable to update /etc/pointercal") # ask for console access - if install_type == "console" or (install_type is None and shell.prompt("Would you like the console to appear on the PiTFT display?")): + if install_type == "console": shell.info("Updating console to PiTFT...") if not uninstall_fbcp(): shell.bail("Unable to uninstall fbcp") @@ -947,15 +1019,13 @@ restart the script and choose a different orientation.""".format(rotation=pitftr else: shell.info("Making sure console doesn't use PiTFT") if not uninstall_console(): - shell.bail("Unable to configure console") + shell.bail("Unable to uninstall console") # With wayland, PiTFT shows up as an additional display rather than a mirror - mirror_prompt = "Would you like the to use the PiTFT as a desktop display?" - if install_type == "mirror" or (install_type is None and shell.prompt(mirror_prompt)): - shell.info("Updating Wayland desktop settings...") - update_wayfire_settings() - + if install_type == "mirror": if is_desktop: + shell.info("Updating Wayland desktop settings...") + update_wayland_settings() shell.info("Updating Desktop Touch calibration...") if not update_xorg(): shell.bail("Unable to update calibration") From e2af79a70b2b967a504225c6651daa3b789090db Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Wed, 6 Aug 2025 15:02:40 -0700 Subject: [PATCH 5/6] Remove old file --- adafruit-pitft-old.py | 1007 ----------------------------------------- 1 file changed, 1007 deletions(-) delete mode 100644 adafruit-pitft-old.py diff --git a/adafruit-pitft-old.py b/adafruit-pitft-old.py deleted file mode 100644 index d857c84..0000000 --- a/adafruit-pitft-old.py +++ /dev/null @@ -1,1007 +0,0 @@ -""" -Adafruit PiTFT Installer Script -(C) Adafruit Industries, Creative Commons 3.0 - Attribution Share Alike - -Written in Python by Melissa LeBlanc-Williams for Adafruit Industries -""" - -import time -import os -try: - import click -except ImportError: - raise RuntimeError("The library 'Click' was not found. To install, try typing: pip3 install Click") -try: - from adafruit_shell import Shell -except ImportError: - raise RuntimeError("The library 'adafruit_shell' was not found. To install, try typing: pip3 install adafruit-python-shell") - -shell = Shell() -shell.group = 'PITFT' - -__version__ = "3.9.0" - -""" -This is the main configuration. Displays should be placed in the order -they are to appear in the menu. - -Config Parameters: -REQUIRED fields: -type: Unique identifier for the display type. -menulabel: The label to display in the menu. -product: The product name for the display. -kernel_upgrade: Whether the kernel needs to be upgraded for this display. -overlay: The overlay string to apply for the display. -width: Width of the display in pixels. -height: Height of the display in pixels. - -OPTIONAL fields: -touchscreen: Dictionary containing touchscreen calibration data. - identifier: Name of the touchscreen calibration. - product: Product name for the touchscreen. - transforms: Calibration transforms for different rotations. - overlay_params: Overlay parameters for different rotations. -overlay_drm_option: Optional parameter to add for DRM support. -force_x11: Whether to force X11 for the display. -calibrations: Calibration data for the touchscreen. -overlay_src: Source path for the overlay file (if applicable). -overlay_dest: Destination path for the compiled overlay file (if applicable). -mipi_data (Optional): Dictionary containing MIPI display data. - command_bin: Name of the command binary for MIPI. - gpio: GPIO settings for the display. - viewport: Viewport settings for different rotations. -use_kms: Whether to use KMS for the display. -""" -config = [ - { - "type": "28r", - "menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)", - "product": "2.8\" resistive, PID 1601", - "kernel_upgrade": False, - "touchscreen": { - "identifier": "STMPE Touchscreen Calibration", - "product": "stmpe", - "transforms": { - "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", - "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", - "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", - "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", - }, - "overlay_params": { - "0": None, - "90": "touch-swapxy,touch-invx", - "180": "touch-invx,touch-invy", - "270": "touch-swapxy,touch-invy", - }, - }, - "overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30", - "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": { - "0": "4232 11 -879396 1 5786 -752768 65536", - "90": "33 -5782 21364572 4221 35 -1006432 65536", - "180": "-4273 61 16441290 4 -5772 21627524 65536", - "270": "-9 5786 -784608 -4302 19 16620508 65536", - }, - "width": 320, - "height": 240, - }, - { - "type": "22", - "menulabel": "PiTFT 2.2\" no touch", - "product": "2.2\" no touch", - "kernel_upgrade": False, - "overlay": "dtoverlay=pitft22,rotate={pitftrot},speed=64000000,fps=30", - "overlay_drm_option": "drm", - "width": 320, - "height": 240, - }, - { - "type": "28c", - "menulabel": "PiTFT 2.8\" capacitive touch", - "product": "2.8\" capacitive, PID 1983", - "kernel_upgrade": False, - "touchscreen": { - "identifier": "FocalTech Touchscreen Calibration", - "product": "EP0110M09", - "transforms": { - "0": "-1 0 1 0 -1 1 0 0 1", - "90": "0 1 0 -1 0 1 0 0 1", - "180": "1 0 0 0 1 0 0 0 1", - "270": "0 -1 1 1 0 0 0 0 1", - }, - "overlay_params": { - "0": "touch-invx,touch-invy", - "90": "touch-swapxy,touch-invy", - "180": None, - "270": "touch-swapxy,touch-invx", - }, - }, - "overlay": "dtoverlay=pitft28-capacitive,rotate={pitftrot},speed=64000000,fps=30", - "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": "320 65536 0 -65536 0 15728640 65536", - "width": 320, - "height": 240, - }, - { - "type": "35r", - "menulabel": "PiTFT 3.5\" resistive touch", - "product": "3.5\" Resistive", - "kernel_upgrade": False, - "touchscreen": { - "identifier": "STMPE Touchscreen Calibration", - "product": "stmpe", - "transforms": { - "0": "-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1", - "90": "-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1", - "180": "1.102807 0.000030 -0.066352 0.001374 1.085417 -0.027208 0 0 1", - "270": "0.003893 -1.087542 1.025913 1.084281 0.008762 -0.060700 0 0 1", - }, - "overlay_params": { - "0": None, - "90": "touch-swapxy,touch-invx", - "180": "touch-invx,touch-invy", - "270": "touch-swapxy,touch-invy", - }, - }, - "overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20", - "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": { - "0": "5724 -6 -1330074 26 8427 -1034528 65536", - "90": "5 8425 -978304 -5747 61 22119468 65536", - "180": "-5682 -1 22069150 13 -8452 32437698 65536", - "270": "3 -8466 32440206 5703 -1 -1308696 65536", - }, - "width": 480, - "height": 320, - "x11_scale": 1.5, - }, - { - "type": "st7789_240x240", - "menulabel": "PiTFT Mini 1.3\" or 1.54\" display", - "product": "1.54\" or 1.3\" no touch", - "kernel_upgrade": True, - "kernel_module": "fb_st7789v", - "overlay_src": "overlays/minipitft13-overlay.dts", - "overlay_dest": "{boot_dir}/overlays/drm-minipitft13.dtbo", - "overlay": "dtoverlay=drm-minipitft13,rotate={pitftrot},fps=60", - "use_kms": True, - "mipi_data": { - "command_bin": "adafruit_st7789_drm", - "gpio": "dc-gpio=25,backlight-gpio=22", - "viewport": { - "0": "width=240,height=240", - "90": "width=240,height=240,x-offset=80", - "180": "width=240,height=240,y-offset=80", - "270": "width=240,height=240", - }, - }, - "width": 240, - "height": 240, - "fbcp_rotations": { - "0": "0", - "90": "1", - "180": "2", - "270": "3", - }, - }, - { - "type": "st7789_240x320", - "menulabel": "ST7789V 2.0\" no touch", - "product": "2.0\" no touch", - "kernel_upgrade": True, - "kernel_module": "fb_st7789v", - "overlay_src": "overlays/st7789v_240x320-overlay.dts", - "overlay_dest": "{boot_dir}/overlays/drm-st7789v_240x320.dtbo", - "overlay": "dtoverlay=drm-st7789v_240x320,rotate={pitftrot},fps=30", - "use_kms": True, - "mipi_data": { - "command_bin": "adafruit_st7789_drm", - "gpio": "dc-gpio=25,backlight-gpio=22", - "viewport": { - "0": "width=320,height=240", - "90": "width=240,height=320", - "180": "width=320,height=240", - "270": "width=240,height=320", - }, - }, - "width": 320, - "height": 240, - }, - { - "type": "st7789_240x135", - "menulabel": "MiniPiTFT 1.14\" display", - "product": "1.14\" no touch", - "kernel_upgrade": True, - "kernel_module": "fb_st7789v", - "overlay_src": "overlays/minipitft114-overlay.dts", - "overlay_dest": "{boot_dir}/overlays/drm-minipitft114.dtbo", - "overlay": "dtoverlay=drm-minipitft114,rotate={pitftrot},fps=60", - "use_kms": True, - "mipi_data": { - "command_bin": "adafruit_st7789_drm", - "gpio": "dc-gpio=25,backlight-gpio=22", - "viewport": { - "0": "width=136,height=240,x-offset=52,y-offset=40", - "90": "width=240,height=136,y-offset=52,x-offset=40", - "180": "width=136,height=240,x-offset=52,y-offset=40", - "270": "width=240,height=136,y-offset=52,x-offset=40", - }, - }, - "rotations": { - "0": None, - "90": "90", - "180": None, - "270": "270", - }, - "width": 240, - "height": 135, - "fbcp_rotations": { - "0": "3", - "90": "2", - "180": "1", - "270": "0", - }, - }, - { - "type": "st7789v_bonnet_240x240", - "menulabel": "BrainCraft HAT or 1.3\" TFT Bonnet + Joystick", - "product": "1.3\" Joystick", - "kernel_upgrade": True, - "kernel_module": "fb_st7789v", - "overlay_src": "overlays/tftbonnet13-overlay.dts", - "overlay_dest": "{boot_dir}/overlays/drm-tftbonnet13.dtbo", - "overlay": "dtoverlay=drm-tftbonnet13,rotate={pitftrot},fps=60", - "use_kms": True, - "mipi_data": { - "command_bin": "adafruit_st7789_drm", - "gpio": "dc-gpio=25,backlight-gpio=26", - "viewport": { - "0": "width=240,height=240", - "90": "width=240,height=240,x-offset=80", - "180": "width=240,height=240,y-offset=80", - "270": "width=240,height=240", - }, - }, - "width": 240, - "height": 240, - "fbcp_rotations": { - "0": "0", - "90": "1", - "180": "2", - "270": "3", - }, - }, - { - "type": "24v2", - "menulabel": "PiTFT 2.4\" V2 resistive (240x320)", - "product": "2.4\" V2 resistive", - "kernel_upgrade": False, - "overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts", - "overlay_dest": "{boot_dir}/overlays/pitft24v2.dtbo", - "touchscreen": { - "identifier": "STMPE Touchscreen Calibration", - "product": "stmpe", - "transforms": { - "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", - "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", - "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", - "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", - }, - }, - "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", - "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": { - "0": "4232 11 -879396 1 5786 -752768 65536", - "90": "33 -5782 21364572 4221 35 -1006432 65536", - "180": "-4273 61 16441290 4 -5772 21627524 65536", - "270": "-9 5786 -784608 -4302 19 16620508 65536", - }, - "width": 320, - "height": 240, - }, -] - -# default rotations -fbcp_rotations = { - "0": "1", - "90": "0", - "180": "3", - "270": "2", -} - -# default mipi data -mipi_data = { - "speed": 40000000, - "spi": "spi0-0", -} - -PITFT_ROTATIONS = ("90", "180", "270", "0") -UPDATE_DB = False -SYSTEMD = None -REMOVE_KERNEL_PINNING = False -pitft_config = None -pitftrot = None -auto_reboot = None -wayland = False -is_bullseye = False - -def warn_exit(message): - shell.warn(message) - shell.exit(1) - -def uninstall_cb(ctx, param, value): - if not value or ctx.resilient_parsing: - return - uninstall() - -def print_version(ctx, param, value): - if not value or ctx.resilient_parsing: - return - print("Adafruit PiTFT Helper v{}".format(__version__)) - shell.exit(1) - -def progress(ellipsis_count): - for i in range(ellipsis_count): - print("...", end='') - time.sleep(1) - print("") - -def sysupdate(): - global UPDATE_DB - if not UPDATE_DB: - print("Updating apt indexes...", end='') - progress(3) - if not shell.run_command('sudo apt update', suppress_message=True): - warn_exit("Apt failed to update indexes! Try running 'sudo apt update' manually.") - if not shell.run_command('sudo apt-get update', suppress_message=True): - warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.") - print("Reading package lists...") - progress(3) - UPDATE_DB = True - return True - -############################ Sub-Scripts ############################ - -def is_wayland(): - username = os.environ["SUDO_USER"] - output = shell.run_command("loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | grep wayland", suppress_message=True, return_output=True, run_as_user=username).strip() - return len(output) > 0 - -def softwareinstall(): - print("Installing Pre-requisite Software...This may take a few minutes!") - if not shell.run_command("apt-get install -y libts0", suppress_message=True): - if not shell.run_command("apt-get install -y tslib"): - if not shell.run_command("apt-get install -y libts-dev"): - warn_exit("Apt failed to install TSLIB!") - if not shell.run_command("apt-get install -y bc fbi git python3-dev python3-pip python3-smbus python3-spidev evtest libts-bin device-tree-compiler libraspberrypi-dev build-essential python3-evdev"): - warn_exit("Apt failed to install software!") - return True - -def uninstall_bootconfigtxt(): - """Remove any old flexfb/fbtft stuff""" - if shell.pattern_search(f"{boot_dir}/config.txt", "adafruit-pitft-helper"): - print(f"Already have an adafruit-pitft-helper section in {boot_dir}/config.txt.") - print("Removing old section...") - shell.run_command(f"cp {boot_dir}/config.txt {boot_dir}/configtxt.bak") - shell.pattern_replace(f"{boot_dir}/config.txt", '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) - return True - -def uninstall_etc_modules(): - """Remove any old flexfb/fbtft stuff""" - shell.run_command('rm -f /etc/modprobe.d/fbtft.conf') - shell.pattern_replace("/etc/modules", 'spi-bcm2835') - shell.pattern_replace("/etc/modules", 'flexfb') - shell.pattern_replace("/etc/modules", 'fbtft_device') - return True - -def use_mipi_driver(config = None): - """Check if MIPI Overlay is Present""" - if not config: - config = pitft_config - if not "mipi_data" in config: - return False - if not shell.exists(f"{boot_dir}/overlays/mipi-dbi-spi.dtbo"): - return False - return True - -def is_kernel_upgrade_required(config = None): - """Check if kernel upgrade is required""" - if not config: - config = pitft_config - if not config['kernel_upgrade']: - return False - # Only upgrade Kernel if MIPI Driver is not an option - if use_mipi_driver(config): - return False - - return True - -def install_drivers(): - """Compile display driver and overlay if required""" - if "overlay_src" in pitft_config and "overlay_dest" in pitft_config: - print("Compiling Device Tree Overlay") - destination = pitft_config['overlay_dest'].format(boot_dir=boot_dir) - shell.run_command("dtc --warning no-unit_address_vs_reg -I dts -O dtb -o {dest} {src}".format(dest=destination, src=pitft_config['overlay_src'])) - - if use_mipi_driver(): - mipi_data.update(pitft_config['mipi_data']) - if not compile_display_fw(): - shell.bail("Unable to compile MIPI firmware") - - if is_kernel_upgrade_required(): - print("############# UPGRADING KERNEL ###############") - print("Updating packages...") - if not shell.run_command("sudo apt-get update", suppress_message=True): - warn_exit("Apt failed to update itself!") - print("Upgrading packages...") - if not shell.run_command("sudo apt-get -y upgrade"): - warn_exit("Apt failed to install software!") - print("Installing Kernel Headers. This may take a few minutes...") - if not shell.run_command("apt-get install -y raspberrypi-kernel-headers", suppress_message=True): - warn_exit("Apt failed to install software!") - # If the kernel was upgraded, a build folder should exist once it has been loaded - module = pitft_config['kernel_module'] - if not shell.isdir(f"/lib/modules/{shell.release()}/build"): - warn_exit(f"Kernel headers build folder for {shell.release()}, not found. Please reboot now and re-run script!") - print("Compiling and installing display driver...") - shell.pushd("st7789_module") - if not shell.run_command("make"): - warn_exit("Apt failed to compile ST7789V drivers!") - shell.run_command(f"mv /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.ko.xz /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.BACK.xz") - shell.run_command(f"xz -v2 {module}.ko") - shell.run_command(f"mv {module}.ko.xz /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.ko.xz") - shell.popd() - return True - -def update_configtxt(rotation_override=None, tinydrm_install=False): - """update /boot/firmware/config.txt (or equivalent folder) with appropriate values""" - uninstall_bootconfigtxt() - uninstall_etc_modules() - overlay_key = "overlay" - overlay = pitft_config[overlay_key] - if "{pitftrot}" in overlay: - rotation = str(rotation_override) if rotation_override is not None else pitftrot - overlay = overlay.format(pitftrot=rotation) - if use_mipi_driver(): - # Mipi Driver does not work if hdmi_force_hotplug=1 is present - shell.pattern_replace(f"{boot_dir}/config.txt", "hdmi_force_hotplug=1", "hdmi_force_hotplug=0") - # Use the MIPI Overlay instead - overlay = f"dtoverlay=mipi-dbi-spi,{mipi_data['spi']},speed=40000000" - overlay += f"\ndtparam=compatible={mipi_data['command_bin']}\\0panel-mipi-dbi-spi" - viewport = "" - if mipi_data['viewport'][pitftrot] is not None: - viewport = mipi_data['viewport'][pitftrot] - overlay += f"\ndtparam={viewport}" - if "gpio" in mipi_data: - overlay += f"\ndtparam={mipi_data['gpio']}" - - if tinydrm_install and "overlay_drm_option" in pitft_config: - overlay += "," + pitft_config["overlay_drm_option"] - if tinydrm_install: # Wayland ignores X11 Transformations, so use params instead - if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: - overlay += "," + pitft_config["overlay_params"][pitftrot] - shell.write_text_file(f"{boot_dir}/config.txt", """ -# --- added by adafruit-pitft-helper {date} --- -[all] -hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! -dtparam=spi=on -dtparam=i2c1=on -dtparam=i2c_arm=on -{overlay} -# --- end adafruit-pitft-helper {date} --- -""".format(date=shell.date(), overlay=overlay)) - return True - -def update_udev(): - shell.write_text_file("/etc/udev/rules.d/95-touchmouse.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) - shell.write_text_file("/etc/udev/rules.d/95-ftcaptouch.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) - shell.write_text_file("/etc/udev/rules.d/95-stmpe.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) - return True - -def compile_display_fw(): - command_src = "mipi/panel.txt" - - # We could just copy the file to panel.txt, edit that, then remove it after - shell.copy(f"mipi/{mipi_data['command_bin']}.txt", command_src) - - # Make sure each of the lines with "# rotation" starts with exactly 1 # and no more - shell.pattern_replace(command_src, "^#*(.*?# rotation.*?$)", "#\\1") - # Uncomment the one for the rotation we are going for - shell.pattern_replace(command_src, "^#(.*?# rotation " + pitftrot + ".*?$)", "\\1") - # Download the mipi-dbi-cmd script if it doesn't exist - if not shell.exists("mipi-dbi-cmd"): - shell.run_command("wget https://raw.githubusercontent.com/notro/panel-mipi-dbi/main/mipi-dbi-cmd") - shell.run_command("chmod +x mipi-dbi-cmd") - # Run the mipi-dbi-script and output directly to the /lib/firmware folder - shell.run_command(f"./mipi-dbi-cmd /lib/firmware/{mipi_data['command_bin']}.bin mipi/panel.txt") - shell.remove(command_src) - return True - -def update_pointercal(): - if "calibrations" in pitft_config: - if isinstance(pitft_config["calibrations"], dict): - shell.write_text_file("/etc/pointercal", pitft_config["calibrations"][pitftrot]) - else: - shell.write_text_file("/etc/pointercal", pitft_config["calibrations"]) - return True - -def install_console(): - print("Set up main console turn on") - if not shell.pattern_search(f"{boot_dir}/cmdline.txt", 'fbcon=map:10 fbcon=font:VGA8x8'): - print(f"Updating {boot_dir}/cmdline.txt") - shell.pattern_replace(f"{boot_dir}/cmdline.txt", "rootwait", "rootwait fbcon=map:10 fbcon=font:VGA8x8") - else: - print(f"{boot_dir}/cmdline.txt already updated") - - print("Turning off console blanking") - - # pre-stretch this is what you'd do: - if shell.exists("/etc/kbd/config"): - shell.pattern_replace("/etc/kbd/config", "BLANK_TIME=.*", "BLANK_TIME=0") - - # as of stretch.... - # removing any old version - shell.pattern_replace("/etc/rc.local", '# disable console blanking.*') - shell.pattern_replace("/etc/rc.local", 'sudo sh -c "TERM=linux setterm -blank.*') - shell.pattern_replace("/etc/rc.local", '^exit 0', "# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0") - - shell.reconfig("/etc/default/console-setup", "^.*FONTFACE.*$", "FONTFACE=\"Terminus\"") - shell.reconfig("/etc/default/console-setup", "^.*FONTSIZE.*$", "FONTSIZE=\"6x12\"") - - print("Setting raspi-config to boot to console w/o login...") - shell.chdir(target_homedir) - shell.run_command("raspi-config nonint do_boot_behaviour B2") - - # remove fbcp - shell.pattern_replace("/etc/rc.local", "^.*fbcp.*$") - return True - -def uninstall_console(): - print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") - shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') - if shell.exists("/etc/kbd/config"): - print("Screen blanking time reset to 10 minutes") - shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'BLANK_TIME=0', 'BLANK_TIME=10') - shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') - shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') - return True - -def install_fbcp(): - global fbcp_rotations - print("Installing cmake...") - if not shell.run_command("apt-get --yes --allow-downgrades --allow-remove-essential --allow-change-held-packages install cmake", suppress_message=True): - warn_exit("Apt failed to install software!") - print("Downloading rpi-fbcp...") - shell.pushd("/tmp") - shell.run_command("curl -sLO https://github.com/adafruit/rpi-fbcp/archive/master.zip") - print("Uncompressing rpi-fbcp...") - shell.run_command("rm -rf /tmp/rpi-fbcp-master") - if not shell.run_command("unzip master.zip", suppress_message=True): - warn_exit("Failed to uncompress fbcp!") - shell.chdir("rpi-fbcp-master") - shell.run_command("mkdir build") - shell.chdir("build") - print("Building rpi-fbcp...") - shell.write_text_file("../CMakeLists.txt", "\nset (CMAKE_C_FLAGS \"-std=gnu99 ${CMAKE_C_FLAGS}\")") - if not shell.run_command("cmake ..", suppress_message=True): - warn_exit("Failed to cmake fbcp!") - if not shell.run_command("make", suppress_message=True): - warn_exit("Failed to make fbcp!") - print("Installing rpi-fbcp...") - shell.run_command("install fbcp /usr/local/bin/fbcp") - shell.popd() - shell.run_command("rm -rf /tmp/rpi-fbcp-master") - - if "fbcp_rotations" in pitft_config: - fbcp_rotations = pitft_config['fbcp_rotations'] - - # Start fbcp in the appropriate place, depending on init system: - if SYSTEMD: - # Add fbcp to /etc/rc.local: - print("We have sysvinit, so add fbcp to /etc/rc.local...") - if shell.pattern_search("/etc/rc.local", "fbcp"): - # fbcp already in rc.local, but make sure correct: - shell.pattern_replace("/etc/rc.local", "^.*fbcp.*$", "/usr/local/bin/fbcp \&") - else: - # Insert fbcp into rc.local before final 'exit 0': - shell.pattern_replace("/etc/rc.local", "^exit 0", "/usr/local/bin/fbcp \&\\nexit 0") - else: - # Install fbcp systemd unit, first making sure it's not in rc.local: - uninstall_fbcp_rclocal() - print("We have systemd, so install fbcp systemd unit...") - if not install_fbcp_unit(): - shell.bail("Unable to install fbcp unit file") - shell.run_command("sudo systemctl enable fbcp.service") - - # if there's X11 installed... - if shell.exists("/etc/lightdm"): - print("Setting raspi-config to boot to desktop w/o login...") - shell.run_command("raspi-config nonint do_boot_behaviour B4") - - # Disable overscan compensation (use full screen): - shell.run_command("raspi-config nonint do_overscan 1") - # Set up HDMI parameters: - print("Configuring boot/config.txt for forced HDMI") - shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_force_hotplug.*$", "hdmi_force_hotplug=1") - shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_group.*$", "hdmi_group=2") - shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") - - # Don't use the 3D driver if we're on X11, unless the display specifically uses kms - if (is_bullseye or not wayland) and ("use_kms" not in pitft_config or not pitft_config["use_kms"]): - shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-kms-v3d.*$", "#dtoverlay=vc4-kms-v3d") - shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-fkms-v3d.*$", "#dtoverlay=vc4-fkms-v3d") - - # if there's X11 installed... - scale = 1 - if shell.exists("/etc/lightdm"): - if "x11_scale" in pitft_config: - scale = pitft_config["x11_scale"] - else: - scale = 2 - WIDTH = int(pitft_config['width'] * scale) - HEIGHT = int(pitft_config['height'] * scale) - - shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_cvt.*$", "hdmi_cvt={} {} 60 1 0 0 0".format(WIDTH, HEIGHT)) - - try: - default_orientation = int(list(fbcp_rotations.keys())[list(fbcp_rotations.values()).index("0")]) - except ValueError: - default_orientation = 90 - - if fbcp_rotations[pitftrot] == "0": - # dont rotate HDMI on default orientation - shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "") - else: - display_rotate = fbcp_rotations[pitftrot] - shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "display_hdmi_rotate={}".format(display_rotate)) - # Because we rotate HDMI we have to 'unrotate' the TFT by overriding pitftrot! - if not update_configtxt(default_orientation): - shell.bail(f"Unable to update {boot_dir}/config.txt") - return True - -def update_wayfire_settings(): - # Set the scale factor for Wayland, which is the reciprocal of the X11 scale factor - if "x11_scale" in pitft_config: - scale = 1/pitft_config["x11_scale"] - else: - scale = 0.5 - device_name = "SPI-1" - wayfire_config = f"{target_homedir}/.config/wayfire.ini" - # Remove any existing settings previously added by this script - shell.pattern_replace(wayfire_config, '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) - date = shell.date() - shell.write_text_file(wayfire_config, f""" -# --- added by adafruit-pitft-helper {date} --- -[output:{device_name}] -scale = {scale} -# --- end adafruit-pitft-helper {date} --- -""") - - shell.pattern_replace(target_homedir, "^.*[output:SPI-1].*$", "hdmi_force_hotplug=0") - - -def install_fbcp_unit(): - shell.write_text_file("/etc/systemd/system/fbcp.service", - """[Unit] -Description=Framebuffer copy utility for PiTFT -After=network.target - -[Service] -Type=simple -ExecStartPre=/bin/sleep 10 -ExecStart=/usr/local/bin/fbcp - -[Install] -WantedBy=multi-user.target -""", append=False) - return True - -def uninstall_fbcp(): - uninstall_fbcp_rclocal() - # Enable overscan compensation - if shell.exists("/etc/systemd/system/fbcp.service"): - shell.run_command("sudo systemctl disable fbcp.service") - # Set up HDMI parameters: - shell.run_command("raspi-config nonint do_overscan 0") - print("Configuring boot/config.txt for default HDMI") - shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_force_hotplug.*$", "hdmi_force_hotplug=0") - shell.pattern_replace(f"{boot_dir}/config.txt", "^.*#.*dtoverlay=vc4-kms-v3d.*$", "dtoverlay=vc4-kms-v3d") - shell.pattern_replace(f"{boot_dir}/config.txt", "^.*#.*dtoverlay=vc4-fkms-v3d.*$", "dtoverlay=vc4-fkms-v3d") - shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_group=2.*$') - shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_mode=87.*$') - shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_cvt=.*$') - - if not wayland and not is_bullseye and shell.exists("/etc/lightdm"): - print("Restoring Wayland as default display manager...") - shell.set_window_manager("wayland") - - return True - -def uninstall_fbcp_rclocal(): - """Remove fbcp from /etc/rc.local:""" - print("Remove fbcp from /etc/rc.local, if it's there...") - shell.pattern_replace("/etc/rc.local", '^.*fbcp.*$') - return True - -def update_xorg(tinydrm_install=False): - if "touchscreen" in pitft_config: - transform_setting = pitft_config["touchscreen"]["transforms"][pitftrot] - if not tinydrm_install and "old_transforms" in pitft_config["touchscreen"]: - transform_setting = pitft_config["touchscreen"]["old_transforms"][pitftrot] - transform = f"Option \"TransformationMatrix\" \"{transform_setting}\"" - shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ -Section "InputClass" - Identifier "{identifier}" - MatchProduct "{product}" - MatchDevicePath "/dev/input/event*" - Driver "libinput" - {transform} -EndSection -""".format( - identifier=pitft_config["touchscreen"]["identifier"], - product=pitft_config["touchscreen"]["product"], - transform=transform - ), - append=False, - ) - return True - -def get_config_types(): - types = [] - for item in config: - types.append(item["type"]) - return types - -def get_config(type): - for item in config: - if item["type"] == type: - return item - return None - -def uninstall(): - shell.info("Uninstalling PiTFT") - uninstall_bootconfigtxt() - uninstall_console() - uninstall_fbcp() - uninstall_fbcp_rclocal() - uninstall_etc_modules() - success() - -def success(): - global auto_reboot - shell.info("Success!") - print(""" -Settings take effect on next boot. -""") - if auto_reboot is None: - auto_reboot = shell.prompt("REBOOT NOW?", default="y") - if not auto_reboot: - print("Exiting without reboot.") - shell.exit() - print("Reboot started...") - shell.reboot() - shell.exit() - -####################################################### MAIN -target_homedir = "/home/pi" -username = os.environ["SUDO_USER"] -user_homedir = os.path.expanduser(f"~{username}") -if shell.isdir(user_homedir): - target_homedir = user_homedir - -boot_dir = "/boot/firmware" if shell.isdir("/boot/firmware") else "/boot" - -# If neither directory is valid, use shell.get_boot_config() -if not shell.isdir(boot_dir): - boot_dir = os.path.dirname(shell.get_boot_config()) - -# Final safeguard: Ensure boot_dir is actually a directory and not a file path -if boot_dir is None or not shell.isdir(boot_dir): - shell.bail("Unable to find boot directory") - -if shell.get_raspbian_version() == "bullseye": - is_bullseye = True - -@click.command() -@click.option('-v', '--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="Print version information") -@click.option('-u', '--user', nargs=1, default=target_homedir, type=str, help="Specify path of primary user's home directory", show_default=True) -@click.option('--display', nargs=1, default=None, help="Specify a display option (1-{}) or type {}".format(len(config), get_config_types())) -@click.option('--rotation', nargs=1, default=None, type=int, help="Specify a rotation option (1-4) or degrees {}".format(tuple(sorted([int(x) for x in PITFT_ROTATIONS])))) -@click.option('--install-type', nargs=1, default=None, type=click.Choice(['mirror', 'fbcp', 'console', 'uninstall']), help="Installation Type") -@click.option('--reboot', nargs=1, default=None, type=click.Choice(['yes', 'no']), help="Specify whether to reboot after the script is finished") -@click.option('--boot', nargs=1, default=boot_dir, type=str, help="Specify the boot directory", show_default=True) -def main(user, display, rotation, install_type, reboot, boot): - global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, wayland - shell.clear() - if user != target_homedir: - target_homedir = user - print(f"Homedir = {target_homedir}") - if boot != boot_dir: - if shell.isdir(boot): - boot_dir = boot - print(f"Boot dir = {boot_dir}") - else: - print(f"{boot} not found or not a directory. Using {boot_dir} instead.") - wayland = is_wayland() - print(("Wayland" if wayland else "X11") + " Detected") - if is_bullseye: - print("Bullseye Detected") - print() - # "mirror" will be the new install type, but keep fbcp for backwards compatibility - if install_type == "fbcp": - install_type = "mirror" - - print("""This script downloads and installs -PiTFT Support using userspace touch -controls and a DTO for display drawing. -one of several configuration files. -Run time of up to 5 minutes. Reboot required! -""") - if reboot is not None: - auto_reboot = reboot.lower() == 'yes' - - if install_type == "uninstall": - uninstall() - - def select_display(config, interactive=False): - global pitft_config, wayland - pitft_config = config - print("Display Type: {}".format(pitft_config["menulabel"])) - if is_kernel_upgrade_required(): - print("WARNING! WILL UPGRADE YOUR KERNEL TO LATEST") - if "force_x11" in pitft_config and pitft_config["force_x11"] and wayland: - if not interactive or shell.prompt("This display works better with X11, but Wayland is currently running. Use X11 instead? (Recommended)", default="y"): - shell.set_window_manager("x11") - wayland = False - - if display in [str(x) for x in range(1, len(config) + 1)]: - select_display(config[int(display) - 1]) - elif display in get_config_types(): - select_display(get_config(display)) - else: - # Build menu from config - selections = [] - for item in config: - option = "{} ({}x{})".format(item['menulabel'], item['width'], item['height']) - if is_kernel_upgrade_required(item): - option += " - WARNING! WILL UPGRADE YOUR KERNEL TO LATEST" - selections.append(option) - selections.append("Uninstall PiTFT") - selections.append("Quit without installing") - - PITFT_SELECT = shell.select_n("Select configuration:", selections) - if PITFT_SELECT == len(config) + 2: - shell.exit(1) - if PITFT_SELECT == len(config) + 1: - uninstall() - select_display(config[PITFT_SELECT - 1], True) - - if rotation is not None and 1 <= rotation <= 4: - pitftrot = PITFT_ROTATIONS[rotation - 1] - shell.info("Rotation: {}".format(pitftrot)) - elif str(rotation) in PITFT_ROTATIONS: - pitftrot = str(rotation) - shell.info("Rotation: {}".format(pitftrot)) - else: - PITFT_ROTATE = shell.select_n( - "Select rotation:", ( - "90 degrees (landscape)", - "180 degrees (portrait)", - "270 degrees (landscape)", - "0 degrees (portrait)" - )) - pitftrot = PITFT_ROTATIONS[PITFT_ROTATE - 1] - - if 'rotations' in pitft_config and isinstance(pitft_config['rotations'], dict) and pitftrot in pitft_config['rotations'] and pitft_config['rotations'][pitftrot] is None: - shell.bail("""Unfortunately {rotation} degrees for the {display} is not working at this time. Please -restart the script and choose a different orientation.""".format(rotation=pitftrot, display=pitft_config["menulabel"])) - - if REMOVE_KERNEL_PINNING: - # Checking if kernel is pinned - if shell.exists('/etc/apt/preferences.d/99-adafruit-pin-kernel'): - shell.warn("WARNING! Script detected your system is currently pinned to an older kernel. The pin will be removed and your system will be updated.") - if not shell.prompt("Continue?"): - shell.exit() - shell.remove('/etc/apt/preferences.d/99-adafruit-pin-kernel') - - # check init system (technique borrowed from raspi-config): - shell.info('Checking init system...') - if shell.run_command("which systemctl", suppress_message=True) and shell.run_command("systemctl | grep '\-\.mount'", suppress_message=True): - SYSTEMD = True - print("Found systemd") - elif os.path.isfile("/etc/init.d/cron") and not os.path.islink("/etc/init.d/cron"): - SYSTEMD = False - print("Found sysvinit") - else: - shell.bail("Unrecognised init system") - - if shell.grep("boot", "/proc/mounts"): - print("/boot is mounted") - else: - print("/boot must be mounted. if you think it's not, quit here and try: sudo mount /dev/mmcblk0p1 /boot") - if shell.prompt("Continue?"): - print("Proceeding.") - else: - shell.bail("Aborting.") - - if not shell.isdir(target_homedir): - shell.bail("{} must be an existing directory (use -u /home/foo to specify)".format(target_homedir)) - - shell.info("System update") - if not sysupdate(): - shell.bail("Unable to apt-get update") - - shell.info("Installing Python libraries & Software...") - if not softwareinstall(): - shell.bail("Unable to install software") - - if "overlay_src" in pitft_config and "overlay_dest" in pitft_config: - shell.info("Installing display drivers and device tree overlay...") - if not install_drivers(): - shell.bail("Unable to install display drivers") - - shell.info(f"Updating {boot_dir}/config.txt...") - if not update_configtxt(tinydrm_install=(not is_bullseye)): - shell.bail(f"Unable to update {boot_dir}/config.txt") - - if "touchscreen" in pitft_config: - shell.info("Updating SysFS rules for Touchscreen...") - if not update_udev(): - shell.bail("Unable to update /etc/udev/rules.d") - - shell.info("Updating TSLib default calibration...") - if not update_pointercal(): - shell.bail("Unable to update /etc/pointercal") - - # ask for console access - if install_type == "console" or (install_type is None and shell.prompt("Would you like the console to appear on the PiTFT display?")): - shell.info("Updating console to PiTFT...") - if not uninstall_fbcp(): - shell.bail("Unable to uninstall fbcp") - if not install_console(): - shell.bail("Unable to configure console") - else: - shell.info("Making sure console doesn't use PiTFT") - if not uninstall_console(): - shell.bail("Unable to configure console") - - mirror_prompt = "Would you like the HDMI display to mirror to the PiTFT display?" - if wayland: - # With wayland, PiTFT shows up as an additional display rather than a mirror - mirror_prompt = "Would you like the to use the PiTFT as a desktop display?" - if install_type == "mirror" or (install_type is None and shell.prompt(mirror_prompt)): - if wayland: - shell.info("Updating Wayland desktop settings...") - update_wayfire_settings() - else: - shell.info("Adding FBCP support...") - if not install_fbcp(): - shell.bail("Unable to configure fbcp") - - if shell.exists("/etc/lightdm"): - shell.info("Updating Desktop Touch calibration...") - if not update_xorg(tinydrm_install=wayland): - shell.bail("Unable to update calibration") - else: - if not uninstall_fbcp(): - shell.bail("Unable to uninstall fbcp") - success() - -# Main function -if __name__ == "__main__": - shell.require_root() - if shell.is_raspberry_pi_os() and shell.is_kernel_userspace_mismatched() and shell.is_pi5_or_newer(): - shell.bail("Unable to proceed on Pi 5 or newer boards with a with a 32-bit OS. Please reinstall with a 64-bit OS.") - shell.check_kernel_userspace_mismatch() - main() From 2bbb0844ea01547b12a575d4cdb3c077f3e77ca8 Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Wed, 6 Aug 2025 15:04:46 -0700 Subject: [PATCH 6/6] Remove unused import --- adafruit-pitft.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit-pitft.py b/adafruit-pitft.py index 857f845..bd5ca91 100644 --- a/adafruit-pitft.py +++ b/adafruit-pitft.py @@ -8,7 +8,6 @@ Written in Python by Melissa LeBlanc-Williams for Adafruit Industries import time import os import glob -import re try: import click