Compare commits

...

20 commits

Author SHA1 Message Date
foamyguy
205d655dc4
Merge pull request #25 from FoamyGuy/working_on_stuff_again
Some checks failed
Validate Build / validate-build (push) Has been cancelled
pass assets upload_url thru to the second job
2025-06-24 09:36:37 -05:00
foamyguy
6d1665b6ee pass assets upload_url thru to the second job 2025-06-24 09:33:02 -05:00
foamyguy
675143d56d
Merge pull request #24 from FoamyGuy/working_on_stuff_again
Some checks failed
Validate Build / validate-build (push) Has been cancelled
print json response
2025-06-24 09:24:41 -05:00
foamyguy
49ecec7b14 print json response 2025-06-24 09:24:01 -05:00
foamyguy
9f58e6b912
Merge pull request #23 from FoamyGuy/working_on_stuff_again
Some checks failed
Validate Build / validate-build (push) Has been cancelled
fix output variable. rename to use underscores
2025-06-24 09:12:45 -05:00
foamyguy
4c629abbee fix output variable. rename to use underscores 2025-06-24 09:02:39 -05:00
foamyguy
ecda53b0cf
Merge pull request #22 from FoamyGuy/working_on_stuff_again
Some checks failed
Validate Build / validate-build (push) Has been cancelled
output value from job instead of just step
2025-06-23 16:45:14 -05:00
foamyguy
fea27725f7 output value from job instead of just step 2025-06-23 16:38:47 -05:00
foamyguy
96fe7ddc7b
Merge pull request #21 from FoamyGuy/working_on_stuff_again
Fix auto release assets
2025-06-23 11:17:43 -05:00
foamyguy
d58747960f upload release assets after auto release action. Only make release if arg is passed. 2025-06-23 11:00:27 -05:00
foamyguy
469fc2a1b9
Merge pull request #20 from FoamyGuy/working_on_stuff_again
Some checks failed
Validate Build / validate-build (push) Has been cancelled
create the release if needed
2025-06-23 09:57:08 -05:00
foamyguy
16a50d8a4d create the release 2025-06-23 09:53:11 -05:00
foamyguy
b54c7e0be7
Merge pull request #19 from FoamyGuy/working_on_stuff_again
starting make release.
2025-06-23 09:36:21 -05:00
foamyguy
1f776f3b4d starting make release. 2025-06-23 09:33:16 -05:00
foamyguy
eee9a86c08
Merge pull request #18 from FoamyGuy/working_on_stuff_again
Launcher Improvements & Release Actions condition
2025-06-23 09:11:14 -05:00
foamyguy
8679a4dafa limit release actions to adafruit repo 2025-06-23 09:06:24 -05:00
foamyguy
7efd046d44 launcher page keys, new projects in build, launcher config, launcher favorites, cleanup 2025-06-21 11:49:14 -05:00
foamyguy
160edc8d61
Merge pull request #17 from FoamyGuy/working_on_stuff_again
update actions names.
2025-06-21 10:23:30 -05:00
foamyguy
4151d3b80c update argv import in editor 2025-06-21 10:18:33 -05:00
foamyguy
0699a56feb update actions names. 2025-06-21 10:14:07 -05:00
7 changed files with 254 additions and 76 deletions

View file

@ -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

View file

@ -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 }}

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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")

View file

@ -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]}")