mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-24 13:48:51 +08:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33d4199f7c | ||
|
|
a6f4df405b | ||
|
|
97960f3a6b | ||
|
|
53c657aa55 | ||
|
|
941de898fb | ||
|
|
6abcafc205 | ||
|
|
171e559b0d | ||
|
|
41f1e29583 |
@@ -50,7 +50,7 @@ def sign_update(payload, private_key, expected_public_key):
|
|||||||
return {"payload": payload, "signature": signature.hex()}
|
return {"payload": payload, "signature": signature.hex()}
|
||||||
|
|
||||||
|
|
||||||
def version_info(version, fallback_date=None):
|
def version_fields(version):
|
||||||
version = version.removeprefix("v")
|
version = version.removeprefix("v")
|
||||||
base = r"[0-9]+(?:\.[0-9]+){1,3}"
|
base = r"[0-9]+(?:\.[0-9]+){1,3}"
|
||||||
for pattern in (
|
for pattern in (
|
||||||
@@ -60,18 +60,41 @@ def version_info(version, fallback_date=None):
|
|||||||
):
|
):
|
||||||
match = re.fullmatch(pattern, version)
|
match = re.fullmatch(pattern, version)
|
||||||
if match:
|
if match:
|
||||||
fields = match.groupdict()
|
return match.groupdict()
|
||||||
date = fields.get("date")
|
|
||||||
release_date = (datetime.datetime.strptime(date, "%Y%m%d").date().isoformat()
|
|
||||||
if date else fallback_date)
|
|
||||||
if not release_date:
|
|
||||||
raise ValueError(f"Release date is missing for {version}")
|
|
||||||
datetime.date.fromisoformat(release_date)
|
|
||||||
return {"latest_version": version, "version": version,
|
|
||||||
"releaseDate": release_date, "patch": int(fields.get("patch") or 0)}
|
|
||||||
raise ValueError(f"Invalid release version: {version}")
|
raise ValueError(f"Invalid release version: {version}")
|
||||||
|
|
||||||
|
|
||||||
|
def version_info(version, fallback_date=None):
|
||||||
|
fields = version_fields(version)
|
||||||
|
version = version.removeprefix("v")
|
||||||
|
date = fields.get("date")
|
||||||
|
release_date = (datetime.datetime.strptime(date, "%Y%m%d").date().isoformat()
|
||||||
|
if date else fallback_date)
|
||||||
|
if not release_date:
|
||||||
|
raise ValueError(f"Release date is missing for {version}")
|
||||||
|
datetime.date.fromisoformat(release_date)
|
||||||
|
return {"latest_version": version, "version": version,
|
||||||
|
"releaseDate": release_date, "patch": int(fields.get("patch") or 0)}
|
||||||
|
|
||||||
|
|
||||||
|
def release_order(metadata):
|
||||||
|
"""Match client ordering: zero-padded numeric components, then patch, never date."""
|
||||||
|
version = metadata.get("latest_version") or metadata.get("version")
|
||||||
|
if not version:
|
||||||
|
return None
|
||||||
|
fields = version_fields(version)
|
||||||
|
numbers = tuple(int(part) for part in fields["base"].split("."))
|
||||||
|
inline_patch = fields.get("patch") or "0"
|
||||||
|
patch = int(inline_patch) if len(inline_patch) <= 4 else 0
|
||||||
|
# Legacy manifests may store the effective patch outside the version string.
|
||||||
|
explicit_patch = metadata.get("patch")
|
||||||
|
if isinstance(explicit_patch, str) and re.fullmatch(r"[0-9]+", explicit_patch):
|
||||||
|
explicit_patch = int(explicit_patch)
|
||||||
|
if type(explicit_patch) is int and 0 <= explicit_patch <= 2147483647:
|
||||||
|
patch = explicit_patch
|
||||||
|
return numbers + (0,) * (4 - len(numbers)) + (patch,)
|
||||||
|
|
||||||
|
|
||||||
def artifact_downloads(directory):
|
def artifact_downloads(directory):
|
||||||
downloads = {}
|
downloads = {}
|
||||||
private_key = os.environ.get("CROSSDESK_UPDATE_PRIVATE_KEY", "")
|
private_key = os.environ.get("CROSSDESK_UPDATE_PRIVATE_KEY", "")
|
||||||
@@ -129,10 +152,34 @@ def merge_release(current, tag, name, notes, downloads, published_date=None):
|
|||||||
if not tag.startswith("v"):
|
if not tag.startswith("v"):
|
||||||
raise ValueError(f"Expected a version tag, got {tag!r}")
|
raise ValueError(f"Expected a version tag, got {tag!r}")
|
||||||
metadata = version_info(tag, published_date)
|
metadata = version_info(tag, published_date)
|
||||||
|
# Clients require the full artifact version to match the advertised
|
||||||
|
# desktop version, including its date and patch spelling.
|
||||||
|
for platform, download in downloads.items():
|
||||||
|
if (platform.startswith(("windows-", "macos-", "linux-"))
|
||||||
|
and download["version"] != metadata["version"]):
|
||||||
|
raise ValueError(
|
||||||
|
f"Desktop release {tag} expects version {metadata['version']}, "
|
||||||
|
f"but {platform} artifact {download['filename']} "
|
||||||
|
f"has version {download['version']}")
|
||||||
|
current_order = release_order(current)
|
||||||
|
if current_order is not None and release_order(metadata) < current_order:
|
||||||
|
current_version = current.get("latest_version") or current.get("version")
|
||||||
|
print(f"Skipping {tag}: older than current desktop release {current_version}")
|
||||||
|
return dict(current)
|
||||||
|
|
||||||
|
accepted_downloads = {}
|
||||||
|
for platform, download in downloads.items():
|
||||||
|
previous_order = release_order(current["downloads"].get(platform, {}))
|
||||||
|
if previous_order is not None and release_order(download) < previous_order:
|
||||||
|
print(f"Skipping {tag} download for {platform}: a newer version is already published")
|
||||||
|
continue
|
||||||
|
accepted_downloads[platform] = download
|
||||||
|
if mobile and not accepted_downloads:
|
||||||
|
return dict(current)
|
||||||
|
|
||||||
result = dict(current)
|
result = dict(current)
|
||||||
result.pop("platforms", None)
|
result.pop("platforms", None)
|
||||||
result["downloads"] = {**current["downloads"], **downloads}
|
result["downloads"] = {**current["downloads"], **accepted_downloads}
|
||||||
if not mobile:
|
if not mobile:
|
||||||
result.update({**metadata, "releaseName": name, "releaseNotes": notes, "tagName": tag})
|
result.update({**metadata, "releaseName": name, "releaseNotes": notes, "tagName": tag})
|
||||||
return result
|
return result
|
||||||
@@ -159,11 +206,16 @@ def main():
|
|||||||
parser.error("Supply --release-json or --tag, --name and --notes")
|
parser.error("Supply --release-json or --tag, --name and --notes")
|
||||||
tag, name, notes = args.tag, args.name, args.notes.read_text()
|
tag, name, notes = args.tag, args.name, args.notes.read_text()
|
||||||
published_date = None
|
published_date = None
|
||||||
current = json.loads(args.current.read_text())
|
current_text = args.current.read_text()
|
||||||
|
current = json.loads(current_text)
|
||||||
downloads = artifact_downloads(args.artifacts)
|
downloads = artifact_downloads(args.artifacts)
|
||||||
result = merge_release(current, tag, name, notes, downloads, published_date)
|
result = merge_release(current, tag, name, notes, downloads, published_date)
|
||||||
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
|
if result == current:
|
||||||
print(f"Updated {tag}: {', '.join(downloads)}")
|
args.output.write_text(current_text)
|
||||||
|
print(f"No changes to version.json for {tag}")
|
||||||
|
else:
|
||||||
|
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
|
||||||
|
print(f"Updated version.json for {tag}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+73
-219
@@ -36,9 +36,68 @@ env:
|
|||||||
CROSSDESK_DESKTOP_VERSION: ${{ inputs.desktop_version }}
|
CROSSDESK_DESKTOP_VERSION: ${{ inputs.desktop_version }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
prepare-version:
|
||||||
|
name: Resolve desktop version
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
# Resolve the date once for every desktop build and the release job.
|
||||||
|
steps:
|
||||||
|
- name: Resolve desktop version
|
||||||
|
id: version
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
PATCH_NUMBER: ${{ inputs.patch || '0' }}
|
||||||
|
run: |
|
||||||
|
VERSION_REF="${GITHUB_REF##*/}"
|
||||||
|
if [[ "${GITHUB_REF}" == refs/heads/* ]]; then
|
||||||
|
VERSION_REF="${CROSSDESK_DESKTOP_VERSION:-0.0.0}"
|
||||||
|
if [[ ! "${VERSION_REF}" =~ ^v?[0-9]+(\.[0-9]+){1,3}$ ]]; then
|
||||||
|
echo "::error::desktop_version must be a numeric version such as 1.7.2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [[ -n "${CROSSDESK_DESKTOP_VERSION}" ]]; then
|
||||||
|
echo "::error::desktop_version is only supported for branch builds; tags determine release versions"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
VERSION_BASE="${VERSION_REF#v}"
|
||||||
|
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
|
||||||
|
|
||||||
|
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||||
|
PATCH_NUMBER=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
||||||
|
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||||
|
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||||
|
BUILD_DATE="${BASH_REMATCH[4]}"
|
||||||
|
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||||
|
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||||
|
BUILD_DATE="${BASH_REMATCH[3]}"
|
||||||
|
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
||||||
|
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
||||||
|
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||||
|
BUILD_DATE="${BASH_REMATCH[3]}"
|
||||||
|
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
|
||||||
|
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||||
|
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${PATCH_NUMBER}" != "0" ]]; then
|
||||||
|
VERSION_NUM="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
|
||||||
|
else
|
||||||
|
VERSION_NUM="v${VERSION_BASE}-${BUILD_DATE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "version=${VERSION_NUM}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Desktop version: ${VERSION_NUM}"
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
name: Build Linux (${{ matrix.arch }})
|
name: Build Linux (${{ matrix.arch }})
|
||||||
|
needs: prepare-version
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
|
env:
|
||||||
|
LEGAL_VERSION: ${{ needs.prepare-version.outputs.version }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@@ -58,69 +117,6 @@ jobs:
|
|||||||
options: --user root
|
options: --user root
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Extract version number
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
VERSION_REF="${GITHUB_REF##*/}"
|
|
||||||
if [[ "${GITHUB_REF}" == refs/heads/* ]]; then
|
|
||||||
VERSION_REF="${CROSSDESK_DESKTOP_VERSION:-0.0.0}"
|
|
||||||
if [[ ! "${VERSION_REF}" =~ ^v?[0-9]+(\.[0-9]+){1,3}$ ]]; then
|
|
||||||
echo "::error::desktop_version must be a numeric version such as 1.7.2"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
elif [[ -n "${CROSSDESK_DESKTOP_VERSION}" ]]; then
|
|
||||||
echo "::error::desktop_version is only supported for branch builds; tags determine release versions"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
VERSION_BASE="${VERSION_REF#v}"
|
|
||||||
PATCH_NUMBER="${{ github.event.inputs.patch }}"
|
|
||||||
BUILD_DATE_OVERRIDE=""
|
|
||||||
|
|
||||||
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
|
||||||
PATCH_NUMBER=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
|
||||||
BUILD_DATE_OVERRIDE="${BASH_REMATCH[4]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_ENV
|
|
||||||
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV
|
|
||||||
echo "BUILD_DATE_OVERRIDE=${BUILD_DATE_OVERRIDE}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set legal Debian version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
BUILD_DATE="${BUILD_DATE_OVERRIDE}"
|
|
||||||
if [[ -z "${BUILD_DATE}" ]]; then
|
|
||||||
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! "${VERSION_BASE}" =~ ^[0-9] ]]; then
|
|
||||||
VERSION_BASE="0.0.0-${VERSION_BASE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${PATCH_NUMBER}" != "0" ]]; then
|
|
||||||
LEGAL_VERSION="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
|
|
||||||
else
|
|
||||||
LEGAL_VERSION="v${VERSION_BASE}-${BUILD_DATE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
|
||||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -218,8 +214,10 @@ jobs:
|
|||||||
# macOS
|
# macOS
|
||||||
build-macos:
|
build-macos:
|
||||||
name: Build on macOS
|
name: Build on macOS
|
||||||
|
needs: prepare-version
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
env:
|
env:
|
||||||
|
VERSION_NUM: ${{ needs.prepare-version.outputs.version }}
|
||||||
MACOSX_DEPLOYMENT_TARGET: "14.0"
|
MACOSX_DEPLOYMENT_TARGET: "14.0"
|
||||||
EXPECTED_MACOS_SDK: "26.5"
|
EXPECTED_MACOS_SDK: "26.5"
|
||||||
strategy:
|
strategy:
|
||||||
@@ -239,56 +237,6 @@ jobs:
|
|||||||
package_script: ./apps/desktop/scripts/macos/pkg_arm64.sh
|
package_script: ./apps/desktop/scripts/macos/pkg_arm64.sh
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Extract version number
|
|
||||||
id: version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
VERSION_REF="${GITHUB_REF##*/}"
|
|
||||||
if [[ "${GITHUB_REF}" == refs/heads/* ]]; then
|
|
||||||
VERSION_REF="${CROSSDESK_DESKTOP_VERSION:-0.0.0}"
|
|
||||||
if [[ ! "${VERSION_REF}" =~ ^v?[0-9]+(\.[0-9]+){1,3}$ ]]; then
|
|
||||||
echo "::error::desktop_version must be a numeric version such as 1.7.2"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
elif [[ -n "${CROSSDESK_DESKTOP_VERSION}" ]]; then
|
|
||||||
echo "::error::desktop_version is only supported for branch builds; tags determine release versions"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
VERSION_BASE="${VERSION_REF#v}"
|
|
||||||
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
|
|
||||||
PATCH_NUMBER="${{ github.event.inputs.patch }}"
|
|
||||||
|
|
||||||
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
|
||||||
PATCH_NUMBER=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
|
||||||
BUILD_DATE="${BASH_REMATCH[4]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${PATCH_NUMBER}" != "0" ]]; then
|
|
||||||
VERSION_NUM="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
|
|
||||||
else
|
|
||||||
VERSION_NUM="v${VERSION_BASE}-${BUILD_DATE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
|
||||||
echo "VERSION_NUM=${VERSION_NUM}"
|
|
||||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
|
|
||||||
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Select Xcode 26.6
|
- name: Select Xcode 26.6
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -380,62 +328,12 @@ jobs:
|
|||||||
# Windows
|
# Windows
|
||||||
build-windows-x64:
|
build-windows-x64:
|
||||||
name: Build on Windows x64
|
name: Build on Windows x64
|
||||||
|
needs: prepare-version
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
env:
|
env:
|
||||||
|
VERSION_NUM: ${{ needs.prepare-version.outputs.version }}
|
||||||
XMAKE_GLOBALDIR: D:\xmake_global
|
XMAKE_GLOBALDIR: D:\xmake_global
|
||||||
steps:
|
steps:
|
||||||
- name: Extract version number
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$ref = $env:GITHUB_REF
|
|
||||||
$version = $ref -replace '^refs/(tags|heads)/', ''
|
|
||||||
if ($ref.StartsWith("refs/heads/")) {
|
|
||||||
$version = if ([string]::IsNullOrEmpty($env:CROSSDESK_DESKTOP_VERSION)) {
|
|
||||||
"0.0.0"
|
|
||||||
} else {
|
|
||||||
$env:CROSSDESK_DESKTOP_VERSION
|
|
||||||
}
|
|
||||||
if ($version -notmatch '^v?[0-9]+(\.[0-9]+){1,3}\z') {
|
|
||||||
throw "desktop_version must be a numeric version such as 1.7.2"
|
|
||||||
}
|
|
||||||
} elseif (![string]::IsNullOrEmpty($env:CROSSDESK_DESKTOP_VERSION)) {
|
|
||||||
throw "desktop_version is only supported for branch builds; tags determine release versions"
|
|
||||||
}
|
|
||||||
$version = $version -replace '^v', ''
|
|
||||||
$version = $version -replace '/', '-'
|
|
||||||
$BUILD_DATE = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "China Standard Time")).ToString("yyyyMMdd")
|
|
||||||
$PATCH_NUMBER = "${{ github.event.inputs.patch }}"
|
|
||||||
|
|
||||||
if ($PATCH_NUMBER -notmatch '^[0-9]+$') {
|
|
||||||
$PATCH_NUMBER = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$') {
|
|
||||||
$version = $Matches[1]
|
|
||||||
$PATCH_NUMBER = $Matches[3]
|
|
||||||
$BUILD_DATE = $Matches[4]
|
|
||||||
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$') {
|
|
||||||
$version = $Matches[1]
|
|
||||||
$BUILD_DATE = $Matches[3]
|
|
||||||
$PATCH_NUMBER = $Matches[4]
|
|
||||||
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$') {
|
|
||||||
$version = $Matches[1]
|
|
||||||
$BUILD_DATE = $Matches[3]
|
|
||||||
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$' -and $PATCH_NUMBER -eq "0") {
|
|
||||||
$version = $Matches[1]
|
|
||||||
$PATCH_NUMBER = $Matches[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($PATCH_NUMBER -ne "0") {
|
|
||||||
$VERSION_NUM = "v$version-$PATCH_NUMBER-$BUILD_DATE"
|
|
||||||
} else {
|
|
||||||
$VERSION_NUM = "v$version-$BUILD_DATE"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "VERSION_NUM=$VERSION_NUM" >> $env:GITHUB_ENV
|
|
||||||
echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV
|
|
||||||
echo "PATCH_NUMBER=$PATCH_NUMBER" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Cache xmake dependencies
|
- name: Cache xmake dependencies
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
@@ -636,7 +534,7 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
name: Publish Release
|
name: Publish Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
needs: [build-linux, build-macos, build-ios, build-android, sign-android, build-windows-x64]
|
needs: [prepare-version, build-linux, build-macos, build-ios, build-android, sign-android, build-windows-x64]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
concurrency:
|
concurrency:
|
||||||
group: publish-release-metadata
|
group: publish-release-metadata
|
||||||
@@ -651,50 +549,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Extract version number
|
|
||||||
id: version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
VERSION_REF="${GITHUB_REF##*/}"
|
|
||||||
VERSION_BASE="${VERSION_REF#v}"
|
|
||||||
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
|
|
||||||
PATCH_NUMBER="${{ github.event.inputs.patch }}"
|
|
||||||
|
|
||||||
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
|
||||||
PATCH_NUMBER=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
|
||||||
BUILD_DATE="${BASH_REMATCH[4]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
|
||||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
|
|
||||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
|
||||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
BUILD_DATE_ISO="${BUILD_DATE:0:4}-${BUILD_DATE:4:2}-${BUILD_DATE:6:2}"
|
|
||||||
if [[ "${PATCH_NUMBER}" != "0" ]]; then
|
|
||||||
VERSION_NUM="${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
|
|
||||||
else
|
|
||||||
VERSION_NUM="${VERSION_BASE}-${BUILD_DATE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERSION_WITH_V="v${VERSION_NUM}"
|
|
||||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
|
||||||
echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_OUTPUT
|
|
||||||
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_OUTPUT
|
|
||||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT
|
|
||||||
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
|
|
||||||
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Prepare release notes
|
- name: Prepare release notes
|
||||||
run: |
|
run: |
|
||||||
cat > "$RUNNER_TEMP/release-notes.md" << EOF
|
cat > "$RUNNER_TEMP/release-notes.md" << EOF
|
||||||
@@ -724,18 +578,18 @@ jobs:
|
|||||||
- name: Rename artifacts
|
- name: Rename artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p release
|
mkdir -p release
|
||||||
cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
|
cp artifacts/crossdesk-macos-x64-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-macos-x64-${{ needs.prepare-version.outputs.version }}.pkg
|
||||||
cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
|
cp artifacts/crossdesk-macos-arm64-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-macos-arm64-${{ needs.prepare-version.outputs.version }}.pkg
|
||||||
for ARCH in amd64 arm64; do
|
for ARCH in amd64 arm64; do
|
||||||
for PACKAGE in crossdesk crossdesk-virtual-desktop-xfce; do
|
for PACKAGE in crossdesk crossdesk-virtual-desktop-xfce; do
|
||||||
FILE="${PACKAGE}-linux-${ARCH}-${{ steps.version.outputs.VERSION_WITH_V }}.deb"
|
FILE="${PACKAGE}-linux-${ARCH}-${{ needs.prepare-version.outputs.version }}.deb"
|
||||||
cp "artifacts/crossdesk-linux-${ARCH}-${{ steps.version.outputs.VERSION_WITH_V }}/${FILE}" "release/${FILE}"
|
cp "artifacts/crossdesk-linux-${ARCH}-${{ needs.prepare-version.outputs.version }}/${FILE}" "release/${FILE}"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
cp artifacts/crossdesk-ios-arm64-unsigned-${{ needs.build-ios.outputs.version }}/* release/crossdesk-ios-arm64-unsigned-${{ needs.build-ios.outputs.version }}.zip
|
cp artifacts/crossdesk-ios-arm64-unsigned-${{ needs.build-ios.outputs.version }}/* release/crossdesk-ios-arm64-unsigned-${{ needs.build-ios.outputs.version }}.zip
|
||||||
cp artifacts/crossdesk-android-arm64-${{ needs.build-android.outputs.version }}/*.apk release/crossdesk-android-arm64-${{ needs.build-android.outputs.version }}.apk
|
cp artifacts/crossdesk-android-arm64-${{ needs.build-android.outputs.version }}/*.apk release/crossdesk-android-arm64-${{ needs.build-android.outputs.version }}.apk
|
||||||
cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe
|
cp artifacts/crossdesk-win-x64-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-win-x64-${{ needs.prepare-version.outputs.version }}.exe
|
||||||
cp artifacts/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip
|
cp artifacts/crossdesk-win-x64-portable-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-win-x64-portable-${{ needs.prepare-version.outputs.version }}.zip
|
||||||
|
|
||||||
- name: List release files
|
- name: List release files
|
||||||
run: ls -lh release/
|
run: ls -lh release/
|
||||||
@@ -743,10 +597,10 @@ jobs:
|
|||||||
- name: Upload to Versioned GitHub Release
|
- name: Upload to Versioned GitHub Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.version.outputs.VERSION_WITH_V }}
|
tag_name: ${{ needs.prepare-version.outputs.version }}
|
||||||
# The pushed tag already determines the release source commit.
|
# The pushed tag already determines the release source commit.
|
||||||
target_commitish: ${{ github.event.repository.default_branch }}
|
target_commitish: ${{ github.event.repository.default_branch }}
|
||||||
name: Release ${{ steps.version.outputs.VERSION_WITH_V }}
|
name: Release ${{ needs.prepare-version.outputs.version }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: release/*
|
files: release/*
|
||||||
@@ -789,7 +643,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate version.json for all platforms
|
- name: Generate version.json for all platforms
|
||||||
env:
|
env:
|
||||||
RELEASE_TAG: ${{ steps.version.outputs.VERSION_WITH_V }}
|
RELEASE_TAG: ${{ needs.prepare-version.outputs.version }}
|
||||||
CROSSDESK_UPDATE_PRIVATE_KEY: ${{ secrets.CROSSDESK_UPDATE_PRIVATE_KEY }}
|
CROSSDESK_UPDATE_PRIVATE_KEY: ${{ secrets.CROSSDESK_UPDATE_PRIVATE_KEY }}
|
||||||
run: |
|
run: |
|
||||||
python3 .github/scripts/update_version_manifest.py \
|
python3 .github/scripts/update_version_manifest.py \
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "crossdesk_ui.h"
|
#include "crossdesk_ui.h"
|
||||||
#include "fa_solid_900.h"
|
#include "fa_solid_900.h"
|
||||||
|
#include "features/about/about_catalog.h"
|
||||||
#include "features/file_transfer/transfer_notification_layout.h"
|
#include "features/file_transfer/transfer_notification_layout.h"
|
||||||
#include "features/gallery/gallery_fullscreen_transition.h"
|
#include "features/gallery/gallery_fullscreen_transition.h"
|
||||||
#include "features/gallery/gallery_window_state.h"
|
#include "features/gallery/gallery_window_state.h"
|
||||||
@@ -1785,6 +1786,11 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
|||||||
window->set_update_install_busy(updater_->busy());
|
window->set_update_install_busy(updater_->busy());
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
|
about::Populate(ui_->Window(gallery_window));
|
||||||
|
main->on_open_about_link([this](slint::SharedString url) {
|
||||||
|
const std::string link(url);
|
||||||
|
if (link.starts_with("https://")) OpenUrl(link);
|
||||||
|
});
|
||||||
main->on_open_website([this] { OpenUrl("https://crossdesk.cn"); });
|
main->on_open_website([this] { OpenUrl("https://crossdesk.cn"); });
|
||||||
main->on_open_afdian_sponsor([this] {
|
main->on_open_afdian_sponsor([this] {
|
||||||
OpenUrl("https://afdian.com/a/kunkundi");
|
OpenUrl("https://afdian.com/a/kunkundi");
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-21
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "about_catalog.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "about_resources.h"
|
||||||
|
|
||||||
|
namespace crossdesk::about {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
slint::SharedString Text(const nlohmann::json& object, const char* key) {
|
||||||
|
return slint::SharedString(object.value(key, std::string{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view TrimLayoutWhitespace(std::string_view line) {
|
||||||
|
const auto first = line.find_first_not_of(" \t\r");
|
||||||
|
if (first == std::string_view::npos) return {};
|
||||||
|
return line.substr(first, line.find_last_not_of(" \t\r") - first + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
slint::SharedString DocumentText(const nlohmann::json& document) {
|
||||||
|
const auto title = document.value("title", std::string{});
|
||||||
|
const auto text = document.value("text", std::string{});
|
||||||
|
if (title != "GNU GPL v3" && title != "GNU LGPL v3") {
|
||||||
|
return slint::SharedString(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GNU's plain-text copies use fixed-width wrapping and space-aligned headings.
|
||||||
|
// Reflow prose for the reader, retaining paragraph boundaries, notice examples,
|
||||||
|
// and every non-whitespace character. The embedded source stays untouched.
|
||||||
|
std::string formatted;
|
||||||
|
std::vector<std::string_view> paragraph;
|
||||||
|
auto append_paragraph = [&] {
|
||||||
|
if (paragraph.empty()) return;
|
||||||
|
if (!formatted.empty()) formatted += "\n\n";
|
||||||
|
const bool preserve_lines = paragraph.front().starts_with("GNU ") ||
|
||||||
|
paragraph.front().starts_with("<");
|
||||||
|
for (size_t index = 0; index < paragraph.size(); ++index) {
|
||||||
|
if (index > 0) {
|
||||||
|
const bool after_copyright =
|
||||||
|
paragraph[index - 1].starts_with("Copyright (C)");
|
||||||
|
formatted += preserve_lines || after_copyright ? '\n' : ' ';
|
||||||
|
}
|
||||||
|
formatted += paragraph[index];
|
||||||
|
}
|
||||||
|
paragraph.clear();
|
||||||
|
};
|
||||||
|
std::string_view remaining(text);
|
||||||
|
while (!remaining.empty()) {
|
||||||
|
const auto end = remaining.find('\n');
|
||||||
|
const auto line = TrimLayoutWhitespace(remaining.substr(0, end));
|
||||||
|
if (line.empty()) {
|
||||||
|
append_paragraph();
|
||||||
|
} else {
|
||||||
|
paragraph.push_back(line);
|
||||||
|
}
|
||||||
|
if (end == std::string_view::npos) break;
|
||||||
|
remaining.remove_prefix(end + 1);
|
||||||
|
}
|
||||||
|
append_paragraph();
|
||||||
|
return slint::SharedString(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Populate(const slint::ComponentHandle<ui::MainWindow>& window) {
|
||||||
|
// Both the main window and DeskHub share the immutable, embedded catalog.
|
||||||
|
static const auto catalog = nlohmann::json::parse(
|
||||||
|
std::begin(kAboutResources), std::end(kAboutResources));
|
||||||
|
static const auto model = [] {
|
||||||
|
std::vector<ui::AboutComponent> components;
|
||||||
|
for (const auto& item : catalog.at("components")) {
|
||||||
|
ui::AboutComponent component;
|
||||||
|
component.id = Text(item, "id");
|
||||||
|
component.name = Text(item, "name");
|
||||||
|
component.version = Text(item, "version");
|
||||||
|
component.license = Text(item, "license");
|
||||||
|
component.source_url = Text(item, "sourceURL");
|
||||||
|
std::vector<ui::AboutDocument> documents;
|
||||||
|
for (const auto& document : item.at("documents")) {
|
||||||
|
ui::AboutDocument entry;
|
||||||
|
entry.title = Text(document, "title");
|
||||||
|
entry.text = DocumentText(document);
|
||||||
|
documents.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
component.documents =
|
||||||
|
std::make_shared<slint::VectorModel<ui::AboutDocument>>(documents);
|
||||||
|
components.push_back(std::move(component));
|
||||||
|
}
|
||||||
|
return std::make_shared<slint::VectorModel<ui::AboutComponent>>(components);
|
||||||
|
}();
|
||||||
|
window->set_about_components(model);
|
||||||
|
for (size_t index = 0; index < model->row_count(); ++index) {
|
||||||
|
if (model->row_data(index)->id == "openfec") {
|
||||||
|
window->set_about_openfec_index(static_cast<int>(index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window->set_about_revision(Text(catalog, "revision"));
|
||||||
|
window->set_about_source_url(Text(catalog, "sourceURL"));
|
||||||
|
window->set_about_build_url(Text(catalog, "buildURL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk::about
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-21
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ABOUT_CATALOG_H_
|
||||||
|
#define _ABOUT_CATALOG_H_
|
||||||
|
|
||||||
|
#include "crossdesk_ui.h"
|
||||||
|
|
||||||
|
namespace crossdesk::about {
|
||||||
|
|
||||||
|
void Populate(const slint::ComponentHandle<ui::MainWindow>& window);
|
||||||
|
|
||||||
|
} // namespace crossdesk::about
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import { ScrollView, TextEdit } from "std-widgets.slint";
|
||||||
|
import { SettingsDialog, SettingsButton } from "settings_controls.slint";
|
||||||
|
import { WorkbenchStyle } from "workbench_controls.slint";
|
||||||
|
import { UiRadius } from "ui_radius.slint";
|
||||||
|
import { GalleryStrings } from "device_strings.slint";
|
||||||
|
import { FontAwesomeIcons } from "common.slint";
|
||||||
|
import "../assets/fonts/message_spacing.ttf";
|
||||||
|
|
||||||
|
export enum AboutPage { overview, components, component, document, source }
|
||||||
|
export struct AboutDocument { title: string, text: string }
|
||||||
|
export struct AboutComponent {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
version: string,
|
||||||
|
license: string,
|
||||||
|
source-url: string,
|
||||||
|
documents: [AboutDocument],
|
||||||
|
}
|
||||||
|
|
||||||
|
export global AboutStrings {
|
||||||
|
out property <string> software-license: GalleryStrings.tr("软件许可", "Software license", "Лицензия программы");
|
||||||
|
out property <string> components: GalleryStrings.tr("开源组件", "Open-source components", "Компоненты с открытым кодом");
|
||||||
|
out property <string> source: GalleryStrings.tr("源码与构建说明", "Source code and build instructions", "Исходный код и сборка");
|
||||||
|
out property <string> notices: GalleryStrings.tr("许可与版权声明", "Licenses and copyright notices", "Лицензии и авторские права");
|
||||||
|
out property <string> view-source: GalleryStrings.tr("查看源码", "View source code", "Открыть исходный код");
|
||||||
|
out property <string> back: GalleryStrings.tr("返回", "Back", "Назад");
|
||||||
|
}
|
||||||
|
|
||||||
|
export component AboutNavigationGroup inherits Rectangle {
|
||||||
|
height: rows.preferred-height + 2px;
|
||||||
|
background: WorkbenchStyle.surface;
|
||||||
|
border-radius: UiRadius.control;
|
||||||
|
border-width: 1px; border-color: WorkbenchStyle.separator;
|
||||||
|
clip: true;
|
||||||
|
rows := VerticalLayout {
|
||||||
|
x: 1px; y: 1px; width: parent.width - 2px;
|
||||||
|
height: self.preferred-height; padding: 0px; spacing: 0px;
|
||||||
|
@children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export component AboutRowSeparator inherits Rectangle {
|
||||||
|
height: 1px;
|
||||||
|
Rectangle {
|
||||||
|
x: 14px; width: parent.width - 30px; height: 1px;
|
||||||
|
background: WorkbenchStyle.separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export component AboutNavigationRow inherits Rectangle {
|
||||||
|
in property <string> title;
|
||||||
|
in property <string> subtitle;
|
||||||
|
in property <string> icon;
|
||||||
|
in property <string> detail;
|
||||||
|
in property <bool> grouped: false;
|
||||||
|
in property <bool> external: false;
|
||||||
|
callback clicked;
|
||||||
|
min-width: 0px;
|
||||||
|
min-height: max(44px, labels.preferred-height + 20px);
|
||||||
|
background: touch.pressed ? WorkbenchStyle.pressed-surface
|
||||||
|
: touch.has-hover ? WorkbenchStyle.hover-surface : WorkbenchStyle.surface;
|
||||||
|
border-radius: root.grouped ? 0px : UiRadius.control;
|
||||||
|
border-width: root.grouped ? 0px : 1px; border-color: WorkbenchStyle.separator;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: root.title + (root.subtitle != "" ? ", " + root.subtitle : "")
|
||||||
|
+ (root.detail != "" ? ", " + root.detail : "");
|
||||||
|
accessible-action-default => { root.clicked(); }
|
||||||
|
if root.icon != "": Rectangle {
|
||||||
|
x: 12px; y: (parent.height - self.height) / 2;
|
||||||
|
width: 28px; height: 28px;
|
||||||
|
border-radius: UiRadius.control;
|
||||||
|
background: WorkbenchStyle.field-surface;
|
||||||
|
Text {
|
||||||
|
text: root.icon; font-family: "Font Awesome 6 Free"; font-weight: 900;
|
||||||
|
font-size: 13px; color: WorkbenchStyle.muted;
|
||||||
|
horizontal-alignment: center; vertical-alignment: center;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
labels := VerticalLayout {
|
||||||
|
x: root.icon != "" ? 52px : 14px;
|
||||||
|
y: (parent.height - self.height) / 2;
|
||||||
|
width: parent.width - self.x - 36px - (root.detail != "" ? detail-label.width + 12px : 0px);
|
||||||
|
height: self.preferred-height; spacing: 4px;
|
||||||
|
Text { text: root.title; color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap; }
|
||||||
|
if root.subtitle != "": Text {
|
||||||
|
text: root.subtitle; color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detail-label := Text {
|
||||||
|
x: parent.width - self.width - 36px; height: parent.height;
|
||||||
|
width: min(100px, self.preferred-width);
|
||||||
|
text: root.detail; color: WorkbenchStyle.muted; font-size: 11px;
|
||||||
|
vertical-alignment: center; overflow: elide;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
x: parent.width - 30px; width: 18px; height: parent.height;
|
||||||
|
text: root.external ? FontAwesomeIcons.arrow-up-right-from-square : FontAwesomeIcons.angle-right;
|
||||||
|
font-family: "Font Awesome 6 Free"; font-weight: 900;
|
||||||
|
color: touch.has-hover ? WorkbenchStyle.accent : WorkbenchStyle.muted;
|
||||||
|
font-size: 11px; vertical-alignment: center; horizontal-alignment: center;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
touch := TouchArea { mouse-cursor: pointer; clicked => { root.clicked(); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export component AboutPanel inherits SettingsDialog {
|
||||||
|
show-close-button: false;
|
||||||
|
in-out property <AboutPage> page: AboutPage.components;
|
||||||
|
in-out property <int> component-index: 0;
|
||||||
|
private property <int> document-index: 0;
|
||||||
|
in property <[AboutComponent]> components;
|
||||||
|
in property <int> openfec-index: -1;
|
||||||
|
in property <string> revision;
|
||||||
|
in property <string> source-url;
|
||||||
|
in property <string> build-url;
|
||||||
|
private property <AboutComponent> selected: root.components[root.component-index];
|
||||||
|
callback back;
|
||||||
|
callback open-link(string);
|
||||||
|
title: root.page == AboutPage.components ? AboutStrings.components
|
||||||
|
: root.page == AboutPage.source ? AboutStrings.source
|
||||||
|
: root.page == AboutPage.document ? AboutStrings.notices
|
||||||
|
: root.selected.name;
|
||||||
|
|
||||||
|
if root.page != AboutPage.document: ScrollView {
|
||||||
|
x: 24px; y: root.content-top; width: parent.width - 48px + 22px;
|
||||||
|
height: footer.y - self.y - 16px;
|
||||||
|
viewport-width: self.visible-width;
|
||||||
|
viewport-height: content.preferred-height;
|
||||||
|
horizontal-scrollbar-policy: always-off;
|
||||||
|
vertical-scrollbar-policy: as-needed;
|
||||||
|
content := VerticalLayout {
|
||||||
|
width: parent.width; height: self.preferred-height;
|
||||||
|
padding: 0px; padding-right: 22px; spacing: 10px;
|
||||||
|
if root.page == AboutPage.components: VerticalLayout {
|
||||||
|
padding: 0px; spacing: 10px;
|
||||||
|
Text {
|
||||||
|
text: GalleryStrings.tr("以下列出本版本的主要开源组件。各组件遵循各自的开源许可。", "Main open-source components used in this build. Each component is covered by its own license.", "Основные открытые компоненты этой сборки. Каждый распространяется по своей лицензии.");
|
||||||
|
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
height: notice.preferred-height + 32px;
|
||||||
|
background: WorkbenchStyle.field-surface; border-radius: UiRadius.control;
|
||||||
|
notice := VerticalLayout {
|
||||||
|
x: 16px; y: 16px; width: parent.width - 32px;
|
||||||
|
height: self.preferred-height; spacing: 12px;
|
||||||
|
Text { text: GalleryStrings.tr("OpenFEC 告知", "OpenFEC notice", "Уведомление OpenFEC"); color: WorkbenchStyle.text; font-size: 14px; font-weight: 600; }
|
||||||
|
Text {
|
||||||
|
text: GalleryStrings.tr("本应用使用 OpenFEC 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。该许可规定了有限保证及责任限制,详情见许可全文。", "This application uses OpenFEC for forward error correction under CeCILL-C 1.0, which provides limited warranties and limits liability. See the full license for details.", "Приложение использует OpenFEC для прямого исправления ошибок по лицензии CeCILL-C 1.0 с ограниченной гарантией и ответственностью. Подробности — в тексте лицензии.");
|
||||||
|
color: WorkbenchStyle.muted; font-size: 13px; wrap: word-wrap;
|
||||||
|
font-family: "CrossDesk Message Spacing";
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: "(c) Copyright 2009 - 2012 INRIA - All rights reserved";
|
||||||
|
color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap;
|
||||||
|
font-family: "CrossDesk Message Spacing";
|
||||||
|
}
|
||||||
|
if root.openfec-index >= 0: AboutNavigationRow {
|
||||||
|
title: GalleryStrings.tr("OpenFEC 许可与源码", "OpenFEC license and source", "Лицензия и исходный код OpenFEC");
|
||||||
|
clicked => { root.component-index = root.openfec-index; root.page = AboutPage.component; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for component[index] in root.components: VerticalLayout {
|
||||||
|
padding: 0px; spacing: 0px;
|
||||||
|
if component.id != "crossdesk" && component.id != "openfec": AboutNavigationRow {
|
||||||
|
title: component.name;
|
||||||
|
subtitle: component.version + " · " + component.license;
|
||||||
|
clicked => { root.component-index = index; root.page = AboutPage.component; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if root.page == AboutPage.component: VerticalLayout {
|
||||||
|
padding: 0px; spacing: 12px;
|
||||||
|
Text { text: root.selected.version + " · " + root.selected.license; color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap; }
|
||||||
|
AboutNavigationRow {
|
||||||
|
title: AboutStrings.view-source; external: true;
|
||||||
|
clicked => { root.open-link(root.selected.source-url); }
|
||||||
|
}
|
||||||
|
Text { text: AboutStrings.notices; color: WorkbenchStyle.text; font-size: 13px; font-weight: 600; }
|
||||||
|
for document[index] in root.selected.documents: AboutNavigationRow {
|
||||||
|
title: document.title;
|
||||||
|
clicked => { root.document-index = index; root.page = AboutPage.document; }
|
||||||
|
}
|
||||||
|
if root.selected.documents.length == 0: Text {
|
||||||
|
text: GalleryStrings.tr("本组件未内置许可文本,请通过“查看源码”查阅许可与版权声明。", "License text is not bundled for this component. Use View source code to read its license and copyright notices.", "Текст лицензии не включён. Откройте исходный код для просмотра лицензии и авторских прав.");
|
||||||
|
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if root.page == AboutPage.source: VerticalLayout {
|
||||||
|
padding: 0px; spacing: 12px;
|
||||||
|
Text {
|
||||||
|
text: root.revision != "" ? GalleryStrings.tr("源码版本:", "Source revision: ", "Ревизия исходного кода: ") + root.revision
|
||||||
|
: GalleryStrings.tr("本版本源码信息暂不可用。", "Source revision is unavailable for this build.", "Ревизия исходного кода этой сборки недоступна.");
|
||||||
|
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
AboutNavigationRow { title: AboutStrings.view-source; external: true; clicked => { root.open-link(root.source-url); } }
|
||||||
|
AboutNavigationRow {
|
||||||
|
title: GalleryStrings.tr("构建说明", "Build instructions", "Инструкции по сборке"); external: true;
|
||||||
|
clicked => { root.open-link(root.build-url); }
|
||||||
|
}
|
||||||
|
AboutNavigationRow {
|
||||||
|
title: GalleryStrings.tr("项目主页", "Project website", "Сайт проекта"); external: true;
|
||||||
|
clicked => { root.open-link("https://crossdesk.cn"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if root.page == AboutPage.document: VerticalLayout {
|
||||||
|
x: 24px; y: root.content-top; width: parent.width - 48px;
|
||||||
|
height: footer.y - self.y - 16px; spacing: 12px;
|
||||||
|
Text { text: root.selected.documents[root.document-index].title; color: WorkbenchStyle.text; font-size: 13px; font-weight: 600; wrap: word-wrap; }
|
||||||
|
TextEdit {
|
||||||
|
text: root.selected.documents[root.document-index].text;
|
||||||
|
read-only: true; wrap: word-wrap; font-size: 14px;
|
||||||
|
// Use the same 1.5em line spacing as other long-form reading views.
|
||||||
|
font-family: "CrossDesk Message Spacing";
|
||||||
|
vertical-stretch: 1;
|
||||||
|
key-pressed(event) => {
|
||||||
|
if event.text == Key.Escape { root.back(); return accept; }
|
||||||
|
return reject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
footer := VerticalLayout {
|
||||||
|
x: 24px; y: parent.height - self.height - 24px;
|
||||||
|
width: parent.width - 48px; height: self.preferred-height; spacing: 12px;
|
||||||
|
Rectangle { height: 1px; background: WorkbenchStyle.separator; }
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: end;
|
||||||
|
SettingsButton { text: AboutStrings.back; clicked => { root.back(); } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,9 +32,11 @@ export global FontAwesomeIcons {
|
|||||||
out property <string> angle-right: "\u{f105}";
|
out property <string> angle-right: "\u{f105}";
|
||||||
out property <string> angle-up: "\u{f106}";
|
out property <string> angle-up: "\u{f106}";
|
||||||
out property <string> arrow-right-long: "\u{f178}";
|
out property <string> arrow-right-long: "\u{f178}";
|
||||||
|
out property <string> arrow-up-right-from-square: "\u{f35d}";
|
||||||
out property <string> bars: "\u{f0c9}";
|
out property <string> bars: "\u{f0c9}";
|
||||||
out property <string> check: "\u{f00c}";
|
out property <string> check: "\u{f00c}";
|
||||||
out property <string> circle-arrow-up: "\u{f0aa}";
|
out property <string> circle-arrow-up: "\u{f0aa}";
|
||||||
|
out property <string> code: "\u{f121}";
|
||||||
out property <string> compress: "\u{f066}";
|
out property <string> compress: "\u{f066}";
|
||||||
out property <string> computer-mouse: "\u{f8cc}";
|
out property <string> computer-mouse: "\u{f8cc}";
|
||||||
out property <string> copy: "\u{f0c5}";
|
out property <string> copy: "\u{f0c5}";
|
||||||
@@ -42,8 +44,10 @@ export global FontAwesomeIcons {
|
|||||||
out property <string> expand: "\u{f065}";
|
out property <string> expand: "\u{f065}";
|
||||||
out property <string> eye: "\u{f06e}";
|
out property <string> eye: "\u{f06e}";
|
||||||
out property <string> eye-slash: "\u{f070}";
|
out property <string> eye-slash: "\u{f070}";
|
||||||
|
out property <string> file-lines: "\u{f15c}";
|
||||||
out property <string> folder-open: "\u{f07c}";
|
out property <string> folder-open: "\u{f07c}";
|
||||||
out property <string> keyboard: "\u{f11c}";
|
out property <string> keyboard: "\u{f11c}";
|
||||||
|
out property <string> layer-group: "\u{f5fd}";
|
||||||
out property <string> minus: "\u{f068}";
|
out property <string> minus: "\u{f068}";
|
||||||
out property <string> pen: "\u{f304}";
|
out property <string> pen: "\u{f304}";
|
||||||
out property <string> rotate: "\u{f2f1}";
|
out property <string> rotate: "\u{f2f1}";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Single AOT entry point. Generating one header for the entire UI avoids
|
// Single AOT entry point. Generating one header for the entire UI avoids
|
||||||
// duplicate imported-component definitions when C++ uses all three windows.
|
// duplicate imported-component definitions when C++ uses all three windows.
|
||||||
export { AnnouncementItem, MessageStrings } from "message_panel.slint";
|
export { AnnouncementItem, MessageStrings } from "message_panel.slint";
|
||||||
|
export { AboutComponent, AboutDocument, AboutPage } from "about_panel.slint";
|
||||||
export { MainWindow, RecentConnection, UpdateCheckStatus, AutostartStatus, UiStrings } from "main_window.slint";
|
export { MainWindow, RecentConnection, UpdateCheckStatus, AutostartStatus, UiStrings } from "main_window.slint";
|
||||||
export { StreamWindow, StreamTab, FileTransferEntry, NetworkStatsRow, StreamStrings } from "stream_window.slint";
|
export { StreamWindow, StreamTab, FileTransferEntry, NetworkStatsRow, StreamStrings } from "stream_window.slint";
|
||||||
export { ServerWindow, ServerStatusBar, ServerStrings, ControllerEntry } from "server_window.slint";
|
export { ServerWindow, ServerStatusBar, ServerStrings, ControllerEntry } from "server_window.slint";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AboutPanel, AboutPage, AboutComponent, AboutStrings, AboutNavigationRow, AboutNavigationGroup, AboutRowSeparator } from "about_panel.slint";
|
||||||
import { UiRadius } from "ui_radius.slint";
|
import { UiRadius } from "ui_radius.slint";
|
||||||
import { WorkbenchStyle, WorkbenchButton, WorkbenchCard, WorkbenchNotice } from "workbench_controls.slint";
|
import { WorkbenchStyle, WorkbenchButton, WorkbenchCard, WorkbenchNotice } from "workbench_controls.slint";
|
||||||
import { GalleryStrings } from "device_strings.slint";
|
import { GalleryStrings } from "device_strings.slint";
|
||||||
@@ -536,6 +537,20 @@ export component MainWindow inherits Window {
|
|||||||
if !root.self-host-settings-open { root.self-host-settings-error = ""; }
|
if !root.self-host-settings-open { root.self-host-settings-error = ""; }
|
||||||
}
|
}
|
||||||
in-out property <bool> about-open: false;
|
in-out property <bool> about-open: false;
|
||||||
|
in-out property <AboutPage> about-page: overview;
|
||||||
|
private property <length> about-scroll-y: 0px;
|
||||||
|
in-out property <int> about-component-index: 0;
|
||||||
|
in property <[AboutComponent]> about-components;
|
||||||
|
in property <int> about-openfec-index: -1;
|
||||||
|
in property <string> about-revision;
|
||||||
|
in property <string> about-source-url;
|
||||||
|
in property <string> about-build-url;
|
||||||
|
callback open-about-link(string);
|
||||||
|
function about-back() {
|
||||||
|
if root.about-page == AboutPage.document { root.about-page = AboutPage.component; }
|
||||||
|
else if root.about-page == AboutPage.component && root.about-component-index != 0 { root.about-page = AboutPage.components; }
|
||||||
|
else { root.about-page = AboutPage.overview; }
|
||||||
|
}
|
||||||
in-out property <bool> sponsorship-open: false;
|
in-out property <bool> sponsorship-open: false;
|
||||||
// Automatic prompts must not interrupt another dialog or menu.
|
// Automatic prompts must not interrupt another dialog or menu.
|
||||||
private property <bool> sponsorship-unobstructed: !root.gallery-mode
|
private property <bool> sponsorship-unobstructed: !root.gallery-mode
|
||||||
@@ -627,6 +642,15 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
changed about-open => {
|
changed about-open => {
|
||||||
if root.about-open { root.dialog-focus-pending = true; }
|
if root.about-open { root.dialog-focus-pending = true; }
|
||||||
|
else {
|
||||||
|
root.about-page = AboutPage.overview;
|
||||||
|
root.about-component-index = 0;
|
||||||
|
root.about-scroll-y = 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed about-page => {
|
||||||
|
// Restore dialog shortcuts after leaving the selectable license text.
|
||||||
|
if root.about-open { root.dialog-focus-pending = true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
changed sponsorship-open => {
|
changed sponsorship-open => {
|
||||||
@@ -855,7 +879,10 @@ export component MainWindow inherits Window {
|
|||||||
return accept;
|
return accept;
|
||||||
}
|
}
|
||||||
if root.about-open && !root.update-open {
|
if root.about-open && !root.update-open {
|
||||||
if event.text == Key.Escape { root.about-open = false; }
|
if event.text == Key.Escape {
|
||||||
|
if root.about-page != AboutPage.overview { root.about-back(); }
|
||||||
|
else { root.about-open = false; }
|
||||||
|
}
|
||||||
return accept;
|
return accept;
|
||||||
}
|
}
|
||||||
if root.sponsorship-open && root.sponsorship-unobstructed {
|
if root.sponsorship-open && root.sponsorship-unobstructed {
|
||||||
@@ -1459,25 +1486,24 @@ export component MainWindow inherits Window {
|
|||||||
text: root.current-version;
|
text: root.current-version;
|
||||||
font-size: 11px; color: WorkbenchStyle.muted; vertical-alignment: center;
|
font-size: 11px; color: WorkbenchStyle.muted; vertical-alignment: center;
|
||||||
}
|
}
|
||||||
if !root.signal-connected && !root.signal-tls-error: Rectangle {
|
if !root.signal-connected && !root.signal-tls-error: WorkbenchButton {
|
||||||
x: parent.width - self.width - 20px;
|
x: parent.width - self.width - 20px;
|
||||||
y: 0px;
|
y: (parent.height - self.height) / 2;
|
||||||
height: parent.height;
|
width: max(64px, reconnect-label-metrics.preferred-width + 22px);
|
||||||
width: reconnect-label.preferred-width;
|
height: 22px;
|
||||||
reconnect-label := Text {
|
text: UiStrings.signal-reconnect;
|
||||||
text: UiStrings.signal-reconnect;
|
text-size: 11px;
|
||||||
color: WorkbenchStyle.accent;
|
text-color: WorkbenchStyle.accent;
|
||||||
font-size: 11px;
|
clicked => {
|
||||||
horizontal-alignment: center;
|
root.copy-feedback = CopyFeedback.idle;
|
||||||
vertical-alignment: center;
|
root.signal-reconnect-requested();
|
||||||
}
|
}
|
||||||
TouchArea {
|
reconnect-label-metrics := Text {
|
||||||
mouse-cursor: pointer;
|
visible: false;
|
||||||
clicked => {
|
text: UiStrings.signal-reconnect;
|
||||||
root.copy-feedback = CopyFeedback.idle;
|
font-size: 11px;
|
||||||
root.signal-reconnect-requested();
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2365,17 +2391,36 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if root.about-open: Rectangle {
|
if root.about-open: Rectangle {
|
||||||
|
// Use the same bounds throughout the About navigation stack.
|
||||||
|
private property <length> dialog-width: min(480px, self.width - 40px);
|
||||||
|
private property <length> dialog-height: min(520px, self.height - 32px);
|
||||||
background: #15223850;
|
background: #15223850;
|
||||||
z: 46;
|
z: 46;
|
||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea { clicked => { focus-sink.focus(); } }
|
||||||
SettingsDialog {
|
if root.about-page != AboutPage.overview: AboutPanel {
|
||||||
x: (parent.width - self.width) / 2;
|
x: (parent.width - self.width) / 2;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
width: min(480px, parent.width - 40px);
|
width: parent.dialog-width;
|
||||||
|
min-height: 0px; max-height: parent.height - 32px;
|
||||||
|
height: parent.dialog-height;
|
||||||
|
page <=> root.about-page;
|
||||||
|
component-index <=> root.about-component-index;
|
||||||
|
components: root.about-components;
|
||||||
|
openfec-index: root.about-openfec-index;
|
||||||
|
revision: root.about-revision;
|
||||||
|
source-url: root.about-source-url;
|
||||||
|
build-url: root.about-build-url;
|
||||||
|
back => { root.about-back(); }
|
||||||
|
open-link(url) => { root.open-about-link(url); }
|
||||||
|
}
|
||||||
|
if root.about-page == AboutPage.overview: SettingsDialog {
|
||||||
|
x: (parent.width - self.width) / 2;
|
||||||
|
y: (parent.height - self.height) / 2;
|
||||||
|
width: parent.dialog-width;
|
||||||
// The overlay excludes any custom titlebar on every platform.
|
// The overlay excludes any custom titlebar on every platform.
|
||||||
min-height: 0px; max-height: parent.height - 32px;
|
min-height: 0px; max-height: parent.height - 32px;
|
||||||
// Keep the dialog stable as update status and release details change.
|
// Keep the dialog stable as update status and release details change.
|
||||||
height: min(488px, parent.height - 32px);
|
height: parent.dialog-height;
|
||||||
title: UiStrings.about;
|
title: UiStrings.about;
|
||||||
close-label: MessageStrings.close;
|
close-label: MessageStrings.close;
|
||||||
close => { root.about-open = false; }
|
close => { root.about-open = false; }
|
||||||
@@ -2388,6 +2433,8 @@ export component MainWindow inherits Window {
|
|||||||
height: about-footer.y - self.y - 16px;
|
height: about-footer.y - self.y - 16px;
|
||||||
viewport-width: self.visible-width;
|
viewport-width: self.visible-width;
|
||||||
viewport-height: about-content.preferred-height;
|
viewport-height: about-content.preferred-height;
|
||||||
|
// Keep the position when navigation recreates the overview.
|
||||||
|
viewport-y <=> root.about-scroll-y;
|
||||||
horizontal-scrollbar-policy: always-off;
|
horizontal-scrollbar-policy: always-off;
|
||||||
vertical-scrollbar-policy: as-needed;
|
vertical-scrollbar-policy: as-needed;
|
||||||
about-content := VerticalLayout {
|
about-content := VerticalLayout {
|
||||||
@@ -2465,6 +2512,26 @@ export component MainWindow inherits Window {
|
|||||||
Text { text: UiStrings.copyright; color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap; }
|
Text { text: UiStrings.copyright; color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap; }
|
||||||
Text { text: UiStrings.license; color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap; }
|
Text { text: UiStrings.license; color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap; }
|
||||||
}
|
}
|
||||||
|
AboutNavigationGroup {
|
||||||
|
AboutNavigationRow {
|
||||||
|
grouped: true;
|
||||||
|
title: AboutStrings.software-license;
|
||||||
|
detail: "GPL v3";
|
||||||
|
clicked => { root.about-component-index = 0; root.about-page = AboutPage.component; }
|
||||||
|
}
|
||||||
|
AboutRowSeparator { }
|
||||||
|
AboutNavigationRow {
|
||||||
|
grouped: true;
|
||||||
|
title: AboutStrings.components;
|
||||||
|
clicked => { root.about-page = AboutPage.components; }
|
||||||
|
}
|
||||||
|
AboutRowSeparator { }
|
||||||
|
AboutNavigationRow {
|
||||||
|
grouped: true;
|
||||||
|
title: AboutStrings.source;
|
||||||
|
clicked => { root.about-page = AboutPage.source; }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
about-footer := VerticalLayout {
|
about-footer := VerticalLayout {
|
||||||
@@ -2472,17 +2539,14 @@ export component MainWindow inherits Window {
|
|||||||
width: parent.width - 48px; height: self.preferred-height;
|
width: parent.width - 48px; height: self.preferred-height;
|
||||||
spacing: 16px;
|
spacing: 16px;
|
||||||
Rectangle { height: 1px; background: WorkbenchStyle.separator; }
|
Rectangle { height: 1px; background: WorkbenchStyle.separator; }
|
||||||
|
if root.update-status-text != "": Text {
|
||||||
|
text: root.update-status-text;
|
||||||
|
color: root.update-check-status == UpdateCheckStatus.failed ? #b3261e
|
||||||
|
: root.update-check-status == UpdateCheckStatus.up-to-date ? WorkbenchStyle.success : WorkbenchStyle.muted;
|
||||||
|
font-size: 12px; wrap: word-wrap;
|
||||||
|
}
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
alignment: end; spacing: 8px;
|
alignment: end; spacing: 8px;
|
||||||
// Status updates use the spare footer space without reflowing the content.
|
|
||||||
Text {
|
|
||||||
min-width: 0px; height: 32px; horizontal-stretch: 1;
|
|
||||||
text: root.update-status-text;
|
|
||||||
color: root.update-check-status == UpdateCheckStatus.failed ? #b3261e
|
|
||||||
: root.update-check-status == UpdateCheckStatus.up-to-date ? WorkbenchStyle.success : WorkbenchStyle.muted;
|
|
||||||
font-size: 12px; wrap: word-wrap; overflow: elide;
|
|
||||||
vertical-alignment: center;
|
|
||||||
}
|
|
||||||
if root.update-available: WorkbenchButton {
|
if root.update-available: WorkbenchButton {
|
||||||
width: max(80px, about-update-button-metrics.preferred-width + 24px);
|
width: max(80px, about-update-button-metrics.preferred-width + 24px);
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ function setup_options_and_dependencies()
|
|||||||
end
|
end
|
||||||
|
|
||||||
add_requireconfs("**.python", {version = "3.12", override = true, configs = {pgo = false}})
|
add_requireconfs("**.python", {version = "3.12", override = true, configs = {pgo = false}})
|
||||||
|
-- Keep application and build-tool dependencies on the same OpenSSL release.
|
||||||
|
add_requireconfs("**.openssl", "**.openssl3",
|
||||||
|
{version = "3.3.2", system = false, override = true})
|
||||||
add_requires("spdlog 1.14.1", {system = false})
|
add_requires("spdlog 1.14.1", {system = false})
|
||||||
add_requires("slint 1.17.1", {configs = {shared = true}})
|
add_requires("slint 1.17.1", {configs = {shared = true}})
|
||||||
add_requires("libsdl3 3.2.26", {configs = {shared = false}})
|
add_requires("libsdl3 3.2.26", {configs = {shared = false}})
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
-- Embed notices so the About pages also work in offline/portable installations.
|
||||||
|
rule("crossdesk.about_resources")
|
||||||
|
after_load(function(target)
|
||||||
|
target:add("includedirs", path.join(target:autogendir(), "about"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
before_build(function(target)
|
||||||
|
import("core.base.json")
|
||||||
|
import("core.project.config")
|
||||||
|
|
||||||
|
local root = os.projectdir()
|
||||||
|
local function read(relative)
|
||||||
|
return assert(io.readfile(path.join(root, relative)))
|
||||||
|
end
|
||||||
|
local function git(directory, args)
|
||||||
|
local result
|
||||||
|
try {function() result = os.iorunv("git", table.join({"-C", directory}, args)) end}
|
||||||
|
return result and result:trim() or ""
|
||||||
|
end
|
||||||
|
local repository = "https://github.com/kunkundi/crossdesk"
|
||||||
|
local revision = git(root, {"rev-parse", "HEAD"})
|
||||||
|
local source = repository .. (revision ~= "" and "/tree/" .. revision or "")
|
||||||
|
local mini_root = path.join(root, "deps/submodules/minirtc")
|
||||||
|
local mini_revision = git(mini_root, {"rev-parse", "HEAD"})
|
||||||
|
local mini_source = "https://github.com/kunkundi/minirtc" ..
|
||||||
|
(mini_revision ~= "" and "/tree/" .. mini_revision or "")
|
||||||
|
local catalog = json.decode(read("apps/ios/CrossDeskMobile/Resources/ThirdPartyLicenses.json"))
|
||||||
|
local known = {}
|
||||||
|
for _, component in ipairs(catalog.components) do known[component.id] = component end
|
||||||
|
|
||||||
|
-- These desktop dependencies are not part of the shared mobile catalog.
|
||||||
|
local desktop = {
|
||||||
|
slint = {"Slint", "https://github.com/slint-ui/slint/tree/v"},
|
||||||
|
libsdl3 = {"SDL", "https://github.com/libsdl-org/SDL/tree/release-"},
|
||||||
|
tinyfiledialogs = {"tinyfiledialogs", "https://sourceforge.net/projects/tinyfiledialogs/"},
|
||||||
|
["cpp-httplib"] = {"cpp-httplib", "https://github.com/yhirose/cpp-httplib/tree/v"},
|
||||||
|
openh264 = {"OpenH264", "https://github.com/cisco/openh264/tree/v"},
|
||||||
|
miniaudio = {"miniaudio", "https://github.com/mackron/miniaudio/tree/"},
|
||||||
|
aom = {"libaom", "https://aomedia.googlesource.com/aom/+/v"}
|
||||||
|
}
|
||||||
|
local components = {{
|
||||||
|
id = "crossdesk", name = "CrossDesk", version = config.get("CROSSDESK_VERSION") or "0.0.0",
|
||||||
|
license = "GPL-3.0-only", sourceURL = source,
|
||||||
|
documents = {{title = "GNU GPL v3", text = read("LICENSE")}}
|
||||||
|
}, {
|
||||||
|
id = "minirtc", name = "MiniRTC", version = mini_revision:sub(1, 12),
|
||||||
|
license = "LGPL-3.0-only", sourceURL = mini_source,
|
||||||
|
documents = {{title = "GNU LGPL v3", text = read("deps/submodules/minirtc/LICENSE")},
|
||||||
|
{title = "GNU GPL v3", text = read("LICENSE")}}
|
||||||
|
}}
|
||||||
|
local seen = {}
|
||||||
|
for _, dependency in ipairs(table.join({target}, target:orderdeps())) do
|
||||||
|
for _, package in ipairs(dependency:orderpkgs()) do
|
||||||
|
local name = package:name()
|
||||||
|
if not seen[name] then
|
||||||
|
seen[name] = true
|
||||||
|
local version = package:get("version") or ""
|
||||||
|
local shared = known[name]
|
||||||
|
local extra = desktop[name]
|
||||||
|
local item = {id = name, name = shared and shared.name or extra and extra[1] or name,
|
||||||
|
version = version, license = package:license() or "",
|
||||||
|
sourceURL = "https://github.com/xmake-io/xmake-repo/tree/master/packages/" ..
|
||||||
|
name:sub(1, 1) .. "/" .. name,
|
||||||
|
documents = json.mark_as_array({})}
|
||||||
|
if shared and shared.version:gsub("^v", "") == version:gsub("^v", "") then
|
||||||
|
item.license = shared.license
|
||||||
|
item.sourceURL = shared.sourceURL
|
||||||
|
item.documents = shared.documents
|
||||||
|
elseif extra then
|
||||||
|
item.sourceURL = extra[2] .. (name == "tinyfiledialogs" and "" or version:gsub("^v", ""))
|
||||||
|
end
|
||||||
|
-- Prefer installed notices when a package is absent from the shared catalog.
|
||||||
|
if #item.documents == 0 then
|
||||||
|
local install = package:installdir()
|
||||||
|
if install then
|
||||||
|
for _, file in ipairs(os.files(path.join(install, "share/licenses/**"))) do
|
||||||
|
local text = io.readfile(file)
|
||||||
|
if text and not text:find("\0", 1, true) then
|
||||||
|
table.insert(item.documents, {title = path.relative(file, install), text = text})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local notice_header = name == "tinyfiledialogs" and "include/tinyfiledialogs.h"
|
||||||
|
or name == "openh264" and "include/wels/codec_api.h"
|
||||||
|
if notice_header then
|
||||||
|
local header_path = path.join(install, notice_header)
|
||||||
|
local header = os.isfile(header_path) and io.readfile(header_path)
|
||||||
|
local notice = header and header:match("^/%*.-%*/")
|
||||||
|
if notice then
|
||||||
|
table.insert(item.documents, {title = notice_header, text = notice})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if name == "slint" then
|
||||||
|
table.insert(item.documents, {title = "GNU GPL v3", text = read("LICENSE")})
|
||||||
|
end
|
||||||
|
table.insert(components, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(components, function(a, b)
|
||||||
|
if a.id == b.id then return false end
|
||||||
|
if a.id == "crossdesk" then return true end
|
||||||
|
if b.id == "crossdesk" then return false end
|
||||||
|
return a.name:lower() < b.name:lower()
|
||||||
|
end)
|
||||||
|
local data = json.encode({components = components, revision = revision:sub(1, 12),
|
||||||
|
sourceURL = source,
|
||||||
|
buildURL = repository .. "/blob/" .. (revision ~= "" and revision or "main") .. "/docs/BUILD_EN.md"},
|
||||||
|
{pure = true, orderkeys = true})
|
||||||
|
-- A byte array avoids MSVC's limit on individual/concatenated string literals.
|
||||||
|
local bytes = {}
|
||||||
|
for index = 1, #data do
|
||||||
|
bytes[index] = tostring(data:byte(index)) .. (index % 32 == 0 and "\n" or "")
|
||||||
|
end
|
||||||
|
local output = [[/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-21
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ABOUT_RESOURCES_H_
|
||||||
|
#define _ABOUT_RESOURCES_H_
|
||||||
|
|
||||||
|
// Generated by the desktop build. Do not edit.
|
||||||
|
inline constexpr unsigned char kAboutResources[] = {
|
||||||
|
]] .. table.concat(bytes, ",") .. "\n};\n\n#endif\n"
|
||||||
|
local destination = path.join(target:autogendir(), "about", "about_resources.h")
|
||||||
|
if not os.isfile(destination) or io.readfile(destination) ~= output then
|
||||||
|
os.mkdir(path.directory(destination))
|
||||||
|
io.writefile(destination, output)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
rule_end()
|
||||||
@@ -560,6 +560,7 @@ function setup_targets()
|
|||||||
|
|
||||||
target("gui")
|
target("gui")
|
||||||
set_kind("object")
|
set_kind("object")
|
||||||
|
add_rules("crossdesk.about_resources")
|
||||||
set_languages("c++20")
|
set_languages("c++20")
|
||||||
-- spdlog 1.14 bundles fmt 10, whose consteval parser is rejected by
|
-- spdlog 1.14 bundles fmt 10, whose consteval parser is rejected by
|
||||||
-- current Apple Clang in C++20 mode. Keep the established dependency
|
-- current Apple Clang in C++20 mode. Keep the established dependency
|
||||||
@@ -585,6 +586,7 @@ function setup_targets()
|
|||||||
add_files("apps/desktop/src/gui/render.cpp", "apps/desktop/src/gui/application/gui_application.cpp",
|
add_files("apps/desktop/src/gui/render.cpp", "apps/desktop/src/gui/application/gui_application.cpp",
|
||||||
"apps/desktop/src/gui/rendering/*.cpp",
|
"apps/desktop/src/gui/rendering/*.cpp",
|
||||||
"apps/desktop/src/gui/runtime/*.cpp",
|
"apps/desktop/src/gui/runtime/*.cpp",
|
||||||
|
"apps/desktop/src/gui/features/about/*.cpp",
|
||||||
"apps/desktop/src/platform/common/gui/service_status_runtime.cpp",
|
"apps/desktop/src/platform/common/gui/service_status_runtime.cpp",
|
||||||
"apps/desktop/src/gui/features/devices/*.cpp", "apps/desktop/src/gui/features/input/*.cpp",
|
"apps/desktop/src/gui/features/devices/*.cpp", "apps/desktop/src/gui/features/input/*.cpp",
|
||||||
"apps/desktop/src/gui/features/clipboard/*.cpp", "apps/desktop/src/gui/features/file_transfer/*.cpp",
|
"apps/desktop/src/gui/features/clipboard/*.cpp", "apps/desktop/src/gui/features/file_transfer/*.cpp",
|
||||||
|
|||||||
@@ -43,6 +43,58 @@ def write_json(path, value):
|
|||||||
write(path, json.dumps(value, ensure_ascii=False, indent=2) + '\n')
|
write(path, json.dumps(value, ensure_ascii=False, indent=2) + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
def write_about_resources(output, version):
|
||||||
|
"""Supply the embedded catalog required by the shared desktop About UI."""
|
||||||
|
def git(directory, *args):
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(['git', '-C', str(directory), *args],
|
||||||
|
text=True, stderr=subprocess.DEVNULL).strip()
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
revision = git(ROOT, 'rev-parse', 'HEAD')
|
||||||
|
mini = git(ROOT / 'deps/submodules/minirtc', 'rev-parse', 'HEAD')
|
||||||
|
repository = 'https://github.com/kunkundi/crossdesk'
|
||||||
|
source = repository + (f'/tree/{revision}' if revision else '')
|
||||||
|
gpl = {'title': 'GNU GPL v3', 'text': (ROOT / 'LICENSE').read_text()}
|
||||||
|
components = [
|
||||||
|
{'id': 'crossdesk', 'name': 'CrossDesk', 'version': version,
|
||||||
|
'license': 'GPL-3.0-only', 'sourceURL': source, 'documents': [gpl]},
|
||||||
|
{'id': 'minirtc', 'name': 'MiniRTC', 'version': mini[:12],
|
||||||
|
'license': 'LGPL-3.0-only',
|
||||||
|
'sourceURL': 'https://github.com/kunkundi/minirtc' + (f'/tree/{mini}' if mini else ''),
|
||||||
|
'documents': [{'title': 'GNU LGPL v3',
|
||||||
|
'text': (ROOT / 'deps/submodules/minirtc/LICENSE').read_text()}, gpl]},
|
||||||
|
{'id': 'slint', 'name': 'Slint', 'version': '1.17.1', 'license': 'GPL-3.0-only',
|
||||||
|
'sourceURL': 'https://github.com/slint-ui/slint/tree/v1.17.1',
|
||||||
|
'documents': [{'title': 'Slint license', 'text': (SOURCE / 'LICENSE.md').read_text()}, gpl]},
|
||||||
|
{'id': 'cpp-httplib', 'name': 'cpp-httplib', 'version': 'v0.26.0', 'license': 'MIT',
|
||||||
|
'sourceURL': 'https://github.com/yhirose/cpp-httplib/tree/v0.26.0', 'documents': []}]
|
||||||
|
shared = json.loads((ROOT / 'apps/ios/CrossDeskMobile/Resources/ThirdPartyLicenses.json').read_text())['components']
|
||||||
|
shared += json.loads((ROOT / 'apps/android/licenses/additional-notices.json').read_text())
|
||||||
|
seen = {item['id'] for item in components}
|
||||||
|
for item in shared:
|
||||||
|
name = item['id']
|
||||||
|
package = APP / '.native/packages' / name[0] / name
|
||||||
|
if name not in seen and any((package / tag).is_dir() for tag in
|
||||||
|
[item['version'], item['version'].removeprefix('v'),
|
||||||
|
'v' + item['version'].removeprefix('v')]):
|
||||||
|
components.append(item)
|
||||||
|
seen.add(name)
|
||||||
|
components.sort(key=lambda item: (item['id'] != 'crossdesk', item['name'].lower()))
|
||||||
|
data = json.dumps({'components': components, 'revision': revision[:12],
|
||||||
|
'sourceURL': source,
|
||||||
|
'buildURL': f'{repository}/blob/{revision or "main"}/apps/harmony/README.md'},
|
||||||
|
ensure_ascii=False, sort_keys=True).encode()
|
||||||
|
body = '\n'.join(','.join(str(byte) for byte in data[i:i + 32]) + ','
|
||||||
|
for i in range(0, len(data), 32))
|
||||||
|
write(output, '/*\n * @Author: DI JUNKUN\n * @Date: 2026-10-21\n'
|
||||||
|
' * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.\n */\n\n'
|
||||||
|
'#ifndef _ABOUT_RESOURCES_H_\n#define _ABOUT_RESOURCES_H_\n\n'
|
||||||
|
'// Generated by the Harmony desktop build. Do not edit.\n'
|
||||||
|
'inline constexpr unsigned char kAboutResources[] = {\n' + body + '\n};\n\n#endif\n')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
bundle = json.loads((APP / 'AppScope/app.json5').read_text())['app']['bundleName']
|
bundle = json.loads((APP / 'AppScope/app.json5').read_text())['app']['bundleName']
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
@@ -157,6 +209,7 @@ def main():
|
|||||||
urllib.request.urlretrieve('https://raw.githubusercontent.com/yhirose/cpp-httplib/v0.26.0/httplib.h', header)
|
urllib.request.urlretrieve('https://raw.githubusercontent.com/yhirose/cpp-httplib/v0.26.0/httplib.h', header)
|
||||||
if hashlib.sha256(header.read_bytes()).hexdigest() != '4a42da28ce477d06f53942c172197d3b83b4fe42b7e37b8af78c70e063c06da7':
|
if hashlib.sha256(header.read_bytes()).hexdigest() != '4a42da28ce477d06f53942c172197d3b83b4fe42b7e37b8af78c70e063c06da7':
|
||||||
raise SystemExit('cpp-httplib source checksum mismatch')
|
raise SystemExit('cpp-httplib source checksum mismatch')
|
||||||
|
write_about_resources(generated / 'about_resources.h', args.version)
|
||||||
legacy_types = PROJECT / 'entry/src/main/cpp/types/libslint_probe'
|
legacy_types = PROJECT / 'entry/src/main/cpp/types/libslint_probe'
|
||||||
if legacy_types.exists():
|
if legacy_types.exists():
|
||||||
run([studio / 'tools/hvigor/bin/hvigorw', 'clean', '--no-daemon'], cwd=PROJECT, env=env)
|
run([studio / 'tools/hvigor/bin/hvigorw', 'clean', '--no-daemon'], cwd=PROJECT, env=env)
|
||||||
|
|||||||
Vendored
+1
-1
Submodule deps/submodules/minirtc updated: 5ac8e11b6f...2e15671370
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
-- Python's recipe looks up its dependency as "openssl". Keep that name while
|
||||||
|
-- using the OpenSSL 3 recipe, including its sources, patches and build scripts.
|
||||||
|
package("openssl")
|
||||||
|
set_base("openssl3")
|
||||||
|
-- Rebuild consumers cached against the old OpenSSL 1.x ABI.
|
||||||
|
set_policy("package.strict_compatibility", true)
|
||||||
|
package_end()
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
includes("cpp-httplib", "rust", "slint")
|
includes("cpp-httplib", "openssl", "rust", "slint")
|
||||||
|
|||||||
@@ -54,11 +54,27 @@ GitHub Actions 从分支手动打包时,在 `Build and Release` 的
|
|||||||
构建使用 `0.0.0-日期`;版本标签构建继续使用标签中的版本号。
|
构建使用 `0.0.0-日期`;版本标签构建继续使用标签中的版本号。
|
||||||
`desktop_version` 只影响桌面安装包,不改变移动端版本。
|
`desktop_version` 只影响桌面安装包,不改变移动端版本。
|
||||||
|
|
||||||
|
`prepare-version` 前置任务统一生成完整桌面版本,通过 job outputs 传给
|
||||||
|
Linux、macOS、Windows 和发布任务。无日期标签使用前置任务运行当天的
|
||||||
|
北京时间日期,显式日期标签使用标签中的日期;后续任务不再重新计算。
|
||||||
|
构建跨午夜或单独重跑发布任务时,继续使用前置任务确定的版本。
|
||||||
|
|
||||||
不要使用 `vauto-update-20261020` 这类分支名作为版本号验证升级。
|
不要使用 `vauto-update-20261020` 这类分支名作为版本号验证升级。
|
||||||
新版本必须增加数字版本或 patch;只改变日期不视为升级。例如先手动安装
|
新版本必须增加数字版本或 patch;只改变日期不视为升级。例如先手动安装
|
||||||
`v1.7.2-20261020`,再验证到 `v1.7.3-20261020` 的升级。
|
`v1.7.2-20261020`,再验证到 `v1.7.3-20261020` 的升级。
|
||||||
分支手动构建只上传 Actions 构建产物,不会自动发布版本清单。
|
分支手动构建只上传 Actions 构建产物,不会自动发布版本清单。
|
||||||
|
|
||||||
|
发布脚本按数字版本和 patch 比较版本,忽略构建日期。编辑或重新发布旧的
|
||||||
|
桌面 Release 时,保留服务器现有清单并在 Actions 日志中说明跳过原因;
|
||||||
|
同版本仍可更新发布说明,新版本正常发布。各平台下载记录也不会被较低
|
||||||
|
版本覆盖,包括独立移动端发布和桌面 Release 附带的移动端安装包。
|
||||||
|
|
||||||
|
桌面 Release 中所有桌面安装包及便携包的完整版本必须与标签去掉 `v`
|
||||||
|
前缀后的版本一致,包括日期和 patch 的位置;不能只按数字版本比较。
|
||||||
|
任一包不匹配时,脚本报告标签、平台、文件名及双方版本,终止清单生成,
|
||||||
|
不写入输出文件。手动发布时应使用与安装包一致的完整版本标签,例如
|
||||||
|
`v1.7.4-20261021`。iOS、Android 安装包保持独立版本。
|
||||||
|
|
||||||
本地构建可以设置同名环境变量,或使用:
|
本地构建可以设置同名环境变量,或使用:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ if desktop_platform then
|
|||||||
includes(path.join(source_dir, "apps/desktop/xmake/options.lua"))
|
includes(path.join(source_dir, "apps/desktop/xmake/options.lua"))
|
||||||
includes(path.join(source_dir, "apps/desktop/xmake/platform.lua"))
|
includes(path.join(source_dir, "apps/desktop/xmake/platform.lua"))
|
||||||
includes(path.join(source_dir, "apps/desktop/xmake/rules/slint.lua"))
|
includes(path.join(source_dir, "apps/desktop/xmake/rules/slint.lua"))
|
||||||
|
includes(path.join(source_dir, "apps/desktop/xmake/rules/about_resources.lua"))
|
||||||
includes(path.join(source_dir, "apps/desktop/xmake/targets.lua"))
|
includes(path.join(source_dir, "apps/desktop/xmake/targets.lua"))
|
||||||
|
|
||||||
setup_options_and_dependencies()
|
setup_options_and_dependencies()
|
||||||
|
|||||||
Reference in New Issue
Block a user