Add labwc support

This commit is contained in:
Melissa LeBlanc-Williams 2025-08-06 14:59:03 -07:00
parent d14339d748
commit 7ba3f50d36

View file

@ -7,6 +7,9 @@ Written in Python by Melissa LeBlanc-Williams for Adafruit Industries
import time import time
import os import os
import glob
import re
try: try:
import click import click
except ImportError: except ImportError:
@ -58,6 +61,27 @@ TS_TSC2007 = "tsc2007"
TS_FOCALTOUCH = "EP0110M09" TS_FOCALTOUCH = "EP0110M09"
config = [ 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", "type": "28r",
"menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)", "menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)",
@ -155,7 +179,7 @@ config = [
"overlay_drm_option": "drm", "overlay_drm_option": "drm",
"width": 480, "width": 480,
"height": 320, "height": 320,
"x11_scale": 1.5, "display_scale": 1.5,
}, },
{ {
"type": "st7789_240x240", "type": "st7789_240x240",
@ -269,29 +293,6 @@ config = [
"270": "3", "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 # default rotations
@ -308,6 +309,12 @@ mipi_data = {
"spi": "spi0-0", "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") PITFT_ROTATIONS = ("90", "180", "270", "0")
UPDATE_DB = False UPDATE_DB = False
SYSTEMD = None SYSTEMD = None
@ -316,6 +323,7 @@ pitft_config = None
pitftrot = None pitftrot = None
auto_reboot = None auto_reboot = None
is_desktop = False is_desktop = False
manager = None
def warn_exit(message): def warn_exit(message):
shell.warn(message) shell.warn(message)
@ -551,8 +559,6 @@ def install_console():
return True return True
def uninstall_console(): def uninstall_console():
global is_desktop
print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") 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') 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_group.*$", "hdmi_group=2")
shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") 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 scale = 1
if is_desktop: if is_desktop:
if "x11_scale" in pitft_config: if "display_scale" in pitft_config:
scale = pitft_config["x11_scale"] scale = pitft_config["display_scale"]
else: else:
scale = 2 scale = 2
WIDTH = int(pitft_config['width'] * scale) WIDTH = int(pitft_config['width'] * scale)
@ -666,17 +672,20 @@ def install_mirror():
shell.bail(f"Unable to update {boot_dir}/config.txt") shell.bail(f"Unable to update {boot_dir}/config.txt")
return True 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 # Set the scale factor for Wayland, which is the reciprocal of the X11 scale factor
if "x11_scale" in pitft_config: if "display_scale" in pitft_config:
scale = 1/pitft_config["x11_scale"] scale = 1/pitft_config["display_scale"]
else: else:
scale = 0.5 scale = 0.5 # Default Scale
device_name = "SPI-1" device_name = "SPI-1"
date = shell.date()
if manager == "wayfire":
### WAYFIRE ###
wayfire_config = f"{target_homedir}/.config/wayfire.ini" wayfire_config = f"{target_homedir}/.config/wayfire.ini"
# Remove any existing settings previously added by this script # 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.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""" shell.write_text_file(wayfire_config, f"""
# --- added by adafruit-pitft-helper {date} --- # --- added by adafruit-pitft-helper {date} ---
[output:{device_name}] [output:{device_name}]
@ -684,8 +693,47 @@ scale = {scale}
# --- end adafruit-pitft-helper {date} --- # --- 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(): def install_fbcp_service():
shell.write_templated_file("/etc/systemd/system/", "templates/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.reboot()
shell.exit() 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 ####################################################### MAIN
target_homedir = "/home/pi" target_homedir = "/home/pi"
username = os.environ["SUDO_USER"] 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('--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) @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): 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() shell.clear()
if user != target_homedir: if user != target_homedir:
target_homedir = user 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 # Check if we are running on a desktop environment or lite
is_desktop = shell.exists("/etc/lightdm") is_desktop = shell.exists("/etc/lightdm")
print("Running on a {} environment".format("desktop" if is_desktop else "lite")) 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() 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 print("""This script downloads and installs
PiTFT Support using userspace touch PiTFT Support using userspace touch
@ -830,7 +898,6 @@ Run time of up to 5 minutes. Reboot required!
if install_type == "uninstall": if install_type == "uninstall":
uninstall() uninstall()
def select_display(config, interactive=False): def select_display(config, interactive=False):
global pitft_config global pitft_config
pitft_config = 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 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"])) 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: if REMOVE_KERNEL_PINNING:
# Checking if kernel is pinned # Checking if kernel is pinned
if shell.exists('/etc/apt/preferences.d/99-adafruit-pin-kernel'): 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") shell.bail("Unable to update /etc/pointercal")
# ask for console access # 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...") shell.info("Updating console to PiTFT...")
if not uninstall_fbcp(): if not uninstall_fbcp():
shell.bail("Unable to uninstall fbcp") shell.bail("Unable to uninstall fbcp")
@ -947,15 +1019,13 @@ restart the script and choose a different orientation.""".format(rotation=pitftr
else: else:
shell.info("Making sure console doesn't use PiTFT") shell.info("Making sure console doesn't use PiTFT")
if not uninstall_console(): 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 # 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":
if install_type == "mirror" or (install_type is None and shell.prompt(mirror_prompt)):
shell.info("Updating Wayland desktop settings...")
update_wayfire_settings()
if is_desktop: if is_desktop:
shell.info("Updating Wayland desktop settings...")
update_wayland_settings()
shell.info("Updating Desktop Touch calibration...") shell.info("Updating Desktop Touch calibration...")
if not update_xorg(): if not update_xorg():
shell.bail("Unable to update calibration") shell.bail("Unable to update calibration")