mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-07-25 16:58:40 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f5faa7111 | |||
| 3a72e4849e | |||
| 43e44af730 | |||
| 97e592861c | |||
| 31b8983328 | |||
| 522f519d38 | |||
| 37c4fe2e8a | |||
| 2c20599800 | |||
| f5c4e2ba4a |
@@ -0,0 +1,20 @@
|
|||||||
|
**
|
||||||
|
|
||||||
|
!.dockerignore
|
||||||
|
!docker/
|
||||||
|
!docker/linux-build/
|
||||||
|
!docker/linux-build/**
|
||||||
|
|
||||||
|
# Dependency manifests and local xmake package recipes are the only project
|
||||||
|
# files needed while pre-warming the build image.
|
||||||
|
!xmake.lua
|
||||||
|
!xmake/
|
||||||
|
!xmake/**
|
||||||
|
!thirdparty/
|
||||||
|
!thirdparty/**
|
||||||
|
!submodules/
|
||||||
|
!submodules/xmake.lua
|
||||||
|
!submodules/minirtc/
|
||||||
|
!submodules/minirtc/xmake.lua
|
||||||
|
!submodules/minirtc/thirdparty/
|
||||||
|
!submodules/minirtc/thirdparty/**
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
name: Build Linux Build Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- ci/linux-build-image
|
||||||
|
paths:
|
||||||
|
- .dockerignore
|
||||||
|
- .gitmodules
|
||||||
|
- .github/workflows/build-linux-image.yml
|
||||||
|
- docker/linux-build/**
|
||||||
|
- xmake.lua
|
||||||
|
- xmake/**
|
||||||
|
- thirdparty/**
|
||||||
|
- submodules/xmake.lua
|
||||||
|
- submodules/minirtc
|
||||||
|
- submodules/minirtc/xmake.lua
|
||||||
|
- submodules/minirtc/thirdparty/**
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: linux-build-image-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: crossdesk/ubuntu22.04
|
||||||
|
STACK_TAG: buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
|
||||||
|
XMAKE_VERSION: 3.0.9
|
||||||
|
RUST_VERSION: 1.92.0
|
||||||
|
RUSTUP_VERSION: 1.28.2
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build candidate (${{ matrix.arch }})
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 180
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: amd64
|
||||||
|
platform: linux/amd64
|
||||||
|
runner: ubuntu-24.04
|
||||||
|
base_image: nvidia/cuda:12.6.3-devel-ubuntu22.04
|
||||||
|
- arch: arm64
|
||||||
|
platform: linux/arm64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
base_image: ubuntu:22.04
|
||||||
|
steps:
|
||||||
|
- name: Set candidate tag
|
||||||
|
id: image
|
||||||
|
run: |
|
||||||
|
CANDIDATE_TAG="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-${{ matrix.arch }}"
|
||||||
|
echo "CANDIDATE_TAG=${CANDIDATE_TAG}" >> "${GITHUB_ENV}"
|
||||||
|
echo "candidate=${CANDIDATE_TAG}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push candidate
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/linux-build/Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
push: true
|
||||||
|
pull: true
|
||||||
|
tags: ${{ steps.image.outputs.candidate }}
|
||||||
|
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:${{ env.STACK_TAG }}
|
||||||
|
cache-to: type=inline
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=${{ matrix.base_image }}
|
||||||
|
XMAKE_VERSION=${{ env.XMAKE_VERSION }}
|
||||||
|
RUST_VERSION=${{ env.RUST_VERSION }}
|
||||||
|
RUSTUP_VERSION=${{ env.RUSTUP_VERSION }}
|
||||||
|
|
||||||
|
verify:
|
||||||
|
name: Verify candidate (${{ matrix.arch }})
|
||||||
|
needs: build
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 30
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: amd64
|
||||||
|
runner: ubuntu-24.04
|
||||||
|
- arch: arm64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- name: Set candidate tag
|
||||||
|
run: |
|
||||||
|
CANDIDATE_TAG="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-${{ matrix.arch }}"
|
||||||
|
echo "CANDIDATE_TAG=${CANDIDATE_TAG}" >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Verify tools, architecture, and Ubuntu baseline
|
||||||
|
run: |
|
||||||
|
docker pull "${CANDIDATE_TAG}"
|
||||||
|
docker run --rm \
|
||||||
|
--env EXPECTED_ARCH="${{ matrix.arch }}" \
|
||||||
|
"${CANDIDATE_TAG}" \
|
||||||
|
verify-crossdesk-build-image
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish multi-arch image
|
||||||
|
needs: verify
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Promote verified candidates
|
||||||
|
run: |
|
||||||
|
AMD64_IMAGE="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-amd64"
|
||||||
|
ARM64_IMAGE="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-arm64"
|
||||||
|
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag "${IMAGE_NAME}:${STACK_TAG}" \
|
||||||
|
--tag "${IMAGE_NAME}:sha-${GITHUB_SHA}" \
|
||||||
|
--tag "${IMAGE_NAME}:latest" \
|
||||||
|
"${AMD64_IMAGE}" \
|
||||||
|
"${ARM64_IMAGE}"
|
||||||
|
|
||||||
|
- name: Inspect published manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect "${IMAGE_NAME}:${STACK_TAG}"
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
|
- "!ci/linux-build-image"
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -27,13 +28,13 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- arch: amd64
|
- arch: amd64
|
||||||
runner: ubuntu-22.04
|
runner: ubuntu-24.04
|
||||||
image: crossdesk/ubuntu20.04:latest
|
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
|
||||||
package_script: ./scripts/linux/pkg_amd64.sh
|
package_script: ./scripts/linux/pkg_amd64.sh
|
||||||
|
|
||||||
- arch: arm64
|
- arch: arm64
|
||||||
runner: ubuntu-22.04-arm
|
runner: ubuntu-24.04-arm
|
||||||
image: crossdesk/ubuntu20.04-arm64v8:latest
|
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
|
||||||
package_script: ./scripts/linux/pkg_arm64.sh
|
package_script: ./scripts/linux/pkg_arm64.sh
|
||||||
|
|
||||||
container:
|
container:
|
||||||
@@ -99,12 +100,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Verify Linux build image
|
||||||
|
run: verify-crossdesk-build-image
|
||||||
|
|
||||||
- name: Build CrossDesk
|
- name: Build CrossDesk
|
||||||
env:
|
env:
|
||||||
CUDA_PATH: /usr/local/cuda
|
CUDA_PATH: /usr/local/cuda
|
||||||
XMAKE_GLOBALDIR: /data
|
XMAKE_GLOBALDIR: /data
|
||||||
run: |
|
run: |
|
||||||
apt install -y libxft-dev
|
|
||||||
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y
|
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y
|
||||||
xmake b -vy --root crossdesk
|
xmake b -vy --root crossdesk
|
||||||
|
|
||||||
@@ -113,6 +116,14 @@ jobs:
|
|||||||
chmod +x ${{ matrix.package_script }}
|
chmod +x ${{ matrix.package_script }}
|
||||||
${{ matrix.package_script }} ${LEGAL_VERSION}
|
${{ matrix.package_script }} ${LEGAL_VERSION}
|
||||||
|
|
||||||
|
- name: Verify packaged Ubuntu 22.04 glibc baseline
|
||||||
|
run: |
|
||||||
|
PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb"
|
||||||
|
VERIFY_DIR="${RUNNER_TEMP}/crossdesk-glibc-${{ matrix.arch }}"
|
||||||
|
install -d "${VERIFY_DIR}"
|
||||||
|
dpkg-deb --extract "${PACKAGE_FILE}" "${VERIFY_DIR}"
|
||||||
|
verify-crossdesk-glibc-baseline "${VERIFY_DIR}" 2.35
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
@@ -123,17 +134,21 @@ jobs:
|
|||||||
build-macos:
|
build-macos:
|
||||||
name: Build on macOS
|
name: Build on macOS
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
|
env:
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: "14.0"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- arch: x64
|
- arch: x64
|
||||||
|
binary_arch: x86_64
|
||||||
runner: macos-15-intel
|
runner: macos-15-intel
|
||||||
cache-key: intel
|
cache-key: intel-macos14
|
||||||
out-dir: ./build/macosx/x86_64/release/crossdesk
|
out-dir: ./build/macosx/x86_64/release/crossdesk
|
||||||
package_script: ./scripts/macosx/pkg_x64.sh
|
package_script: ./scripts/macosx/pkg_x64.sh
|
||||||
- arch: arm64
|
- arch: arm64
|
||||||
|
binary_arch: arm64
|
||||||
runner: macos-14
|
runner: macos-14
|
||||||
cache-key: arm
|
cache-key: arm-macos14
|
||||||
out-dir: ./build/macosx/arm64/release/crossdesk
|
out-dir: ./build/macosx/arm64/release/crossdesk
|
||||||
package_script: ./scripts/macosx/pkg_arm64.sh
|
package_script: ./scripts/macosx/pkg_arm64.sh
|
||||||
|
|
||||||
@@ -189,6 +204,9 @@ jobs:
|
|||||||
- name: Install xmake
|
- name: Install xmake
|
||||||
run: brew install xmake
|
run: brew install xmake
|
||||||
|
|
||||||
|
- name: Update xmake repositories
|
||||||
|
run: xmake repo -u
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
@@ -197,7 +215,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build CrossDesk
|
- name: Build CrossDesk
|
||||||
run: |
|
run: |
|
||||||
xmake f --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y
|
xmake f --target_minver=${MACOSX_DEPLOYMENT_TARGET} --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y
|
||||||
xmake b -vy crossdesk
|
xmake b -vy crossdesk
|
||||||
|
|
||||||
- name: Package CrossDesk app
|
- name: Package CrossDesk app
|
||||||
@@ -205,6 +223,22 @@ jobs:
|
|||||||
chmod +x ${{ matrix.package_script }}
|
chmod +x ${{ matrix.package_script }}
|
||||||
${{ matrix.package_script }} ${VERSION_NUM}
|
${{ matrix.package_script }} ${VERSION_NUM}
|
||||||
|
|
||||||
|
- name: Verify packaged app
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-macos-${{ matrix.arch }}-${VERSION_NUM}.pkg"
|
||||||
|
VERIFY_DIR="$(mktemp -d "${RUNNER_TEMP}/crossdesk-macos-pkg.XXXXXX")"
|
||||||
|
trap 'rm -rf "${VERIFY_DIR}"' EXIT
|
||||||
|
|
||||||
|
pkgutil --expand-full "${PACKAGE_FILE}" "${VERIFY_DIR}/expanded"
|
||||||
|
APP_PATH="$(find "${VERIFY_DIR}/expanded" -type d -name 'CrossDesk.app' -print -quit)"
|
||||||
|
if [[ -z "${APP_PATH}" ]]; then
|
||||||
|
echo "CrossDesk.app is missing from ${PACKAGE_FILE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
./scripts/macosx/verify_app.sh "${APP_PATH}" "${{ matrix.binary_arch }}"
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
@@ -275,6 +309,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
|
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
|
||||||
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
xmake repo -u
|
||||||
xmake create cuda
|
xmake create cuda
|
||||||
Set-Location cuda
|
Set-Location cuda
|
||||||
xmake g --theme=plain
|
xmake g --theme=plain
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
ARG BASE_IMAGE=nvidia/cuda:12.6.3-devel-ubuntu22.04
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG XMAKE_VERSION=3.0.9
|
||||||
|
ARG RUST_VERSION=1.92.0
|
||||||
|
ARG RUSTUP_VERSION=1.28.2
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="CrossDesk Linux build environment" \
|
||||||
|
org.opencontainers.image.description="Ubuntu 22.04 build environment for CrossDesk" \
|
||||||
|
org.opencontainers.image.source="https://github.com/kunkundi/crossdesk" \
|
||||||
|
org.opencontainers.image.licenses="GPL-3.0-only"
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
TZ=Etc/UTC \
|
||||||
|
LANG=C.UTF-8 \
|
||||||
|
LC_ALL=C.UTF-8 \
|
||||||
|
RUSTUP_HOME=/opt/rustup \
|
||||||
|
CARGO_HOME=/opt/cargo \
|
||||||
|
XMAKE_GLOBALDIR=/data \
|
||||||
|
XMAKE_ROOT=y \
|
||||||
|
CUDA_PATH=/usr/local/cuda \
|
||||||
|
PATH=/root/.local/bin:/opt/cargo/bin:/usr/local/bin:${PATH}
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
binutils \
|
||||||
|
bison \
|
||||||
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
|
ccache \
|
||||||
|
cmake \
|
||||||
|
curl \
|
||||||
|
dbus \
|
||||||
|
dpkg-dev \
|
||||||
|
file \
|
||||||
|
flex \
|
||||||
|
gettext \
|
||||||
|
git \
|
||||||
|
libasound2-dev \
|
||||||
|
libdbus-1-dev \
|
||||||
|
libdrm-dev \
|
||||||
|
libegl1-mesa-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libfontconfig1-dev \
|
||||||
|
libfreetype6-dev \
|
||||||
|
libgl1-mesa-dev \
|
||||||
|
libmount-dev \
|
||||||
|
libpulse-dev \
|
||||||
|
libreadline-dev \
|
||||||
|
libsndio-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libtool \
|
||||||
|
libx11-dev \
|
||||||
|
libxcb-xfixes0-dev \
|
||||||
|
libxcb-randr0-dev \
|
||||||
|
libxcb-shape0-dev \
|
||||||
|
libxcb-shm0-dev \
|
||||||
|
libxcb-xinerama0-dev \
|
||||||
|
libxcb-xkb-dev \
|
||||||
|
libxcb-xtest0-dev \
|
||||||
|
libxcursor-dev \
|
||||||
|
libxext-dev \
|
||||||
|
libxfixes-dev \
|
||||||
|
libxft-dev \
|
||||||
|
libxi-dev \
|
||||||
|
libxinerama-dev \
|
||||||
|
libxkbcommon-dev \
|
||||||
|
libxrandr-dev \
|
||||||
|
libxrender-dev \
|
||||||
|
libxtst-dev \
|
||||||
|
libxv-dev \
|
||||||
|
make \
|
||||||
|
nasm \
|
||||||
|
ninja-build \
|
||||||
|
patch \
|
||||||
|
patchelf \
|
||||||
|
perl \
|
||||||
|
pkg-config \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
unzip \
|
||||||
|
xutils-dev \
|
||||||
|
xz-utils \
|
||||||
|
yasm \
|
||||||
|
zip \
|
||||||
|
zlib1g-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN export GITHUB_ACTIONS=true \
|
||||||
|
&& curl -fsSL https://xmake.io/shget.text \
|
||||||
|
| bash -s -- "v${XMAKE_VERSION}" \
|
||||||
|
&& source ~/.xmake/profile \
|
||||||
|
&& xmake --version | grep -F "v${XMAKE_VERSION}"
|
||||||
|
|
||||||
|
RUN case "${TARGETARCH}" in \
|
||||||
|
amd64) \
|
||||||
|
rustup_arch="x86_64-unknown-linux-gnu"; \
|
||||||
|
rustup_sha256="20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c" \
|
||||||
|
;; \
|
||||||
|
arm64) \
|
||||||
|
rustup_arch="aarch64-unknown-linux-gnu"; \
|
||||||
|
rustup_sha256="e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c" \
|
||||||
|
;; \
|
||||||
|
*) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
|
||||||
|
esac \
|
||||||
|
&& curl --proto '=https' --tlsv1.2 --fail --show-error --location \
|
||||||
|
"https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustup_arch}/rustup-init" \
|
||||||
|
-o /tmp/rustup-init \
|
||||||
|
&& echo "${rustup_sha256} /tmp/rustup-init" | sha256sum --check - \
|
||||||
|
&& chmod 0755 /tmp/rustup-init \
|
||||||
|
&& /tmp/rustup-init -y --no-modify-path --profile minimal \
|
||||||
|
--default-toolchain "${RUST_VERSION}" \
|
||||||
|
&& rm -f /tmp/rustup-init \
|
||||||
|
&& rustc --version \
|
||||||
|
&& cargo --version
|
||||||
|
|
||||||
|
WORKDIR /tmp/crossdesk-dependencies
|
||||||
|
|
||||||
|
COPY xmake.lua ./xmake.lua
|
||||||
|
COPY xmake ./xmake
|
||||||
|
COPY thirdparty ./thirdparty
|
||||||
|
COPY submodules/xmake.lua ./submodules/xmake.lua
|
||||||
|
COPY submodules/minirtc/xmake.lua ./submodules/minirtc/xmake.lua
|
||||||
|
COPY submodules/minirtc/thirdparty ./submodules/minirtc/thirdparty
|
||||||
|
|
||||||
|
# Configure the real project manifests so the image cache stays aligned with
|
||||||
|
# CrossDesk's exact dependency graph. Source files are intentionally excluded
|
||||||
|
# from the Docker context; source-only changes therefore reuse this layer.
|
||||||
|
RUN xmake repo -u --root \
|
||||||
|
&& xmake f -m release --USE_CUDA=true --root -y \
|
||||||
|
&& rm -rf /data/.xmake/cache /tmp/crossdesk-dependencies \
|
||||||
|
&& mkdir -p /workspace
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
COPY docker/linux-build/verify-build-image.sh \
|
||||||
|
/usr/local/bin/verify-crossdesk-build-image
|
||||||
|
COPY docker/linux-build/verify-glibc-baseline.sh \
|
||||||
|
/usr/local/bin/verify-crossdesk-glibc-baseline
|
||||||
|
|
||||||
|
RUN chmod 0755 \
|
||||||
|
/usr/local/bin/verify-crossdesk-build-image \
|
||||||
|
/usr/local/bin/verify-crossdesk-glibc-baseline \
|
||||||
|
&& verify-crossdesk-build-image
|
||||||
|
|
||||||
|
CMD ["bash"]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# CrossDesk Linux 构建镜像
|
||||||
|
|
||||||
|
这个目录维护 CrossDesk 专用的 Ubuntu 22.04 构建环境。镜像包含 xmake、
|
||||||
|
Rust、Linux 开发库以及根据项目 `xmake.lua` 提前编译好的依赖。
|
||||||
|
|
||||||
|
## 发布方式
|
||||||
|
|
||||||
|
推送影响镜像的文件到 `ci/linux-build-image` 分支时,
|
||||||
|
`build-linux-image.yml` 会在 GitHub 原生 amd64/arm64 runner 上分别构建和验证,
|
||||||
|
全部通过后再合并为同一个 multi-arch 镜像:
|
||||||
|
|
||||||
|
```text
|
||||||
|
crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker 会根据运行机器自动选择正确架构。amd64 变体以
|
||||||
|
`nvidia/cuda:12.6.3-devel-ubuntu22.04` 为基础;arm64 变体不包含当前构建
|
||||||
|
用不到的 CUDA SDK。两个变体都使用 Ubuntu 22.04/glibc 2.35。
|
||||||
|
因此该镜像生成的 Linux 安装包最低支持 Ubuntu 22.04,不再保证兼容
|
||||||
|
Ubuntu 20.04。
|
||||||
|
|
||||||
|
每次成功发布还会生成 `sha-<完整提交 SHA>`,用于精确回滚;`latest` 和上面的
|
||||||
|
工具链版本 tag 只会在两个架构都验证成功后更新。
|
||||||
|
|
||||||
|
## GitHub 配置
|
||||||
|
|
||||||
|
在仓库的 Actions secrets 中配置:
|
||||||
|
|
||||||
|
- `DOCKERHUB_USERNAME`:有权推送 `crossdesk/ubuntu22.04` 的 Docker Hub 用户名
|
||||||
|
- `DOCKERHUB_TOKEN`:该用户的 Docker Hub access token
|
||||||
|
|
||||||
|
工作流文件进入默认分支后,也可以从 Actions 页面手动运行。为了避免首次发布
|
||||||
|
新 tag 时应用构建先于镜像完成,常规 `build.yml` 不响应
|
||||||
|
`ci/linux-build-image` 分支的 push。第一次启用时,应先推送该分支并等待镜像
|
||||||
|
发布成功,再把应用构建工作流的修改合并到 `main`。
|
||||||
|
|
||||||
|
## 更新依赖
|
||||||
|
|
||||||
|
修改 Dockerfile、xmake 清单、本地包配方或子模块中的包配方后,推送该分支即可。
|
||||||
|
升级工具链时应同时修改 Dockerfile 的版本参数、工作流中的 `STACK_TAG`,以及
|
||||||
|
常规构建工作流引用的镜像 tag。
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
EXPECTED_ARCH="${EXPECTED_ARCH:-}"
|
||||||
|
EXPECTED_XMAKE_VERSION="${EXPECTED_XMAKE_VERSION:-3.0.9}"
|
||||||
|
EXPECTED_RUST_VERSION="${EXPECTED_RUST_VERSION:-1.92.0}"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/os-release
|
||||||
|
if [[ "${ID}" != "ubuntu" || "${VERSION_ID}" != "22.04" ]]; then
|
||||||
|
echo "Expected Ubuntu 22.04, found ${ID:-unknown} ${VERSION_ID:-unknown}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GLIBC_VERSION="$(getconf GNU_LIBC_VERSION | awk '{print $2}')"
|
||||||
|
if [[ "${GLIBC_VERSION}" != "2.35" ]]; then
|
||||||
|
echo "Expected glibc 2.35, found ${GLIBC_VERSION}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ACTUAL_ARCH="$(dpkg --print-architecture)"
|
||||||
|
if [[ -n "${EXPECTED_ARCH}" && "${ACTUAL_ARCH}" != "${EXPECTED_ARCH}" ]]; then
|
||||||
|
echo "Expected architecture ${EXPECTED_ARCH}, found ${ACTUAL_ARCH}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
XMAKE_VERSION_OUTPUT="$(xmake --version 2>&1)"
|
||||||
|
grep -F "v${EXPECTED_XMAKE_VERSION}" <<<"${XMAKE_VERSION_OUTPUT}" >/dev/null
|
||||||
|
rustc --version | grep -F "rustc ${EXPECTED_RUST_VERSION}" >/dev/null
|
||||||
|
cargo --version >/dev/null
|
||||||
|
cmake --version >/dev/null
|
||||||
|
g++ --version >/dev/null
|
||||||
|
pkg-config --exists xft
|
||||||
|
|
||||||
|
for package_path in \
|
||||||
|
"s/slint" \
|
||||||
|
"l/libdatachannel" \
|
||||||
|
"o/openssl3" \
|
||||||
|
"s/spdlog"; do
|
||||||
|
if [[ ! -d "${XMAKE_GLOBALDIR}/.xmake/packages/${package_path}" ]]; then
|
||||||
|
echo "Prebuilt xmake package is missing: ${package_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! find "${XMAKE_GLOBALDIR}/.xmake/packages/s/slint" \
|
||||||
|
-type f -name 'slint-compiler*' -print -quit | grep -q .; then
|
||||||
|
echo "The prebuilt Slint compiler is missing" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${ACTUAL_ARCH}" == "amd64" && ! -f "${CUDA_PATH}/include/cuda.h" ]]; then
|
||||||
|
echo "The amd64 image is missing CUDA headers under ${CUDA_PATH}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verified CrossDesk build image: Ubuntu ${VERSION_ID}, ${ACTUAL_ARCH}, glibc ${GLIBC_VERSION}"
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||||
|
echo "Usage: $0 <ELF-file-or-directory> [maximum-glibc-version]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
INPUT_PATH="$1"
|
||||||
|
MAXIMUM_VERSION="${2:-2.35}"
|
||||||
|
|
||||||
|
verify_elf() {
|
||||||
|
local elf_file="$1"
|
||||||
|
local version=""
|
||||||
|
local highest_version="none"
|
||||||
|
local -a required_versions=()
|
||||||
|
|
||||||
|
mapfile -t required_versions < <(
|
||||||
|
readelf --version-info "${elf_file}" \
|
||||||
|
| grep -oE 'GLIBC_[0-9]+(\.[0-9]+)+' \
|
||||||
|
| sed 's/^GLIBC_//' \
|
||||||
|
| sort -Vu \
|
||||||
|
|| true
|
||||||
|
)
|
||||||
|
|
||||||
|
for version in "${required_versions[@]}"; do
|
||||||
|
if dpkg --compare-versions "${version}" gt "${MAXIMUM_VERSION}"; then
|
||||||
|
echo "${elf_file} requires GLIBC_${version}; maximum allowed is GLIBC_${MAXIMUM_VERSION}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#required_versions[@]} -gt 0 ]]; then
|
||||||
|
highest_version="${required_versions[-1]}"
|
||||||
|
fi
|
||||||
|
echo "Verified ${elf_file}: highest required glibc version is ${highest_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -f "${INPUT_PATH}" ]]; then
|
||||||
|
if ! readelf -h "${INPUT_PATH}" >/dev/null 2>&1; then
|
||||||
|
echo "Not an ELF file: ${INPUT_PATH}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
verify_elf "${INPUT_PATH}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "${INPUT_PATH}" ]]; then
|
||||||
|
echo "File or directory does not exist: ${INPUT_PATH}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ELF_COUNT=0
|
||||||
|
while IFS= read -r -d '' candidate; do
|
||||||
|
if readelf -h "${candidate}" >/dev/null 2>&1; then
|
||||||
|
verify_elf "${candidate}"
|
||||||
|
ELF_COUNT=$((ELF_COUNT + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "${INPUT_PATH}" -type f -print0)
|
||||||
|
|
||||||
|
if [[ ${ELF_COUNT} -eq 0 ]]; then
|
||||||
|
echo "No ELF files found under ${INPUT_PATH}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -147,7 +147,7 @@ Version: $DEB_VERSION
|
|||||||
Architecture: $DEBIAN_ARCH
|
Architecture: $DEBIAN_ARCH
|
||||||
Maintainer: $MAINTAINER
|
Maintainer: $MAINTAINER
|
||||||
Description: $DESCRIPTION
|
Description: $DESCRIPTION
|
||||||
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxext6,
|
Depends: libc6 (>= 2.35), libstdc++6 (>= 9), libx11-6, libxext6,
|
||||||
libxrender1, libxft2, libxrandr2, libxfixes3, libxcb1, libxcb-randr0,
|
libxrender1, libxft2, libxrandr2, libxfixes3, libxcb1, libxcb-randr0,
|
||||||
libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, libxcb-xkb1,
|
libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, libxcb-xkb1,
|
||||||
libxcb-xfixes0, libxv1, libxtst6, $ALSA_RUNTIME_DEP, libsndio7.0,
|
libxcb-xfixes0, libxv1, libxtst6, $ALSA_RUNTIME_DEP, libsndio7.0,
|
||||||
|
|||||||
Executable
+131
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 2 ]]; then
|
||||||
|
echo "Usage: $0 <app-bundle> <expected-architecture>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_BUNDLE="$1"
|
||||||
|
EXPECTED_ARCH="$2"
|
||||||
|
APP_EXECUTABLE="$APP_BUNDLE/Contents/MacOS/CrossDesk"
|
||||||
|
FRAMEWORKS_DIR="$APP_BUNDLE/Contents/Frameworks"
|
||||||
|
|
||||||
|
for command_name in codesign install_name_tool lipo otool; do
|
||||||
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||||
|
echo "Required bundling command is missing: $command_name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -x "$APP_EXECUTABLE" ]]; then
|
||||||
|
echo "Missing app executable: $APP_EXECUTABLE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! lipo "$APP_EXECUTABLE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||||
|
echo "App executable does not contain the expected architecture: $EXPECTED_ARCH" >&2
|
||||||
|
lipo -archs "$APP_EXECUTABLE" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SLINT_REFERENCE="$(otool -L "$APP_EXECUTABLE" | awk \
|
||||||
|
'$1 ~ /(^|\/)libslint_cpp[^\/[:space:]]*\.dylib$/ { print $1; exit }')"
|
||||||
|
if [[ -z "$SLINT_REFERENCE" ]]; then
|
||||||
|
echo "The app executable does not reference the Slint runtime" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SLINT_NAME="${SLINT_REFERENCE##*/}"
|
||||||
|
SLINT_CURRENT_VERSION="$(otool -L "$APP_EXECUTABLE" | awk -v dependency="$SLINT_REFERENCE" '
|
||||||
|
$1 == dependency {
|
||||||
|
for (i = 1; i <= NF; ++i) {
|
||||||
|
if ($i == "current") {
|
||||||
|
version = $(i + 2)
|
||||||
|
gsub(/[),]/, "", version)
|
||||||
|
print version
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}')"
|
||||||
|
|
||||||
|
slint_runtime_version() {
|
||||||
|
local library="$1"
|
||||||
|
otool -L "$library" | awk -v name="$SLINT_NAME" '
|
||||||
|
index($1, "/" name) || $1 == "@rpath/" name {
|
||||||
|
for (i = 1; i <= NF; ++i) {
|
||||||
|
if ($i == "current") {
|
||||||
|
version = $(i + 2)
|
||||||
|
gsub(/[),]/, "", version)
|
||||||
|
print version
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
find_slint_runtime() {
|
||||||
|
local candidate=""
|
||||||
|
local candidate_version=""
|
||||||
|
local package_root=""
|
||||||
|
local package_roots=()
|
||||||
|
|
||||||
|
if [[ "$SLINT_REFERENCE" == /* && -f "$SLINT_REFERENCE" ]]; then
|
||||||
|
if lipo "$SLINT_REFERENCE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||||
|
echo "$SLINT_REFERENCE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${XMAKE_GLOBALDIR:-}" ]]; then
|
||||||
|
package_roots+=("$XMAKE_GLOBALDIR/.xmake/packages")
|
||||||
|
fi
|
||||||
|
package_roots+=("$HOME/.xmake/packages" "/data/.xmake/packages")
|
||||||
|
|
||||||
|
for package_root in "${package_roots[@]}"; do
|
||||||
|
[[ -d "$package_root/s/slint" ]] || continue
|
||||||
|
while IFS= read -r candidate; do
|
||||||
|
if ! lipo "$candidate" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
candidate_version="$(slint_runtime_version "$candidate")"
|
||||||
|
if [[ -n "$SLINT_CURRENT_VERSION" && "$candidate_version" != "$SLINT_CURRENT_VERSION" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$candidate"
|
||||||
|
return 0
|
||||||
|
done < <(find "$package_root/s/slint" -type f -path "*/lib/$SLINT_NAME" -print 2>/dev/null)
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
SLINT_LIBRARY="$(find_slint_runtime || true)"
|
||||||
|
if [[ -z "$SLINT_LIBRARY" ]]; then
|
||||||
|
echo "Unable to locate $SLINT_NAME for $EXPECTED_ARCH (current version $SLINT_CURRENT_VERSION)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d "$FRAMEWORKS_DIR"
|
||||||
|
install -m 0755 "$SLINT_LIBRARY" "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||||
|
|
||||||
|
install_name_tool -id "@rpath/$SLINT_NAME" "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||||
|
if [[ "$SLINT_REFERENCE" != "@rpath/$SLINT_NAME" ]]; then
|
||||||
|
install_name_tool -change "$SLINT_REFERENCE" "@rpath/$SLINT_NAME" "$APP_EXECUTABLE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! otool -l "$APP_EXECUTABLE" | awk '
|
||||||
|
$1 == "cmd" { in_rpath = ($2 == "LC_RPATH"); next }
|
||||||
|
in_rpath && $1 == "path" && $2 == "@executable_path/../Frameworks" { found = 1 }
|
||||||
|
END { exit(found ? 0 : 1) }'; then
|
||||||
|
install_name_tool -add_rpath "@executable_path/../Frameworks" "$APP_EXECUTABLE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# install_name_tool invalidates the linker's ad-hoc signature. Sign nested code
|
||||||
|
# first, then seal the complete app bundle so local packages remain launchable.
|
||||||
|
codesign --force --sign - "$FRAMEWORKS_DIR/$SLINT_NAME"
|
||||||
|
codesign --force --deep --sign - "$APP_BUNDLE"
|
||||||
|
|
||||||
|
echo "Bundled Slint runtime: $SLINT_LIBRARY"
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
APP_NAME="crossdesk"
|
APP_NAME="crossdesk"
|
||||||
APP_NAME_UPPER="CrossDesk"
|
APP_NAME_UPPER="CrossDesk"
|
||||||
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
|
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
|
||||||
PLATFORM="macos"
|
PLATFORM="macos"
|
||||||
ARCH="arm64"
|
ARCH="arm64"
|
||||||
|
BINARY_ARCH="arm64"
|
||||||
IDENTIFIER="cn.crossdesk.app"
|
IDENTIFIER="cn.crossdesk.app"
|
||||||
ICON_PATH="icons/macos/crossdesk.icns"
|
ICON_PATH="icons/macos/crossdesk.icns"
|
||||||
MACOS_MIN_VERSION="10.12"
|
MACOS_MIN_VERSION="14.0"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
normalize_app_version() {
|
normalize_app_version() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
@@ -95,6 +97,9 @@ EOF
|
|||||||
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
||||||
find "${APP_BUNDLE}" -name '._*' -delete
|
find "${APP_BUNDLE}" -name '._*' -delete
|
||||||
|
|
||||||
|
"${SCRIPT_DIR}/bundle_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||||
|
"${SCRIPT_DIR}/verify_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||||
|
|
||||||
echo ".app created successfully."
|
echo ".app created successfully."
|
||||||
|
|
||||||
mkdir -p build_pkg_scripts
|
mkdir -p build_pkg_scripts
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
APP_NAME="crossdesk"
|
APP_NAME="crossdesk"
|
||||||
APP_NAME_UPPER="CrossDesk"
|
APP_NAME_UPPER="CrossDesk"
|
||||||
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
|
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
|
||||||
PLATFORM="macos"
|
PLATFORM="macos"
|
||||||
ARCH="x64"
|
ARCH="x64"
|
||||||
|
BINARY_ARCH="x86_64"
|
||||||
IDENTIFIER="cn.crossdesk.app"
|
IDENTIFIER="cn.crossdesk.app"
|
||||||
ICON_PATH="icons/macos/crossdesk.icns"
|
ICON_PATH="icons/macos/crossdesk.icns"
|
||||||
MACOS_MIN_VERSION="10.12"
|
MACOS_MIN_VERSION="14.0"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
normalize_app_version() {
|
normalize_app_version() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
@@ -95,6 +97,9 @@ EOF
|
|||||||
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
||||||
find "${APP_BUNDLE}" -name '._*' -delete
|
find "${APP_BUNDLE}" -name '._*' -delete
|
||||||
|
|
||||||
|
"${SCRIPT_DIR}/bundle_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||||
|
"${SCRIPT_DIR}/verify_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
|
||||||
|
|
||||||
echo ".app created successfully."
|
echo ".app created successfully."
|
||||||
|
|
||||||
mkdir -p build_pkg_scripts
|
mkdir -p build_pkg_scripts
|
||||||
|
|||||||
Executable
+99
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 2 ]]; then
|
||||||
|
echo "Usage: $0 <app-bundle> <expected-architecture>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_BUNDLE="$1"
|
||||||
|
EXPECTED_ARCH="$2"
|
||||||
|
APP_EXECUTABLE="$APP_BUNDLE/Contents/MacOS/CrossDesk"
|
||||||
|
FRAMEWORKS_DIR="$APP_BUNDLE/Contents/Frameworks"
|
||||||
|
INFO_PLIST="$APP_BUNDLE/Contents/Info.plist"
|
||||||
|
|
||||||
|
if [[ ! -x "$APP_EXECUTABLE" ]]; then
|
||||||
|
echo "Missing app executable: $APP_EXECUTABLE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! lipo "$APP_EXECUTABLE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||||
|
echo "Unexpected app architecture; expected $EXPECTED_ARCH" >&2
|
||||||
|
lipo -archs "$APP_EXECUTABLE" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! otool -l "$APP_EXECUTABLE" | awk '
|
||||||
|
$1 == "cmd" { in_rpath = ($2 == "LC_RPATH"); next }
|
||||||
|
in_rpath && $1 == "path" && $2 == "@executable_path/../Frameworks" { found = 1 }
|
||||||
|
END { exit(found ? 0 : 1) }'; then
|
||||||
|
echo "Missing app rpath: @executable_path/../Frameworks" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
verify_dependencies() {
|
||||||
|
local macho="$1"
|
||||||
|
local dependency=""
|
||||||
|
local relative_path=""
|
||||||
|
|
||||||
|
while IFS= read -r dependency; do
|
||||||
|
case "$dependency" in
|
||||||
|
/System/Library/*|/usr/lib/*)
|
||||||
|
;;
|
||||||
|
@rpath/*)
|
||||||
|
relative_path="${dependency#@rpath/}"
|
||||||
|
if [[ ! -f "$FRAMEWORKS_DIR/$relative_path" ]]; then
|
||||||
|
echo "Unbundled dependency in $macho: $dependency" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
@executable_path/../Frameworks/*)
|
||||||
|
relative_path="${dependency#@executable_path/../Frameworks/}"
|
||||||
|
if [[ ! -f "$FRAMEWORKS_DIR/$relative_path" ]]; then
|
||||||
|
echo "Unbundled dependency in $macho: $dependency" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
@loader_path/*)
|
||||||
|
relative_path="${dependency#@loader_path/}"
|
||||||
|
if [[ ! -f "$(dirname "$macho")/$relative_path" ]]; then
|
||||||
|
echo "Unbundled dependency in $macho: $dependency" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "External non-system dependency in $macho: $dependency" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(otool -L "$macho" | tail -n +2 | awk '{ print $1 }')
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_dependencies "$APP_EXECUTABLE"
|
||||||
|
|
||||||
|
SLINT_LIBRARY="$(find "$FRAMEWORKS_DIR" -maxdepth 1 -type f -name 'libslint_cpp*.dylib' -print -quit 2>/dev/null || true)"
|
||||||
|
if [[ -z "$SLINT_LIBRARY" ]]; then
|
||||||
|
echo "The packaged app does not contain libslint_cpp.dylib" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! lipo "$SLINT_LIBRARY" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
|
||||||
|
echo "Unexpected Slint runtime architecture; expected $EXPECTED_ARCH" >&2
|
||||||
|
lipo -archs "$SLINT_LIBRARY" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
verify_dependencies "$SLINT_LIBRARY"
|
||||||
|
|
||||||
|
PLIST_MIN_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$INFO_PLIST")"
|
||||||
|
BINARY_MIN_VERSION="$(otool -l "$APP_EXECUTABLE" | awk '
|
||||||
|
$1 == "cmd" { build_version = ($2 == "LC_BUILD_VERSION"); version_min = ($2 == "LC_VERSION_MIN_MACOSX"); next }
|
||||||
|
build_version && $1 == "minos" { print $2; exit }
|
||||||
|
version_min && $1 == "version" { print $2; exit }')"
|
||||||
|
if [[ -z "$BINARY_MIN_VERSION" || "$PLIST_MIN_VERSION" != "$BINARY_MIN_VERSION" ]]; then
|
||||||
|
echo "Minimum macOS version mismatch: plist=$PLIST_MIN_VERSION binary=${BINARY_MIN_VERSION:-unknown}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"
|
||||||
|
|
||||||
|
echo "Verified $APP_BUNDLE ($EXPECTED_ARCH, macOS $BINARY_MIN_VERSION+)"
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
#include "keyboard_capturer.h"
|
#include "keyboard_capturer.h"
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/XTest.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,6 @@
|
|||||||
#ifndef _KEYBOARD_CAPTURER_H_
|
#ifndef _KEYBOARD_CAPTURER_H_
|
||||||
#define _KEYBOARD_CAPTURER_H_
|
#define _KEYBOARD_CAPTURER_H_
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
#include <X11/extensions/XTest.h>
|
|
||||||
#include <X11/keysym.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -21,6 +17,7 @@
|
|||||||
|
|
||||||
struct DBusConnection;
|
struct DBusConnection;
|
||||||
struct DBusMessageIter;
|
struct DBusMessageIter;
|
||||||
|
struct _XDisplay;
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
@@ -48,8 +45,8 @@ class KeyboardCapturer : public DeviceController {
|
|||||||
append_args);
|
append_args);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Display* display_;
|
_XDisplay* display_;
|
||||||
Window root_;
|
unsigned long root_;
|
||||||
std::atomic<bool> running_;
|
std::atomic<bool> running_;
|
||||||
std::thread event_thread_;
|
std::thread event_thread_;
|
||||||
bool use_wayland_portal_ = false;
|
bool use_wayland_portal_ = false;
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
|
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
|
||||||
#include <dbus/dbus.h>
|
#include <dbus/dbus.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "mouse_controller.h"
|
#include "mouse_controller.h"
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
#include <X11/extensions/XTest.h>
|
#include <X11/extensions/XTest.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
|||||||
@@ -7,10 +7,6 @@
|
|||||||
#ifndef _MOUSE_CONTROLLER_H_
|
#ifndef _MOUSE_CONTROLLER_H_
|
||||||
#define _MOUSE_CONTROLLER_H_
|
#define _MOUSE_CONTROLLER_H_
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
#include <X11/Xutil.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -20,6 +16,7 @@
|
|||||||
|
|
||||||
struct DBusConnection;
|
struct DBusConnection;
|
||||||
struct DBusMessageIter;
|
struct DBusMessageIter;
|
||||||
|
struct _XDisplay;
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
@@ -53,8 +50,8 @@ class MouseController : public DeviceController {
|
|||||||
|
|
||||||
enum class WaylandAbsoluteMode { kUnknown, kPixels, kNormalized, kDisabled };
|
enum class WaylandAbsoluteMode { kUnknown, kPixels, kNormalized, kDisabled };
|
||||||
|
|
||||||
Display* display_ = nullptr;
|
_XDisplay* display_ = nullptr;
|
||||||
Window root_ = 0;
|
unsigned long root_ = 0;
|
||||||
std::vector<DisplayInfo> display_info_list_;
|
std::vector<DisplayInfo> display_info_list_;
|
||||||
int screen_width_ = 0;
|
int screen_width_ = 0;
|
||||||
int screen_height_ = 0;
|
int screen_height_ = 0;
|
||||||
|
|||||||
@@ -186,6 +186,6 @@ struct ApplicationState : MainWindowState,
|
|||||||
ServerWindowState,
|
ServerWindowState,
|
||||||
UiState {};
|
UiState {};
|
||||||
|
|
||||||
} // namespace crossdesk::gui_detail
|
} // namespace crossdesk::gui_detail
|
||||||
|
|
||||||
#endif // CROSSDESK_GUI_APPLICATION_STATE_H_
|
#endif // CROSSDESK_GUI_APPLICATION_STATE_H_
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,9 @@ private:
|
|||||||
void SyncPlatformDialogs();
|
void SyncPlatformDialogs();
|
||||||
void SyncStreamWindow();
|
void SyncStreamWindow();
|
||||||
void SyncServerWindow();
|
void SyncServerWindow();
|
||||||
|
#if defined(__linux__) && !defined(__APPLE__)
|
||||||
|
void SyncXWaylandWindowActivation();
|
||||||
|
#endif
|
||||||
void UpdateLocalization();
|
void UpdateLocalization();
|
||||||
void ResetSettingsUi();
|
void ResetSettingsUi();
|
||||||
void SaveSettingsFromUi();
|
void SaveSettingsFromUi();
|
||||||
@@ -59,6 +62,9 @@ private:
|
|||||||
bool OpenUrl(const std::string &url);
|
bool OpenUrl(const std::string &url);
|
||||||
|
|
||||||
std::unique_ptr<SlintUi> ui_;
|
std::unique_ptr<SlintUi> ui_;
|
||||||
|
#if defined(__linux__) && !defined(__APPLE__)
|
||||||
|
bool use_xwayland_gui_ = false;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ struct TranslationRow {
|
|||||||
X(notification, u8"通知", "Notification", u8"Уведомление") \
|
X(notification, u8"通知", "Notification", u8"Уведомление") \
|
||||||
X(new_version_available, u8"新版本可用", "New Version Available", \
|
X(new_version_available, u8"新版本可用", "New Version Available", \
|
||||||
u8"Доступна новая версия") \
|
u8"Доступна новая версия") \
|
||||||
|
X(release_notes, u8"更新内容", "Release Notes", u8"Содержание обновления") \
|
||||||
X(version, u8"版本", "Version", u8"Версия") \
|
X(version, u8"版本", "Version", u8"Версия") \
|
||||||
X(release_date, u8"发布日期: ", "Release Date: ", u8"Дата релиза: ") \
|
X(release_date, u8"发布日期: ", "Release Date: ", u8"Дата релиза: ") \
|
||||||
X(access_website, u8"访问官网: ", \
|
X(access_website, u8"访问官网: ", \
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export component ImGuiCheckBox inherits Rectangle {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
if root.checked: Text {
|
if root.checked: Text {
|
||||||
text: FontAwesomeIcons.check;
|
text: FontAwesomeIcons.check;
|
||||||
color: #2e86de;
|
color: root.enabled ? #2e86de : #8b8f95;
|
||||||
font-family: "Font Awesome 6 Free";
|
font-family: "Font Awesome 6 Free";
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
+284
-37
@@ -9,6 +9,11 @@ export struct RecentConnection {
|
|||||||
thumbnail: image,
|
thumbnail: image,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export struct ReleaseNoteBlock {
|
||||||
|
content: styled-text,
|
||||||
|
section-gap: bool,
|
||||||
|
}
|
||||||
|
|
||||||
export global UiStrings {
|
export global UiStrings {
|
||||||
in-out property <string> local-desktop: "Local Desktop";
|
in-out property <string> local-desktop: "Local Desktop";
|
||||||
in-out property <string> remote-desktop: "Remote Desktop";
|
in-out property <string> remote-desktop: "Remote Desktop";
|
||||||
@@ -48,8 +53,8 @@ export global UiStrings {
|
|||||||
in-out property <string> server-port: "Signal port";
|
in-out property <string> server-port: "Signal port";
|
||||||
in-out property <string> coturn-port: "TURN port";
|
in-out property <string> coturn-port: "TURN port";
|
||||||
in-out property <string> version: "Version";
|
in-out property <string> version: "Version";
|
||||||
in-out property <string> copyright: "Copyright © CrossDesk contributors";
|
in-out property <string> copyright: "© 2026 by JUNKUN DI. All right reserved.";
|
||||||
in-out property <string> license: "Licensed under GPL-3.0-only";
|
in-out property <string> license: "Licensed under GNU GPL v3.";
|
||||||
in-out property <string> signal-connected: "Signal server connected";
|
in-out property <string> signal-connected: "Signal server connected";
|
||||||
in-out property <string> signal-disconnected: "Signal server disconnected";
|
in-out property <string> signal-disconnected: "Signal server disconnected";
|
||||||
in-out property <string> tls-error: "Signal server TLS certificate error";
|
in-out property <string> tls-error: "Signal server TLS certificate error";
|
||||||
@@ -179,15 +184,21 @@ component ConnectionDialogButton inherits Rectangle {
|
|||||||
export component MainWindow inherits Window {
|
export component MainWindow inherits Window {
|
||||||
title: "CrossDesk";
|
title: "CrossDesk";
|
||||||
preferred-width: 640px;
|
preferred-width: 640px;
|
||||||
preferred-height: 450px;
|
preferred-height: 450px + root.titlebar-height;
|
||||||
min-width: 640px;
|
min-width: 640px;
|
||||||
max-width: 640px;
|
max-width: 640px;
|
||||||
min-height: 450px;
|
min-height: 450px + root.titlebar-height;
|
||||||
max-height: 450px;
|
max-height: 450px + root.titlebar-height;
|
||||||
no-frame: false;
|
no-frame: root.custom-titlebar;
|
||||||
background: white;
|
background: root.wayland-titlebar ? transparent : white;
|
||||||
default-font-size: ImGuiFontStyle.base;
|
default-font-size: ImGuiFontStyle.base;
|
||||||
|
|
||||||
|
in property <bool> custom-titlebar: false;
|
||||||
|
in property <bool> wayland-titlebar: false;
|
||||||
|
in property <bool> window-active: true;
|
||||||
|
private property <length> titlebar-height:
|
||||||
|
root.custom-titlebar ? (root.wayland-titlebar ? 32px : 31px) : 0px;
|
||||||
|
private property <length> wayland-corner-radius: 12px;
|
||||||
in property <string> local-id: "";
|
in property <string> local-id: "";
|
||||||
in property <string> local-password: "";
|
in property <string> local-password: "";
|
||||||
in-out property <string> remote-id-input: "";
|
in-out property <string> remote-id-input: "";
|
||||||
@@ -199,7 +210,7 @@ export component MainWindow inherits Window {
|
|||||||
in property <string> current-version: "";
|
in property <string> current-version: "";
|
||||||
in property <string> latest-version: "";
|
in property <string> latest-version: "";
|
||||||
in property <string> release-name: "";
|
in property <string> release-name: "";
|
||||||
in property <string> release-notes: "";
|
in property <[ReleaseNoteBlock]> release-note-blocks;
|
||||||
in property <string> release-date: "";
|
in property <string> release-date: "";
|
||||||
in-out property <string> offline-warning: "";
|
in-out property <string> offline-warning: "";
|
||||||
in property <bool> connection-dialog-open: false;
|
in property <bool> connection-dialog-open: false;
|
||||||
@@ -237,7 +248,9 @@ export component MainWindow inherits Window {
|
|||||||
in-out property <string> server-port: "";
|
in-out property <string> server-port: "";
|
||||||
in-out property <string> coturn-port: "";
|
in-out property <string> coturn-port: "";
|
||||||
in property <bool> settings-session-active: false;
|
in property <bool> settings-session-active: false;
|
||||||
in property <bool> hardware-codec-visible: true;
|
// Keep the setting visible on every platform. Unsupported builds expose it
|
||||||
|
// as disabled so the fixed settings layout never turns into a blank row.
|
||||||
|
in property <bool> hardware-codec-available: true;
|
||||||
|
|
||||||
callback copy-local-id;
|
callback copy-local-id;
|
||||||
callback toggle-password-visibility;
|
callback toggle-password-visibility;
|
||||||
@@ -263,6 +276,9 @@ export component MainWindow inherits Window {
|
|||||||
callback cancel-portable-service;
|
callback cancel-portable-service;
|
||||||
callback acknowledge-portable-service;
|
callback acknowledge-portable-service;
|
||||||
callback acknowledge-portable-service-suppressed;
|
callback acknowledge-portable-service-suppressed;
|
||||||
|
callback main-title-drag(int, length, length);
|
||||||
|
callback minimize-main-window;
|
||||||
|
callback close-main-window;
|
||||||
|
|
||||||
private property <bool> menu-open: false;
|
private property <bool> menu-open: false;
|
||||||
in-out property <bool> settings-open: false;
|
in-out property <bool> settings-open: false;
|
||||||
@@ -299,7 +315,7 @@ export component MainWindow inherits Window {
|
|||||||
? root.recent-tooltip-pointer-x - root.recent-tooltip-width - 12px
|
? root.recent-tooltip-pointer-x - root.recent-tooltip-width - 12px
|
||||||
: root.recent-tooltip-pointer-x + 12px));
|
: root.recent-tooltip-pointer-x + 12px));
|
||||||
private property <length> recent-tooltip-y: min(
|
private property <length> recent-tooltip-y: min(
|
||||||
root.height - root.recent-tooltip-height - 8px,
|
root.height - root.titlebar-height - root.recent-tooltip-height - 8px,
|
||||||
max(8px, root.recent-tooltip-pointer-y - root.recent-tooltip-height - 8px));
|
max(8px, root.recent-tooltip-pointer-y - root.recent-tooltip-height - 8px));
|
||||||
|
|
||||||
reset-remote-id => {
|
reset-remote-id => {
|
||||||
@@ -307,9 +323,201 @@ export component MainWindow inherits Window {
|
|||||||
remote-id-editor.focus();
|
remote-id-editor.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
window-surface := Rectangle {
|
||||||
width: root.width;
|
width: root.width;
|
||||||
height: root.height;
|
height: root.height;
|
||||||
|
background: root.wayland-titlebar ? #f6f5f4 : transparent;
|
||||||
|
border-radius:
|
||||||
|
root.wayland-titlebar ? root.wayland-corner-radius : 0px;
|
||||||
|
clip: root.wayland-titlebar;
|
||||||
|
|
||||||
|
if root.custom-titlebar && root.wayland-titlebar: Rectangle {
|
||||||
|
x: 0px;
|
||||||
|
y: 0px;
|
||||||
|
width: parent.width;
|
||||||
|
height: root.titlebar-height;
|
||||||
|
background: #f6f5f4;
|
||||||
|
z: 100;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 80px;
|
||||||
|
width: parent.width - 160px;
|
||||||
|
height: parent.height;
|
||||||
|
text: "CrossDesk";
|
||||||
|
color: root.window-active ? #3d3d3d : #747474;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
wayland-title-drag := TouchArea {
|
||||||
|
width: parent.width - 80px;
|
||||||
|
property <int> phase: self.pressed ? 1 : 0;
|
||||||
|
changed phase => {
|
||||||
|
root.main-title-drag(phase, self.mouse-x, self.mouse-y);
|
||||||
|
}
|
||||||
|
moved => {
|
||||||
|
if (self.pressed) {
|
||||||
|
root.main-title-drag(2, self.mouse-x, self.mouse-y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wayland-minimize-button := Rectangle {
|
||||||
|
x: parent.width - 74px;
|
||||||
|
y: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: wayland-minimize-touch.pressed ? #c0bfbc
|
||||||
|
: wayland-minimize-touch.has-hover ? #deddda
|
||||||
|
: root.window-active ? #e6e6e6
|
||||||
|
: transparent;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: "Minimize";
|
||||||
|
accessible-action-default => { wayland-minimize-touch.clicked(); }
|
||||||
|
Rectangle {
|
||||||
|
x: 8px;
|
||||||
|
y: 13px;
|
||||||
|
width: 8px;
|
||||||
|
height: 0.75px;
|
||||||
|
background: root.window-active
|
||||||
|
|| wayland-minimize-touch.has-hover
|
||||||
|
|| wayland-minimize-touch.pressed ? #454545 : #898989;
|
||||||
|
}
|
||||||
|
wayland-minimize-touch := TouchArea {
|
||||||
|
clicked => { root.minimize-main-window(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wayland-close-button := Rectangle {
|
||||||
|
x: parent.width - 34px;
|
||||||
|
y: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: wayland-close-touch.pressed ? #b2161d
|
||||||
|
: wayland-close-touch.has-hover ? #e01b24
|
||||||
|
: root.window-active ? #e6e6e6
|
||||||
|
: transparent;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: "Close";
|
||||||
|
accessible-action-default => { wayland-close-touch.clicked(); }
|
||||||
|
Path {
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
viewbox-width: 24;
|
||||||
|
viewbox-height: 24;
|
||||||
|
commands: "M 14.75 14.75 L 8.25 8.25 M 14.75 8.25 L 8.25 14.75";
|
||||||
|
stroke: wayland-close-touch.has-hover
|
||||||
|
|| wayland-close-touch.pressed ? white
|
||||||
|
: root.window-active ? #454545 : #898989;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
wayland-close-touch := TouchArea {
|
||||||
|
clicked => { root.close-main-window(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
y: parent.height - 1px;
|
||||||
|
height: 1px;
|
||||||
|
background: #d5d3d0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.custom-titlebar && !root.wayland-titlebar: Rectangle {
|
||||||
|
x: 0px;
|
||||||
|
y: 0px;
|
||||||
|
width: root.width;
|
||||||
|
height: 31px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
z: 100;
|
||||||
|
|
||||||
|
Image {
|
||||||
|
x: 8px;
|
||||||
|
y: 7px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
source: @image-url("../../../icons/linux/crossdesk_16x16.png");
|
||||||
|
image-fit: contain;
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
x: 30px;
|
||||||
|
width: parent.width - 122px;
|
||||||
|
height: parent.height;
|
||||||
|
text: "CrossDesk";
|
||||||
|
color: #202124;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-alignment: center;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
title-drag := TouchArea {
|
||||||
|
x: 0px;
|
||||||
|
width: parent.width - 92px;
|
||||||
|
property <int> phase: self.pressed ? 1 : 0;
|
||||||
|
changed phase => { root.main-title-drag(phase, self.mouse-x, self.mouse-y); }
|
||||||
|
moved => { if (self.pressed) { root.main-title-drag(2, self.mouse-x, self.mouse-y); } }
|
||||||
|
}
|
||||||
|
minimize-button := Rectangle {
|
||||||
|
x: parent.width - 92px;
|
||||||
|
width: 46px;
|
||||||
|
height: parent.height;
|
||||||
|
background: minimize-touch.pressed ? #c7c7c7
|
||||||
|
: minimize-touch.has-hover ? #e5e5e5
|
||||||
|
: transparent;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: "Minimize";
|
||||||
|
accessible-action-default => { minimize-touch.clicked(); }
|
||||||
|
Text {
|
||||||
|
text: FontAwesomeIcons.minus;
|
||||||
|
color: #202124;
|
||||||
|
font-family: "Font Awesome 6 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 10px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
minimize-touch := TouchArea { clicked => { root.minimize-main-window(); } }
|
||||||
|
}
|
||||||
|
close-button := Rectangle {
|
||||||
|
x: parent.width - 46px;
|
||||||
|
width: 46px;
|
||||||
|
height: parent.height;
|
||||||
|
background: close-touch.pressed ? #b3251b
|
||||||
|
: close-touch.has-hover ? #c42b1c
|
||||||
|
: transparent;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: "Close";
|
||||||
|
accessible-action-default => { close-touch.clicked(); }
|
||||||
|
Text {
|
||||||
|
text: FontAwesomeIcons.xmark;
|
||||||
|
color: close-touch.has-hover || close-touch.pressed ? white : #202124;
|
||||||
|
font-family: "Font Awesome 6 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 12px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
close-touch := TouchArea { clicked => { root.close-main-window(); } }
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
y: parent.height - 1px;
|
||||||
|
height: 1px;
|
||||||
|
background: #d4d4d4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content-layer := Rectangle {
|
||||||
|
x: 0px;
|
||||||
|
y: root.titlebar-height;
|
||||||
|
width: root.width;
|
||||||
|
height: root.height - self.y;
|
||||||
|
clip: true;
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
background: white;
|
background: white;
|
||||||
border-width: 0px;
|
border-width: 0px;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
@@ -329,8 +537,8 @@ export component MainWindow inherits Window {
|
|||||||
z: 7;
|
z: 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// App actions live in the content header; native window controls and
|
// App actions live in the content header; window controls and dragging
|
||||||
// dragging are provided by the system title bar.
|
// remain in the native or Windows-specific title bar above it.
|
||||||
menu-button := IconButton {
|
menu-button := IconButton {
|
||||||
x: parent.width - 40px;
|
x: parent.width - 40px;
|
||||||
y: 4px;
|
y: 4px;
|
||||||
@@ -775,8 +983,8 @@ export component MainWindow inherits Window {
|
|||||||
if root.menu-open: TouchArea {
|
if root.menu-open: TouchArea {
|
||||||
x: 0px;
|
x: 0px;
|
||||||
y: 0px;
|
y: 0px;
|
||||||
width: root.width;
|
width: parent.width;
|
||||||
height: root.height;
|
height: parent.height;
|
||||||
z: 9;
|
z: 9;
|
||||||
clicked => {
|
clicked => {
|
||||||
root.menu-open = false;
|
root.menu-open = false;
|
||||||
@@ -1213,8 +1421,8 @@ export component MainWindow inherits Window {
|
|||||||
CompactComboBox { x: root.compact-language ? 120px : 188px; y: 120px; enabled: !root.settings-session-active; model: [UiStrings.codec-h264, UiStrings.codec-av1]; current-index <=> root.codec-index; }
|
CompactComboBox { x: root.compact-language ? 120px : 188px; y: 120px; enabled: !root.settings-session-active; model: [UiStrings.codec-h264, UiStrings.codec-av1]; current-index <=> root.codec-index; }
|
||||||
Rectangle { x: 7px; y: 147px; width: parent.width - 14px; height: 1px; background: ImGuiLineStyle.separator; }
|
Rectangle { x: 7px; y: 147px; width: parent.width - 14px; height: 1px; background: ImGuiLineStyle.separator; }
|
||||||
|
|
||||||
if root.hardware-codec-visible: Text { x: 8px; y: 151px; width: root.compact-language ? 155px : 225px; height: 24px; text: UiStrings.hardware-codec; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
Text { x: 8px; y: 151px; width: root.compact-language ? 155px : 225px; height: 24px; text: UiStrings.hardware-codec; color: root.hardware-codec-available ? #202124 : #8b8f95; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
||||||
if root.hardware-codec-visible: ImGuiCheckBox { x: root.compact-language ? 171px : 238px; y: 151px; checked <=> root.hardware-codec-enabled; enabled: !root.settings-session-active; }
|
ImGuiCheckBox { x: root.compact-language ? 171px : 238px; y: 151px; checked <=> root.hardware-codec-enabled; enabled: root.hardware-codec-available && !root.settings-session-active; }
|
||||||
Rectangle { x: 7px; y: 177px; width: parent.width - 14px; height: 1px; background: ImGuiLineStyle.separator; }
|
Rectangle { x: 7px; y: 177px; width: parent.width - 14px; height: 1px; background: ImGuiLineStyle.separator; }
|
||||||
|
|
||||||
Text { x: 8px; y: 181px; width: root.compact-language ? 155px : 225px; height: 24px; text: UiStrings.turn-relay; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
Text { x: 8px; y: 181px; width: root.compact-language ? 155px : 225px; height: 24px; text: UiStrings.turn-relay; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
||||||
@@ -1341,19 +1549,19 @@ export component MainWindow inherits Window {
|
|||||||
Rectangle { y: parent.height - 7px; height: 7px; background: #d3d3d3; }
|
Rectangle { y: parent.height - 7px; height: 7px; background: #d3d3d3; }
|
||||||
Text { x: 6px; text: UiStrings.about; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
|
Text { x: 6px; text: UiStrings.about; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
|
||||||
}
|
}
|
||||||
Text { x: 30px; y: 29px; width: 240px; height: 20px; text: UiStrings.version + ": CrossDesk " + root.current-version; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
Text { x: 30px; y: 40px; width: 240px; height: 20px; text: UiStrings.version + ": CrossDesk " + root.current-version; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
||||||
if root.update-available: Text { x: 30px; y: 51px; width: 240px; height: 20px; text: UiStrings.update-available + ":"; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
if root.update-available: Text { x: 30px; y: 60px; width: 240px; height: 20px; text: UiStrings.update-available + ":"; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
||||||
if root.update-available: Rectangle {
|
if root.update-available: Rectangle {
|
||||||
x: 30px;
|
x: 30px;
|
||||||
y: 70px;
|
y: 60px;
|
||||||
width: 240px;
|
width: 240px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
version-link := Text { text: root.latest-version; color: version-touch.has-hover ? #174ea6 : #334fd1; font-size: ImGuiFontStyle.body; horizontal-alignment: center; vertical-alignment: center; }
|
version-link := Text { text: root.latest-version; color: version-touch.has-hover ? #174ea6 : #334fd1; font-size: ImGuiFontStyle.body; horizontal-alignment: center; vertical-alignment: center; }
|
||||||
version-touch := TouchArea { mouse-cursor: pointer; clicked => { root.open-download(); } }
|
version-touch := TouchArea { mouse-cursor: pointer; clicked => { root.open-download(); } }
|
||||||
}
|
}
|
||||||
Text { x: 30px; y: 96px; width: 240px; height: 20px; text: UiStrings.copyright; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
Text { x: 30px; y: 85px; width: 240px; height: 20px; text: UiStrings.copyright; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
||||||
Text { x: 30px; y: 118px; width: 240px; height: 20px; text: UiStrings.license; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
Text { x: 30px; y: 105px; width: 240px; height: 20px; text: UiStrings.license; color: #202124; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
||||||
CompactButton { x: 133px; y: 157px; primary: true; text: UiStrings.ok; clicked => { root.about-open = false; } }
|
CompactButton { x: 133px; y: 145px; primary: true; text: UiStrings.ok; clicked => { root.about-open = false; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1364,24 +1572,43 @@ export component MainWindow inherits Window {
|
|||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea { clicked => { focus-sink.focus(); } }
|
||||||
DialogSurface {
|
DialogSurface {
|
||||||
x: 120px;
|
x: 120px;
|
||||||
y: 65px;
|
y: 52px;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 320px;
|
height: 346px;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
Text { x: 40px; y: 22px; width: 320px; height: 24px; text: UiStrings.update-available + ": " + root.latest-version; color: #202124; font-size: ImGuiFontStyle.prominent; vertical-alignment: center; }
|
Text { x: 30px; y: 22px; width: 320px; height: 24px; text: UiStrings.update-available + ": " + root.latest-version; color: #202124; font-size: ImGuiFontStyle.prominent; vertical-alignment: center; }
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: 40px;
|
x: 30px;
|
||||||
y: 47px;
|
y: 47px;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
Text { text: UiStrings.access-website + "https://crossdesk.cn"; color: website-touch.has-hover ? #174ea6 : #334fd1; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
Text {
|
||||||
|
x: 0px;
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
text: UiStrings.access-website + "https://crossdesk.cn";
|
||||||
|
color: website-touch.has-hover ? #174ea6 : #334fd1;
|
||||||
|
font-size: ImGuiFontStyle.body;
|
||||||
|
horizontal-alignment: left;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
website-touch := TouchArea { mouse-cursor: pointer; clicked => { root.open-download(); } }
|
website-touch := TouchArea { mouse-cursor: pointer; clicked => { root.open-download(); } }
|
||||||
}
|
}
|
||||||
|
Text {
|
||||||
|
x: 30px;
|
||||||
|
y: 75px;
|
||||||
|
width: 320px;
|
||||||
|
height: 20px;
|
||||||
|
text: UiStrings.release-notes;
|
||||||
|
color: #202124;
|
||||||
|
font-size: 12px;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: 20px;
|
x: 30px;
|
||||||
y: 74px;
|
y: 105px;
|
||||||
width: 360px;
|
width: 360px;
|
||||||
height: 200px;
|
height: 190px;
|
||||||
background: white;
|
background: white;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: #b9bdc3;
|
border-color: #b9bdc3;
|
||||||
@@ -1392,19 +1619,24 @@ export component MainWindow inherits Window {
|
|||||||
height: parent.height - 2px;
|
height: parent.height - 2px;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
width: 340px;
|
width: 340px;
|
||||||
padding-left: 19px;
|
padding-left: 8px;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
spacing: 7px;
|
spacing: 7px;
|
||||||
if root.release-name != "": Text { width: 312px; text: root.release-name; color: #202124; font-size: ImGuiFontStyle.body; wrap: word-wrap; }
|
VerticalLayout {
|
||||||
Text { width: 312px; text: root.release-notes; color: #202124; font-size: ImGuiFontStyle.body; wrap: word-wrap; }
|
width: 312px;
|
||||||
if root.release-date != "": Text { width: 312px; text: UiStrings.release-date-label + root.release-date; color: #4f555e; font-size: ImGuiFontStyle.small; wrap: word-wrap; }
|
spacing: 10px;
|
||||||
|
for block in root.release-note-blocks: VerticalLayout {
|
||||||
|
padding-top: block.section-gap ? 10px : 0px;
|
||||||
|
StyledText { width: 312px; text: block.content; default-color: #202124; default-font-size: ImGuiFontStyle.body; }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompactButton { x: root.compact-language ? 163px : 130px; y: 283px; width: root.compact-language ? 34px : 70px; primary: true; text: UiStrings.download; clicked => { root.open-download(); root.update-open = false; } }
|
CompactButton { x: root.compact-language ? 163px : 130px; y: 309px; width: root.compact-language ? 34px : 70px; primary: true; text: UiStrings.download; clicked => { root.open-download(); root.update-open = false; } }
|
||||||
CompactButton { x: root.compact-language ? 204px : 207px; y: 283px; width: root.compact-language ? 34px : 56px; text: UiStrings.later; clicked => { root.update-open = false; } }
|
CompactButton { x: root.compact-language ? 204px : 207px; y: 309px; width: root.compact-language ? 34px : 56px; text: UiStrings.later; clicked => { root.update-open = false; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1478,4 +1710,19 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.custom-titlebar: Rectangle {
|
||||||
|
x: 0px;
|
||||||
|
y: 0px;
|
||||||
|
width: root.width;
|
||||||
|
height: root.height;
|
||||||
|
background: transparent;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: root.wayland-titlebar ? #aaa8a6 : #8b8b8b;
|
||||||
|
border-radius:
|
||||||
|
root.wayland-titlebar ? root.wayland-corner-radius : 0px;
|
||||||
|
z: 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,13 +112,16 @@ component ControlBarButton inherits Rectangle {
|
|||||||
export component StreamWindow inherits Window {
|
export component StreamWindow inherits Window {
|
||||||
title: "CrossDesk";
|
title: "CrossDesk";
|
||||||
preferred-width: 1280px;
|
preferred-width: 1280px;
|
||||||
preferred-height: 720px;
|
preferred-height: 720px + (root.custom-titlebar ? 32px : 0px);
|
||||||
min-width: 640px;
|
min-width: 640px;
|
||||||
min-height: 360px;
|
min-height: 360px + (root.custom-titlebar ? 32px : 0px);
|
||||||
no-frame: false;
|
no-frame: root.custom-titlebar;
|
||||||
background: black;
|
background: root.custom-titlebar ? transparent : black;
|
||||||
default-font-size: ImGuiFontStyle.base;
|
default-font-size: ImGuiFontStyle.base;
|
||||||
|
|
||||||
|
in property <bool> custom-titlebar: false;
|
||||||
|
in property <bool> window-active: true;
|
||||||
|
in property <bool> window-maximized: false;
|
||||||
in property <[StreamTab]> tabs;
|
in property <[StreamTab]> tabs;
|
||||||
in-out property <int> selected-tab: 0;
|
in-out property <int> selected-tab: 0;
|
||||||
in property <image> frame;
|
in property <image> frame;
|
||||||
@@ -153,6 +156,10 @@ export component StreamWindow inherits Window {
|
|||||||
callback toggle-stats;
|
callback toggle-stats;
|
||||||
callback toggle-fullscreen;
|
callback toggle-fullscreen;
|
||||||
callback disconnect;
|
callback disconnect;
|
||||||
|
callback stream-title-drag(int, length, length);
|
||||||
|
callback minimize-stream-window;
|
||||||
|
callback toggle-maximize-stream-window;
|
||||||
|
callback close-stream-window;
|
||||||
callback pointer-input(PointerEventButton, PointerEventKind, length, length);
|
callback pointer-input(PointerEventButton, PointerEventKind, length, length);
|
||||||
callback scroll-input(length, length, length, length);
|
callback scroll-input(length, length, length, length);
|
||||||
callback key-input(string, bool, bool, bool, bool, bool);
|
callback key-input(string, bool, bool, bool, bool, bool);
|
||||||
@@ -169,6 +176,9 @@ export component StreamWindow inherits Window {
|
|||||||
private property <length> control-press-x: 0px;
|
private property <length> control-press-x: 0px;
|
||||||
private property <length> control-press-y: 0px;
|
private property <length> control-press-y: 0px;
|
||||||
private property <int> dragging-tab: -1;
|
private property <int> dragging-tab: -1;
|
||||||
|
private property <length> titlebar-height:
|
||||||
|
root.custom-titlebar && !root.fullscreen-enabled ? 32px : 0px;
|
||||||
|
private property <length> window-corner-radius: 12px;
|
||||||
callback begin-control-drag(length, length);
|
callback begin-control-drag(length, length);
|
||||||
callback move-control-drag(length, length);
|
callback move-control-drag(length, length);
|
||||||
callback end-control-drag;
|
callback end-control-drag;
|
||||||
@@ -202,10 +212,167 @@ export component StreamWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
window-surface := Rectangle {
|
||||||
width: root.width;
|
width: root.width;
|
||||||
height: root.height;
|
height: root.height;
|
||||||
background: black;
|
background: black;
|
||||||
|
border-radius:
|
||||||
|
root.custom-titlebar && !root.fullscreen-enabled && !root.window-maximized
|
||||||
|
? root.window-corner-radius : 0px;
|
||||||
|
clip: root.custom-titlebar && !root.fullscreen-enabled;
|
||||||
|
|
||||||
|
if root.custom-titlebar && !root.fullscreen-enabled: Rectangle {
|
||||||
|
x: 0px;
|
||||||
|
y: 0px;
|
||||||
|
width: parent.width;
|
||||||
|
height: root.titlebar-height;
|
||||||
|
background: #f6f5f4;
|
||||||
|
z: 100;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 120px;
|
||||||
|
width: parent.width - 240px;
|
||||||
|
height: parent.height;
|
||||||
|
text: "CrossDesk";
|
||||||
|
color: root.window-active ? #3d3d3d : #747474;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
stream-title-drag-area := TouchArea {
|
||||||
|
width: parent.width - 120px;
|
||||||
|
property <int> phase: self.pressed ? 1 : 0;
|
||||||
|
changed phase => {
|
||||||
|
root.stream-title-drag(phase, self.mouse-x, self.mouse-y);
|
||||||
|
}
|
||||||
|
moved => {
|
||||||
|
if (self.pressed) {
|
||||||
|
root.stream-title-drag(2, self.mouse-x, self.mouse-y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double-clicked => {
|
||||||
|
root.toggle-maximize-stream-window();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream-minimize-button := Rectangle {
|
||||||
|
x: parent.width - 114px;
|
||||||
|
y: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: stream-minimize-touch.pressed ? #c0bfbc
|
||||||
|
: stream-minimize-touch.has-hover ? #deddda
|
||||||
|
: root.window-active ? #e6e6e6
|
||||||
|
: transparent;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: "Minimize";
|
||||||
|
accessible-action-default => { stream-minimize-touch.clicked(); }
|
||||||
|
Rectangle {
|
||||||
|
x: 8px;
|
||||||
|
y: 13px;
|
||||||
|
width: 8px;
|
||||||
|
height: 0.75px;
|
||||||
|
background: root.window-active
|
||||||
|
|| stream-minimize-touch.has-hover
|
||||||
|
|| stream-minimize-touch.pressed ? #454545 : #898989;
|
||||||
|
}
|
||||||
|
stream-minimize-touch := TouchArea {
|
||||||
|
clicked => { root.minimize-stream-window(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream-maximize-button := Rectangle {
|
||||||
|
x: parent.width - 74px;
|
||||||
|
y: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: stream-maximize-touch.pressed ? #c0bfbc
|
||||||
|
: stream-maximize-touch.has-hover ? #deddda
|
||||||
|
: root.window-active ? #e6e6e6
|
||||||
|
: transparent;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: root.window-maximized ? "Restore" : "Maximize";
|
||||||
|
accessible-action-default => { stream-maximize-touch.clicked(); }
|
||||||
|
if !root.window-maximized: Rectangle {
|
||||||
|
x: 8px;
|
||||||
|
y: 8px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: transparent;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: root.window-active
|
||||||
|
|| stream-maximize-touch.has-hover
|
||||||
|
|| stream-maximize-touch.pressed ? #454545 : #898989;
|
||||||
|
}
|
||||||
|
if root.window-maximized: Rectangle {
|
||||||
|
x: 9px;
|
||||||
|
y: 7px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: transparent;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: root.window-active
|
||||||
|
|| stream-maximize-touch.has-hover
|
||||||
|
|| stream-maximize-touch.pressed ? #454545 : #898989;
|
||||||
|
}
|
||||||
|
if root.window-maximized: Rectangle {
|
||||||
|
x: 7px;
|
||||||
|
y: 9px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: stream-maximize-button.background;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: root.window-active
|
||||||
|
|| stream-maximize-touch.has-hover
|
||||||
|
|| stream-maximize-touch.pressed ? #454545 : #898989;
|
||||||
|
}
|
||||||
|
stream-maximize-touch := TouchArea {
|
||||||
|
clicked => { root.toggle-maximize-stream-window(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream-close-button := Rectangle {
|
||||||
|
x: parent.width - 34px;
|
||||||
|
y: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: stream-close-touch.pressed ? #b2161d
|
||||||
|
: stream-close-touch.has-hover ? #e01b24
|
||||||
|
: root.window-active ? #e6e6e6
|
||||||
|
: transparent;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: "Close";
|
||||||
|
accessible-action-default => { stream-close-touch.clicked(); }
|
||||||
|
Path {
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
viewbox-width: 24;
|
||||||
|
viewbox-height: 24;
|
||||||
|
commands: "M 14.75 14.75 L 8.25 8.25 M 14.75 8.25 L 8.25 14.75";
|
||||||
|
stroke: stream-close-touch.has-hover
|
||||||
|
|| stream-close-touch.pressed ? white
|
||||||
|
: root.window-active ? #454545 : #898989;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
stream-close-touch := TouchArea {
|
||||||
|
clicked => { root.close-stream-window(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
y: parent.height - 1px;
|
||||||
|
height: 1px;
|
||||||
|
background: #d5d3d0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content-layer := Rectangle {
|
||||||
|
y: root.titlebar-height;
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height - self.y;
|
||||||
|
background: black;
|
||||||
|
clip: true;
|
||||||
|
|
||||||
// Keep the ImGui tab strip behavior, including pointer-drag reordering.
|
// Keep the ImGui tab strip behavior, including pointer-drag reordering.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -645,5 +812,17 @@ export component StreamWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.custom-titlebar && !root.fullscreen-enabled: Rectangle {
|
||||||
|
width: parent.width;
|
||||||
|
height: parent.height;
|
||||||
|
background: transparent;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #aaa8a6;
|
||||||
|
border-radius:
|
||||||
|
root.window-maximized ? 0px : root.window-corner-radius;
|
||||||
|
z: 200;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <algorithm>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "application/gui_application.h"
|
#include "application/gui_application.h"
|
||||||
@@ -8,51 +7,9 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
std::string CleanMarkdown(const std::string &markdown) {
|
|
||||||
std::string result = markdown;
|
|
||||||
|
|
||||||
// remove # title mark
|
|
||||||
size_t pos = 0;
|
|
||||||
while (pos < result.length()) {
|
|
||||||
if (result[pos] == '\n' || pos == 0) {
|
|
||||||
size_t line_start = (result[pos] == '\n') ? pos + 1 : pos;
|
|
||||||
if (line_start < result.length() && result[line_start] == '#') {
|
|
||||||
size_t hash_end = line_start;
|
|
||||||
while (hash_end < result.length() &&
|
|
||||||
(result[hash_end] == '#' || result[hash_end] == ' ')) {
|
|
||||||
hash_end++;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.erase(line_start, hash_end - line_start);
|
|
||||||
pos = line_start;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove ** bold mark
|
|
||||||
pos = 0;
|
|
||||||
while ((pos = result.find("**", pos)) != std::string::npos) {
|
|
||||||
result.erase(pos, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove all spaces
|
|
||||||
result.erase(std::remove(result.begin(), result.end(), ' '), result.end());
|
|
||||||
|
|
||||||
// replace . with 、
|
|
||||||
pos = 0;
|
|
||||||
while ((pos = result.find('.', pos)) != std::string::npos) {
|
|
||||||
result.replace(pos, 1, "、");
|
|
||||||
pos += 1; // Move to next position after the replacement
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GuiApplication::UpdateNotificationWindow() {
|
int GuiApplication::UpdateNotificationWindow() {
|
||||||
if (show_update_notification_window_ && update_available_) {
|
if (show_update_notification_window_ && update_available_) {
|
||||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||||
|
|
||||||
float update_notification_window_width = title_bar_button_width_ * 10.0f;
|
float update_notification_window_width = title_bar_button_width_ * 10.0f;
|
||||||
float update_notification_window_height = title_bar_button_width_ * 8.0f;
|
float update_notification_window_height = title_bar_button_width_ * 8.0f;
|
||||||
@@ -142,8 +99,7 @@ int GuiApplication::UpdateNotificationWindow() {
|
|||||||
// release notes
|
// release notes
|
||||||
if (!release_notes_.empty()) {
|
if (!release_notes_.empty()) {
|
||||||
ImGui::SetCursorPosX(update_notification_window_width * 0.05f);
|
ImGui::SetCursorPosX(update_notification_window_width * 0.05f);
|
||||||
std::string cleaned_notes = CleanMarkdown(release_notes_);
|
ImGui::TextWrapped("%s", release_notes_.c_str());
|
||||||
ImGui::TextWrapped("%s", cleaned_notes.c_str());
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,4 +160,4 @@ int GuiApplication::UpdateNotificationWindow() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
|
|||||||
v_fit.data(), (fit_w + 1) / 2, abgr.data(), fit_w * 4,
|
v_fit.data(), (fit_w + 1) / 2, abgr.data(), fit_w * 4,
|
||||||
fit_w, fit_h);
|
fit_w, fit_h);
|
||||||
|
|
||||||
memset(dst_rgba, 0, dst_w * dst_h * 4);
|
std::memset(dst_rgba, 0, dst_w * dst_h * 4);
|
||||||
for (int i = 0; i < dst_w * dst_h; ++i) {
|
for (int i = 0; i < dst_w * dst_h; ++i) {
|
||||||
dst_rgba[i * 4 + 3] = static_cast<char>(0xFF);
|
dst_rgba[i * 4 + 3] = static_cast<char>(0xFF);
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,8 @@ void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
|
|||||||
for (int row = 0; row < fit_h; ++row) {
|
for (int row = 0; row < fit_h; ++row) {
|
||||||
int dst_offset =
|
int dst_offset =
|
||||||
((row + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
|
((row + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
|
||||||
memcpy(dst_rgba + dst_offset, abgr.data() + row * fit_w * 4, fit_w * 4);
|
std::memcpy(dst_rgba + dst_offset, abgr.data() + row * fit_w * 4,
|
||||||
|
fit_w * 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +86,8 @@ Thumbnail::Thumbnail(std::string save_path, unsigned char* aes128_key,
|
|||||||
save_path_ = save_path;
|
save_path_ = save_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(aes128_key_, aes128_key, sizeof(aes128_key_));
|
std::memcpy(aes128_key_, aes128_key, sizeof(aes128_key_));
|
||||||
memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_));
|
std::memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_));
|
||||||
std::filesystem::create_directories(save_path_);
|
std::filesystem::create_directories(save_path_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
|
|||||||
thumbnail_height_, rgba_buffer_);
|
thumbnail_height_, rgba_buffer_);
|
||||||
} else {
|
} else {
|
||||||
// If yuv420p is null, fill the buffer with black pixels
|
// If yuv420p is null, fill the buffer with black pixels
|
||||||
memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
|
std::memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
|
||||||
for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) {
|
for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) {
|
||||||
// Set alpha channel to opaque
|
// Set alpha channel to opaque
|
||||||
rgba_buffer_[i * 4 + 3] = static_cast<char>(0xFF);
|
rgba_buffer_[i * 4 + 3] = static_cast<char>(0xFF);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#ifndef _THUMBNAIL_H_
|
#ifndef _THUMBNAIL_H_
|
||||||
#define _THUMBNAIL_H_
|
#define _THUMBNAIL_H_
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -52,18 +53,18 @@ class Thumbnail {
|
|||||||
int DeleteAllFilesInDirectory();
|
int DeleteAllFilesInDirectory();
|
||||||
|
|
||||||
int GetKey(unsigned char* aes128_key) {
|
int GetKey(unsigned char* aes128_key) {
|
||||||
memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
|
std::memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
|
||||||
return sizeof(aes128_key_);
|
return sizeof(aes128_key_);
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetIv(unsigned char* aes128_iv) {
|
int GetIv(unsigned char* aes128_iv) {
|
||||||
memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
|
std::memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
|
||||||
return sizeof(aes128_iv_);
|
return sizeof(aes128_iv_);
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetKeyAndIv(unsigned char* aes128_key, unsigned char* aes128_iv) {
|
int GetKeyAndIv(unsigned char* aes128_key, unsigned char* aes128_iv) {
|
||||||
memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
|
std::memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
|
||||||
memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
|
std::memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,91 @@
|
|||||||
#include "crossdesk_ui.h"
|
#include "crossdesk_ui.h"
|
||||||
|
#include "fa_solid_900.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Set the CROSSDESK_*_UI_SNAPSHOT variables while using Slint's software
|
||||||
|
// renderer to export deterministic active and inactive visual-check images.
|
||||||
|
bool WriteWindowSnapshot(const slint::Window &window, const char *path) {
|
||||||
|
const auto snapshot = window.take_snapshot();
|
||||||
|
if (!snapshot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream output(path, std::ios::binary);
|
||||||
|
output << "P6\n"
|
||||||
|
<< snapshot->width() << ' ' << snapshot->height() << "\n255\n";
|
||||||
|
for (const auto &pixel : *snapshot) {
|
||||||
|
output.put(static_cast<char>(pixel.r));
|
||||||
|
output.put(static_cast<char>(pixel.g));
|
||||||
|
output.put(static_cast<char>(pixel.b));
|
||||||
|
}
|
||||||
|
return output.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegisterFontAwesome(slint::Window &window) {
|
||||||
|
return !window.window_handle()
|
||||||
|
.register_font_from_data(fa_solid_900_ttf,
|
||||||
|
fa_solid_900_ttf_len)
|
||||||
|
.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
auto window = crossdesk::ui::MainWindow::create();
|
auto window = crossdesk::ui::MainWindow::create();
|
||||||
auto stream = crossdesk::ui::StreamWindow::create();
|
auto stream = crossdesk::ui::StreamWindow::create();
|
||||||
auto server = crossdesk::ui::ServerWindow::create();
|
auto server = crossdesk::ui::ServerWindow::create();
|
||||||
|
if (!RegisterFontAwesome(window->window()) ||
|
||||||
|
!RegisterFontAwesome(stream->window()) ||
|
||||||
|
!RegisterFontAwesome(server->window())) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
window->set_local_id("123 456 789");
|
window->set_local_id("123 456 789");
|
||||||
window->set_local_password("123456");
|
window->set_local_password("123456");
|
||||||
window->set_connection_dialog_open(true);
|
window->set_connection_dialog_open(true);
|
||||||
window->set_connection_password_required(true);
|
window->set_connection_password_required(true);
|
||||||
|
window->set_custom_titlebar(true);
|
||||||
|
window->set_wayland_titlebar(true);
|
||||||
window->set_settings_session_active(true);
|
window->set_settings_session_active(true);
|
||||||
|
window->set_hardware_codec_available(false);
|
||||||
window->set_portable_service_settings_visible(true);
|
window->set_portable_service_settings_visible(true);
|
||||||
assert(std::string(window->get_local_id()) == "123 456 789");
|
assert(std::string(window->get_local_id()) == "123 456 789");
|
||||||
assert(window->get_connection_dialog_open());
|
assert(window->get_connection_dialog_open());
|
||||||
assert(window->get_connection_password_required());
|
assert(window->get_connection_password_required());
|
||||||
|
assert(window->get_custom_titlebar());
|
||||||
|
assert(window->get_wayland_titlebar());
|
||||||
assert(window->get_settings_session_active());
|
assert(window->get_settings_session_active());
|
||||||
|
assert(!window->get_hardware_codec_available());
|
||||||
assert(window->get_portable_service_settings_visible());
|
assert(window->get_portable_service_settings_visible());
|
||||||
|
if (const char *snapshot_path =
|
||||||
|
std::getenv("CROSSDESK_MAIN_UI_SNAPSHOT")) {
|
||||||
|
window->show();
|
||||||
|
const bool snapshot_written =
|
||||||
|
WriteWindowSnapshot(window->window(), snapshot_path);
|
||||||
|
window->hide();
|
||||||
|
if (!snapshot_written) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (const char *snapshot_path =
|
||||||
|
std::getenv("CROSSDESK_MAIN_INACTIVE_UI_SNAPSHOT")) {
|
||||||
|
window->set_window_active(false);
|
||||||
|
window->show();
|
||||||
|
const bool snapshot_written =
|
||||||
|
WriteWindowSnapshot(window->window(), snapshot_path);
|
||||||
|
window->hide();
|
||||||
|
window->set_window_active(true);
|
||||||
|
if (!snapshot_written) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
window->set_settings_open(true);
|
window->set_settings_open(true);
|
||||||
window->set_about_open(true);
|
window->set_about_open(true);
|
||||||
assert(window->get_settings_open());
|
assert(window->get_settings_open());
|
||||||
@@ -62,7 +128,37 @@ int main() {
|
|||||||
stream->set_tabs(
|
stream->set_tabs(
|
||||||
std::make_shared<slint::VectorModel<crossdesk::ui::StreamTab>>(
|
std::make_shared<slint::VectorModel<crossdesk::ui::StreamTab>>(
|
||||||
std::vector{tab}));
|
std::vector{tab}));
|
||||||
|
stream->set_custom_titlebar(true);
|
||||||
|
stream->set_window_maximized(true);
|
||||||
assert(stream->get_tabs()->row_count() == 1);
|
assert(stream->get_tabs()->row_count() == 1);
|
||||||
|
assert(stream->get_custom_titlebar());
|
||||||
|
assert(stream->get_window_maximized());
|
||||||
|
if (const char *snapshot_path =
|
||||||
|
std::getenv("CROSSDESK_STREAM_UI_SNAPSHOT")) {
|
||||||
|
stream->set_window_maximized(false);
|
||||||
|
stream->show();
|
||||||
|
const bool snapshot_written =
|
||||||
|
WriteWindowSnapshot(stream->window(), snapshot_path);
|
||||||
|
stream->hide();
|
||||||
|
stream->set_window_maximized(true);
|
||||||
|
if (!snapshot_written) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (const char *snapshot_path =
|
||||||
|
std::getenv("CROSSDESK_STREAM_INACTIVE_UI_SNAPSHOT")) {
|
||||||
|
stream->set_window_maximized(false);
|
||||||
|
stream->set_window_active(false);
|
||||||
|
stream->show();
|
||||||
|
const bool snapshot_written =
|
||||||
|
WriteWindowSnapshot(stream->window(), snapshot_path);
|
||||||
|
stream->hide();
|
||||||
|
stream->set_window_active(true);
|
||||||
|
stream->set_window_maximized(true);
|
||||||
|
if (!snapshot_written) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool tab_reordered = false;
|
bool tab_reordered = false;
|
||||||
stream->on_reorder_tab([&](int index, float x, float width) {
|
stream->on_reorder_tab([&](int index, float x, float width) {
|
||||||
@@ -71,6 +167,12 @@ int main() {
|
|||||||
stream->invoke_reorder_tab(0, 120.0f, 110.0f);
|
stream->invoke_reorder_tab(0, 120.0f, 110.0f);
|
||||||
assert(tab_reordered);
|
assert(tab_reordered);
|
||||||
|
|
||||||
|
bool maximize_requested = false;
|
||||||
|
stream->on_toggle_maximize_stream_window(
|
||||||
|
[&] { maximize_requested = true; });
|
||||||
|
stream->invoke_toggle_maximize_stream_window();
|
||||||
|
assert(maximize_requested);
|
||||||
|
|
||||||
crossdesk::ui::FileTransferEntry transfer;
|
crossdesk::ui::FileTransferEntry transfer;
|
||||||
transfer.name = "archive.zip";
|
transfer.name = "archive.zip";
|
||||||
transfer.status = "Sending";
|
transfer.status = "Sending";
|
||||||
|
|||||||
Vendored
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package("rust")
|
||||||
|
set_kind("toolchain")
|
||||||
|
set_homepage("https://rust-lang.org")
|
||||||
|
set_description("Rust is a general-purpose programming language emphasizing performance, type safety, and concurrency.")
|
||||||
|
|
||||||
|
add_versions("1.92.0", "")
|
||||||
|
|
||||||
|
add_deps("ca-certificates", {host = true, private = true})
|
||||||
|
add_deps("rustup", {host = true, private = true, system = false})
|
||||||
|
|
||||||
|
on_install(function(package)
|
||||||
|
import("core.tools.rustc.target_triple")
|
||||||
|
|
||||||
|
local host_target = assert(target_triple(package:plat(), package:arch()),
|
||||||
|
"failed to determine the Rust host target triple")
|
||||||
|
local version = package:version():shortstr()
|
||||||
|
local toolchain_name = version .. "-" .. host_target
|
||||||
|
local rustup_home = assert(os.getenv("RUSTUP_HOME"), "cannot find rustup home")
|
||||||
|
|
||||||
|
os.vrunv("rustup", {"install", "--no-self-update", toolchain_name})
|
||||||
|
os.vmv(path.join(rustup_home, "toolchains", toolchain_name, "*"), package:installdir())
|
||||||
|
|
||||||
|
-- Keep the Xmake-managed toolchain isolated from rustup after installation.
|
||||||
|
os.vrm(path.join(rustup_home, "toolchains", toolchain_name))
|
||||||
|
os.vrm(path.join(rustup_home, "update-hashes", toolchain_name))
|
||||||
|
|
||||||
|
package:addenv("RC", "bin/rustc" .. (is_host("windows") and ".exe" or ""))
|
||||||
|
package:mark_as_pathenv("RC")
|
||||||
|
end)
|
||||||
|
|
||||||
|
on_test(function(package)
|
||||||
|
os.vrun("cargo --version")
|
||||||
|
os.vrun("rustc --version")
|
||||||
|
end)
|
||||||
|
package_end()
|
||||||
@@ -10,17 +10,21 @@ package("slint")
|
|||||||
add_configs("shared", {description = "Build the Slint runtime as a shared library", default = true, type = "boolean", readonly = true})
|
add_configs("shared", {description = "Build the Slint runtime as a shared library", default = true, type = "boolean", readonly = true})
|
||||||
|
|
||||||
add_deps("cmake")
|
add_deps("cmake")
|
||||||
|
add_deps("rust 1.92.0", {host = true, private = true, system = false})
|
||||||
|
|
||||||
on_load(function(package)
|
on_load(function(package)
|
||||||
package:add("includedirs", "include/slint")
|
package:add("includedirs", "include/slint")
|
||||||
package:add("links", "slint_cpp")
|
if package:is_plat("windows") then
|
||||||
|
package:add("links", "slint_cpp.dll")
|
||||||
|
else
|
||||||
|
package:add("links", "slint_cpp")
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
on_install("windows", "linux", "macosx", function(package)
|
on_install("windows", "linux", "macosx", function(package)
|
||||||
local configs = {
|
local configs = {
|
||||||
"-DSLINT_BUILD_TESTING=OFF",
|
"-DSLINT_BUILD_TESTING=OFF",
|
||||||
"-DSLINT_BUILD_EXAMPLES=OFF",
|
"-DSLINT_BUILD_EXAMPLES=OFF",
|
||||||
"-DSLINT_COMPILER=download",
|
|
||||||
"-DSLINT_FEATURE_INTERPRETER=OFF",
|
"-DSLINT_FEATURE_INTERPRETER=OFF",
|
||||||
"-DSLINT_FEATURE_LIVE_PREVIEW=OFF",
|
"-DSLINT_FEATURE_LIVE_PREVIEW=OFF",
|
||||||
"-DSLINT_FEATURE_TESTING=OFF",
|
"-DSLINT_FEATURE_TESTING=OFF",
|
||||||
@@ -36,23 +40,6 @@ package("slint")
|
|||||||
"-DBUILD_SHARED_LIBS=ON"
|
"-DBUILD_SHARED_LIBS=ON"
|
||||||
}
|
}
|
||||||
import("package.tools.cmake").install(package, configs)
|
import("package.tools.cmake").install(package, configs)
|
||||||
|
|
||||||
-- SLINT_COMPILER=download only configures downstream CMake projects.
|
|
||||||
-- xmake's native Slint rule needs the matching host executable here.
|
|
||||||
import("net.http")
|
|
||||||
import("utils.archive")
|
|
||||||
local host_system = is_host("windows") and "windows" or (is_host("macosx") and "Darwin" or "Linux")
|
|
||||||
local host_arch = os.arch()
|
|
||||||
if host_arch == "x64" then
|
|
||||||
host_arch = "x86_64"
|
|
||||||
end
|
|
||||||
local suffix = host_system .. "-" .. host_arch
|
|
||||||
local archive_name = "slint-compiler-" .. suffix .. ".tar.gz"
|
|
||||||
local archive_file = path.join(package:builddir(), archive_name)
|
|
||||||
local download_url = "https://github.com/slint-ui/slint/releases/download/v" .. package:version_str() .. "/" .. archive_name
|
|
||||||
http.download(download_url, archive_file)
|
|
||||||
os.mkdir(path.join(package:installdir(), "bin"))
|
|
||||||
archive.extract(archive_file, package:installdir())
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
on_test(function(package)
|
on_test(function(package)
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
includes("cpp-httplib")
|
includes("cpp-httplib", "rust", "slint")
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
set_project("crossdesk")
|
set_project("crossdesk")
|
||||||
set_license("GPL-3.0-only")
|
set_license("GPL-3.0-only")
|
||||||
|
|
||||||
add_repositories("crossdesk-packages " .. path.join(os.scriptdir(), "xmake", "repository"))
|
|
||||||
|
|
||||||
includes("xmake/options.lua")
|
includes("xmake/options.lua")
|
||||||
includes("xmake/platform.lua")
|
includes("xmake/platform.lua")
|
||||||
includes("xmake/rules/slint.lua")
|
includes("xmake/rules/slint.lua")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ end
|
|||||||
function setup_platform_settings()
|
function setup_platform_settings()
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
add_requires("libyuv", "miniaudio 0.11.21")
|
add_requires("libyuv", "miniaudio 0.11.21")
|
||||||
|
add_defines("NOMINMAX")
|
||||||
add_links("Shell32", "dwmapi", "User32", "kernel32",
|
add_links("Shell32", "dwmapi", "User32", "kernel32",
|
||||||
"gdi32", "winmm", "setupapi", "version",
|
"gdi32", "winmm", "setupapi", "version",
|
||||||
"Imm32", "iphlpapi", "d3d11", "dxgi")
|
"Imm32", "iphlpapi", "d3d11", "dxgi")
|
||||||
|
|||||||
@@ -3,6 +3,22 @@ function setup_targets()
|
|||||||
|
|
||||||
includes("submodules", "thirdparty")
|
includes("submodules", "thirdparty")
|
||||||
|
|
||||||
|
local function copy_slint_runtime(target)
|
||||||
|
if not target:is_plat("windows") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local slint = target:pkg("slint")
|
||||||
|
if not slint and target:dep("gui") then
|
||||||
|
slint = target:dep("gui"):pkg("slint")
|
||||||
|
end
|
||||||
|
assert(slint, "the Slint package is required to copy its Windows runtime")
|
||||||
|
local runtime_dir = path.join(slint:installdir(), "lib")
|
||||||
|
local runtime_dll = path.join(runtime_dir, "slint_cpp.dll")
|
||||||
|
assert(os.isfile(runtime_dll), "Slint runtime not found: " .. runtime_dll)
|
||||||
|
os.cp(runtime_dll, target:targetdir())
|
||||||
|
end
|
||||||
|
|
||||||
local crossdesk_windows_resource = "scripts/windows/crossdesk.rc"
|
local crossdesk_windows_resource = "scripts/windows/crossdesk.rc"
|
||||||
if is_config("CROSSDESK_PORTABLE", true) then
|
if is_config("CROSSDESK_PORTABLE", true) then
|
||||||
crossdesk_windows_resource = "scripts/windows/crossdesk_portable.rc"
|
crossdesk_windows_resource = "scripts/windows/crossdesk_portable.rc"
|
||||||
@@ -87,10 +103,15 @@ function setup_targets()
|
|||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
set_languages("c++20")
|
set_languages("c++20")
|
||||||
set_default(false)
|
set_default(false)
|
||||||
|
if is_os("windows") then
|
||||||
|
add_cxxflags("/bigobj")
|
||||||
|
end
|
||||||
add_packages("slint")
|
add_packages("slint")
|
||||||
|
add_includedirs("src/gui/assets/fonts")
|
||||||
add_rules("slint")
|
add_rules("slint")
|
||||||
add_files("src/gui/ui/crossdesk_ui.slint")
|
add_files("src/gui/ui/crossdesk_ui.slint")
|
||||||
add_files("tests/slint_ui_smoke_test.cpp")
|
add_files("tests/slint_ui_smoke_test.cpp")
|
||||||
|
after_build(copy_slint_runtime)
|
||||||
|
|
||||||
target("version_checker_test")
|
target("version_checker_test")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
@@ -240,6 +261,7 @@ function setup_targets()
|
|||||||
"src/gui/features/settings/*.cpp", "src/gui/ui/crossdesk_ui.slint")
|
"src/gui/features/settings/*.cpp", "src/gui/ui/crossdesk_ui.slint")
|
||||||
add_includedirs("src/gui", {public = true})
|
add_includedirs("src/gui", {public = true})
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
|
add_cxxflags("/bigobj")
|
||||||
add_files("src/gui/platform/tray/win_tray.cpp")
|
add_files("src/gui/platform/tray/win_tray.cpp")
|
||||||
add_includedirs("src/service/windows", {public = true})
|
add_includedirs("src/service/windows", {public = true})
|
||||||
elseif is_os("macosx") then
|
elseif is_os("macosx") then
|
||||||
@@ -295,4 +317,5 @@ function setup_targets()
|
|||||||
add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper")
|
add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper")
|
||||||
add_files(crossdesk_windows_resource)
|
add_files(crossdesk_windows_resource)
|
||||||
end
|
end
|
||||||
|
after_build(copy_slint_runtime)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user