Compare commits
20 commits
36626af858
...
205d655dc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
205d655dc4 | ||
|
|
6d1665b6ee | ||
|
|
675143d56d | ||
|
|
49ecec7b14 | ||
|
|
9f58e6b912 | ||
|
|
4c629abbee | ||
|
|
ecda53b0cf | ||
|
|
fea27725f7 | ||
|
|
96fe7ddc7b | ||
|
|
d58747960f | ||
|
|
469fc2a1b9 | ||
|
|
16a50d8a4d | ||
|
|
b54c7e0be7 | ||
|
|
1f776f3b4d | ||
|
|
eee9a86c08 | ||
|
|
8679a4dafa | ||
|
|
7efd046d44 | ||
|
|
160edc8d61 | ||
|
|
4151d3b80c | ||
|
|
0699a56feb |
7 changed files with 254 additions and 76 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: GitHub Release Actions
|
||||
name: Validate Build
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
shell: bash
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
- name: Build assets
|
||||
- name: Build Fruit Jam OS
|
||||
shell: bash
|
||||
run: |
|
||||
python build.py
|
||||
|
|
|
|||
48
.github/workflows/daily_release_check.yml
vendored
48
.github/workflows/daily_release_check.yml
vendored
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: GitHub Release Actions
|
||||
name: Daily Release Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
|
|
@ -10,8 +10,12 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate-build:
|
||||
release_check:
|
||||
if: github.repository_owner == 'adafruit'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_created: ${{ steps.check_release.outputs.release_created }}
|
||||
assets_upload_url: ${{ steps.check_release.outputs.assets_upload_url }}
|
||||
steps:
|
||||
- name: Set up requested Python version
|
||||
uses: actions/setup-python@v5
|
||||
|
|
@ -30,7 +34,43 @@ jobs:
|
|||
shell: bash
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
- name: Build assets
|
||||
- name: Create Release If Needed
|
||||
id: check_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
python release_updater.py
|
||||
python release_updater.py make_release
|
||||
|
||||
upload_release_assets:
|
||||
needs: release_check
|
||||
if: github.repository_owner == 'adafruit' && needs.release_check.outputs.release_created == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up requested Python version
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
- name: Versions
|
||||
shell: bash
|
||||
run: |
|
||||
python3 --version
|
||||
- name: Checkout Current Repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
show-progress: false
|
||||
- name: Install reqs
|
||||
shell: bash
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
- name: Build assets
|
||||
shell: bash
|
||||
run: |
|
||||
python build.py
|
||||
- name: Upload Release Assets
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
with:
|
||||
asset_path: "dist/*"
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
upload_url: ${{ needs.release_check.outputs.assets_upload_url }}
|
||||
|
|
|
|||
1
.github/workflows/release_gh.yml
vendored
1
.github/workflows/release_gh.yml
vendored
|
|
@ -10,6 +10,7 @@ on:
|
|||
|
||||
jobs:
|
||||
upload-release-assets:
|
||||
if: github.repository_owner == 'adafruit'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up requested Python version
|
||||
|
|
|
|||
5
build.py
5
build.py
|
|
@ -7,6 +7,9 @@ from pathlib import Path
|
|||
import requests
|
||||
from circup.commands import main as circup_cli
|
||||
|
||||
# TODO: maybe change these to use the first URLs i.e. https://learn.adafruit.com/elements/3198279/download?type=zip
|
||||
# instead of the redirect URLs that are direct to the CDN. That will make easier for users to add apps here.
|
||||
# The code will need to follow the redirect and get the filename from the next URL.
|
||||
LEARN_PROJECT_URLS = [
|
||||
"https://cdn-learn.adafruit.com/downloads/zip/3194974/Metro/Metro_RP2350_Snake.zip?timestamp={}",
|
||||
"https://cdn-learn.adafruit.com/downloads/zip/3195762/Metro/Metro_RP2350_Memory/memory_game.zip?timestamp={}",
|
||||
|
|
@ -15,6 +18,8 @@ LEARN_PROJECT_URLS = [
|
|||
"https://cdn-learn.adafruit.com/downloads/zip/3196927/Metro/Metro_RP2350_Match3/match3_game.zip?timestamp={}",
|
||||
"https://cdn-learn.adafruit.com/downloads/zip/3194422/Metro/Metro_RP2350_Breakout.zip?timestamp={}",
|
||||
"https://cdn-learn.adafruit.com/downloads/zip/3196755/Metro/Metro_RP2350_Chips_Challenge.zip?timestamp={}",
|
||||
"https://cdn-learn.adafruit.com/downloads/zip/3198116/Metro/Metro_RP2350_Minesweeper.zip?timestamp={}",
|
||||
"https://cdn-learn.adafruit.com/downloads/zip/3198279/Fruit_Jam/Larsio_Paint_Music.zip?timestamp=1750522464"
|
||||
]
|
||||
|
||||
def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: Path, output_dir: Path):
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from . import util
|
|||
from adafruit_pathlib import Path
|
||||
import time
|
||||
import json
|
||||
from argv_file_helper import argv_filename
|
||||
from adafruit_argv_file import argv_filename
|
||||
|
||||
# pylint: disable=redefined-builtin
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import hashlib
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
|
||||
from build import main as build_main
|
||||
|
|
@ -83,5 +86,111 @@ def is_release_required():
|
|||
return False
|
||||
|
||||
|
||||
|
||||
def parse_semantic_version(version_string):
|
||||
"""Parse semantic version string and return (major, minor, patch)."""
|
||||
|
||||
# Match semantic version pattern
|
||||
match = re.match(r'^(\d+)\.(\d+)\.(\d+)(?:-.*)?(?:\+.*)?$', version_string)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid semantic version: {version_string}")
|
||||
|
||||
return int(match.group(1)), int(match.group(2)), int(match.group(3))
|
||||
|
||||
|
||||
def increment_patch_version(version_string):
|
||||
"""Increment patch version by 1."""
|
||||
major, minor, patch = parse_semantic_version(version_string)
|
||||
new_patch = patch + 1
|
||||
return f"{major}.{minor}.{new_patch}"
|
||||
|
||||
|
||||
def get_latest_release():
|
||||
"""Fetch the latest release from GitHub API."""
|
||||
url = f"https://api.github.com/repos/adafruit/Fruit-Jam-OS/releases/latest"
|
||||
headers = {
|
||||
"Authorization": f"token {os.getenv('GITHUB_TOKEN')}",
|
||||
"Accept": "application/vnd.github.v3+json"
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error fetching latest release: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
sys.exit(1)
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def create_release(tag_name):
|
||||
"""Create a new GitHub release."""
|
||||
url = f"https://api.github.com/repos/adafruit/Fruit-Jam-OS/releases"
|
||||
headers = {
|
||||
"Authorization": f"token {os.getenv('GITHUB_TOKEN')}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
data = {
|
||||
"tag_name": tag_name,
|
||||
"name": tag_name,
|
||||
"body": f"Release {tag_name}",
|
||||
"draft": False,
|
||||
"prerelease": False
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, data=json.dumps(data))
|
||||
|
||||
if response.status_code != 201:
|
||||
print(f"Error creating release: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
sys.exit(1)
|
||||
|
||||
return response.json()
|
||||
|
||||
if __name__ == '__main__':
|
||||
is_release_required()
|
||||
|
||||
if is_release_required():
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "make_release":
|
||||
print(f"Creating release for Fruit Jam OS")
|
||||
|
||||
# Get latest release
|
||||
latest_release = get_latest_release()
|
||||
|
||||
if latest_release:
|
||||
latest_tag = latest_release["tag_name"]
|
||||
print(f"Latest release: {latest_tag}")
|
||||
|
||||
try:
|
||||
new_tag = increment_patch_version(latest_tag)
|
||||
except ValueError as e:
|
||||
print(f"Error parsing version: {e}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
new_tag = "0.1.0"
|
||||
|
||||
print(f"Creating new release: {new_tag}")
|
||||
|
||||
new_release = create_release(
|
||||
tag_name=new_tag,
|
||||
)
|
||||
|
||||
#print(new_release)
|
||||
|
||||
github_output_path = os.environ.get('GITHUB_OUTPUT')
|
||||
|
||||
if github_output_path:
|
||||
with open(github_output_path, 'a') as f:
|
||||
f.write(f"release_created=true\n")
|
||||
f.write(f"assets_upload_url={new_release['upload_url']}\n")
|
||||
|
||||
print(f"Successfully created release: {new_tag}")
|
||||
print(f"Release URL: {new_release['html_url']}")
|
||||
else:
|
||||
github_output_path = os.environ.get('GITHUB_OUTPUT')
|
||||
|
||||
if github_output_path:
|
||||
with open(github_output_path, 'a') as f:
|
||||
f.write(f"release_created=false\n")
|
||||
f.write(f"assets_upload_url=None\n")
|
||||
|
|
|
|||
157
src/code.py
157
src/code.py
|
|
@ -88,72 +88,52 @@ mouse_tg.x = display.width // (2 * scale)
|
|||
mouse_tg.y = display.height // (2 * scale)
|
||||
# 046d:c52f
|
||||
|
||||
launcher_config = {}
|
||||
if pathlib.Path("launcher.conf.json").exists():
|
||||
with open("launcher.conf.json", "r") as f:
|
||||
launcher_config = json.load(f)
|
||||
|
||||
# mouse = usb.core.find(idVendor=0x046d, idProduct=0xc52f)
|
||||
|
||||
DIR_IN = 0x80
|
||||
mouse_interface_index, mouse_endpoint_address = None, None
|
||||
mouse = None
|
||||
# scan for connected USB device and loop over any found
|
||||
print("scanning usb")
|
||||
for device in usb.core.find(find_all=True):
|
||||
# print device info
|
||||
print(f"{device.idVendor:04x}:{device.idProduct:04x}")
|
||||
print(device.manufacturer, device.product)
|
||||
print()
|
||||
config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor(
|
||||
device, 0
|
||||
)
|
||||
print(config_descriptor)
|
||||
#
|
||||
# i = 0
|
||||
# while i < len(config_descriptor):
|
||||
# descriptor_len = config_descriptor[i]
|
||||
# descriptor_type = config_descriptor[i + 1]
|
||||
# if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION:
|
||||
# config_value = config_descriptor[i + 5]
|
||||
# print(f" value {config_value:d}")
|
||||
# elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE:
|
||||
# interface_number = config_descriptor[i + 2]
|
||||
# interface_class = config_descriptor[i + 5]
|
||||
# interface_subclass = config_descriptor[i + 6]
|
||||
# interface_protocol = config_descriptor[i + 7]
|
||||
# print(f" interface[{interface_number:d}]")
|
||||
# print(
|
||||
# f" class {interface_class:02x} subclass {interface_subclass:02x}"
|
||||
# )
|
||||
# print(f"protocol: {interface_protocol}")
|
||||
# elif descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT:
|
||||
# endpoint_address = config_descriptor[i + 2]
|
||||
# if endpoint_address & DIR_IN:
|
||||
# print(f" IN {endpoint_address:02x}")
|
||||
# else:
|
||||
# print(f" OUT {endpoint_address:02x}")
|
||||
# i += descriptor_len
|
||||
# print()
|
||||
#
|
||||
# # assume the device is the mouse
|
||||
# mouse = device
|
||||
_possible_interface_index, _possible_endpoint_address = adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device)
|
||||
if _possible_interface_index is not None and _possible_endpoint_address is not None:
|
||||
mouse = device
|
||||
mouse_interface_index = _possible_interface_index
|
||||
mouse_endpoint_address = _possible_endpoint_address
|
||||
print(f"mouse interface: {mouse_interface_index} endpoint_address: {hex(mouse_endpoint_address)}")
|
||||
|
||||
mouse_was_attached = None
|
||||
if mouse is not None:
|
||||
# detach the kernel driver if needed
|
||||
if mouse.is_kernel_driver_active(0):
|
||||
mouse_was_attached = True
|
||||
mouse.detach_kernel_driver(0)
|
||||
else:
|
||||
mouse_was_attached = False
|
||||
if "use_mouse" in launcher_config and launcher_config["use_mouse"]:
|
||||
|
||||
# set configuration on the mouse so we can use it
|
||||
mouse.set_configuration()
|
||||
# scan for connected USB device and loop over any found
|
||||
print("scanning usb")
|
||||
for device in usb.core.find(find_all=True):
|
||||
# print device info
|
||||
print(f"{device.idVendor:04x}:{device.idProduct:04x}")
|
||||
print(device.manufacturer, device.product)
|
||||
print()
|
||||
config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor(
|
||||
device, 0
|
||||
)
|
||||
print(config_descriptor)
|
||||
|
||||
_possible_interface_index, _possible_endpoint_address = adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device)
|
||||
if _possible_interface_index is not None and _possible_endpoint_address is not None:
|
||||
mouse = device
|
||||
mouse_interface_index = _possible_interface_index
|
||||
mouse_endpoint_address = _possible_endpoint_address
|
||||
print(f"mouse interface: {mouse_interface_index} endpoint_address: {hex(mouse_endpoint_address)}")
|
||||
|
||||
mouse_was_attached = None
|
||||
if mouse is not None:
|
||||
# detach the kernel driver if needed
|
||||
if mouse.is_kernel_driver_active(0):
|
||||
mouse_was_attached = True
|
||||
mouse.detach_kernel_driver(0)
|
||||
else:
|
||||
mouse_was_attached = False
|
||||
|
||||
# set configuration on the mouse so we can use it
|
||||
mouse.set_configuration()
|
||||
|
||||
mouse_buf = array.array("b", [0] * 8)
|
||||
|
||||
mouse_buf = array.array("b", [0] * 8)
|
||||
WIDTH = 280
|
||||
HEIGHT = 182
|
||||
|
||||
|
|
@ -248,11 +228,23 @@ for path in app_path.iterdir():
|
|||
apps.append({
|
||||
"title": title,
|
||||
"icon": str(icon_file.absolute()) if icon_file is not None else None,
|
||||
"file": str(code_file.absolute())
|
||||
"file": str(code_file.absolute()),
|
||||
"dir": path
|
||||
})
|
||||
|
||||
i += 1
|
||||
|
||||
print("launcher config", launcher_config)
|
||||
if "favorites" in launcher_config:
|
||||
|
||||
for favorite_app in reversed(launcher_config["favorites"]):
|
||||
print("checking favorite", favorite_app)
|
||||
for app in apps:
|
||||
print(f"checking app: {app["dir"]}")
|
||||
if app["dir"] == f"/apps/{favorite_app}":
|
||||
apps.remove(app)
|
||||
apps.insert(0, app)
|
||||
|
||||
|
||||
def reuse_cell(grid_coords):
|
||||
try:
|
||||
|
|
@ -317,6 +309,9 @@ def _unhide_cell_group(cell_group):
|
|||
|
||||
|
||||
def display_page(page_index):
|
||||
max_pages = math.ceil(len(apps) / 6)
|
||||
page_txt.text = f"{page_index + 1}/{max_pages}"
|
||||
|
||||
for grid_index in range(6):
|
||||
grid_pos = (grid_index % config["width"], grid_index // config["width"])
|
||||
try:
|
||||
|
|
@ -342,6 +337,11 @@ def display_page(page_index):
|
|||
print(f"{grid_index} | {grid_index % config["width"], grid_index // config["width"]}")
|
||||
|
||||
|
||||
page_txt = Label(terminalio.FONT, text="", scale=2)
|
||||
page_txt.anchor_point = (1.0, 1.0)
|
||||
page_txt.anchored_position = (display.width - 2, display.height - 2)
|
||||
main_group.append(page_txt)
|
||||
|
||||
cur_page = 0
|
||||
display_page(cur_page)
|
||||
|
||||
|
|
@ -370,7 +370,7 @@ if mouse:
|
|||
scaled_group.append(mouse_tg)
|
||||
|
||||
|
||||
help_txt = Label(terminalio.FONT, text="[Arrow]: Move\n[E]: Edit\n[Enter]: Run")
|
||||
help_txt = Label(terminalio.FONT, text="[Arrow]: Move\n[E]: Edit\n[Enter]: Run\n[1-9]: Page")
|
||||
# help_txt = TextBox(terminalio.FONT, width=88, height=30, align=TextBox.ALIGN_RIGHT, background_color=0x008800, text="[E]: Edit\n[Enter]: Run")
|
||||
help_txt.anchor_point = (0, 0)
|
||||
|
||||
|
|
@ -417,6 +417,18 @@ def change_selected(new_selected):
|
|||
|
||||
change_selected((0, 0))
|
||||
|
||||
def page_right():
|
||||
global cur_page
|
||||
if cur_page < math.ceil(len(apps) / 6) - 1:
|
||||
cur_page += 1
|
||||
display_page(cur_page)
|
||||
|
||||
def page_left():
|
||||
global cur_page
|
||||
if cur_page > 0:
|
||||
cur_page -= 1
|
||||
display_page(cur_page)
|
||||
|
||||
|
||||
def handle_key_press(key):
|
||||
global index, editor_index, cur_page
|
||||
|
|
@ -476,15 +488,9 @@ def handle_key_press(key):
|
|||
index = None
|
||||
print("go!")
|
||||
elif selected is left_tg:
|
||||
if cur_page > 0:
|
||||
cur_page -= 1
|
||||
display_page(cur_page)
|
||||
|
||||
page_left()
|
||||
elif selected is right_tg:
|
||||
if cur_page < math.ceil(len(apps) / 6) - 1:
|
||||
cur_page += 1
|
||||
display_page(cur_page)
|
||||
|
||||
page_right()
|
||||
elif key == "e":
|
||||
if isinstance(selected, tuple):
|
||||
editor_index = (selected[1] * 3 + selected[0]) + (cur_page * 6)
|
||||
|
|
@ -492,6 +498,17 @@ def handle_key_press(key):
|
|||
editor_index = None
|
||||
|
||||
print("go!")
|
||||
elif key in "123456789":
|
||||
if key != "9":
|
||||
requested_page = int(key)
|
||||
max_page = math.ceil(len(apps) / 6)
|
||||
if requested_page <= max_page:
|
||||
cur_page = requested_page - 1
|
||||
display_page(requested_page-1)
|
||||
else: # key == 9
|
||||
max_page = math.ceil(len(apps) / 6)
|
||||
cur_page = max_page - 1
|
||||
display_page(max_page - 1)
|
||||
else:
|
||||
print(f"unhandled key: {repr(key)}")
|
||||
|
||||
|
|
@ -534,6 +551,12 @@ while True:
|
|||
if clicked_cell is not None:
|
||||
index = clicked_cell[1] * config["width"] + clicked_cell[0]
|
||||
|
||||
if right_tg.contains((mouse_tg.x, mouse_tg.y, 0)):
|
||||
page_right()
|
||||
if left_tg.contains((mouse_tg.x, mouse_tg.y, 0)):
|
||||
page_left()
|
||||
|
||||
|
||||
if index is not None:
|
||||
print("index", index)
|
||||
print(f"selected: {apps[index]}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue