Fetch and build layout #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Fetch and build layout | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| layout_id: | |
| description: "Layout id available in URL https://configure.zsa.io/voyager/layouts/[ID_IS_HERE]/latest" | |
| required: true | |
| default: "emVdK" | |
| layout_geometry: | |
| description: "Keyboard type" | |
| type: choice | |
| options: | |
| - voyager | |
| - moonlander/reva | |
| - moonlander/revb | |
| - ergodox_ez | |
| - ergodox_ez/m32u4/glow | |
| - ergodox_ez/m32u4/shine | |
| - ergodox_ez/stm32 | |
| - ergodox_ez/stm32/glow | |
| - ergodox_ez/stm32/shine | |
| - planck_ez | |
| - planck_ez/glow | |
| default: voyager | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - .github/workflows/fetch-and-build-layout.yml | |
| env: | |
| QMK_OPENRGB_REF: module_revD | |
| LAYOUT_ID: ${{ github.event.inputs.layout_id || 'emVdK' }} | |
| LAYOUT_GEOMETRY: ${{ github.event.inputs.layout_geometry || 'voyager' }} | |
| permissions: | |
| contents: write | |
| jobs: | |
| fetch-and-build-layout: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout Oryx source branch | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: oryx | |
| fetch-depth: 0 | |
| - name: Download latest Oryx layout source | |
| id: download-layout-source | |
| run: | | |
| response=$(curl --fail --silent --show-error --location 'https://oryx.zsa.io/graphql' \ | |
| --header 'Content-Type: application/json' \ | |
| --data '{"query":"query getLayout($hashId: String!, $revisionId: String!, $geometry: String) {layout(hashId: $hashId, geometry: $geometry, revisionId: $revisionId) { revision { hashId, qmkVersion, title }}}","variables":{"hashId":"'"${LAYOUT_ID}"'","geometry":"'"${LAYOUT_GEOMETRY}"'","revisionId":"latest"}}') | |
| hash_id=$(echo "${response}" | jq -r '.data.layout.revision.hashId') | |
| firmware_version=$(printf '%.0f' "$(echo "${response}" | jq -r '.data.layout.revision.qmkVersion')") | |
| change_description=$(echo "${response}" | jq -r '.data.layout.revision.title') | |
| test -n "${hash_id}" | |
| test "${hash_id}" != "null" | |
| test -n "${firmware_version}" | |
| if [[ -z "${change_description}" || "${change_description}" == "null" ]]; then | |
| change_description="latest layout modification made with Oryx" | |
| fi | |
| echo "Oryx revision: ${hash_id}" | |
| echo "Oryx QMK firmware: firmware${firmware_version}" | |
| echo "Oryx title: ${change_description}" | |
| curl --fail --silent --show-error --location \ | |
| "https://oryx.zsa.io/source/${hash_id}" \ | |
| -o source.zip | |
| rm -rf "${LAYOUT_ID}" | |
| mkdir -p "${LAYOUT_ID}" | |
| unzip -oj source.zip '*_source/*' -d "${LAYOUT_ID}" | |
| rm source.zip | |
| echo "hash_id=${hash_id}" >> "$GITHUB_OUTPUT" | |
| echo "firmware_version=${firmware_version}" >> "$GITHUB_OUTPUT" | |
| echo "change_description=${change_description}" >> "$GITHUB_OUTPUT" | |
| - name: Save pristine Oryx source | |
| env: | |
| CHANGE_DESCRIPTION: ${{ steps.download-layout-source.outputs.change_description }} | |
| run: | | |
| git config --local user.name "github-actions" | |
| git config --local user.email "github-actions@github.com" | |
| git add "${LAYOUT_ID}" | |
| if ! git diff --cached --quiet; then | |
| printf "✨(oryx): %s\n" "$CHANGE_DESCRIPTION" | git commit -F - | |
| git push origin HEAD:oryx | |
| else | |
| echo "Oryx source unchanged" | |
| fi | |
| - name: Replace main layout with latest Oryx source | |
| run: | | |
| git fetch origin main | |
| git checkout -B main origin/main | |
| rm -rf "${LAYOUT_ID}" | |
| git checkout oryx -- "${LAYOUT_ID}" | |
| - name: Apply Voyager custom configuration | |
| if: ${{ env.LAYOUT_GEOMETRY == 'voyager' }} | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import re | |
| from pathlib import Path | |
| layout = Path(os.environ["LAYOUT_ID"]) | |
| keymap_json = layout / "keymap.json" | |
| data = json.loads(keymap_json.read_text()) | |
| modules = data.setdefault("modules", []) | |
| for module in ( | |
| "zsa/oryx", | |
| "zsa/navigator_trackball", | |
| "zsa/navigator_trackpad", | |
| "zsa/automouse", | |
| "zsa/defaults", | |
| "openrgb", | |
| ): | |
| if module not in modules: | |
| modules.append(module) | |
| keymap_json.write_text(json.dumps(data, indent=4) + "\n") | |
| config = layout / "config.h" | |
| text = config.read_text() | |
| for name, value in ( | |
| ("NAVIGATOR_TRACKBALL_ROTATION", "325"), | |
| ("NAVIGATOR_TRACKPAD_ROTATION", "0"), | |
| ): | |
| pattern = rf"^#define\s+{name}\s+.*$" | |
| replacement = f"#define {name} {value}" | |
| if re.search(pattern, text, flags=re.MULTILINE): | |
| text = re.sub(pattern, replacement, text, flags=re.MULTILINE) | |
| else: | |
| if text and not text.endswith("\n"): | |
| text += "\n" | |
| text += replacement + "\n" | |
| config.write_text(text) | |
| rules = layout / "rules.mk" | |
| text = rules.read_text() | |
| pattern = r"^MOUSE_SHARED_EP\s*=.*$" | |
| if re.search(pattern, text, flags=re.MULTILINE): | |
| text = re.sub(pattern, "MOUSE_SHARED_EP = yes", text, flags=re.MULTILINE) | |
| else: | |
| if text and not text.endswith("\n"): | |
| text += "\n" | |
| text += "MOUSE_SHARED_EP = yes\n" | |
| rules.write_text(text) | |
| PY | |
| - name: Save customized layout on main | |
| run: | | |
| git config --local user.name "github-actions" | |
| git config --local user.email "github-actions@github.com" | |
| git add "${LAYOUT_ID}" | |
| git commit -m "✨(layout): Refresh from Oryx with custom QMK settings" || echo "No customized layout change" | |
| git push origin HEAD:main | |
| - name: Update QMK firmware submodule to matching version (${{ steps.download-layout-source.outputs.firmware_version }}) | |
| run: | | |
| git submodule update --init --remote --depth=1 --no-single-branch | |
| cd qmk_firmware | |
| git checkout -B "firmware${{ steps.download-layout-source.outputs.firmware_version }}" \ | |
| "origin/firmware${{ steps.download-layout-source.outputs.firmware_version }}" | |
| git submodule update --init --recursive | |
| cd .. | |
| git add qmk_firmware | |
| git commit -m "✨(qmk): Update firmware" || echo "No QMK change" | |
| git push origin HEAD:main | |
| - name: Install QMK-OpenRGB module | |
| if: ${{ env.LAYOUT_GEOMETRY == 'voyager' }} | |
| run: | | |
| rm -rf qmk_firmware/modules/openrgb | |
| git clone \ | |
| --depth 1 \ | |
| --branch "${QMK_OPENRGB_REF}" \ | |
| https://gitlab.com/OpenRGBDevelopers/QMK-OpenRGB.git \ | |
| qmk_firmware/modules/openrgb | |
| echo "QMK-OpenRGB commit: $(git -C qmk_firmware/modules/openrgb rev-parse HEAD)" | |
| - name: Apply Voyager dual-HID patch | |
| if: ${{ env.LAYOUT_GEOMETRY == 'voyager' }} | |
| run: | | |
| python3 scripts/patch-voyager-dual-hid.py qmk_firmware | |
| - name: Validate Voyager custom integration | |
| if: ${{ env.LAYOUT_GEOMETRY == 'voyager' }} | |
| run: | | |
| grep -q '"zsa/navigator_trackball"' "${LAYOUT_ID}/keymap.json" | |
| grep -q '"zsa/navigator_trackpad"' "${LAYOUT_ID}/keymap.json" | |
| grep -q '"openrgb"' "${LAYOUT_ID}/keymap.json" | |
| grep -q 'NAVIGATOR_TRACKBALL_ROTATION 325' "${LAYOUT_ID}/config.h" | |
| grep -q 'NAVIGATOR_TRACKPAD_ROTATION 0' "${LAYOUT_ID}/config.h" | |
| grep -q '^MOUSE_SHARED_EP = yes$' "${LAYOUT_ID}/rules.mk" | |
| # Stock OpenRGB remains on QMK Raw HID / interface 1. | |
| grep -q 'void raw_hid_receive(uint8_t \*data, uint8_t length)' \ | |
| qmk_firmware/modules/openrgb/openrgb.c | |
| # ZSA Oryx remains on the dedicated second HID interface. | |
| grep -q 'ORYX_INTERFACE' qmk_firmware/tmk_core/protocol/usb_descriptor.h | |
| grep -q 'USB_ENDPOINT_IN_ORYX' qmk_firmware/tmk_core/protocol/chibios/usb_endpoints.c | |
| grep -q 'oryx_hid_task' qmk_firmware/tmk_core/protocol/chibios/usb_main.c | |
| grep -q 'void oryx_hid_receive(uint8_t \*data, uint8_t length)' \ | |
| qmk_firmware/modules/zsa/oryx/oryx.c | |
| - name: Build qmk docker image | |
| run: docker build -t qmk . | |
| - name: Build the layout | |
| id: build-layout | |
| run: | | |
| if [ "${{ steps.download-layout-source.outputs.firmware_version }}" -ge 24 ]; then | |
| keyboard_directory="qmk_firmware/keyboards/zsa" | |
| make_prefix="zsa/" | |
| else | |
| keyboard_directory="qmk_firmware/keyboards" | |
| make_prefix="" | |
| fi | |
| rm -rf "${keyboard_directory}/${LAYOUT_GEOMETRY}/keymaps/${LAYOUT_ID}" | |
| mkdir -p "${keyboard_directory}/${LAYOUT_GEOMETRY}/keymaps" | |
| cp -r "${LAYOUT_ID}" \ | |
| "${keyboard_directory}/${LAYOUT_GEOMETRY}/keymaps/" | |
| docker run \ | |
| -v ./qmk_firmware:/root \ | |
| --rm \ | |
| qmk \ | |
| /bin/sh -c " | |
| qmk setup zsa/qmk_firmware \ | |
| -b firmware${{ steps.download-layout-source.outputs.firmware_version }} \ | |
| -y | |
| make ${make_prefix}${LAYOUT_GEOMETRY}:${LAYOUT_ID} | |
| " | |
| normalized_layout_geometry="$(echo "${LAYOUT_GEOMETRY}" | sed 's/\//_/g')" | |
| echo built_layout_file=$(find ./qmk_firmware \ | |
| -maxdepth 1 \ | |
| -type f \ | |
| -regex ".*${normalized_layout_geometry}.*\.\(bin\|hex\)$") >> "$GITHUB_OUTPUT" | |
| echo normalized_layout_geometry=${normalized_layout_geometry} >> "$GITHUB_OUTPUT" | |
| - name: Upload layout | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ steps.build-layout.outputs.normalized_layout_geometry }}_${{ env.LAYOUT_ID }} | |
| path: ${{ steps.build-layout.outputs.built_layout_file }} | |
| if-no-files-found: error |