Compare commits
380 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34fb3fda92 | |||
| 1a34c6bc1d | |||
| ab74361fd3 | |||
| db26753eca | |||
| fb00234498 | |||
| 38c88e27e0 | |||
| df6f4321e8 | |||
| bc3dd680f9 | |||
| ded00d5c19 | |||
| 50fbc9cadf | |||
| d40ca8814c | |||
| f48d940b80 | |||
| 9972047199 | |||
| dee2d4f5ca | |||
| 772db42494 | |||
| a36b352039 | |||
| c69818bb1d | |||
| f361347795 | |||
| e399260daa | |||
| f9717f8481 | |||
| 794d57eb40 | |||
| 506ded3027 | |||
| 0d9d3c5eca | |||
| 0f07058fb9 | |||
| 28dc9bf62d | |||
| dff7948245 | |||
| bcf01791f7 | |||
| 160ee9feef | |||
| d17b29dfa4 | |||
| 1d41b6499f | |||
| d79bcdf1e5 | |||
| 7def8a9568 | |||
| b50175f943 | |||
| 1cd9ea1b0e | |||
| 257581e5e9 | |||
| 2d59c74669 | |||
| fa667df1e0 | |||
| b0306d510c | |||
| 5c598be51d | |||
| bd65d87137 | |||
| d2b45b91e7 | |||
| 23df1f3b60 | |||
| 2bf60a9c81 | |||
| 092e894622 | |||
| 7f2ebbde5a | |||
| 8b4ff8cd1f | |||
| de0386f08a | |||
| 19fc8cda89 | |||
| 706beaa2ce | |||
| 84ba2d8339 | |||
| 02f00642e9 | |||
| 0dbc0236bf | |||
| cf374a0ff3 | |||
| ebfeaf4754 | |||
| fba5993866 | |||
| 3bb12e3f60 | |||
| 962d856946 | |||
| b7a5066c6b | |||
| ee70280056 | |||
| d1177747fd | |||
| a39d0f6652 | |||
| f797cc3f91 | |||
| 899ab05cda | |||
| cbf9ccc284 | |||
| 4aa9925e56 | |||
| 71b9c78dd5 | |||
| 1ef7c536f1 | |||
| 7b4bba4166 | |||
| 1db57bfc76 | |||
| 36704c7e4c | |||
| 7a8be01e7b | |||
| 59275af1ee | |||
| 0595112d4f | |||
| 2c48ce12c5 | |||
| 1f3c93c77a | |||
| 61ac3a9971 | |||
| 8d7068aa32 | |||
| 316a0220a8 | |||
| 3accdf2192 | |||
| 1d85247785 | |||
| dbb31f8298 | |||
| 60d5885b8b | |||
| 794e33c325 | |||
| 2d5749f93a | |||
| 7b839ab773 | |||
| cd349cd98d | |||
| ea592f5a58 | |||
| 477fd1f13b | |||
| 5bbd182a3f | |||
| 6e2a52e506 | |||
| a8e9609736 | |||
| ba268016e4 | |||
| 63ed77e43a | |||
| 49b74ffcd6 | |||
| de212a8e75 | |||
| 7a84b25b5c | |||
| 601fedfd76 | |||
| 0737eee95a | |||
| eef35ff0d4 | |||
| 7ddcca53e4 | |||
| 2512e1eb15 | |||
| c6d4b172fc | |||
| bacf62c6b8 | |||
| b29f3de868 | |||
| 2cb92ddd72 | |||
| 348df6a4bf | |||
| 6e3c8c488d | |||
| 49d6307154 | |||
| b0fdcd9193 | |||
| daef6f19dc | |||
| 11d84068a4 | |||
| 21737c354a | |||
| 1b7c8905b7 | |||
| 826fc2d312 | |||
| 7e3856a68d | |||
| 8d56a76844 | |||
| 09c0ab9235 | |||
| 98c6501846 | |||
| 38fabc9741 | |||
| c5c85f0785 | |||
| 9d2e6f0c2a | |||
| 35d4f522c5 | |||
| 30c167e6cc | |||
| b10e41ccab | |||
| 6e622b4ab2 | |||
| 9a6def32fd | |||
| c13cffb58e | |||
| 6bda59b1a7 | |||
| 1407f67d3c | |||
| 7388a2c288 | |||
| 2740f31642 | |||
| bbd60570a1 | |||
| aa1bc1a936 | |||
| c7b934026b | |||
| 5eb455b6c8 | |||
| f48e5a7350 | |||
| 8083d4b4c9 | |||
| 9d2e122fcc | |||
| f4cf4d826b | |||
| e48b29a2c8 | |||
| 0e3da6daf8 | |||
| a0bccfe53d | |||
| c67ce332f6 | |||
| bbd05bcb8d | |||
| a8333c622b | |||
| 2f16d22ab7 | |||
| 255ef0edec | |||
| c477643aed | |||
| c0c2b18b8b | |||
| d285d7971a | |||
| d78dc4585f | |||
| fd392922d7 | |||
| 98bd477af5 | |||
| 0b11646619 | |||
| 0b0e61cdc4 | |||
| 35f26283de | |||
| 6ae12771c2 | |||
| 949aa804e7 | |||
| 5536311920 | |||
| e9be021c0f | |||
| 4eac29b6de | |||
| 61b57dd3fe | |||
| d6599abf81 | |||
| c056bb6f7d | |||
| 5f8e60d1c8 | |||
| e2e053a285 | |||
| 4624d4f27f | |||
| 9e30203e90 | |||
| f02286365c | |||
| 1626b482de | |||
| 10cb335779 | |||
| 911f209fda | |||
| a308094fbd | |||
| dd8ab05d0f | |||
| 8dc96eeb4c | |||
| 3453d4e0c4 | |||
| 0edeec3d16 | |||
| 66fc4d3f95 | |||
| 54179722e5 | |||
| af49ebe63d | |||
| 15419cc313 | |||
| e73f9b3457 | |||
| 792a286899 | |||
| 28fb35faf5 | |||
| c575a9170c | |||
| eee3b2a95e | |||
| caba77765d | |||
| 5e804349d9 | |||
| 460d644d64 | |||
| 0adaf357ec | |||
| 2c640db255 | |||
| 9122d0d15f | |||
| 3ea4d0724d | |||
| 5d6861233e | |||
| 28f9d9cfd6 | |||
| 5deb52ce2d | |||
| a20927ec6c | |||
| 3fc8f9f616 | |||
| 5decc4b007 | |||
| fdb8819926 | |||
| c95a2a32dc | |||
| 57ff14ada4 | |||
| 6df90ff55a | |||
| 1439751ae3 | |||
| 83861a8ba0 | |||
| c8d21794f5 | |||
| 6bc8aaabdc | |||
| e0d2ab5a9f | |||
| 114c80cd72 | |||
| 95da7ff52d | |||
| 182c7dbec6 | |||
| 35857488dc | |||
| 8d22d1855c | |||
| 76465a95c2 | |||
| 5deaacab51 | |||
| dfc72b5ccc | |||
| f5586a7922 | |||
| a309627ca3 | |||
| d6cd6a8099 | |||
| 82dafa9782 | |||
| 0b80124b3c | |||
| d4abc318a4 | |||
| 2b0a3938bc | |||
| 6a6e922fc1 | |||
| 936348ba9e | |||
| 62f85bb333 | |||
| 25c345f675 | |||
| 13ea652278 | |||
| 052d479004 | |||
| f99a9bea13 | |||
| 3eef1f8d84 | |||
| 079ceddae7 | |||
| 3a1be00ca5 | |||
| d79720532d | |||
| c8d404ea7d | |||
| e44c5b1cc7 | |||
| 19506af831 | |||
| b68d6820bb | |||
| 256f3611ba | |||
| 2a9760c5e5 | |||
| 0b90feed79 | |||
| bd2722f408 | |||
| 4ec26a1fd7 | |||
| 53a5f35fcd | |||
| 9bb5dae126 | |||
| d901508adf | |||
| b0bee226d1 | |||
| 31b5d7f2e3 | |||
| 5c0a6646c8 | |||
| fd667c8b86 | |||
| e410e4e0c4 | |||
| 270ad8df43 | |||
| 2040db4eec | |||
| 6d89a2aa35 | |||
| f833a503ae | |||
| 54529960d4 | |||
| a4681de246 | |||
| 325ec69f5f | |||
| 7409351b66 | |||
| 007134838b | |||
| 27bfcb7621 | |||
| 99360f5da2 | |||
| e456ae3577 | |||
| 325f626fb5 | |||
| 5ae756bf7f | |||
| 6e19e53603 | |||
| 9be5d90a69 | |||
| 821d0cd68b | |||
| 7d2c3dda4e | |||
| a79fc87c63 | |||
| 64e0b9767b | |||
| 344bddb863 | |||
| 888e43b7d0 | |||
| 75e6e93069 | |||
| 5e11e6057a | |||
| df42aaf225 | |||
| 18b2a4a175 | |||
| ca4f379d3b | |||
| 027780160e | |||
| a1940c9cf7 | |||
| 5b46218f9b | |||
| 48b1d025fc | |||
| 34274001dc | |||
| 4261dff416 | |||
| 4a9cc19b23 | |||
| 907daa581f | |||
| a25b7f0aad | |||
| 24b94b8ffc | |||
| 682fa0ff17 | |||
| 683235fa3f | |||
| aba06f3289 | |||
| 4efaccd5e4 | |||
| 63db76b99c | |||
| 78c2766730 | |||
| 766248be61 | |||
| 9f0653211e | |||
| 50c6a24e26 | |||
| 4e06d111fa | |||
| 95d2d74979 | |||
| 85582f8339 | |||
| d42f4305b5 | |||
| 8cc04796b1 | |||
| 7de31fd78f | |||
| 40f3d19af6 | |||
| 8f14230a04 | |||
| e2e2fb53ae | |||
| bf4f5ca87d | |||
| 5c9b20a112 | |||
| 678b38049e | |||
| 0a7dd291f1 | |||
| 36305eef46 | |||
| 0e1da712f5 | |||
| 2299aee315 | |||
| aa198a4629 | |||
| 9355b670b8 | |||
| 156172accb | |||
| b4318cc8d6 | |||
| a794cd43b9 | |||
| 3d4e1effe9 | |||
| b16b29780b | |||
| ebd7d87e91 | |||
| 44f787b360 | |||
| caf46a33bf | |||
| df686461a5 | |||
| a0abb7455c | |||
| e2533d18e4 | |||
| ea74495b5a | |||
| c1d31790d4 | |||
| 8c545f7544 | |||
| 3a291fe171 | |||
| a2d7bb7ff5 | |||
| f52142fc00 | |||
| 0899fe2f1d | |||
| 79c838629a | |||
| dc11f50d82 | |||
| ab71838483 | |||
| b2e725e564 | |||
| 58c73f10c6 | |||
| 952bb02df5 | |||
| cea0cfdb95 | |||
| 213318bfa3 | |||
| 3389dc5751 | |||
| daf7caf5bb | |||
| 6989d58e47 | |||
| d20df60472 | |||
| af285f4b5b | |||
| 3c1f7973d0 | |||
| a4cd77dcb0 | |||
| 1bef5a1cab | |||
| dcd5273cf6 | |||
| b9b836119f | |||
| ed8c81540a | |||
| 8f803cbd4c | |||
| 69ee2ed5d5 | |||
| 36f4367c79 | |||
| 28560cbac3 | |||
| dc84a0becf | |||
| 1c0d80fa3a | |||
| e31405c78b | |||
| 2e28ad61f8 | |||
| 932944ad86 | |||
| 5a38aabb55 | |||
| d5c1c26fc9 | |||
| 515f0c06bd | |||
| 3a55dd0938 | |||
| a9db3d290b | |||
| c46396a18a | |||
| 481f6af9e7 | |||
| ff8e1ed34d | |||
| 7ad8375ddc | |||
| 5792ded4ac | |||
| fbfaf9ac75 | |||
| 5362d9591e | |||
| 03cb767f1f | |||
| ea9ffbd6d8 | |||
| 8276dd6c20 | |||
| efc1e3bd85 | |||
| 1c064a1cff | |||
| 8cd87a2646 | |||
| ef6a04dc97 |
@@ -1,20 +0,0 @@
|
||||
**
|
||||
|
||||
!.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/**
|
||||
@@ -1,3 +0,0 @@
|
||||
*.h linguist-language=C++
|
||||
*.cpp linguist-language=C++
|
||||
*.lua linguist-language=Xmake
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: 问题反馈
|
||||
about: 请在此提交问题报告,以便持续优化产品。
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: kunkundi
|
||||
|
||||
---
|
||||
|
||||
**描述问题**
|
||||
清晰简洁地描述遇到的错误。
|
||||
|
||||
**复现步骤**
|
||||
复现该问题的步骤:
|
||||
1. 前往 '...'
|
||||
2. 点击 '....'
|
||||
3. 出现错误
|
||||
|
||||
**预期行为**
|
||||
清晰简洁地描述你期望发生的行为。
|
||||
|
||||
**截图**
|
||||
如果适用,请添加截图以帮助说明问题。
|
||||
|
||||
**桌面端信息(请填写以下内容):**
|
||||
- 操作系统: [例如 Windows 11]
|
||||
- 版本: [例如 v1.1.10]
|
||||
|
||||
**移动端信息(请填写以下内容):**
|
||||
- 设备: [例如 iPhone 17]
|
||||
- 操作系统: [例如 iOS 26.1]
|
||||
- 浏览器: [例如 系统浏览器、Safari]
|
||||
|
||||
**补充信息**
|
||||
在此添加与问题相关的其他上下文内容。
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: 需求建议
|
||||
about: 请在此提交功能需求或改进建议,以便后续迭代参考。
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: kunkundi
|
||||
|
||||
---
|
||||
|
||||
**功能/改进建议描述**
|
||||
清晰简洁地描述希望新增的功能或改进的内容。
|
||||
|
||||
**使用场景 / 背景**
|
||||
说明该功能或改进的使用场景,以及解决后带来的价值。
|
||||
|
||||
**预期效果**
|
||||
描述你认为最理想的功能表现或改进效果。
|
||||
|
||||
**参考示例(可选)**
|
||||
提供类似功能截图、参考链接或其他说明,帮助更好理解需求。
|
||||
|
||||
**优先级(可选)**
|
||||
- [ ] 高
|
||||
- [ ] 中
|
||||
- [ ] 低
|
||||
|
||||
**补充信息(可选)**
|
||||
其他相关信息或特殊要求。
|
||||
@@ -1,155 +0,0 @@
|
||||
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}"
|
||||
@@ -1,565 +0,0 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
- "!ci/linux-build-image"
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
patch:
|
||||
description: "Hotfix patch number, for example 1 or 2. Use 0 for a normal build."
|
||||
required: false
|
||||
default: "0"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
name: Build Linux (${{ matrix.arch }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
runner: ubuntu-24.04
|
||||
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
|
||||
package_script: ./scripts/linux/pkg_amd64.sh
|
||||
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
|
||||
package_script: ./scripts/linux/pkg_arm64.sh
|
||||
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
options: --user root
|
||||
|
||||
steps:
|
||||
- name: Extract version number
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION_REF="${GITHUB_REF##*/}"
|
||||
VERSION_BASE="${VERSION_REF#v}"
|
||||
PATCH_NUMBER="${{ github.event.inputs.patch }}"
|
||||
BUILD_DATE_OVERRIDE=""
|
||||
|
||||
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||
PATCH_NUMBER=0
|
||||
fi
|
||||
|
||||
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||
BUILD_DATE_OVERRIDE="${BASH_REMATCH[4]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||
fi
|
||||
|
||||
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_ENV
|
||||
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE_OVERRIDE=${BUILD_DATE_OVERRIDE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set legal Debian version
|
||||
shell: bash
|
||||
run: |
|
||||
BUILD_DATE="${BUILD_DATE_OVERRIDE}"
|
||||
if [[ -z "${BUILD_DATE}" ]]; then
|
||||
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
|
||||
fi
|
||||
|
||||
if [[ ! "${VERSION_BASE}" =~ ^[0-9] ]]; then
|
||||
VERSION_BASE="0.0.0-${VERSION_BASE}"
|
||||
fi
|
||||
|
||||
if [[ "${PATCH_NUMBER}" != "0" ]]; then
|
||||
LEGAL_VERSION="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
|
||||
else
|
||||
LEGAL_VERSION="v${VERSION_BASE}-${BUILD_DATE}"
|
||||
fi
|
||||
|
||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Verify Linux build image
|
||||
run: verify-crossdesk-build-image
|
||||
|
||||
- name: Build CrossDesk
|
||||
env:
|
||||
CUDA_PATH: /usr/local/cuda
|
||||
XMAKE_GLOBALDIR: /data
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y
|
||||
xmake b -vy --root crossdesk
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
chmod +x ${{ matrix.package_script }}
|
||||
${{ 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
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}
|
||||
path: ${{ github.workspace }}/crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}.deb
|
||||
|
||||
# macOS
|
||||
build-macos:
|
||||
name: Build on macOS
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
runner: macos-15-intel
|
||||
cache-key: intel
|
||||
out-dir: ./build/macosx/x86_64/release/crossdesk
|
||||
package_script: ./scripts/macosx/pkg_x64.sh
|
||||
- arch: arm64
|
||||
runner: macos-14
|
||||
cache-key: arm
|
||||
out-dir: ./build/macosx/arm64/release/crossdesk
|
||||
package_script: ./scripts/macosx/pkg_arm64.sh
|
||||
|
||||
steps:
|
||||
- name: Extract version number
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION_REF="${GITHUB_REF##*/}"
|
||||
VERSION_BASE="${VERSION_REF#v}"
|
||||
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
|
||||
PATCH_NUMBER="${{ github.event.inputs.patch }}"
|
||||
|
||||
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||
PATCH_NUMBER=0
|
||||
fi
|
||||
|
||||
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||
BUILD_DATE="${BASH_REMATCH[4]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||
fi
|
||||
|
||||
if [[ "${PATCH_NUMBER}" != "0" ]]; then
|
||||
VERSION_NUM="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
|
||||
else
|
||||
VERSION_NUM="v${VERSION_BASE}-${BUILD_DATE}"
|
||||
fi
|
||||
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||
echo "VERSION_NUM=${VERSION_NUM}"
|
||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
|
||||
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache xmake dependencies
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.xmake/packages
|
||||
key: "${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ github.run_id }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
|
||||
|
||||
- name: Install xmake
|
||||
run: brew install xmake
|
||||
|
||||
- name: Update xmake repositories
|
||||
run: xmake repo -u
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Initialize submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Build CrossDesk
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y
|
||||
xmake b -vy crossdesk
|
||||
|
||||
- name: Package CrossDesk app
|
||||
run: |
|
||||
chmod +x ${{ matrix.package_script }}
|
||||
${{ matrix.package_script }} ${VERSION_NUM}
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}
|
||||
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
|
||||
|
||||
- name: Move files to release dir
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/
|
||||
|
||||
# Windows
|
||||
build-windows-x64:
|
||||
name: Build on Windows x64
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
XMAKE_GLOBALDIR: D:\xmake_global
|
||||
steps:
|
||||
- name: Extract version number
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ref = $env:GITHUB_REF
|
||||
$version = $ref -replace '^refs/(tags|heads)/', ''
|
||||
$version = $version -replace '^v', ''
|
||||
$version = $version -replace '/', '-'
|
||||
$BUILD_DATE = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "China Standard Time")).ToString("yyyyMMdd")
|
||||
$PATCH_NUMBER = "${{ github.event.inputs.patch }}"
|
||||
|
||||
if ($PATCH_NUMBER -notmatch '^[0-9]+$') {
|
||||
$PATCH_NUMBER = "0"
|
||||
}
|
||||
|
||||
if ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$') {
|
||||
$version = $Matches[1]
|
||||
$PATCH_NUMBER = $Matches[3]
|
||||
$BUILD_DATE = $Matches[4]
|
||||
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$') {
|
||||
$version = $Matches[1]
|
||||
$BUILD_DATE = $Matches[3]
|
||||
$PATCH_NUMBER = $Matches[4]
|
||||
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$') {
|
||||
$version = $Matches[1]
|
||||
$BUILD_DATE = $Matches[3]
|
||||
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$' -and $PATCH_NUMBER -eq "0") {
|
||||
$version = $Matches[1]
|
||||
$PATCH_NUMBER = $Matches[3]
|
||||
}
|
||||
|
||||
if ($PATCH_NUMBER -ne "0") {
|
||||
$VERSION_NUM = "v$version-$PATCH_NUMBER-$BUILD_DATE"
|
||||
} else {
|
||||
$VERSION_NUM = "v$version-$BUILD_DATE"
|
||||
}
|
||||
|
||||
echo "VERSION_NUM=$VERSION_NUM" >> $env:GITHUB_ENV
|
||||
echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV
|
||||
echo "PATCH_NUMBER=$PATCH_NUMBER" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Cache xmake dependencies
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: D:\xmake_global\.xmake\packages
|
||||
key: "${{ runner.os }}-xmake-deps-intel-${{ github.run_id }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-xmake-deps-intel-
|
||||
|
||||
- name: Install xmake
|
||||
run: |
|
||||
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
|
||||
xmake repo -u
|
||||
xmake create cuda
|
||||
Set-Location cuda
|
||||
xmake g --theme=plain
|
||||
$cudaPath = ""
|
||||
$packagesPath = "D:\xmake_global\.xmake\packages"
|
||||
|
||||
if (Test-Path $packagesPath) {
|
||||
Write-Host "Packages directory exists: $packagesPath"
|
||||
try {
|
||||
$info = xmake require --info "cuda 12.6.3" 2>$null
|
||||
if ($null -ne $info -and $info -ne "") {
|
||||
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
|
||||
}
|
||||
} catch {}
|
||||
} else {
|
||||
Write-Host "Packages directory not found: $packagesPath"
|
||||
Write-Host "Installing CUDA package..."
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
$info = xmake require --info "cuda 12.6.3"
|
||||
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
|
||||
}
|
||||
|
||||
echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV
|
||||
Write-Host "Resolved CUDA_PATH = $cudaPath"
|
||||
Pop-Location
|
||||
|
||||
- name: Download INetC plugin
|
||||
shell: powershell
|
||||
run: |
|
||||
$url = "https://github.com/DigitalMediaServer/NSIS-INetC-plugin/releases/download/v1.0.5.7/InetC.zip"
|
||||
$out = "$env:RUNNER_TEMP\InetC.zip"
|
||||
Invoke-WebRequest -Uri $url -OutFile $out
|
||||
Expand-Archive $out -DestinationPath "$env:RUNNER_TEMP\InetC" -Force
|
||||
|
||||
$source = "$env:RUNNER_TEMP\InetC\x86-unicode\INetC.dll"
|
||||
$target = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\INetC.dll"
|
||||
|
||||
Write-Host "Copying $source to $target"
|
||||
Copy-Item $source $target -Force
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Initialize submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Copy nsProcess plugin to NSIS folder
|
||||
run: |
|
||||
$nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode"
|
||||
copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir
|
||||
|
||||
- name: Build CrossDesk
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true -y
|
||||
xmake b -vy crossdesk
|
||||
|
||||
- name: Package
|
||||
shell: pwsh
|
||||
run: |
|
||||
& "${{ github.workspace }}\scripts\windows\pkg_x64.ps1" $env:VERSION_NUM
|
||||
|
||||
- name: Build Portable CrossDesk
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true --CROSSDESK_PORTABLE=true -y
|
||||
xmake b -vy crossdesk
|
||||
|
||||
- name: Package Portable
|
||||
shell: pwsh
|
||||
run: |
|
||||
$buildDir = "${{ github.workspace }}\build\windows\x64\release"
|
||||
$portableDir = "${{ github.workspace }}\portable"
|
||||
New-Item -ItemType Directory -Force -Path $portableDir
|
||||
|
||||
$portableFiles = @(
|
||||
@("crossdesk.exe", "CrossDesk.exe"),
|
||||
@("crossdesk_service.exe", "crossdesk_service.exe"),
|
||||
@("crossdesk_session_helper.exe", "crossdesk_session_helper.exe")
|
||||
)
|
||||
|
||||
foreach ($file in $portableFiles) {
|
||||
$source = Join-Path $buildDir $file[0]
|
||||
$destination = Join-Path $portableDir $file[1]
|
||||
if (!(Test-Path $source)) {
|
||||
throw "Missing portable package file: $source"
|
||||
}
|
||||
Copy-Item $source $destination -Force
|
||||
}
|
||||
|
||||
Copy-Item (Join-Path $buildDir "*.dll") $portableDir -Force
|
||||
|
||||
foreach ($file in $portableFiles) {
|
||||
$packagedFile = Join-Path $portableDir $file[1]
|
||||
if (!(Test-Path $packagedFile)) {
|
||||
throw "Portable package is missing: $packagedFile"
|
||||
}
|
||||
}
|
||||
|
||||
Compress-Archive -Path "$portableDir\*" -DestinationPath "${{ github.workspace }}\crossdesk-win-x64-portable-${{ env.VERSION_NUM }}.zip"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: crossdesk-win-x64-${{ env.VERSION_NUM }}
|
||||
path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe
|
||||
|
||||
- name: Upload portable artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: crossdesk-win-x64-portable-${{ env.VERSION_NUM }}
|
||||
path: ${{ github.workspace }}/crossdesk-win-x64-portable-${{ env.VERSION_NUM }}.zip
|
||||
|
||||
release:
|
||||
name: Publish Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs:
|
||||
[build-linux, build-macos, build-windows-x64]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Extract version number
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION_REF="${GITHUB_REF##*/}"
|
||||
VERSION_BASE="${VERSION_REF#v}"
|
||||
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
|
||||
PATCH_NUMBER="${{ github.event.inputs.patch }}"
|
||||
|
||||
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||
PATCH_NUMBER=0
|
||||
fi
|
||||
|
||||
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||
BUILD_DATE="${BASH_REMATCH[4]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
BUILD_DATE="${BASH_REMATCH[3]}"
|
||||
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||
fi
|
||||
|
||||
BUILD_DATE_ISO="${BUILD_DATE:0:4}-${BUILD_DATE:4:2}-${BUILD_DATE:6:2}"
|
||||
if [[ "${PATCH_NUMBER}" != "0" ]]; then
|
||||
VERSION_NUM="${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
|
||||
else
|
||||
VERSION_NUM="${VERSION_BASE}-${BUILD_DATE}"
|
||||
fi
|
||||
|
||||
VERSION_WITH_V="v${VERSION_NUM}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
|
||||
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Rename artifacts
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
|
||||
cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
|
||||
cp artifacts/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb
|
||||
cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb
|
||||
cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe
|
||||
cp artifacts/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip
|
||||
|
||||
- name: List release files
|
||||
run: ls -lh release/
|
||||
|
||||
- name: Upload to Versioned GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.VERSION_WITH_V }}
|
||||
name: Release ${{ steps.version.outputs.VERSION_WITH_V }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: release/*
|
||||
generate_release_notes: false
|
||||
body: |
|
||||
Binary release only. Source code is not included.
|
||||
|
||||
- name: Create or update 'latest' tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -f latest
|
||||
git push origin latest --force
|
||||
|
||||
- name: Upload to GitHub Release (latest)
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: latest
|
||||
name: Latest Release
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: release/*
|
||||
generate_release_notes: false
|
||||
|
||||
- name: Upload artifacts to server
|
||||
uses: burnett01/rsync-deployments@5.2
|
||||
with:
|
||||
switches: -avzr --progress --delete
|
||||
path: release/*
|
||||
remote_path: /var/www/html/downloads/
|
||||
remote_host: ${{ secrets.SERVER_HOST }}
|
||||
remote_user: ${{ secrets.SERVER_USER }}
|
||||
remote_key: ${{ secrets.SERVER_KEY }}
|
||||
|
||||
- name: Generate version.json
|
||||
run: |
|
||||
cat > version.json << EOF
|
||||
{
|
||||
"latest_version": "${{ steps.version.outputs.VERSION_NUM }}",
|
||||
"version": "${{ steps.version.outputs.VERSION_NUM }}",
|
||||
"releaseDate": "${{ steps.version.outputs.BUILD_DATE_ISO }}",
|
||||
"patch": ${{ steps.version.outputs.PATCH_NUMBER }},
|
||||
"releaseName": "",
|
||||
"releaseNotes": "",
|
||||
"tagName": "${{ steps.version.outputs.VERSION_WITH_V }}",
|
||||
"downloads": {
|
||||
"windows-x64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe",
|
||||
"filename": "crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe"
|
||||
},
|
||||
"windows-x64-portable": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip",
|
||||
"filename": "crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip"
|
||||
},
|
||||
"macos-x64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg",
|
||||
"filename": "crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg"
|
||||
},
|
||||
"macos-arm64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg",
|
||||
"filename": "crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg"
|
||||
},
|
||||
"linux-amd64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb",
|
||||
"filename": "crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb",
|
||||
"filename": "crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
cat version.json
|
||||
|
||||
- name: Upload version.json to server
|
||||
uses: burnett01/rsync-deployments@5.2
|
||||
with:
|
||||
switches: -avzr --delete
|
||||
path: version.json
|
||||
remote_path: /var/www/html/version/
|
||||
remote_host: ${{ secrets.SERVER_HOST }}
|
||||
remote_user: ${{ secrets.SERVER_USER }}
|
||||
remote_key: ${{ secrets.SERVER_KEY }}
|
||||
@@ -1,69 +0,0 @@
|
||||
name: Close Inactive Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run every day at midnight
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
close_inactive_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check inactive issues and close them
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
script: |
|
||||
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||
const now = Date.now();
|
||||
|
||||
// paginate through all open issues (listForRepo also returns PRs)
|
||||
const issues = await github.paginate(github.rest.issues.listForRepo, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
});
|
||||
|
||||
for (const issue of issues) {
|
||||
// skip pull requests (they are also returned by listForRepo)
|
||||
if (issue.pull_request) continue;
|
||||
|
||||
// skip labeled issues
|
||||
if (issue.labels.length > 0) {
|
||||
console.log(`Skipping issue #${issue.number} (Has labels).`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// last activity time = the issue's own updated_at, which is
|
||||
// refreshed on comments, labels, etc. This avoids relying on
|
||||
// fetching comments and is accurate even when comments are edited.
|
||||
const lastActivityTime = new Date(issue.updated_at).getTime();
|
||||
|
||||
// check inactivity
|
||||
if (now - lastActivityTime > inactivePeriod) {
|
||||
console.log(`Closing inactive issue: #${issue.number} (No activity for 7 days)`);
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: "This issue has been automatically closed due to inactivity for 7 days."
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: 'closed',
|
||||
});
|
||||
} else {
|
||||
console.log(`Skipping issue #${issue.number} (Active within 7 days).`);
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
name: Update version.json from Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-version-json:
|
||||
name: Update version.json with release information
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
TAG_NAME="${{ github.event.release.tag_name }}"
|
||||
TAG_VERSION="${TAG_NAME#v}"
|
||||
VERSION_FULL="${TAG_VERSION}"
|
||||
VERSION_BASE="${TAG_VERSION}"
|
||||
PATCH_NUMBER=0
|
||||
|
||||
# Extract date and patch from tags such as v1.2.3-1-20251113.
|
||||
if [[ "${TAG_VERSION}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[3]}"
|
||||
DATE_STR="${BASH_REMATCH[4]}"
|
||||
BUILD_DATE_ISO="${DATE_STR:0:4}-${DATE_STR:4:2}-${DATE_STR:6:2}"
|
||||
elif [[ "${TAG_VERSION}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
DATE_STR="${BASH_REMATCH[3]}"
|
||||
PATCH_NUMBER="${BASH_REMATCH[4]}"
|
||||
BUILD_DATE_ISO="${DATE_STR:0:4}-${DATE_STR:4:2}-${DATE_STR:6:2}"
|
||||
elif [[ "${TAG_VERSION}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
|
||||
VERSION_BASE="${BASH_REMATCH[1]}"
|
||||
DATE_STR="${BASH_REMATCH[3]}"
|
||||
BUILD_DATE_ISO="${DATE_STR:0:4}-${DATE_STR:4:2}-${DATE_STR:6:2}"
|
||||
else
|
||||
# Use release published date
|
||||
BUILD_DATE_ISO=$(echo "${{ github.event.release.published_at }}" | cut -d'T' -f1)
|
||||
fi
|
||||
|
||||
echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION_FULL=${VERSION_FULL}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
|
||||
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install jq
|
||||
run: sudo apt-get update && sudo apt-get install -y jq
|
||||
|
||||
- name: Get release information
|
||||
id: release_info
|
||||
run: |
|
||||
# Use jq to properly escape JSON
|
||||
RELEASE_BODY="${{ github.event.release.body }}"
|
||||
RELEASE_NAME="${{ github.event.release.name }}"
|
||||
|
||||
# Handle empty values
|
||||
if [ -z "$RELEASE_BODY" ]; then
|
||||
RELEASE_BODY=""
|
||||
fi
|
||||
if [ -z "$RELEASE_NAME" ]; then
|
||||
RELEASE_NAME=""
|
||||
fi
|
||||
|
||||
# Save to temporary files for proper handling
|
||||
echo -n "$RELEASE_BODY" > /tmp/release_body.txt
|
||||
echo -n "$RELEASE_NAME" > /tmp/release_name.txt
|
||||
|
||||
# Use jq to escape JSON strings
|
||||
RELEASE_BODY_JSON=$(jq -Rs . < /tmp/release_body.txt)
|
||||
RELEASE_NAME_JSON=$(jq -Rs . < /tmp/release_name.txt)
|
||||
|
||||
echo "RELEASE_BODY=${RELEASE_BODY_JSON}" >> $GITHUB_OUTPUT
|
||||
echo "RELEASE_NAME=${RELEASE_NAME_JSON}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download current version.json from server
|
||||
id: download_version
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# Try to download current version.json from server
|
||||
curl -f -s "https://version.crossdesk.cn/version.json" -o version.json || echo "Failed to download, will create new one"
|
||||
|
||||
- name: Generate or update version.json
|
||||
run: |
|
||||
# If version.json exists, try to preserve downloads section
|
||||
if [ -f version.json ] && jq -e '.downloads' version.json > /dev/null 2>&1; then
|
||||
EXISTING_DOWNLOADS=$(jq -c '.downloads' version.json)
|
||||
if [ "$EXISTING_DOWNLOADS" != "null" ] && [ "$EXISTING_DOWNLOADS" != "{}" ]; then
|
||||
DOWNLOADS_JSON="$EXISTING_DOWNLOADS"
|
||||
else
|
||||
DOWNLOADS_JSON=""
|
||||
fi
|
||||
else
|
||||
DOWNLOADS_JSON=""
|
||||
fi
|
||||
|
||||
# If downloads is empty, use default structure
|
||||
if [ -z "$DOWNLOADS_JSON" ]; then
|
||||
DOWNLOADS_JSON=$(cat << DOWNLOADS_EOF
|
||||
{
|
||||
"windows-x64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-${{ steps.version.outputs.TAG_NAME }}.exe",
|
||||
"filename": "crossdesk-win-x64-${{ steps.version.outputs.TAG_NAME }}.exe"
|
||||
},
|
||||
"windows-x64-portable": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-portable-${{ steps.version.outputs.TAG_NAME }}.zip",
|
||||
"filename": "crossdesk-win-x64-portable-${{ steps.version.outputs.TAG_NAME }}.zip"
|
||||
},
|
||||
"macos-x64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-macos-x64-${{ steps.version.outputs.TAG_NAME }}.pkg",
|
||||
"filename": "crossdesk-macos-x64-${{ steps.version.outputs.TAG_NAME }}.pkg"
|
||||
},
|
||||
"macos-arm64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-macos-arm64-${{ steps.version.outputs.TAG_NAME }}.pkg",
|
||||
"filename": "crossdesk-macos-arm64-${{ steps.version.outputs.TAG_NAME }}.pkg"
|
||||
},
|
||||
"linux-amd64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-linux-amd64-${{ steps.version.outputs.TAG_NAME }}.deb",
|
||||
"filename": "crossdesk-linux-amd64-${{ steps.version.outputs.TAG_NAME }}.deb"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"url": "https://downloads.crossdesk.cn/crossdesk-linux-arm64-${{ steps.version.outputs.TAG_NAME }}.deb",
|
||||
"filename": "crossdesk-linux-arm64-${{ steps.version.outputs.TAG_NAME }}.deb"
|
||||
}
|
||||
}
|
||||
DOWNLOADS_EOF
|
||||
)
|
||||
fi
|
||||
|
||||
# Generate version.json using cat and heredoc
|
||||
cat > version.json << EOF
|
||||
{
|
||||
"latest_version": "${{ steps.version.outputs.VERSION_FULL }}",
|
||||
"version": "${{ steps.version.outputs.VERSION_FULL }}",
|
||||
"releaseDate": "${{ steps.version.outputs.BUILD_DATE_ISO }}",
|
||||
"patch": ${{ steps.version.outputs.PATCH_NUMBER }},
|
||||
"releaseName": ${{ steps.release_info.outputs.RELEASE_NAME }},
|
||||
"releaseNotes": ${{ steps.release_info.outputs.RELEASE_BODY }},
|
||||
"tagName": "${{ steps.version.outputs.TAG_NAME }}",
|
||||
"downloads": ${DOWNLOADS_JSON}
|
||||
}
|
||||
EOF
|
||||
|
||||
cat version.json
|
||||
|
||||
- name: Upload version.json to server
|
||||
uses: burnett01/rsync-deployments@5.2
|
||||
with:
|
||||
switches: -avzr --delete
|
||||
path: version.json
|
||||
remote_path: /var/www/html/version/
|
||||
remote_host: ${{ secrets.SERVER_HOST }}
|
||||
remote_user: ${{ secrets.SERVER_USER }}
|
||||
remote_key: ${{ secrets.SERVER_KEY }}
|
||||
@@ -1,9 +1,12 @@
|
||||
# Xmake cache
|
||||
.xmake/
|
||||
build/
|
||||
thirdparty/ffmpeg/lib/*
|
||||
.VSCodeCounter/
|
||||
|
||||
# MacOS Cache
|
||||
.DS_Store
|
||||
|
||||
# VSCode cache
|
||||
.vscode
|
||||
.vscode
|
||||
projectx.code-workspace
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "submodules/minirtc"]
|
||||
path = submodules/minirtc
|
||||
url = https://github.com/kunkundi/minirtc.git
|
||||
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -1,335 +1,34 @@
|
||||
# CrossDesk
|
||||
# projectx
|
||||
|
||||
<a href="https://hellogithub.com/repository/kunkundi/crossdesk" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=55d41367570345f1838e02fd12be7961&claim_uid=cb0OpZRrBuGVAfL&theme=small" alt="Featured|HelloGitHub" /></a>
|
||||
[]()
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/web-client)
|
||||
[](https://github.com/kunkundi/crossdesk/actions)
|
||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
vcpkg/buildtrees/versioning_/versions/pcre/69e232f12c4e3eab4115f0672466a6661978bea2$ vim portfile.cmake
|
||||
|
||||
[ [English](README_EN.md) / 中文 ]
|
||||
- URLS "https://ftp.pcre.org/pub/pcre/pcre-${PCRE_VERSION}.zip"
|
||||
+ URLS "https://sourceforge.net/projects/pcre/files/pcre/${PCRE_VERSION}/pcre-${PCRE_VERSION}.zip"
|
||||
|
||||
PC 客户端
|
||||

|
||||
linux
|
||||
|
||||
Web 客户端
|
||||
<p align="center">
|
||||
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
|
||||
</p>
|
||||
sudo apt-get install nvidia-cuda-toolkit
|
||||
solve <cuda.h>
|
||||
|
||||
## 简介
|
||||
sudo apt-get install libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev
|
||||
solve x11
|
||||
|
||||
CrossDesk 是一个轻量级的跨平台远程桌面软件,支持 Web 端控制远程设备。
|
||||
sudo apt-get -y install libasound2-dev libsndio-dev libxcb-shm0-dev
|
||||
solve asound sndio xcb-shm
|
||||
|
||||
CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频传输库的实验性应用。MiniRTC 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)),视频软硬编解码(H264/AV1),音频编解码([Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制,传输加密([SRTP](https://tools.ietf.org/html/rfc3711))等基础能力。
|
||||
sudo apt-get -y install libasound2-dev libpulse-dev && rebuild
|
||||
solve error dsp no such audio device
|
||||
|
||||
## 系统要求
|
||||
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libavdevice-dev
|
||||
|
||||
| 平台 | 最低版本 |
|
||||
|----------------|---------------------------|
|
||||
| **Windows** | Windows 10 及以上 (64 位) |
|
||||
| **macOS** | macOS Intel 15.0 及以上 ( 大于 14.0 小于 15.0 的版本可自行编译实现兼容 )<br> macOS Apple Silicon 14.0 及以上 |
|
||||
| **Linux** | Ubuntu 22.04 及以上 ( 低版本可自行编译实现兼容 ) |
|
||||
sudo apt remove libssl-dev libglib2.0-dev
|
||||
|
||||
## 使用
|
||||
|
||||
在菜单栏“对端ID”处输入远端桌面的ID,点击“→”即可发起远程连接。
|
||||
|
||||

|
||||
|
||||
如果远端桌面设置了连接密码,则本端需填写正确的连接密码才能成功发起远程连接。
|
||||
|
||||

|
||||
|
||||
发起连接前,可在设置中自定义配置项,如语言、视频编码格式等。
|
||||

|
||||
|
||||
### Web 客户端
|
||||
浏览器访问 [CrossDesk Web Client](https://web.crossdesk.cn/)。
|
||||
输入 **远程设备 ID** 与 **密码**,点击连接即可接入远程设备。如图,**iOS Safari 远程控制 Win11**:
|
||||
|
||||
<img width="645" height="300" alt="_cgi-bin_mmwebwx-bin_webwxgetmsgimg__ MsgID=932911462648581698 skey=@crypt_1f5153b1_b550ca7462b5009ce03c991cca2a92a7 mmweb_appid=wx_webfilehelper" src="https://github.com/user-attachments/assets/a5109e6f-752c-4654-9f4e-7e161bddf43e" />
|
||||
|
||||
### Windows 服务(CrossDesk Service)
|
||||
CrossDesk 在 Windows 平台提供本地辅助服务 **CrossDesk Service**,服务名为 `CrossDeskService`。该服务用于锁屏、登录界面和安全桌面等受保护场景下的远程控制增强能力,包括:
|
||||
- 上报远端当前是否处于锁屏、登录、凭据输入或安全桌面状态;
|
||||
- 支持从控制端发送 `Ctrl+Alt+Del`(SAS);
|
||||
- 在锁屏、登录和安全桌面阶段转发键盘、鼠标输入。
|
||||
|
||||
Windows 安装包会自动打包 `crossdesk_service.exe` 和 `crossdesk_session_helper.exe`,并在安装时注册为按需启动的 Windows 服务。CrossDesk 客户端启动时会尝试启动已安装的服务;当本机没有 CrossDesk 客户端进程运行时,服务会自动退出。卸载客户端时会同步停止并移除该服务。
|
||||
|
||||
如果是手动编译或手动部署 Windows 版本,请确保 `CrossDesk.exe`、`crossdesk_service.exe` 和 `crossdesk_session_helper.exe` 位于同一目录。安装或卸载服务需要使用管理员权限打开 PowerShell:
|
||||
```
|
||||
# 安装
|
||||
.\CrossDesk.exe --service-install
|
||||
# 启动
|
||||
.\CrossDesk.exe --service-start
|
||||
# 查看状态
|
||||
.\CrossDesk.exe --service-status
|
||||
.\CrossDesk.exe --service-stop
|
||||
# 卸载
|
||||
.\CrossDesk.exe --service-uninstall
|
||||
```
|
||||
|
||||
如果远端 Windows 服务未安装、未启动或暂时不可用,基础远程桌面连接仍可使用,但锁屏、登录界面和安全桌面阶段的控制能力会受限,客户端会提示“远端Windows服务不可用”。
|
||||
|
||||
## 如何编译
|
||||
|
||||
依赖:
|
||||
- [xmake](https://xmake.io/#/guide/installation)
|
||||
- [cmake](https://cmake.org/download/)
|
||||
|
||||
### Linux
|
||||
|
||||
Linux 构建目前支持 Ubuntu 22.04 及以上版本的 amd64 和 arm64。先安装基础编译依赖:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
git curl unzip build-essential cmake pkg-config binutils dpkg-dev \
|
||||
libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \
|
||||
libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \
|
||||
libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \
|
||||
libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev \
|
||||
libasound2-dev libsndio-dev libpulse-dev
|
||||
```
|
||||
|
||||
需要启用 Wayland 捕获或 DRM 捕获时,再安装对应依赖:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y libdbus-1-dev libpipewire-0.3-dev libdrm-dev
|
||||
```
|
||||
|
||||
下载子模块并编译 Release 版本:
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/kunkundi/crossdesk.git
|
||||
cd crossdesk
|
||||
|
||||
# 已经克隆过仓库时执行这一行
|
||||
git submodule update --init --recursive
|
||||
|
||||
xmake f -m release --USE_CUDA=false -y
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
amd64 构建结果位于 `build/linux/x86_64/release/crossdesk`,arm64 构建结果位于 `build/linux/arm64/release/crossdesk`。
|
||||
|
||||
启用 Wayland 和 DRM 的配置示例:
|
||||
|
||||
```bash
|
||||
xmake f -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false -y
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
生成 Debian 安装包(必须在对应架构上先完成 Release 编译):
|
||||
|
||||
```bash
|
||||
# amd64
|
||||
./scripts/linux/pkg_amd64.sh 1.0.0
|
||||
|
||||
# arm64
|
||||
./scripts/linux/pkg_arm64.sh 1.0.0
|
||||
```
|
||||
|
||||
打包脚本会将 Slint 共享运行库安装到软件包私有目录 `/usr/lib/crossdesk`,无需用户另外安装 `libslint_cpp.so`。
|
||||
|
||||
### 通用编译选项
|
||||
|
||||
```text
|
||||
--USE_CUDA=true/false: 启用 CUDA 硬件编解码,默认不启用
|
||||
--USE_WAYLAND=true/false: 在 Linux 上启用 Wayland/PipeWire 捕获,默认不启用
|
||||
--USE_DRM=true/false: 在 Linux 上启用 DRM 捕获,默认不启用
|
||||
--CROSSDESK_PORTABLE=true/false: 构建便携版本,默认不启用
|
||||
--CROSSDESK_VERSION=xxx: 指定 CrossDesk 的版本
|
||||
|
||||
# 示例
|
||||
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
|
||||
```
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
xmake r crossdesk
|
||||
```
|
||||
|
||||
### 无 CUDA 环境下的开发支持
|
||||
|
||||
对于**未安装 CUDA 环境的 Linux 开发者,如果希望编译后的成果物拥有硬件编解码能力**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
|
||||
|
||||
进入容器,下载工程后执行:
|
||||
```
|
||||
export CUDA_PATH=/usr/local/cuda
|
||||
export XMAKE_GLOBALDIR=/data
|
||||
|
||||
xmake f --USE_CUDA=true
|
||||
xmake b --root -vy crossdesk
|
||||
```
|
||||
|
||||
对于**未安装 CUDA 环境的 Windows 开发者**,执行下面的命令安装 CUDA 编译环境:
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
安装完成后执行:
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
输出如下:
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入:
|
||||
```
|
||||
set CUDA_PATH=path_to_cuda_installdir
|
||||
```
|
||||
重新执行:
|
||||
```
|
||||
xmake f --USE_CUDA=true
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### 注意
|
||||
运行时如果客户端状态栏显示 **未连接服务器**,请先在 [CrossDesk 官方网站](https://www.crossdesk.cn/) 安装客户端,以便在环境中安装所需的证书文件。
|
||||
|
||||
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
|
||||
|
||||
## 关于 Xmake
|
||||
|
||||
#### 安装 Xmake
|
||||
使用 curl:
|
||||
```
|
||||
curl -fsSL https://xmake.io/shget.text | bash
|
||||
```
|
||||
使用 wget:
|
||||
```
|
||||
wget https://xmake.io/shget.text -O - | bash
|
||||
```
|
||||
使用 powershell:
|
||||
```
|
||||
irm https://xmake.io/psget.text | iex
|
||||
```
|
||||
|
||||
#### 编译选项
|
||||
```
|
||||
# 切换编译模式
|
||||
xmake f -m debug/release
|
||||
|
||||
# 可选编译参数
|
||||
-r :重新构建目标
|
||||
-v :显示详细的构建日志
|
||||
-y :自动确认提示
|
||||
|
||||
# 示例
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### 运行选项
|
||||
```
|
||||
# 使用调试模式运行
|
||||
xmake r -d crossdesk
|
||||
```
|
||||
更多使用方法可参考 [Xmake官方文档](https://xmake.io/guide/quick-start.html) 。
|
||||
|
||||
## 自托管服务器
|
||||
推荐使用Docker部署CrossDesk Server。
|
||||
```bash
|
||||
sudo docker run -d \
|
||||
--name crossdesk_server \
|
||||
--network host \
|
||||
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e CROSSDESK_SERVER_PORT=xxxx \
|
||||
-e COTURN_PORT=xxxx \
|
||||
-e MIN_PORT=xxxxx \
|
||||
-e MAX_PORT=xxxxx \
|
||||
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||
crossdesk/crossdesk-server:v1.1.6
|
||||
```
|
||||
|
||||
上述命令中,用户需注意的参数如下:
|
||||
|
||||
**参数**
|
||||
- EXTERNAL_IP:服务器公网 IP , 对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器地址**
|
||||
- INTERNAL_IP:服务器内网 IP
|
||||
- CROSSDESK_SERVER_PORT:自托管服务使用的端口,对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口**
|
||||
- COTURN_PORT: COTURN 服务使用的端口, 对应 CrossDesk 客户端**自托管服务器配置**中填写的**中继服务端口**
|
||||
- MIN_PORT/MAX_PORT:COTURN 服务使用的端口范围,例如:MIN_PORT=50000, MAX_PORT=60000,范围可根据客户端数量调整。
|
||||
- `-v /var/lib/crossdesk:/var/lib/crossdesk`:持久化数据库和证书文件到宿主机
|
||||
- `-v /var/log/crossdesk:/var/log/crossdesk`:持久化日志文件到宿主机
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
sudo docker run -d \
|
||||
--name crossdesk_server \
|
||||
--network host \
|
||||
-e EXTERNAL_IP=114.114.114.114 \
|
||||
-e INTERNAL_IP=10.0.0.1 \
|
||||
-e CROSSDESK_SERVER_PORT=9099 \
|
||||
-e COTURN_PORT=3478 \
|
||||
-e MIN_PORT=50000 \
|
||||
-e MAX_PORT=60000 \
|
||||
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||
crossdesk/crossdesk-server:v1.1.6
|
||||
```
|
||||
|
||||
**注意**:
|
||||
- **服务器需开放端口:COTURN_PORT/udp,COTURN_PORT/tcp,MIN_PORT-MAX_PORT/udp,CROSSDESK_SERVER_PORT/tcp。**
|
||||
- 如果不挂载 volume,容器删除后数据会丢失
|
||||
- 证书文件会在首次启动时自动生成并持久化到宿主机的 `/var/lib/crossdesk/certs` 路径下。由于默认使用的是自签证书,无法保障安全性,建议在云服务商申请正式证书放到该目录下并重启服务。
|
||||
- 数据库文件会自动创建并持久化到宿主机的 `/var/lib/crossdesk/db/crossdesk-server.db` 路径下
|
||||
- 日志文件会自动创建并持久化到宿主机的 `/var/log/crossdesk/` 路径下
|
||||
|
||||
**权限注意**:如果 Docker 自动创建的目录权限不足(属于 root),容器内用户无法写入,会导致:
|
||||
- 证书生成失败,容器启动脚本会报错退出
|
||||
- 数据库目录创建失败,程序会抛出异常并崩溃
|
||||
- 日志目录创建失败,日志文件无法写入(但程序可能继续运行)
|
||||
|
||||
**解决方案**:在启动容器前手动设置权限:
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/crossdesk /var/log/crossdesk
|
||||
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
|
||||
```
|
||||
|
||||
|
||||
### 客户端
|
||||
1. 点击右上角设置进入设置页面。<br><br>
|
||||
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br>
|
||||
|
||||
2. 点击`自托管服务器配置`按钮。<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br>
|
||||
|
||||
3. 输入`服务器地址`(**EXTERNAL_IP**)、`信令服务端口`(**CROSSDESK_SERVER_PORT**)、`中继服务端口`(**COTURN_PORT**),点击确认按钮。
|
||||
|
||||
4. 勾选`自托管服务器配置`选项,点击确认按钮保存设置。如果服务端使用的是正式证书,则到此步骤为止,客户端即可显示已连接服务器。
|
||||
|
||||
5. 如果使用默认证书(正式证书忽略此步骤),则需要将服务端`/var/lib/crossdesk/certs/`目录下的`api.crossdesk.cn_root.crt`自签根证书下载到运行客户端的机器,并执行下述命令安装证书:
|
||||
|
||||
Windows 平台使用**管理员权限**打开 PowerShell 执行
|
||||
```
|
||||
certutil -addstore "Root" "C:\path\to\api.crossdesk.cn_root.crt"
|
||||
```
|
||||
Linux
|
||||
```
|
||||
sudo cp /path/to/api.crossdesk.cn_root.crt /usr/local/share/ca-certificates/api.crossdesk.cn_root.crt
|
||||
sudo update-ca-certificates
|
||||
```
|
||||
macOS
|
||||
```
|
||||
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain path/to/api.crossdesk.cn_root.crt
|
||||
```
|
||||
|
||||
### Web 客户端
|
||||
详情见项目 [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
|
||||
|
||||
# 常见问题
|
||||
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。
|
||||
|
||||
# 致谢
|
||||
- 感谢 [HelloGitHub](https://hellogithub.com/) 的推荐与关注。
|
||||
- 感谢 [阮一峰的科技爱好者周刊](https://github.com/ruanyf/weekly) 的收录与推荐。
|
||||
- 感谢 [LinuxDo](https://linux.do) 社区的关注、交流与支持,为 CrossDesk 项目的完善提供了帮助。
|
||||
install:
|
||||
@echo hello world
|
||||
install -D build/linux/x86_64/release/remote_desk -t /usr/bin
|
||||
install -D config/config.ini -t /usr/bin
|
||||
install -D build/linux/x86_64/release/libprojectx.so -t /usr/lib
|
||||
install -D thirdparty/nvcodec/Lib/x64/libnvidia-encode.so.1 -t /usr/lib
|
||||
install -D thirdparty/nvcodec/Lib/x64/libnvidia-encode.so -t /usr/lib
|
||||
install -D thirdparty/nvcodec/Lib/x64/libnvcuvid.so.1 -t /usr/lib
|
||||
install -D thirdparty/nvcodec/Lib/x64/libnvcuvid.so -t /usr/lib
|
||||
@@ -1,349 +0,0 @@
|
||||
# CrossDesk
|
||||
|
||||
<a href="https://hellogithub.com/repository/kunkundi/crossdesk" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=55d41367570345f1838e02fd12be7961&claim_uid=cb0OpZRrBuGVAfL&theme=small" alt="Featured|HelloGitHub" /></a>
|
||||
[]()
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/web-client)
|
||||
[](https://github.com/kunkundi/crossdesk/actions)
|
||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
[ [中文](README.md) / English ]
|
||||
|
||||
PC Client
|
||||

|
||||
|
||||
Web Client
|
||||
<p align="center">
|
||||
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
|
||||
</p>
|
||||
|
||||
# Intro
|
||||
|
||||
CrossDesk is a lightweight cross-platform remote desktop software.
|
||||
|
||||
CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)).
|
||||
|
||||
## System Requirements
|
||||
|
||||
| Platform | Minimum Version |
|
||||
|-----------|-----------------|
|
||||
| **Windows** | Windows 10 or later (64-bit) |
|
||||
| **macOS** | macOS Intel 15.0 or later *(versions between 14.0 and 15.0 can be built manually for compatibility)*<br>macOS Apple Silicon 14.0 or later |
|
||||
| **Linux** | Ubuntu 22.04 or later *(older versions can be built manually for compatibility)* |
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Enter the remote desktop ID in the menu bar’s “Remote ID” field and click “→” to initiate a remote connection.
|
||||
|
||||

|
||||
|
||||
If the remote desktop requires a connection password, you must enter the correct password on your side to successfully establish the connection.
|
||||
|
||||

|
||||
|
||||
Before connecting, you can customize configuration options in the settings, such as language and video encoding format.
|
||||
|
||||

|
||||
|
||||
### Web Client
|
||||
|
||||
Visit [CrossDesk Web Client](https://web.crossdesk.cn/).
|
||||
Enter the **Remote Device ID** and **Password**, then click Connect to access the remote device. As shown, **iOS Safari remotely controlling Windows 11**:
|
||||
|
||||
<img width="645" height="300" alt="_cgi-bin_mmwebwx-bin_webwxgetmsgimg__ MsgID=932911462648581698 skey=@crypt_1f5153b1_b550ca7462b5009ce03c991cca2a92a7 mmweb_appid=wx_webfilehelper" src="https://github.com/user-attachments/assets/a5109e6f-752c-4654-9f4e-7e161bddf43e" />
|
||||
|
||||
### Windows Service (CrossDesk Service)
|
||||
CrossDesk provides a local helper service on Windows named **CrossDesk Service**. Its service name is `CrossDeskService`. The service improves remote control in protected Windows states such as the lock screen, sign-in UI, credential UI, and secure desktop. It provides:
|
||||
- Remote status reporting for lock screen, sign-in, credential, and secure desktop states.
|
||||
- Remote `Ctrl+Alt+Del` (SAS) delivery.
|
||||
- Keyboard and mouse input forwarding while the remote Windows device is on the lock screen, sign-in UI, or secure desktop.
|
||||
|
||||
The Windows installer bundles `crossdesk_service.exe` and `crossdesk_session_helper.exe`, then registers the service as an on-demand Windows service during installation. When the CrossDesk client starts, it tries to start the installed service automatically. When no CrossDesk client process is running on the machine, the service exits automatically. Uninstalling the client also stops and removes the service.
|
||||
|
||||
For manual Windows builds or deployments, make sure `CrossDesk.exe`, `crossdesk_service.exe`, and `crossdesk_session_helper.exe` are placed in the same directory. Open PowerShell with administrator privileges to install or uninstall the service:
|
||||
```
|
||||
# install
|
||||
.\CrossDesk.exe --service-install
|
||||
# start
|
||||
.\CrossDesk.exe --service-start
|
||||
# check status
|
||||
.\CrossDesk.exe --service-status
|
||||
.\CrossDesk.exe --service-ping
|
||||
# stop
|
||||
.\CrossDesk.exe --service-stop
|
||||
# uninstall
|
||||
.\CrossDesk.exe --service-uninstall
|
||||
```
|
||||
|
||||
If the remote Windows service is not installed, not running, or temporarily unavailable, the basic remote desktop connection still works, but remote control on the lock screen, sign-in UI, and secure desktop is limited. The client will show “Remote Windows service unavailable”.
|
||||
|
||||
## How to build
|
||||
|
||||
Requirements:
|
||||
- [xmake](https://xmake.io/#/guide/installation)
|
||||
- [cmake](https://cmake.org/download/)
|
||||
|
||||
### Linux
|
||||
|
||||
Linux builds currently support Ubuntu 22.04 or later on amd64 and arm64. Install the base build dependencies first:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
git curl unzip build-essential cmake pkg-config binutils dpkg-dev \
|
||||
libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \
|
||||
libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \
|
||||
libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \
|
||||
libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev \
|
||||
libasound2-dev libsndio-dev libpulse-dev
|
||||
```
|
||||
|
||||
Install the optional dependencies when Wayland capture or DRM capture is enabled:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y libdbus-1-dev libpipewire-0.3-dev libdrm-dev
|
||||
```
|
||||
|
||||
Clone the submodules and build a release binary:
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/kunkundi/crossdesk.git
|
||||
cd crossdesk
|
||||
|
||||
# Run this when the repository has already been cloned
|
||||
git submodule update --init --recursive
|
||||
|
||||
xmake f -m release --USE_CUDA=false -y
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
The amd64 binary is written to `build/linux/x86_64/release/crossdesk`; the arm64 binary is written to `build/linux/arm64/release/crossdesk`.
|
||||
|
||||
Example configuration with Wayland and DRM capture enabled:
|
||||
|
||||
```bash
|
||||
xmake f -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false -y
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
Build a Debian package after compiling a release binary on the matching architecture:
|
||||
|
||||
```bash
|
||||
# amd64
|
||||
./scripts/linux/pkg_amd64.sh 1.0.0
|
||||
|
||||
# arm64
|
||||
./scripts/linux/pkg_arm64.sh 1.0.0
|
||||
```
|
||||
|
||||
The package scripts install the shared Slint runtime in the private `/usr/lib/crossdesk` directory, so users do not need to install `libslint_cpp.so` separately.
|
||||
|
||||
### Common build options
|
||||
|
||||
```text
|
||||
--USE_CUDA=true/false: Enable CUDA hardware codec acceleration, disabled by default
|
||||
--USE_WAYLAND=true/false: Enable Wayland/PipeWire capture on Linux, disabled by default
|
||||
--USE_DRM=true/false: Enable DRM capture on Linux, disabled by default
|
||||
--CROSSDESK_PORTABLE=true/false: Build the portable variant, disabled by default
|
||||
--CROSSDESK_VERSION=xxx: Set the CrossDesk version
|
||||
|
||||
# Example
|
||||
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
xmake r crossdesk
|
||||
```
|
||||
|
||||
#### Development Without CUDA Environment
|
||||
|
||||
For **Linux developers who do not have a CUDA environment installed and want to enable hardware codec feature**, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
|
||||
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
|
||||
|
||||
After entering the container, download the project and run:
|
||||
```
|
||||
export CUDA_PATH=/usr/local/cuda
|
||||
export XMAKE_GLOBALDIR=/data
|
||||
|
||||
xmake f --USE_CUDA=true
|
||||
xmake b --root -vy crossdesk
|
||||
```
|
||||
|
||||
For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment:
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
After the installation is complete, execute:
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
The output will look like this:
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
From the output above, locate the CUDA installation directory — this is the path pointed to by installdir.
|
||||
Add this path to your system environment variable CUDA_PATH, or set it in the terminal using:
|
||||
```
|
||||
set CUDA_PATH=path_to_cuda_installdir:
|
||||
```
|
||||
Then re-run:
|
||||
```
|
||||
xmake f --USE_CUDA=true
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### Notice
|
||||
If the client status bar shows **Disconnected** during runtime, please first install the client from the [CrossDesk official website](https://www.crossdesk.cn/) to ensure the required certificate files are available in the environment.
|
||||
|
||||
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
|
||||
|
||||
## About Xmake
|
||||
#### Installing Xmake
|
||||
|
||||
You can install Xmake using one of the following methods:
|
||||
|
||||
Using curl:
|
||||
```
|
||||
curl -fsSL https://xmake.io/shget.text | bash
|
||||
```
|
||||
Using wget:
|
||||
```
|
||||
wget https://xmake.io/shget.text -O - | bash
|
||||
```
|
||||
Using powershell:
|
||||
```
|
||||
irm https://xmake.io/psget.text | iex
|
||||
```
|
||||
|
||||
#### Build Options
|
||||
```
|
||||
# Switch build mode
|
||||
xmake f -m debug/release
|
||||
|
||||
# Optional build parameters
|
||||
-r : Rebuild the target
|
||||
-v : Show detailed build logs
|
||||
-y : Automatically confirm prompts
|
||||
|
||||
# Example
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### Run Options
|
||||
```
|
||||
# Run in debug mode
|
||||
xmake r -d crossdesk
|
||||
```
|
||||
|
||||
For more information, please refer to the [official Xmake documentation](https://xmake.io/guide/quick-start.html) .
|
||||
|
||||
## Self-Hosted Server
|
||||
It is recommended to deploy CrossDesk Server using Docker.
|
||||
```
|
||||
sudo docker run -d \
|
||||
--name crossdesk_server \
|
||||
--network host \
|
||||
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e CROSSDESK_SERVER_PORT=xxxx \
|
||||
-e COTURN_PORT=xxxx \
|
||||
-e MIN_PORT=xxxxx \
|
||||
-e MAX_PORT=xxxxx \
|
||||
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||
crossdesk/crossdesk-server:v1.1.6
|
||||
```
|
||||
|
||||
The parameters you need to pay attention to are as follows:
|
||||
|
||||
**Parameters**
|
||||
- **EXTERNAL_IP**: The server’s public IP. This corresponds to **Server Address** in the CrossDesk client’s **Self-Hosted Server Configuration**.
|
||||
- **INTERNAL_IP**: The server’s internal IP.
|
||||
- **CROSSDESK_SERVER_PORT**: The port used by the self-hosted service. This corresponds to **Server Port** in the CrossDesk client’s **Self-Hosted Server Configuration**.
|
||||
- **COTURN_PORT**: The port used by the COTURN service. This corresponds to **Relay Service Port** in the CrossDesk client’s **Self-Hosted Server Configuration**.
|
||||
- **MIN_PORT / MAX_PORT**: The port range used by the COTURN service. Example: `MIN_PORT=50000`, `MAX_PORT=60000`. Adjust the range depending on the number of clients.
|
||||
- `-v /var/lib/crossdesk:/var/lib/crossdesk`: Persists database and certificate files on the host machine.
|
||||
- `-v /var/log/crossdesk:/var/log/crossdesk`: Persists log files on the host machine.
|
||||
|
||||
**Example**:
|
||||
```bash
|
||||
sudo docker run -d \
|
||||
--name crossdesk_server \
|
||||
--network host \
|
||||
-e EXTERNAL_IP=114.114.114.114 \
|
||||
-e INTERNAL_IP=10.0.0.1 \
|
||||
-e CROSSDESK_SERVER_PORT=9099 \
|
||||
-e COTURN_PORT=3478 \
|
||||
-e MIN_PORT=50000 \
|
||||
-e MAX_PORT=60000 \
|
||||
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||
crossdesk/crossdesk-server:v1.1.6
|
||||
```
|
||||
|
||||
**Notes**
|
||||
- **The server must open the following ports: COTURN_PORT/udp, COTURN_PORT/tcp, MIN_PORT–MAX_PORT/udp, and CROSSDESK_SERVER_PORT/tcp.**
|
||||
- If you don’t mount volumes, all data will be lost when the container is removed.
|
||||
- Certificate files will be automatically generated on first startup and persisted to the host at `/var/lib/crossdesk/certs`.As the default certificates are self-signed and cannot guarantee security, it is strongly recommended to apply for a trusted certificate from a cloud provider, deploy it to this directory, and restart the service.
|
||||
- The database file will be automatically created and stored at `/var/lib/crossdesk/db/crossdesk-server.db`.
|
||||
- Log files will be created and stored at `/var/log/crossdesk/`.
|
||||
|
||||
**Permission Notice**
|
||||
If the directories automatically created by Docker belong to root and have insufficient write permissions, the container user may not be able to write to them. This can cause:
|
||||
- Certificate generation failure, leading to startup script errors and container exit.
|
||||
- Database directory creation failure, causing the program to throw exceptions and crash.
|
||||
- Log directory creation failure, preventing logs from being written (though the program may continue running).
|
||||
|
||||
**Solution:** Manually set permissions before starting the container:
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/crossdesk /var/log/crossdesk
|
||||
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
|
||||
```
|
||||
|
||||
### Server Side
|
||||
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
|
||||
|
||||
### Client Side
|
||||
1. Click the settings icon in the top-right corner to enter the settings page.<br><br>
|
||||
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br>
|
||||
|
||||
2. Click `Self-Hosted Server Configuration` button.<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br>
|
||||
|
||||
3. Enter the `Server Address` (**EXTERNAL_IP**), `Signaling Service Port` (**CROSSDESK_SERVER_PORT**), and `Relay Service Port` (**COTURN_PORT**) and click OK button.
|
||||
|
||||
4. Check the `Self-hosted server configuration` option and click the OK button to save the settings. If the server is using a valid (official) certificate, the process ends here and the client will show that it is connected to the server.
|
||||
|
||||
5. If the default certificate is used (skip this step if an official certificate is used), download the self-signed root certificate `api.crossdesk.cn_root.crt` from the server directory /var/lib/crossdesk/certs/ to the machine running the client, and install the certificate by executing the following command:
|
||||
|
||||
On Windows, open PowerShell with **administrator privileges** and execute:
|
||||
```
|
||||
certutil -addstore "Root" "C:\path\to\api.crossdesk.cn_root.crt"
|
||||
```
|
||||
Linux
|
||||
```
|
||||
sudo cp /path/to/api.crossdesk.cn_root.crt /usr/local/share/ca-certificates/api.crossdesk.cn_root.crt
|
||||
sudo update-ca-certificates
|
||||
```
|
||||
macOS
|
||||
```
|
||||
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain path/to/api.crossdesk.cn_root.crt
|
||||
```
|
||||
|
||||
|
||||
### Web Client
|
||||
See [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
|
||||
|
||||
# FAQ
|
||||
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .
|
||||
|
||||
# Acknowledgements
|
||||
- Thanks to [HelloGitHub](https://hellogithub.com/) for the recommendation and exposure.
|
||||
- Thanks to [Ruanyf Weekly](https://github.com/ruanyf/weekly) for featuring CrossDesk.
|
||||
- Thanks to the [LinuxDo](https://linux.do) community for the attention, discussions, and support that helped improve CrossDesk.
|
||||
@@ -0,0 +1,19 @@
|
||||
[signal server]
|
||||
ip = 150.158.81.30
|
||||
port = 9099
|
||||
|
||||
[stun server]
|
||||
ip = 150.158.81.30
|
||||
port = 3478
|
||||
|
||||
[turn server]
|
||||
ip = 150.158.81.30
|
||||
port = 3478
|
||||
username = dijunkun
|
||||
password = dijunkunpw
|
||||
|
||||
[hardware acceleration]
|
||||
turn_on = false
|
||||
|
||||
[av1 encoding]
|
||||
turn_on = true
|
||||
@@ -1,153 +0,0 @@
|
||||
# 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"]
|
||||
@@ -1,41 +0,0 @@
|
||||
# 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。
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/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}"
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,33 +0,0 @@
|
||||
# 常见问题(FAQ)
|
||||
|
||||
欢迎来到 **CrossDesk 常见问题** 页面!
|
||||
这里整理了用户和开发者最常见的一些疑问。如果你没有找到答案,欢迎在 [Issues](https://github.com/kunkundi/crossdesk/issues) 中反馈。
|
||||
|
||||
---
|
||||
|
||||
### Q1. 对等连接失败
|
||||
**A:**
|
||||
打开设置,勾选 **启用中继服务** 选项,尝试重新发起连接。
|
||||
|
||||
<img width="396" height="306" alt="Image" src="https://github.com/user-attachments/assets/fd8db148-c782-4f4d-b874-8f1b2a7ec7d6" />
|
||||
|
||||
由于公共中继服务器带宽较小,连接的清晰度流畅度可能会下降,建议自建服务器。 [Issue #8](https://github.com/kunkundi/crossdesk/issues/8)
|
||||
|
||||
### Q2. Windows 无 CUDA 环境下编译
|
||||
**A:**
|
||||
运行下面的命令安装 CUDA 编译环境。
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
安装完成后执行
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
输出如下
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入 set CUDA_PATH=path_to_cuda_installdir,重新执行 xmake b -vy crossdesk 即可。
|
||||
[Issue #6](https://github.com/kunkundi/crossdesk/issues/6)
|
||||
|
||||
---
|
||||
@@ -1,207 +0,0 @@
|
||||
# GUI 目录结构与职责说明
|
||||
|
||||
本文档说明 `src/gui` 的目录结构、核心类型、依赖方向和代码归属规则,用于避免 `Render`、`GuiApplication` 或 `GuiRuntime` 再次演变成职责混杂的大类。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
src/gui/
|
||||
├── render.h # 对外稳定入口 Render
|
||||
├── render.cpp # 创建 GuiApplication 并转发 Run
|
||||
├── application/ # SDL/ImGui 应用外壳
|
||||
│ ├── gui_application.h # GuiApplication 声明
|
||||
│ ├── gui_application.cpp # 初始化、主循环和清理
|
||||
│ ├── application_state.h # 窗口、交互和 UI 状态
|
||||
│ ├── sdl_event_dispatch.cpp # SDL 窗口及应用事件分发
|
||||
│ ├── sdl_events.cpp # 键盘和鼠标事件转换
|
||||
│ ├── window_lifecycle.cpp # 原生窗口及 ImGui 上下文生命周期
|
||||
│ └── window_rendering.cpp # 三类窗口的渲染流程
|
||||
├── runtime/ # 非界面的 GUI 运行时
|
||||
│ ├── gui_runtime.h/.cpp # 运行时协调接口与公共实现
|
||||
│ ├── connection_runtime.cpp # 连接、在线探测、超时及会话清理
|
||||
│ ├── windows_service_runtime.cpp # Windows 服务和安全桌面集成
|
||||
│ ├── mac_permission_runtime.mm # macOS 权限检查和系统设置调用
|
||||
│ ├── gui_state.h # ApplicationState 与 RuntimeState 组合点
|
||||
│ ├── runtime_state.h # 配置、连接、平台和通信状态
|
||||
│ ├── remote_session.h # 单个远端会话 RemoteSession
|
||||
│ ├── device_presence_cache.h # 设备在线状态缓存
|
||||
│ ├── peer_event_handler.h/.cpp # 信令和连接状态回调
|
||||
│ ├── peer_media_callbacks.cpp # 视频和音频回调
|
||||
│ ├── peer_data_callbacks.cpp # 控制、剪贴板和文件数据回调
|
||||
│ └── remote_action_codec.h/.cpp # RemoteAction 编解码
|
||||
├── features/ # 可独立演进的功能模块
|
||||
│ ├── clipboard/ # 本地与远端剪贴板同步
|
||||
│ ├── devices/ # 媒体及输入设备生命周期
|
||||
│ ├── file_transfer/ # 文件队列、发送、确认和进度
|
||||
│ ├── input/ # 键盘状态、命令和超时处理
|
||||
│ └── settings/ # 配置缓存和最近连接别名
|
||||
├── views/ # ImGui 视图实现
|
||||
│ ├── panels/ # 主窗口内嵌面板
|
||||
│ ├── toolbars/ # 标题栏、状态栏和控制栏
|
||||
│ └── windows/ # 独立窗口和模态对话框
|
||||
├── platform/ # 原生桌面平台适配
|
||||
│ └── tray/ # Windows、macOS 和 Linux 托盘
|
||||
└── assets/ # 字体、图标、布局和本地化资源
|
||||
```
|
||||
|
||||
## 分层关系
|
||||
|
||||
```text
|
||||
Render
|
||||
└── GuiApplication
|
||||
├── SDL/ImGui 生命周期
|
||||
├── views/panels / views/toolbars / views/windows
|
||||
└── GuiRuntime
|
||||
├── SessionDeviceManager
|
||||
├── KeyboardController
|
||||
├── ClipboardController
|
||||
├── FileTransferManager
|
||||
├── SettingsManager
|
||||
└── PeerEventHandler
|
||||
```
|
||||
|
||||
依赖方向应保持从上向下:
|
||||
|
||||
```text
|
||||
公开入口 → 应用层 → 运行时协调层 → 功能模块 → 底层库
|
||||
↘ 视图层
|
||||
```
|
||||
|
||||
底层功能模块不应反向依赖具体面板、工具栏或窗口。
|
||||
|
||||
## 核心类型
|
||||
|
||||
### Render
|
||||
|
||||
`Render` 是应用其他部分可见的稳定入口,只负责:
|
||||
|
||||
- 管理 `GuiApplication` 生命周期;
|
||||
- 将 `Run()` 转发给 `GuiApplication`;
|
||||
- 隔离 GUI 内部类型,避免实现细节扩散到其他模块。
|
||||
|
||||
不要向 `Render` 添加窗口状态、连接状态或业务方法。
|
||||
|
||||
### GuiApplication
|
||||
|
||||
`GuiApplication` 是 SDL/ImGui 应用外壳,负责:
|
||||
|
||||
- 初始化日志、配置、SDL 和功能模块;
|
||||
- 创建、销毁原生窗口及 ImGui 上下文;
|
||||
- 执行主事件循环;
|
||||
- 分发 SDL 事件;
|
||||
- 调用面板、工具栏和窗口的绘制方法;
|
||||
- 在程序退出时按顺序清理资源。
|
||||
|
||||
它不应实现连接协议、文件传输、剪贴板或设备控制细节。
|
||||
|
||||
### GuiRuntime
|
||||
|
||||
`GuiRuntime` 是 GUI 进程的非界面协调层,负责:
|
||||
|
||||
- 初始化 MiniRTC Peer 及回调参数;
|
||||
- 协调连接、远端会话和在线状态;
|
||||
- 持有各功能 Manager/Controller;
|
||||
- 处理跨功能模块的调用顺序;
|
||||
- 提供平台集成功能所需的运行时上下文。
|
||||
|
||||
`GuiRuntime` 应保持为协调器。新的独立功能应优先创建 Manager 或 Controller,不应直接继续堆积到 `GuiRuntime`。
|
||||
|
||||
### RemoteSession
|
||||
|
||||
`RemoteSession` 表示一个连接中或已连接的远端端点,其生命周期覆盖:
|
||||
|
||||
- MiniRTC Peer 和连接状态;
|
||||
- 视频帧、纹理和渲染区域;
|
||||
- 远端显示器和控制栏状态;
|
||||
- 音频、鼠标和键盘控制状态;
|
||||
- 文件传输状态;
|
||||
- Windows 服务及安全桌面状态。
|
||||
|
||||
`remote_sessions_` 是按远端 ID 索引的会话表。会话查找、连接和清理统一由运行时处理。
|
||||
|
||||
## 状态划分
|
||||
|
||||
状态定义按生命周期和使用范围划分:
|
||||
|
||||
| 文件 | 状态范围 |
|
||||
| --- | --- |
|
||||
| `application/application_state.h` | SDL 窗口、渲染上下文、交互标志和 UI 显示状态 |
|
||||
| `runtime/runtime_state.h` | 配置、Peer、连接表、在线探测和平台集成状态 |
|
||||
| `runtime/remote_session.h` | 单个远端会话独占的连接、媒体、控制和文件状态 |
|
||||
| `runtime/gui_state.h` | 仅组合 `ApplicationState` 与 `RuntimeState` |
|
||||
|
||||
添加状态前应先判断其生命周期:
|
||||
|
||||
- 只属于某个远端连接:放入 `RemoteSession`;
|
||||
- 只属于窗口或 UI:放入 `ApplicationState` 对应子状态;
|
||||
- 属于整个 GUI 运行期:放入 `RuntimeState` 对应子状态;
|
||||
- 只属于某个功能模块:优先作为 Manager/Controller 的私有成员。
|
||||
|
||||
## 主要运行流程
|
||||
|
||||
### 启动
|
||||
|
||||
```text
|
||||
Render::Run
|
||||
→ GuiApplication::Run
|
||||
→ 初始化路径、日志和配置
|
||||
→ 初始化 SDL 和功能模块
|
||||
→ GuiRuntime::CreateConnectionPeer
|
||||
→ 创建主窗口
|
||||
→ GuiApplication::MainLoop
|
||||
```
|
||||
|
||||
### 建立远端连接
|
||||
|
||||
```text
|
||||
remote_peer_panel
|
||||
→ GuiRuntime::ConnectTo
|
||||
→ 在线状态探测
|
||||
→ 创建或复用 RemoteSession
|
||||
→ MiniRTC JoinConnection
|
||||
→ PeerEventHandler 接收连接状态
|
||||
→ 创建串流窗口并开始渲染
|
||||
```
|
||||
|
||||
### 接收远端数据
|
||||
|
||||
```text
|
||||
MiniRTC callback
|
||||
→ PeerEventHandler
|
||||
├── peer_media_callbacks:视频、音频
|
||||
├── peer_data_callbacks:控制、文件、剪贴板
|
||||
└── peer_event_handler:信令、连接状态、网络统计
|
||||
→ 对应 Manager/Controller
|
||||
→ 更新 RemoteSession 或运行时状态
|
||||
```
|
||||
|
||||
### 退出和清理
|
||||
|
||||
```text
|
||||
SDL quit / tray exit
|
||||
→ GuiApplication::Cleanup
|
||||
→ CloseAllRemoteSessions
|
||||
→ 停止设备及后台任务
|
||||
→ 销毁 Peer
|
||||
→ 销毁 ImGui 上下文和 SDL 窗口
|
||||
→ SDL_Quit
|
||||
```
|
||||
|
||||
## 新代码归属规则
|
||||
|
||||
新增代码时按以下规则选择目录:
|
||||
|
||||
| 功能 | 目录 |
|
||||
| --- | --- |
|
||||
| SDL 初始化、事件循环、窗口生命周期 | `application/` |
|
||||
| 连接、会话、Peer 回调和平台运行时 | `runtime/` |
|
||||
| 视频、音频、鼠标、键盘设备生命周期 | `features/devices/` |
|
||||
| 键盘协议和按键状态 | `features/input/` |
|
||||
| 剪贴板同步 | `features/clipboard/` |
|
||||
| 文件传输 | `features/file_transfer/` |
|
||||
| 配置持久化 | `features/settings/` |
|
||||
| 主窗口内嵌区域 | `views/panels/` |
|
||||
| 标题栏、状态栏、控制栏 | `views/toolbars/` |
|
||||
| 独立窗口和对话框 | `views/windows/` |
|
||||
| 系统托盘 | `platform/tray/` |
|
||||
| 字体、图标、布局和本地化数据 | `assets/` |
|
||||
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 746 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 84 KiB |
@@ -1,183 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 3 || $# -gt 4 ]]; then
|
||||
echo "Usage: $0 <debian-arch> <xmake-arch> <version> [extra-recommendation]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
DEBIAN_ARCH="$1"
|
||||
XMAKE_ARCH="$2"
|
||||
INPUT_VERSION="$3"
|
||||
EXTRA_RECOMMENDATION="${4:-}"
|
||||
|
||||
PKG_NAME="crossdesk"
|
||||
APP_NAME="CrossDesk"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
ALSA_RUNTIME_DEP="libasound2 | libasound2t64"
|
||||
PORTAL_RUNTIME_RECOMMENDS="xdg-desktop-portal, xdg-desktop-portal-gtk | xdg-desktop-portal-kde | xdg-desktop-portal-wlr"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
normalize_app_version() {
|
||||
local input="$1"
|
||||
local prefix=""
|
||||
local body="$input"
|
||||
|
||||
if [[ "$body" == v* ]]; then
|
||||
prefix="v"
|
||||
body="${body#v}"
|
||||
fi
|
||||
|
||||
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
|
||||
else
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
find_slint_runtime() {
|
||||
local needed="$1"
|
||||
local resolved=""
|
||||
|
||||
resolved="$(ldd "$BUILD_BINARY" 2>/dev/null | awk -v needed="$needed" \
|
||||
'$1 == needed && $2 == "=>" && $3 ~ /^\// { print $3; exit }')"
|
||||
if [[ -n "$resolved" && -f "$resolved" ]]; then
|
||||
echo "$resolved"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local package_roots=()
|
||||
if [[ -n "${XMAKE_GLOBALDIR:-}" ]]; then
|
||||
package_roots+=("$XMAKE_GLOBALDIR/.xmake/packages")
|
||||
fi
|
||||
package_roots+=("$HOME/.xmake/packages" "/data/.xmake/packages")
|
||||
|
||||
local package_root=""
|
||||
local candidate=""
|
||||
for package_root in "${package_roots[@]}"; do
|
||||
[[ -d "$package_root/s/slint" ]] || continue
|
||||
candidate="$(find "$package_root/s/slint" -path "*/lib/$needed" -print -quit 2>/dev/null || true)"
|
||||
if [[ -n "$candidate" ]]; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
APP_VERSION="$(normalize_app_version "$INPUT_VERSION")"
|
||||
DEB_VERSION="${APP_VERSION#v}"
|
||||
if [[ ! "$DEB_VERSION" =~ ^[0-9][0-9A-Za-z.+:~_-]*$ ]]; then
|
||||
echo "Invalid Debian version: $DEB_VERSION" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
BUILD_BINARY="$PROJECT_ROOT/build/linux/$XMAKE_ARCH/release/crossdesk"
|
||||
if [[ ! -x "$BUILD_BINARY" ]]; then
|
||||
echo "Missing release binary: $BUILD_BINARY" >&2
|
||||
echo "Build it first with: xmake f -m release -y && xmake b -vy crossdesk" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for command_name in readelf ldd dpkg-deb; do
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
echo "Required packaging command is missing: $command_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/crossdesk-deb.XXXXXX")"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
DEB_DIR="$WORK_DIR/${PKG_NAME}-${DEB_VERSION}"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/bin"
|
||||
PRIVATE_DIR="$DEB_DIR/usr/lib/$PKG_NAME"
|
||||
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
OUTPUT_FILE="$PROJECT_ROOT/${PKG_NAME}-linux-${DEBIAN_ARCH}-${APP_VERSION}.deb"
|
||||
|
||||
install -d "$DEBIAN_DIR" "$BIN_DIR" "$PRIVATE_DIR" "$DESKTOP_DIR"
|
||||
install -m 0755 "$BUILD_BINARY" "$PRIVATE_DIR/crossdesk-bin"
|
||||
|
||||
# Slint is intentionally built as a shared library. Keep that runtime private
|
||||
# to CrossDesk instead of relying on a non-existent distribution package.
|
||||
SLINT_NEEDED="$(readelf -d "$BUILD_BINARY" | sed -n \
|
||||
's/.*Shared library: \[\(libslint_cpp[^]]*\)\].*/\1/p' | head -n 1)"
|
||||
if [[ -n "$SLINT_NEEDED" ]]; then
|
||||
SLINT_LIBRARY="$(find_slint_runtime "$SLINT_NEEDED" || true)"
|
||||
if [[ -z "$SLINT_LIBRARY" ]]; then
|
||||
echo "Unable to locate the required Slint runtime: $SLINT_NEEDED" >&2
|
||||
exit 1
|
||||
fi
|
||||
install -m 0755 "$SLINT_LIBRARY" "$PRIVATE_DIR/$SLINT_NEEDED"
|
||||
fi
|
||||
|
||||
cat > "$BIN_DIR/$PKG_NAME" <<'EOF'
|
||||
#!/bin/sh
|
||||
private_lib_dir="/usr/lib/crossdesk"
|
||||
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
|
||||
export LD_LIBRARY_PATH="$private_lib_dir:$LD_LIBRARY_PATH"
|
||||
else
|
||||
export LD_LIBRARY_PATH="$private_lib_dir"
|
||||
fi
|
||||
exec /usr/lib/crossdesk/crossdesk-bin "$@"
|
||||
EOF
|
||||
chmod 0755 "$BIN_DIR/$PKG_NAME"
|
||||
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
|
||||
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
install -d "$ICON_BASE_DIR/${size}x${size}/apps"
|
||||
install -m 0644 "$PROJECT_ROOT/icons/linux/crossdesk_${size}x${size}.png" \
|
||||
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
|
||||
done
|
||||
|
||||
RECOMMENDS="$PORTAL_RUNTIME_RECOMMENDS"
|
||||
if [[ -n "$EXTRA_RECOMMENDATION" ]]; then
|
||||
RECOMMENDS="$RECOMMENDS, $EXTRA_RECOMMENDATION"
|
||||
fi
|
||||
|
||||
cat > "$DEBIAN_DIR/control" <<EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $DEB_VERSION
|
||||
Architecture: $DEBIAN_ARCH
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
Depends: libc6 (>= 2.35), libstdc++6 (>= 9), libx11-6, libxext6,
|
||||
libxrender1, libxft2, libxrandr2, libxfixes3, libxcb1, libxcb-randr0,
|
||||
libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, libxcb-xkb1,
|
||||
libxcb-xfixes0, libxv1, libxtst6, $ALSA_RUNTIME_DEP, libsndio7.0,
|
||||
libxcb-shm0, libpulse0, libdrm2, libdbus-1-3
|
||||
Recommends: $RECOMMENDS
|
||||
Priority: optional
|
||||
Section: utils
|
||||
EOF
|
||||
|
||||
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Version=$DEB_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/bin/$PKG_NAME
|
||||
Icon=$PKG_NAME
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
EOF
|
||||
|
||||
rm -f "$OUTPUT_FILE"
|
||||
dpkg-deb --root-owner-group --build "$DEB_DIR" "$OUTPUT_FILE"
|
||||
dpkg-deb --info "$OUTPUT_FILE" >/dev/null
|
||||
dpkg-deb --contents "$OUTPUT_FILE" > "$WORK_DIR/package-contents.txt"
|
||||
|
||||
if [[ -n "$SLINT_NEEDED" ]] && \
|
||||
! grep -q "usr/lib/$PKG_NAME/$SLINT_NEEDED" "$WORK_DIR/package-contents.txt"; then
|
||||
echo "Packaged artifact is missing $SLINT_NEEDED" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deb package created: $OUTPUT_FILE"
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 <version>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exec bash "$SCRIPT_DIR/package_deb.sh" amd64 x86_64 "$1" nvidia-cuda-toolkit
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 <version>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
exec bash "$SCRIPT_DIR/package_deb.sh" arm64 arm64 "$1"
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
|
||||
PLATFORM="macos"
|
||||
ARCH="arm64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
normalize_app_version() {
|
||||
local input="$1"
|
||||
local prefix=""
|
||||
local body="$input"
|
||||
|
||||
if [[ "$body" == v* ]]; then
|
||||
prefix="v"
|
||||
body="${body#v}"
|
||||
fi
|
||||
|
||||
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
|
||||
else
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
APP_VERSION="$(normalize_app_version "$1")"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
INSTALLER_TITLE="${APP_NAME_UPPER} ${APP_VERSION}"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
||||
find "${APP_BUNDLE}" -name '._*' -delete
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
mkdir -p build_pkg_scripts
|
||||
cp scripts/macosx/tcc_postinstall.sh build_pkg_scripts/postinstall
|
||||
chmod +x build_pkg_scripts/postinstall
|
||||
|
||||
mkdir -p build_pkg_resources
|
||||
cat > build_pkg_resources/welcome.html <<EOF
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #222;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>欢迎安装 ${INSTALLER_TITLE}</h1>
|
||||
<p>CrossDesk 将安装到“应用程序”文件夹。</p>
|
||||
<p>首次启动时,CrossDesk 会引导你在系统设置中授予必要权限,包括辅助功能、录屏与系统录音等。</p>
|
||||
<p>为避免旧版本授权残留造成状态误判,安装后可能需要重新授权。</p>
|
||||
<p>安装完成后,请从“应用程序”文件夹启动 CrossDesk。</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
--scripts build_pkg_scripts \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
cat > build_pkg_temp/Distribution <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>${INSTALLER_TITLE}</title>
|
||||
<welcome file="welcome.html" mime-type="text/html"/>
|
||||
<options customize="never" require-scripts="false" hostArchitectures="arm64"/>
|
||||
<choices-outline>
|
||||
<line choice="default">
|
||||
<line choice="${IDENTIFIER}"/>
|
||||
</line>
|
||||
</choices-outline>
|
||||
<choice id="default" title="${INSTALLER_TITLE}"/>
|
||||
<choice id="${IDENTIFIER}" title="${INSTALLER_TITLE}" visible="false">
|
||||
<pkg-ref id="${IDENTIFIER}"/>
|
||||
</choice>
|
||||
<pkg-ref id="${IDENTIFIER}" version="${APP_VERSION}" onConclusion="none">crossdesk-component.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
EOF
|
||||
|
||||
productbuild \
|
||||
--distribution build_pkg_temp/Distribution \
|
||||
--package-path build_pkg_temp \
|
||||
--resources build_pkg_resources \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
# Set custom icon for PKG file
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
echo "Setting custom icon for PKG file..."
|
||||
# Create a temporary iconset from icns
|
||||
TEMP_ICON_DIR=$(mktemp -d)
|
||||
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
|
||||
|
||||
# Use sips to create a png from icns for the icon
|
||||
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
|
||||
|
||||
# Method: Use osascript to set file icon (works on macOS)
|
||||
osascript <<APPLESCRIPT
|
||||
use framework "Foundation"
|
||||
use framework "AppKit"
|
||||
|
||||
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
|
||||
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
|
||||
|
||||
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
|
||||
set workspace to current application's NSWorkspace's sharedWorkspace()
|
||||
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
|
||||
APPLESCRIPT
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Custom icon set successfully for ${PKG_NAME}"
|
||||
else
|
||||
echo "Warning: Failed to set custom icon (this is optional)"
|
||||
fi
|
||||
|
||||
rm -rf "${TEMP_ICON_DIR}"
|
||||
fi
|
||||
echo "Set icon finished"
|
||||
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
|
||||
PLATFORM="macos"
|
||||
ARCH="x64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
normalize_app_version() {
|
||||
local input="$1"
|
||||
local prefix=""
|
||||
local body="$input"
|
||||
|
||||
if [[ "$body" == v* ]]; then
|
||||
prefix="v"
|
||||
body="${body#v}"
|
||||
fi
|
||||
|
||||
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
|
||||
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
|
||||
else
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
APP_VERSION="$(normalize_app_version "$1")"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
INSTALLER_TITLE="${APP_NAME_UPPER} ${APP_VERSION}"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
|
||||
find "${APP_BUNDLE}" -name '._*' -delete
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
mkdir -p build_pkg_scripts
|
||||
cp scripts/macosx/tcc_postinstall.sh build_pkg_scripts/postinstall
|
||||
chmod +x build_pkg_scripts/postinstall
|
||||
|
||||
mkdir -p build_pkg_resources
|
||||
cat > build_pkg_resources/welcome.html <<EOF
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #222;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>欢迎安装 ${INSTALLER_TITLE}</h1>
|
||||
<p>CrossDesk 将安装到“应用程序”文件夹。</p>
|
||||
<p>首次启动时,CrossDesk 会引导你在系统设置中授予必要权限,包括辅助功能、录屏与系统录音等。</p>
|
||||
<p>为避免旧版本授权残留造成状态误判,安装后可能需要重新授权。</p>
|
||||
<p>安装完成后,请从“应用程序”文件夹启动 CrossDesk。</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
--scripts build_pkg_scripts \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
cat > build_pkg_temp/Distribution <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>${INSTALLER_TITLE}</title>
|
||||
<welcome file="welcome.html" mime-type="text/html"/>
|
||||
<options customize="never" require-scripts="false" hostArchitectures="x86_64"/>
|
||||
<choices-outline>
|
||||
<line choice="default">
|
||||
<line choice="${IDENTIFIER}"/>
|
||||
</line>
|
||||
</choices-outline>
|
||||
<choice id="default" title="${INSTALLER_TITLE}"/>
|
||||
<choice id="${IDENTIFIER}" title="${INSTALLER_TITLE}" visible="false">
|
||||
<pkg-ref id="${IDENTIFIER}"/>
|
||||
</choice>
|
||||
<pkg-ref id="${IDENTIFIER}" version="${APP_VERSION}" onConclusion="none">crossdesk-component.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
EOF
|
||||
|
||||
productbuild \
|
||||
--distribution build_pkg_temp/Distribution \
|
||||
--package-path build_pkg_temp \
|
||||
--resources build_pkg_resources \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
# Set custom icon for PKG file
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
echo "Setting custom icon for PKG file..."
|
||||
# Create a temporary iconset from icns
|
||||
TEMP_ICON_DIR=$(mktemp -d)
|
||||
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
|
||||
|
||||
# Use sips to create a png from icns for the icon
|
||||
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
|
||||
|
||||
# Method: Use osascript to set file icon (works on macOS)
|
||||
osascript <<APPLESCRIPT
|
||||
use framework "Foundation"
|
||||
use framework "AppKit"
|
||||
|
||||
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
|
||||
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
|
||||
|
||||
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
|
||||
set workspace to current application's NSWorkspace's sharedWorkspace()
|
||||
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
|
||||
APPLESCRIPT
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Custom icon set successfully for ${PKG_NAME}"
|
||||
else
|
||||
echo "Warning: Failed to set custom icon (this is optional)"
|
||||
fi
|
||||
|
||||
rm -rf "${TEMP_ICON_DIR}"
|
||||
fi
|
||||
echo "Set icon finished"
|
||||
|
||||
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_IDENTIFIER="cn.crossdesk.app"
|
||||
|
||||
# Keep known historical identifiers here. tccutil only resets identifiers that
|
||||
# Launch Services can currently resolve, so path/db cleanup below remains a
|
||||
# best-effort fallback for stale entries from unsigned or removed builds.
|
||||
BUNDLE_IDENTIFIERS=(
|
||||
"cn.crossdesk.app"
|
||||
"cn.crossdesk.CrossDesk"
|
||||
"com.crossdesk.app"
|
||||
"com.crossdesk.CrossDesk"
|
||||
"com.kunkundi.crossdesk"
|
||||
"com.kunkundi.CrossDesk"
|
||||
)
|
||||
|
||||
TCC_SERVICES=(
|
||||
"ScreenCapture"
|
||||
"Accessibility"
|
||||
"Microphone"
|
||||
"AudioCapture"
|
||||
)
|
||||
|
||||
run_tccutil() {
|
||||
local user_name="$1"
|
||||
local user_id="$2"
|
||||
local service="$3"
|
||||
local bundle_id="$4"
|
||||
|
||||
if [ -n "$user_name" ] && [ -n "$user_id" ]; then
|
||||
/bin/launchctl asuser "$user_id" \
|
||||
/usr/bin/sudo -u "$user_name" \
|
||||
/usr/bin/tccutil reset "$service" "$bundle_id" >/dev/null 2>&1
|
||||
else
|
||||
/usr/bin/tccutil reset "$service" "$bundle_id" >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
reset_bundle_tcc() {
|
||||
local user_name="$1"
|
||||
local user_id="$2"
|
||||
local bundle_id
|
||||
local service
|
||||
|
||||
for bundle_id in "${BUNDLE_IDENTIFIERS[@]}"; do
|
||||
if run_tccutil "$user_name" "$user_id" "All" "$bundle_id"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
for service in "${TCC_SERVICES[@]}"; do
|
||||
run_tccutil "$user_name" "$user_id" "$service" "$bundle_id" || true
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
cleanup_tcc_db() {
|
||||
local db_path="$1"
|
||||
|
||||
if [ ! -f "$db_path" ] || ! command -v sqlite3 >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
/usr/bin/sqlite3 "$db_path" <<'SQL' >/dev/null 2>&1 || true
|
||||
DELETE FROM access
|
||||
WHERE service IN (
|
||||
'kTCCServiceScreenCapture',
|
||||
'kTCCServiceAccessibility',
|
||||
'kTCCServiceMicrophone',
|
||||
'kTCCServiceAudioCapture'
|
||||
)
|
||||
AND (
|
||||
client IN (
|
||||
'cn.crossdesk.app',
|
||||
'cn.crossdesk.CrossDesk',
|
||||
'com.crossdesk.app',
|
||||
'com.crossdesk.CrossDesk',
|
||||
'com.kunkundi.crossdesk',
|
||||
'com.kunkundi.CrossDesk'
|
||||
)
|
||||
OR lower(client) LIKE '%crossdesk%'
|
||||
);
|
||||
SQL
|
||||
}
|
||||
|
||||
cleanup_user_tcc_db() {
|
||||
local user_name="$1"
|
||||
local home_dir
|
||||
|
||||
home_dir=$(/usr/bin/dscl . -read "/Users/${user_name}" NFSHomeDirectory 2>/dev/null |
|
||||
/usr/bin/awk '{print $2}')
|
||||
if [ -z "$home_dir" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
cleanup_tcc_db "${home_dir}/Library/Application Support/com.apple.TCC/TCC.db"
|
||||
}
|
||||
|
||||
CONSOLE_USER=$(/usr/bin/stat -f "%Su" /dev/console 2>/dev/null || true)
|
||||
if [ -n "$CONSOLE_USER" ] &&
|
||||
[ "$CONSOLE_USER" != "root" ] &&
|
||||
[ "$CONSOLE_USER" != "loginwindow" ]; then
|
||||
CONSOLE_UID=$(/usr/bin/id -u "$CONSOLE_USER" 2>/dev/null || true)
|
||||
reset_bundle_tcc "$CONSOLE_USER" "$CONSOLE_UID"
|
||||
cleanup_user_tcc_db "$CONSOLE_USER"
|
||||
fi
|
||||
|
||||
# Also clear any system/root-scoped decisions as a harmless fallback.
|
||||
reset_bundle_tcc "" ""
|
||||
cleanup_tcc_db "/Library/Application Support/com.apple.TCC/TCC.db"
|
||||
|
||||
exit 0
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
name="CrossDesk"
|
||||
type="win32" />
|
||||
|
||||
<description>CrossDesk Application</description>
|
||||
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
</assembly>
|
||||
@@ -1,11 +0,0 @@
|
||||
// Application icon resource; load by the resource name IDI_ICON1.
|
||||
IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico"
|
||||
|
||||
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
|
||||
#define RT_MANIFEST 24
|
||||
|
||||
#ifdef CROSSDESK_DEBUG
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "crossdesk_debug.manifest"
|
||||
#else
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "crossdesk.manifest"
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
name="CrossDesk"
|
||||
type="win32" />
|
||||
|
||||
<description>CrossDesk Application</description>
|
||||
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
</assembly>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
name="CrossDesk"
|
||||
type="win32" />
|
||||
|
||||
<description>CrossDesk Portable Application</description>
|
||||
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
</assembly>
|
||||
@@ -1,8 +0,0 @@
|
||||
// Portable build resource. The app itself runs as the current user; service
|
||||
// installation raises a separate UAC prompt only when the user chooses it.
|
||||
IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico"
|
||||
|
||||
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
|
||||
#define RT_MANIFEST 24
|
||||
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "crossdesk_portable.manifest"
|
||||
@@ -1,237 +0,0 @@
|
||||
; Set search path
|
||||
!addincludedir "${__FILEDIR__}"
|
||||
|
||||
; Installer initial constants
|
||||
!define PRODUCT_NAME "CrossDesk"
|
||||
!define PRODUCT_VERSION "${VERSION}"
|
||||
!define PRODUCT_PUBLISHER "CrossDesk"
|
||||
!define PRODUCT_WEB_SITE "https://www.crossdesk.cn/"
|
||||
!define APP_NAME "CrossDesk"
|
||||
!define UNINSTALL_REG_KEY "CrossDesk"
|
||||
!define PRODUCT_SERVICE_NAME "CrossDeskService"
|
||||
|
||||
; Installer icon path
|
||||
!define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico"
|
||||
|
||||
; Compression settings
|
||||
SetCompressor /FINAL lzma
|
||||
|
||||
; Request admin privileges (needed to write HKLM)
|
||||
RequestExecutionLevel admin
|
||||
|
||||
; ------ MUI Modern UI Definition ------
|
||||
!include "MUI.nsh"
|
||||
!define MUI_ABORTWARNING
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
; Add run-after-install option
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Run ${PRODUCT_NAME}"
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
|
||||
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
|
||||
; ------ End of MUI Definition ------
|
||||
|
||||
; Include LogicLib for process handling
|
||||
!include "LogicLib.nsh"
|
||||
|
||||
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||
OutFile "crossdesk-win-x64-${PRODUCT_VERSION}.exe"
|
||||
InstallDir "$PROGRAMFILES\CrossDesk"
|
||||
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" "InstallDir"
|
||||
ShowInstDetails show
|
||||
|
||||
Section "MainSection"
|
||||
; Check if CrossDesk is running
|
||||
StrCpy $1 "CrossDesk.exe"
|
||||
|
||||
nsProcess::_FindProcess "$1"
|
||||
Pop $R0
|
||||
${If} $R0 = 0 ;
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and continue the installation?" IDYES closeApp IDNO cancelInstall
|
||||
${Else}
|
||||
Goto installApp
|
||||
${EndIf}
|
||||
|
||||
closeApp:
|
||||
nsProcess::_KillProcess "$1"
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
Goto installApp
|
||||
|
||||
cancelInstall:
|
||||
SetDetailsPrint both
|
||||
MessageBox MB_ICONEXCLAMATION|MB_OK "Installation has been aborted."
|
||||
Abort
|
||||
|
||||
installApp:
|
||||
Call StopInstalledService
|
||||
|
||||
SetOutPath "$INSTDIR"
|
||||
SetOverwrite ifnewer
|
||||
|
||||
; Main application executable path
|
||||
File /oname=CrossDesk.exe "..\..\build\windows\x64\release\crossdesk.exe"
|
||||
; Bundle service-side binaries required by the Windows service flow
|
||||
File "..\..\build\windows\x64\release\crossdesk_service.exe"
|
||||
File "..\..\build\windows\x64\release\crossdesk_session_helper.exe"
|
||||
; Bundle runtime DLLs from the release output directory
|
||||
File "..\..\build\windows\x64\release\*.dll"
|
||||
|
||||
Call RegisterInstalledService
|
||||
|
||||
; Write uninstall information
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\CrossDesk.exe"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1
|
||||
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "InstallDir" "$INSTDIR"
|
||||
SectionEnd
|
||||
|
||||
Section -AdditionalIcons
|
||||
; Desktop shortcut
|
||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\CrossDesk.exe" "" "$INSTDIR\CrossDesk.exe"
|
||||
|
||||
; Start menu shortcut
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\CrossDesk.exe" "" "$INSTDIR\CrossDesk.exe"
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
; Check if CrossDesk is running
|
||||
StrCpy $1 "CrossDesk.exe"
|
||||
|
||||
nsProcess::_FindProcess "$1"
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and uninstall?" IDYES closeApp IDNO cancelUninstall
|
||||
${Else}
|
||||
Goto uninstallApp
|
||||
${EndIf}
|
||||
|
||||
closeApp:
|
||||
nsProcess::_KillProcess "$1"
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
Goto uninstallApp
|
||||
|
||||
cancelUninstall:
|
||||
SetDetailsPrint both
|
||||
MessageBox MB_ICONEXCLAMATION|MB_OK "Uninstallation has been aborted."
|
||||
Abort
|
||||
|
||||
uninstallApp:
|
||||
Call un.UnregisterInstalledService
|
||||
|
||||
; Delete main executable and uninstaller
|
||||
Delete "$INSTDIR\CrossDesk.exe"
|
||||
Delete "$INSTDIR\crossdesk_service.exe"
|
||||
Delete "$INSTDIR\crossdesk_session_helper.exe"
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
; Recursively delete installation directory
|
||||
RMDir /r "$INSTDIR"
|
||||
|
||||
; Delete desktop and start menu shortcuts
|
||||
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
|
||||
|
||||
; Delete registry uninstall entry
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}"
|
||||
|
||||
; Delete remembered install dir
|
||||
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
|
||||
|
||||
; Recursively delete CrossDesk folder in user AppData
|
||||
RMDir /r "$APPDATA\CrossDesk"
|
||||
RMDir /r "$LOCALAPPDATA\CrossDesk"
|
||||
SectionEnd
|
||||
|
||||
; ------ Functions ------
|
||||
Function LaunchApp
|
||||
Exec "$INSTDIR\CrossDesk.exe"
|
||||
FunctionEnd
|
||||
|
||||
Function StopInstalledService
|
||||
IfFileExists "$INSTDIR\CrossDesk.exe" 0 stop_with_sc
|
||||
IfFileExists "$INSTDIR\crossdesk_service.exe" 0 stop_with_sc
|
||||
|
||||
DetailPrint "Stopping existing CrossDesk service"
|
||||
ExecWait '"$INSTDIR\CrossDesk.exe" --service-stop' $0
|
||||
${If} $0 = 0
|
||||
Return
|
||||
${EndIf}
|
||||
|
||||
stop_with_sc:
|
||||
DetailPrint "Stopping existing CrossDesk service via Service Control Manager"
|
||||
ExecWait '"$SYSDIR\sc.exe" stop ${PRODUCT_SERVICE_NAME}' $0
|
||||
${If} $0 != 0
|
||||
${AndIf} $0 != 1060
|
||||
${AndIf} $0 != 1062
|
||||
MessageBox MB_ICONSTOP|MB_OK "Failed to stop the existing CrossDesk service. The installation will be aborted."
|
||||
Abort
|
||||
${EndIf}
|
||||
Sleep 1500
|
||||
FunctionEnd
|
||||
|
||||
Function RegisterInstalledService
|
||||
IfFileExists "$INSTDIR\CrossDesk.exe" 0 missing_service_binary
|
||||
IfFileExists "$INSTDIR\crossdesk_service.exe" 0 missing_service_binary
|
||||
IfFileExists "$INSTDIR\crossdesk_session_helper.exe" 0 missing_service_binary
|
||||
|
||||
DetailPrint "Registering CrossDesk service"
|
||||
ExecWait '"$INSTDIR\CrossDesk.exe" --service-install' $0
|
||||
${If} $0 != 0
|
||||
MessageBox MB_ICONSTOP|MB_OK "Failed to register the CrossDesk service. The installation will be aborted."
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
DetailPrint "CrossDesk service registered for on-demand start"
|
||||
|
||||
Return
|
||||
|
||||
missing_service_binary:
|
||||
MessageBox MB_ICONSTOP|MB_OK "CrossDesk service files are missing from the installer package. The installation will be aborted."
|
||||
Abort
|
||||
FunctionEnd
|
||||
|
||||
Function un.UnregisterInstalledService
|
||||
IfFileExists "$INSTDIR\CrossDesk.exe" 0 unregister_with_sc
|
||||
|
||||
DetailPrint "Stopping CrossDesk service"
|
||||
ExecWait '"$INSTDIR\CrossDesk.exe" --service-stop' $0
|
||||
${If} $0 = 0
|
||||
DetailPrint "Removing CrossDesk service"
|
||||
ExecWait '"$INSTDIR\CrossDesk.exe" --service-uninstall' $0
|
||||
${If} $0 = 0
|
||||
Return
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
unregister_with_sc:
|
||||
DetailPrint "Removing CrossDesk service via Service Control Manager"
|
||||
ExecWait '"$SYSDIR\sc.exe" stop ${PRODUCT_SERVICE_NAME}' $0
|
||||
${If} $0 != 0
|
||||
${AndIf} $0 != 1060
|
||||
${AndIf} $0 != 1062
|
||||
MessageBox MB_ICONSTOP|MB_OK "Failed to stop the CrossDesk service. Uninstall will be aborted."
|
||||
Abort
|
||||
${EndIf}
|
||||
Sleep 1500
|
||||
|
||||
ExecWait '"$SYSDIR\sc.exe" delete ${PRODUCT_SERVICE_NAME}' $0
|
||||
${If} $0 != 0
|
||||
${AndIf} $0 != 1060
|
||||
MessageBox MB_ICONSTOP|MB_OK "Failed to remove the CrossDesk service. Uninstall will be aborted."
|
||||
Abort
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
@@ -1,40 +0,0 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Version
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Normalize-AppVersion {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$InputVersion
|
||||
)
|
||||
|
||||
$prefix = ""
|
||||
$body = $InputVersion
|
||||
|
||||
if ($body.StartsWith("v")) {
|
||||
$prefix = "v"
|
||||
$body = $body.Substring(1)
|
||||
}
|
||||
|
||||
if ($body -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$') {
|
||||
return "${prefix}$($Matches[1])-$($Matches[4])-$($Matches[3])"
|
||||
}
|
||||
|
||||
return $InputVersion
|
||||
}
|
||||
|
||||
$normalizedVersion = Normalize-AppVersion -InputVersion $Version
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
Push-Location $scriptDir
|
||||
try {
|
||||
& makensis "/DVERSION=$normalizedVersion" "nsis_script.nsi"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
#include "daemon.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
#include <tchar.h>
|
||||
#include <windows.h>
|
||||
#elif __APPLE__
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
volatile std::sig_atomic_t Daemon::stop_requested_ = 0;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr int kRestartDelayMs = 1000;
|
||||
#ifndef _WIN32
|
||||
constexpr int kWaitPollIntervalMs = 200;
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
// get executable file path
|
||||
static std::string GetExecutablePath() {
|
||||
#ifdef _WIN32
|
||||
char path[32768];
|
||||
DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path));
|
||||
if (length > 0 && length < sizeof(path)) {
|
||||
return std::string(path);
|
||||
}
|
||||
#elif __APPLE__
|
||||
char path[PATH_MAX];
|
||||
uint32_t size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||
char resolved_path[PATH_MAX];
|
||||
if (realpath(path, resolved_path) != nullptr) {
|
||||
return std::string(resolved_path);
|
||||
}
|
||||
return std::string(path);
|
||||
}
|
||||
#else
|
||||
char path[PATH_MAX];
|
||||
ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||||
if (count != -1) {
|
||||
path[count] = '\0';
|
||||
return std::string(path);
|
||||
}
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
Daemon::Daemon(const std::string& name) : name_(name), running_(false) {}
|
||||
|
||||
void Daemon::stop() {
|
||||
running_.store(false);
|
||||
#ifndef _WIN32
|
||||
stop_requested_ = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Daemon::isRunning() const {
|
||||
#ifndef _WIN32
|
||||
return running_.load() && (stop_requested_ == 0);
|
||||
#else
|
||||
return running_.load();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Daemon::start(MainLoopFunc loop) {
|
||||
#ifdef _WIN32
|
||||
running_.store(true);
|
||||
return runWithRestart(loop);
|
||||
#elif __APPLE__
|
||||
// macOS: Use child process monitoring (like Windows) to preserve GUI
|
||||
stop_requested_ = 0;
|
||||
running_.store(true);
|
||||
return runWithRestart(loop);
|
||||
#else
|
||||
// linux: Daemonize first, then run with restart monitoring
|
||||
stop_requested_ = 0;
|
||||
|
||||
// check if running from terminal before fork
|
||||
bool from_terminal =
|
||||
(isatty(STDIN_FILENO) != 0) || (isatty(STDOUT_FILENO) != 0);
|
||||
|
||||
// first fork: detach from terminal
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
std::cerr << "Failed to fork daemon process" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (pid > 0) _exit(0);
|
||||
|
||||
if (setsid() < 0) {
|
||||
std::cerr << "Failed to create new session" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
std::cerr << "Failed to fork daemon process (second fork)" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (pid > 0) _exit(0);
|
||||
|
||||
umask(0);
|
||||
if (chdir("/") != 0) {
|
||||
std::cerr << "Failed to change daemon working directory to /: "
|
||||
<< std::strerror(errno) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// redirect file descriptors: keep stdout/stderr if from terminal, else
|
||||
// redirect to /dev/null
|
||||
int fd = open("/dev/null", O_RDWR);
|
||||
if (fd >= 0) {
|
||||
dup2(fd, STDIN_FILENO);
|
||||
if (!from_terminal) {
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
dup2(fd, STDERR_FILENO);
|
||||
}
|
||||
if (fd > 2) close(fd);
|
||||
}
|
||||
|
||||
// set up signal handlers
|
||||
signal(SIGTERM, [](int) { stop_requested_ = 1; });
|
||||
signal(SIGINT, [](int) { stop_requested_ = 1; });
|
||||
|
||||
// ignore SIGPIPE
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
running_.store(true);
|
||||
return runWithRestart(loop);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) {
|
||||
try {
|
||||
loop();
|
||||
return 0; // normal exit
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Exception caught: " << e.what() << std::endl;
|
||||
return 1; // c++ exception
|
||||
} catch (...) {
|
||||
std::cerr << "Unknown exception caught" << std::endl;
|
||||
return 1; // other exception
|
||||
}
|
||||
}
|
||||
|
||||
static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) {
|
||||
__try {
|
||||
return RunLoopCatchCpp(loop);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
// catch system-level crashes (access violation, divide by zero, etc.)
|
||||
DWORD code = GetExceptionCode();
|
||||
std::cerr << "System crash detected (SEH exception code: 0x" << std::hex
|
||||
<< code << std::dec << ")" << std::endl;
|
||||
return 2; // System crash
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// run with restart logic: parent monitors child process and restarts on crash
|
||||
bool Daemon::runWithRestart(MainLoopFunc loop) {
|
||||
int restart_count = 0;
|
||||
std::string exe_path = GetExecutablePath();
|
||||
if (exe_path.empty()) {
|
||||
std::cerr
|
||||
<< "Failed to get executable path, falling back to direct execution"
|
||||
<< std::endl;
|
||||
while (isRunning()) {
|
||||
try {
|
||||
loop();
|
||||
break;
|
||||
} catch (...) {
|
||||
restart_count++;
|
||||
std::cerr << "Exception caught, restarting... (attempt "
|
||||
<< restart_count << ")" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
while (isRunning()) {
|
||||
#ifdef _WIN32
|
||||
// windows: use CreateProcess to create child process
|
||||
STARTUPINFOA si = {sizeof(si)};
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
std::string cmd_line = "\"" + exe_path + "\" --child";
|
||||
std::vector<char> cmd_line_buf(cmd_line.begin(), cmd_line.end());
|
||||
cmd_line_buf.push_back('\0');
|
||||
|
||||
BOOL success = CreateProcessA(
|
||||
nullptr, // executable file path (specified in command line)
|
||||
cmd_line_buf.data(), // command line arguments
|
||||
nullptr, // process security attributes
|
||||
nullptr, // thread security attributes
|
||||
FALSE, // don't inherit handles
|
||||
0, // creation flags
|
||||
nullptr, // environment variables (inherit from parent)
|
||||
nullptr, // current directory
|
||||
&si, // startup info
|
||||
&pi // process information
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
std::cerr << "Failed to create child process, error: " << GetLastError()
|
||||
<< std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||
restart_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (isRunning()) {
|
||||
DWORD wait_result = WaitForSingleObject(pi.hProcess, 200);
|
||||
if (wait_result == WAIT_OBJECT_0) {
|
||||
break;
|
||||
}
|
||||
if (wait_result == WAIT_FAILED) {
|
||||
std::cerr << "Failed waiting child process, error: " << GetLastError()
|
||||
<< std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRunning()) {
|
||||
TerminateProcess(pi.hProcess, 1);
|
||||
WaitForSingleObject(pi.hProcess, 3000);
|
||||
}
|
||||
|
||||
DWORD exit_code = 0;
|
||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
if (!isRunning() || exit_code == 0) {
|
||||
break; // normal exit
|
||||
}
|
||||
restart_count++;
|
||||
std::cerr << "Child process exited with code " << exit_code
|
||||
<< ", restarting... (attempt " << restart_count << ")"
|
||||
<< std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||
#else
|
||||
// linux: use fork + exec to create child process
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
execl(exe_path.c_str(), exe_path.c_str(), "--child", nullptr);
|
||||
_exit(1); // exec failed
|
||||
} else if (pid > 0) {
|
||||
int status = 0;
|
||||
pid_t waited_pid = -1;
|
||||
while (isRunning()) {
|
||||
waited_pid = waitpid(pid, &status, WNOHANG);
|
||||
if (waited_pid == pid) {
|
||||
break;
|
||||
}
|
||||
if (waited_pid < 0 && errno != EINTR) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(kWaitPollIntervalMs));
|
||||
}
|
||||
|
||||
if (!isRunning() && waited_pid != pid) {
|
||||
kill(pid, SIGTERM);
|
||||
waited_pid = waitpid(pid, &status, 0);
|
||||
}
|
||||
|
||||
if (waited_pid < 0) {
|
||||
if (!isRunning()) {
|
||||
break;
|
||||
}
|
||||
restart_count++;
|
||||
std::cerr << "waitpid failed, errno: " << errno
|
||||
<< ", restarting... (attempt " << restart_count << ")"
|
||||
<< std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
int exit_code = WEXITSTATUS(status);
|
||||
if (!isRunning() || exit_code == 0) {
|
||||
break; // normal exit
|
||||
}
|
||||
restart_count++;
|
||||
std::cerr << "Child process exited with code " << exit_code
|
||||
<< ", restarting... (attempt " << restart_count << ")"
|
||||
<< std::endl;
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
if (!isRunning()) {
|
||||
break;
|
||||
}
|
||||
restart_count++;
|
||||
std::cerr << "Child process crashed with signal " << WTERMSIG(status)
|
||||
<< ", restarting... (attempt " << restart_count << ")"
|
||||
<< std::endl;
|
||||
} else {
|
||||
restart_count++;
|
||||
std::cerr << "Child process exited with unknown status, restarting... "
|
||||
"(attempt "
|
||||
<< restart_count << ")" << std::endl;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||
} else {
|
||||
std::cerr << "Failed to fork child process" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||
restart_count++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-11-19
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DAEMON_H_
|
||||
#define _DAEMON_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
class Daemon {
|
||||
public:
|
||||
using MainLoopFunc = std::function<void()>;
|
||||
|
||||
Daemon(const std::string& name);
|
||||
|
||||
bool start(MainLoopFunc loop);
|
||||
|
||||
void stop();
|
||||
|
||||
bool isRunning() const;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
bool runWithRestart(MainLoopFunc loop);
|
||||
|
||||
#ifndef _WIN32
|
||||
static volatile std::sig_atomic_t stop_requested_;
|
||||
#endif
|
||||
std::atomic<bool> running_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,231 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
#ifdef CROSSDESK_DEBUG
|
||||
#pragma comment(linker, "/subsystem:\"console\"")
|
||||
#else
|
||||
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <cstdio>
|
||||
|
||||
#include "service_host.h"
|
||||
#endif
|
||||
|
||||
#include "config_center.h"
|
||||
#include "daemon.h"
|
||||
#include "path_manager.h"
|
||||
#include "render.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace {
|
||||
|
||||
void EnsureConsoleForCli() {
|
||||
static bool console_ready = false;
|
||||
if (console_ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != ERROR_ACCESS_DENIED) {
|
||||
AllocConsole();
|
||||
}
|
||||
}
|
||||
|
||||
FILE* stream = nullptr;
|
||||
freopen_s(&stream, "CONOUT$", "w", stdout);
|
||||
freopen_s(&stream, "CONOUT$", "w", stderr);
|
||||
freopen_s(&stream, "CONIN$", "r", stdin);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
console_ready = true;
|
||||
}
|
||||
|
||||
void PrintServiceCliUsage() {
|
||||
std::cout
|
||||
<< "CrossDesk service management commands\n"
|
||||
<< " --service-install Install the sibling crossdesk_service.exe\n"
|
||||
<< " --service-uninstall Remove the installed Windows service\n"
|
||||
<< " --service-start Start the Windows service\n"
|
||||
<< " --service-stop Stop the Windows service\n"
|
||||
<< " --service-sas Ask the service to send Secure Attention "
|
||||
"Sequence\n"
|
||||
<< " --service-ping Ping the service over named pipe IPC\n"
|
||||
<< " --service-status Query service runtime status\n"
|
||||
<< " --service-help Show this help\n";
|
||||
}
|
||||
|
||||
std::wstring GetCurrentExecutablePathW() {
|
||||
wchar_t path[MAX_PATH] = {0};
|
||||
DWORD length = GetModuleFileNameW(nullptr, path, MAX_PATH);
|
||||
if (length == 0 || length >= MAX_PATH) {
|
||||
return L"";
|
||||
}
|
||||
return std::wstring(path, length);
|
||||
}
|
||||
|
||||
std::filesystem::path GetSiblingServiceExecutablePath() {
|
||||
std::wstring current_executable = GetCurrentExecutablePathW();
|
||||
if (current_executable.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::filesystem::path(current_executable).parent_path() /
|
||||
L"crossdesk_service.exe";
|
||||
}
|
||||
|
||||
bool IsServiceCliCommand(const char* arg) {
|
||||
if (arg == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::strcmp(arg, "--service-install") == 0 ||
|
||||
std::strcmp(arg, "--service-uninstall") == 0 ||
|
||||
std::strcmp(arg, "--service-start") == 0 ||
|
||||
std::strcmp(arg, "--service-stop") == 0 ||
|
||||
std::strcmp(arg, "--service-sas") == 0 ||
|
||||
std::strcmp(arg, "--service-ping") == 0 ||
|
||||
std::strcmp(arg, "--service-status") == 0 ||
|
||||
std::strcmp(arg, "--service-help") == 0;
|
||||
}
|
||||
|
||||
void TryStartManagedWindowsService() {
|
||||
std::filesystem::path service_path = GetSiblingServiceExecutablePath();
|
||||
if (service_path.empty() || !std::filesystem::exists(service_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!crossdesk::IsCrossDeskServiceInstalled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
crossdesk::StartCrossDeskService();
|
||||
}
|
||||
|
||||
int HandleServiceCliCommand(const std::string& command) {
|
||||
EnsureConsoleForCli();
|
||||
|
||||
if (command == "--service-help") {
|
||||
PrintServiceCliUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command == "--service-install") {
|
||||
std::filesystem::path service_path = GetSiblingServiceExecutablePath();
|
||||
if (service_path.empty()) {
|
||||
std::cerr << "Failed to locate crossdesk_service.exe" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (!std::filesystem::exists(service_path)) {
|
||||
std::cerr << "Service binary not found: " << service_path.string()
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool success = crossdesk::InstallCrossDeskService(service_path.wstring());
|
||||
std::cout << (success ? "install ok" : "install failed") << std::endl;
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
if (command == "--service-uninstall") {
|
||||
bool success = crossdesk::UninstallCrossDeskService();
|
||||
std::cout << (success ? "uninstall ok" : "uninstall failed") << std::endl;
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
if (command == "--service-start") {
|
||||
bool success = crossdesk::StartCrossDeskService();
|
||||
std::cout << (success ? "start ok" : "start failed") << std::endl;
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
if (command == "--service-stop") {
|
||||
bool success = crossdesk::StopCrossDeskService();
|
||||
std::cout << (success ? "stop ok" : "stop failed") << std::endl;
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
if (command == "--service-sas") {
|
||||
std::cout << crossdesk::QueryCrossDeskService("sas") << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command == "--service-ping") {
|
||||
std::cout << crossdesk::QueryCrossDeskService("ping") << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command == "--service-status") {
|
||||
std::cout << crossdesk::QueryCrossDeskService("status") << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrintServiceCliUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
if (argc > 1 && IsServiceCliCommand(argv[1])) {
|
||||
return HandleServiceCliCommand(argv[1]);
|
||||
}
|
||||
#endif
|
||||
|
||||
// check if running as child process
|
||||
bool is_child = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--child") == 0) {
|
||||
is_child = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_child) {
|
||||
// child process: run render directly
|
||||
crossdesk::Render render;
|
||||
render.Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TryStartManagedWindowsService();
|
||||
#endif
|
||||
|
||||
bool enable_daemon = false;
|
||||
auto path_manager = std::make_unique<crossdesk::PathManager>("CrossDesk");
|
||||
if (path_manager) {
|
||||
std::string cache_path = path_manager->GetCachePath().string();
|
||||
crossdesk::ConfigCenter config_center(cache_path + "/config.ini");
|
||||
enable_daemon = config_center.IsEnableDaemon();
|
||||
}
|
||||
|
||||
if (enable_daemon) {
|
||||
// start daemon with restart monitoring
|
||||
Daemon daemon("CrossDesk");
|
||||
|
||||
// define main loop function: run render and stop daemon on normal exit
|
||||
Daemon::MainLoopFunc main_loop = [&daemon]() {
|
||||
crossdesk::Render render;
|
||||
render.Run();
|
||||
daemon.stop();
|
||||
};
|
||||
|
||||
// start daemon and return result
|
||||
bool success = daemon.start(main_loop);
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
// run without daemon: direct execution
|
||||
crossdesk::Render render;
|
||||
render.Run();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
#include "autostart.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <unistd.h>
|
||||
#elif defined(__linux__)
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
static std::string get_home_dir() {
|
||||
const char* home = std::getenv("HOME");
|
||||
if (!home) {
|
||||
return "";
|
||||
}
|
||||
return std::string(home);
|
||||
}
|
||||
|
||||
static bool file_exists(const std::string& path) {
|
||||
return std::filesystem::exists(path) &&
|
||||
std::filesystem::is_regular_file(path);
|
||||
}
|
||||
|
||||
static std::string GetExecutablePath() {
|
||||
#ifdef _WIN32
|
||||
char path[32768];
|
||||
DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path));
|
||||
if (length > 0 && length < sizeof(path)) {
|
||||
return std::string(path);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
char path[1024];
|
||||
uint32_t size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||
char resolved_path[PATH_MAX];
|
||||
if (realpath(path, resolved_path) != nullptr) {
|
||||
return std::string(resolved_path);
|
||||
}
|
||||
return std::string(path);
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
char path[PATH_MAX];
|
||||
ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||||
if (count != -1) {
|
||||
path[count] = '\0';
|
||||
return std::string(path);
|
||||
}
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
// Windows
|
||||
#ifdef _WIN32
|
||||
static constexpr const char* WINDOWS_RUN_KEY =
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
|
||||
static bool windows_enable(const std::string& appName,
|
||||
const std::string& exePath) {
|
||||
if (exePath.empty() || !std::filesystem::exists(exePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY hKey = nullptr;
|
||||
// Use KEY_WRITE to ensure we have write permission
|
||||
LONG result =
|
||||
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_WRITE, &hKey);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string regValue = exePath;
|
||||
if (!exePath.empty() && exePath.find(' ') != std::string::npos) {
|
||||
if (exePath.front() != '"' || exePath.back() != '"') {
|
||||
regValue = "\"" + exePath + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we close the key even if RegSetValueExA fails
|
||||
result = RegSetValueExA(hKey, appName.c_str(), 0, REG_SZ,
|
||||
reinterpret_cast<const BYTE*>(regValue.c_str()),
|
||||
static_cast<DWORD>(regValue.size() + 1));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static bool windows_disable(const std::string& appName) {
|
||||
HKEY hKey = nullptr;
|
||||
LONG result =
|
||||
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_WRITE, &hKey);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = RegDeleteValueA(hKey, appName.c_str());
|
||||
RegCloseKey(hKey);
|
||||
|
||||
// Return true even if the value doesn't exist (already disabled)
|
||||
return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
static bool windows_exists(const std::string& appName) {
|
||||
HKEY hKey = nullptr;
|
||||
LONG result =
|
||||
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_READ, &hKey);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = RegQueryValueExA(hKey, appName.c_str(), nullptr, nullptr, nullptr,
|
||||
nullptr);
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Linux
|
||||
#if defined(__linux__)
|
||||
static std::string linux_desktop_path(const std::string& appName) {
|
||||
std::string home = get_home_dir();
|
||||
if (home.empty()) {
|
||||
return "";
|
||||
}
|
||||
return home + "/.config/autostart/" + appName + ".desktop";
|
||||
}
|
||||
|
||||
static bool linux_enable(const std::string& appName,
|
||||
const std::string& exePath) {
|
||||
std::string home = get_home_dir();
|
||||
if (home.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path dir =
|
||||
std::filesystem::path(home) / ".config" / "autostart";
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(dir, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path = linux_desktop_path(appName);
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(path);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file << "[Desktop Entry]\n";
|
||||
file << "Type=Application\n";
|
||||
file << "Exec=" << exePath << "\n";
|
||||
file << "Hidden=false\n";
|
||||
file << "NoDisplay=false\n";
|
||||
file << "X-GNOME-Autostart-enabled=true\n";
|
||||
file << "Terminal=false\n";
|
||||
file << "StartupNotify=false\n";
|
||||
file << "Name=" << appName << "\n";
|
||||
file.close();
|
||||
|
||||
return file.good();
|
||||
}
|
||||
|
||||
static bool linux_disable(const std::string& appName) {
|
||||
std::string path = linux_desktop_path(appName);
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
return std::filesystem::remove(path, ec) && !ec;
|
||||
}
|
||||
|
||||
static bool linux_exists(const std::string& appName) {
|
||||
std::string path = linux_desktop_path(appName);
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
return file_exists(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
// macOS
|
||||
#ifdef __APPLE__
|
||||
static std::string mac_plist_path(const std::string& appName) {
|
||||
std::string home = get_home_dir();
|
||||
if (home.empty()) {
|
||||
return "";
|
||||
}
|
||||
return home + "/Library/LaunchAgents/" + appName + ".plist";
|
||||
}
|
||||
|
||||
static bool mac_enable(const std::string& appName, const std::string& exePath) {
|
||||
std::string path = mac_plist_path(appName);
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure LaunchAgents directory exists
|
||||
std::filesystem::path dir =
|
||||
std::filesystem::path(get_home_dir()) / "Library" / "LaunchAgents";
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(dir, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(path);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file << R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>)"
|
||||
<< appName << R"(</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>)"
|
||||
<< exePath << R"(</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>)";
|
||||
file.close();
|
||||
|
||||
return file.good();
|
||||
}
|
||||
|
||||
static bool mac_disable(const std::string& appName) {
|
||||
std::string path = mac_plist_path(appName);
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
return std::filesystem::remove(path, ec) && !ec;
|
||||
}
|
||||
|
||||
static bool mac_exists(const std::string& appName) {
|
||||
std::string path = mac_plist_path(appName);
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
return file_exists(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool EnableAutostart(const std::string& appName) {
|
||||
std::string exePath = GetExecutablePath();
|
||||
if (exePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
return windows_enable(appName, exePath);
|
||||
#elif __APPLE__
|
||||
return mac_enable(appName, exePath);
|
||||
#else
|
||||
return linux_enable(appName, exePath);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DisableAutostart(const std::string& appName) {
|
||||
#ifdef _WIN32
|
||||
return windows_disable(appName);
|
||||
#elif __APPLE__
|
||||
return mac_disable(appName);
|
||||
#else
|
||||
return linux_disable(appName);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsAutostartEnabled(const std::string& appName) {
|
||||
#ifdef _WIN32
|
||||
return windows_exists(appName);
|
||||
#elif __APPLE__
|
||||
return mac_exists(appName);
|
||||
#else
|
||||
return linux_exists(appName);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-11-18
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _AUTOSTART_H_
|
||||
#define _AUTOSTART_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
bool EnableAutostart(const std::string& appName);
|
||||
|
||||
bool DisableAutostart(const std::string& appName);
|
||||
|
||||
bool IsAutostartEnabled(const std::string& appName);
|
||||
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-03-14
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ANY_INVOCABLE_H_
|
||||
#define _ANY_INVOCABLE_H_
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
// 简化版的 AnyInvocable
|
||||
template <typename Signature>
|
||||
class AnyInvocable;
|
||||
|
||||
template <typename R, typename... Args>
|
||||
class AnyInvocable<R(Args...)> {
|
||||
public:
|
||||
// 默认构造函数
|
||||
AnyInvocable() = default;
|
||||
|
||||
AnyInvocable(std::nullptr_t) noexcept : callable_(nullptr) {}
|
||||
|
||||
// 构造函数:接受一个可以调用的对象(排除 nullptr)
|
||||
template <typename Callable, typename = std::enable_if_t<!std::is_same_v<
|
||||
std::decay_t<Callable>, std::nullptr_t>>>
|
||||
AnyInvocable(Callable&& callable)
|
||||
: callable_(std::make_unique<CallableWrapper<Callable>>(
|
||||
std::forward<Callable>(callable))) {}
|
||||
|
||||
// 调用运算符(支持 void 和非 void 返回类型)
|
||||
R operator()(Args... args) {
|
||||
if (!callable_) {
|
||||
throw std::bad_function_call();
|
||||
}
|
||||
if constexpr (std::is_void_v<R>) {
|
||||
callable_->Invoke(std::forward<Args>(args)...);
|
||||
} else {
|
||||
return callable_->Invoke(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// 移动构造函数
|
||||
AnyInvocable(AnyInvocable&&) = default;
|
||||
// 移动赋值运算符
|
||||
AnyInvocable& operator=(AnyInvocable&&) = default;
|
||||
|
||||
// 判断是否有效
|
||||
explicit operator bool() const { return static_cast<bool>(callable_); }
|
||||
|
||||
private:
|
||||
// 抽象基类,允许不同类型的可调用对象
|
||||
struct CallableBase {
|
||||
virtual ~CallableBase() = default;
|
||||
virtual R Invoke(Args&&... args) = 0;
|
||||
};
|
||||
|
||||
// 模板派生类:实际存储 callable 对象
|
||||
template <typename Callable>
|
||||
struct CallableWrapper : public CallableBase {
|
||||
CallableWrapper(Callable&& callable)
|
||||
: callable_(std::forward<Callable>(callable)) {}
|
||||
|
||||
R Invoke(Args&&... args) override {
|
||||
if constexpr (std::is_void_v<R>) {
|
||||
callable_(std::forward<Args>(args)...);
|
||||
} else {
|
||||
return callable_(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
Callable callable_;
|
||||
};
|
||||
|
||||
std::unique_ptr<CallableBase> callable_;
|
||||
};
|
||||
|
||||
// 简单的包装函数
|
||||
template <typename R, typename... Args>
|
||||
AnyInvocable<R(Args...)> MakeMoveOnlyFunction(std::function<R(Args...)>&& f) {
|
||||
return AnyInvocable<R(Args...)>(std::move(f));
|
||||
}
|
||||
|
||||
#endif // _ANY_INVOCABLE_H_
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_ARRAY_VIEW_H_
|
||||
#define API_ARRAY_VIEW_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
#include "rtc_base/type_traits.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
// tl;dr: rtc::ArrayView is the same thing as gsl::span from the Guideline
|
||||
// Support Library.
|
||||
//
|
||||
// Many functions read from or write to arrays. The obvious way to do this is
|
||||
// to use two arguments, a pointer to the first element and an element count:
|
||||
//
|
||||
// bool Contains17(const int* arr, size_t size) {
|
||||
// for (size_t i = 0; i < size; ++i) {
|
||||
// if (arr[i] == 17)
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// This is flexible, since it doesn't matter how the array is stored (C array,
|
||||
// std::vector, rtc::Buffer, ...), but it's error-prone because the caller has
|
||||
// to correctly specify the array length:
|
||||
//
|
||||
// Contains17(arr, arraysize(arr)); // C array
|
||||
// Contains17(arr.data(), arr.size()); // std::vector
|
||||
// Contains17(arr, size); // pointer + size
|
||||
// ...
|
||||
//
|
||||
// It's also kind of messy to have two separate arguments for what is
|
||||
// conceptually a single thing.
|
||||
//
|
||||
// Enter rtc::ArrayView<T>. It contains a T pointer (to an array it doesn't
|
||||
// own) and a count, and supports the basic things you'd expect, such as
|
||||
// indexing and iteration. It allows us to write our function like this:
|
||||
//
|
||||
// bool Contains17(rtc::ArrayView<const int> arr) {
|
||||
// for (auto e : arr) {
|
||||
// if (e == 17)
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// And even better, because a bunch of things will implicitly convert to
|
||||
// ArrayView, we can call it like this:
|
||||
//
|
||||
// Contains17(arr); // C array
|
||||
// Contains17(arr); // std::vector
|
||||
// Contains17(rtc::ArrayView<int>(arr, size)); // pointer + size
|
||||
// Contains17(nullptr); // nullptr -> empty ArrayView
|
||||
// ...
|
||||
//
|
||||
// ArrayView<T> stores both a pointer and a size, but you may also use
|
||||
// ArrayView<T, N>, which has a size that's fixed at compile time (which means
|
||||
// it only has to store the pointer).
|
||||
//
|
||||
// One important point is that ArrayView<T> and ArrayView<const T> are
|
||||
// different types, which allow and don't allow mutation of the array elements,
|
||||
// respectively. The implicit conversions work just like you'd hope, so that
|
||||
// e.g. vector<int> will convert to either ArrayView<int> or ArrayView<const
|
||||
// int>, but const vector<int> will convert only to ArrayView<const int>.
|
||||
// (ArrayView itself can be the source type in such conversions, so
|
||||
// ArrayView<int> will convert to ArrayView<const int>.)
|
||||
//
|
||||
// Note: ArrayView is tiny (just a pointer and a count if variable-sized, just
|
||||
// a pointer if fix-sized) and trivially copyable, so it's probably cheaper to
|
||||
// pass it by value than by const reference.
|
||||
|
||||
namespace array_view_internal {
|
||||
|
||||
// Magic constant for indicating that the size of an ArrayView is variable
|
||||
// instead of fixed.
|
||||
enum : std::ptrdiff_t { kArrayViewVarSize = -4711 };
|
||||
|
||||
// Base class for ArrayViews of fixed nonzero size.
|
||||
template <typename T, std::ptrdiff_t Size>
|
||||
class ArrayViewBase {
|
||||
static_assert(Size > 0, "ArrayView size must be variable or non-negative");
|
||||
|
||||
public:
|
||||
ArrayViewBase(T* data, size_t /* size */) : data_(data) {}
|
||||
|
||||
static constexpr size_t size() { return Size; }
|
||||
static constexpr bool empty() { return false; }
|
||||
T* data() const { return data_; }
|
||||
|
||||
protected:
|
||||
static constexpr bool fixed_size() { return true; }
|
||||
|
||||
private:
|
||||
T* data_;
|
||||
};
|
||||
|
||||
// Specialized base class for ArrayViews of fixed zero size.
|
||||
template <typename T>
|
||||
class ArrayViewBase<T, 0> {
|
||||
public:
|
||||
explicit ArrayViewBase(T* /* data */, size_t /* size */) {}
|
||||
|
||||
static constexpr size_t size() { return 0; }
|
||||
static constexpr bool empty() { return true; }
|
||||
T* data() const { return nullptr; }
|
||||
|
||||
protected:
|
||||
static constexpr bool fixed_size() { return true; }
|
||||
};
|
||||
|
||||
// Specialized base class for ArrayViews of variable size.
|
||||
template <typename T>
|
||||
class ArrayViewBase<T, array_view_internal::kArrayViewVarSize> {
|
||||
public:
|
||||
ArrayViewBase(T* data, size_t size)
|
||||
: data_(size == 0 ? nullptr : data), size_(size) {}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
T* data() const { return data_; }
|
||||
|
||||
protected:
|
||||
static constexpr bool fixed_size() { return false; }
|
||||
|
||||
private:
|
||||
T* data_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
} // namespace array_view_internal
|
||||
|
||||
template <typename T,
|
||||
std::ptrdiff_t Size = array_view_internal::kArrayViewVarSize>
|
||||
class ArrayView final : public array_view_internal::ArrayViewBase<T, Size> {
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
using const_iterator = const T*;
|
||||
|
||||
// Construct an ArrayView from a pointer and a length.
|
||||
template <typename U>
|
||||
ArrayView(U* data, size_t size)
|
||||
: array_view_internal::ArrayViewBase<T, Size>::ArrayViewBase(data, size) {
|
||||
}
|
||||
|
||||
// Construct an empty ArrayView. Note that fixed-size ArrayViews of size > 0
|
||||
// cannot be empty.
|
||||
ArrayView() : ArrayView(nullptr, 0) {}
|
||||
ArrayView(std::nullptr_t) // NOLINT
|
||||
: ArrayView() {}
|
||||
ArrayView(std::nullptr_t, size_t size)
|
||||
: ArrayView(static_cast<T*>(nullptr), size) {
|
||||
static_assert(Size == 0 || Size == array_view_internal::kArrayViewVarSize,
|
||||
"");
|
||||
}
|
||||
|
||||
// Construct an ArrayView from a C-style array.
|
||||
template <typename U, size_t N>
|
||||
ArrayView(U (&array)[N]) // NOLINT
|
||||
: ArrayView(array, N) {
|
||||
static_assert(Size == N || Size == array_view_internal::kArrayViewVarSize,
|
||||
"Array size must match ArrayView size");
|
||||
}
|
||||
|
||||
// (Only if size is fixed.) Construct a fixed size ArrayView<T, N> from a
|
||||
// non-const std::array instance. For an ArrayView with variable size, the
|
||||
// used ctor is ArrayView(U& u) instead.
|
||||
template <typename U, size_t N,
|
||||
typename std::enable_if<
|
||||
Size == static_cast<std::ptrdiff_t>(N)>::type* = nullptr>
|
||||
ArrayView(std::array<U, N>& u) // NOLINT
|
||||
: ArrayView(u.data(), u.size()) {}
|
||||
|
||||
// (Only if size is fixed.) Construct a fixed size ArrayView<T, N> where T is
|
||||
// const from a const(expr) std::array instance. For an ArrayView with
|
||||
// variable size, the used ctor is ArrayView(U& u) instead.
|
||||
template <typename U, size_t N,
|
||||
typename std::enable_if<
|
||||
Size == static_cast<std::ptrdiff_t>(N)>::type* = nullptr>
|
||||
ArrayView(const std::array<U, N>& u) // NOLINT
|
||||
: ArrayView(u.data(), u.size()) {}
|
||||
|
||||
// (Only if size is fixed.) Construct an ArrayView from any type U that has a
|
||||
// static constexpr size() method whose return value is equal to Size, and a
|
||||
// data() method whose return value converts implicitly to T*. In particular,
|
||||
// this means we allow conversion from ArrayView<T, N> to ArrayView<const T,
|
||||
// N>, but not the other way around. We also don't allow conversion from
|
||||
// ArrayView<T> to ArrayView<T, N>, or from ArrayView<T, M> to ArrayView<T,
|
||||
// N> when M != N.
|
||||
template <typename U, typename std::enable_if<
|
||||
Size != array_view_internal::kArrayViewVarSize &&
|
||||
HasDataAndSize<U, T>::value>::type* = nullptr>
|
||||
ArrayView(U& u) // NOLINT
|
||||
: ArrayView(u.data(), u.size()) {
|
||||
static_assert(U::size() == Size, "Sizes must match exactly");
|
||||
}
|
||||
template <typename U, typename std::enable_if<
|
||||
Size != array_view_internal::kArrayViewVarSize &&
|
||||
HasDataAndSize<U, T>::value>::type* = nullptr>
|
||||
ArrayView(const U& u) // NOLINT(runtime/explicit)
|
||||
: ArrayView(u.data(), u.size()) {
|
||||
static_assert(U::size() == Size, "Sizes must match exactly");
|
||||
}
|
||||
|
||||
// (Only if size is variable.) Construct an ArrayView from any type U that
|
||||
// has a size() method whose return value converts implicitly to size_t, and
|
||||
// a data() method whose return value converts implicitly to T*. In
|
||||
// particular, this means we allow conversion from ArrayView<T> to
|
||||
// ArrayView<const T>, but not the other way around. Other allowed
|
||||
// conversions include
|
||||
// ArrayView<T, N> to ArrayView<T> or ArrayView<const T>,
|
||||
// std::vector<T> to ArrayView<T> or ArrayView<const T>,
|
||||
// const std::vector<T> to ArrayView<const T>,
|
||||
// rtc::Buffer to ArrayView<uint8_t> or ArrayView<const uint8_t>, and
|
||||
// const rtc::Buffer to ArrayView<const uint8_t>.
|
||||
template <typename U, typename std::enable_if<
|
||||
Size == array_view_internal::kArrayViewVarSize &&
|
||||
HasDataAndSize<U, T>::value>::type* = nullptr>
|
||||
ArrayView(U& u) // NOLINT
|
||||
: ArrayView(u.data(), u.size()) {}
|
||||
template <typename U, typename std::enable_if<
|
||||
Size == array_view_internal::kArrayViewVarSize &&
|
||||
HasDataAndSize<U, T>::value>::type* = nullptr>
|
||||
ArrayView(const U& u) // NOLINT(runtime/explicit)
|
||||
: ArrayView(u.data(), u.size()) {}
|
||||
|
||||
// Indexing and iteration. These allow mutation even if the ArrayView is
|
||||
// const, because the ArrayView doesn't own the array. (To prevent mutation,
|
||||
// use a const element type.)
|
||||
T& operator[](size_t idx) const { return this->data()[idx]; }
|
||||
T* begin() const { return this->data(); }
|
||||
T* end() const { return this->data() + this->size(); }
|
||||
const T* cbegin() const { return this->data(); }
|
||||
const T* cend() const { return this->data() + this->size(); }
|
||||
std::reverse_iterator<T*> rbegin() const {
|
||||
return std::make_reverse_iterator(end());
|
||||
}
|
||||
std::reverse_iterator<T*> rend() const {
|
||||
return std::make_reverse_iterator(begin());
|
||||
}
|
||||
std::reverse_iterator<const T*> crbegin() const {
|
||||
return std::make_reverse_iterator(cend());
|
||||
}
|
||||
std::reverse_iterator<const T*> crend() const {
|
||||
return std::make_reverse_iterator(cbegin());
|
||||
}
|
||||
|
||||
ArrayView<T> subview(size_t offset, size_t size) const {
|
||||
return offset < this->size()
|
||||
? ArrayView<T>(this->data() + offset,
|
||||
std::min(size, this->size() - offset))
|
||||
: ArrayView<T>();
|
||||
}
|
||||
ArrayView<T> subview(size_t offset) const {
|
||||
return subview(offset, this->size());
|
||||
}
|
||||
};
|
||||
|
||||
// Comparing two ArrayViews compares their (pointer,size) pairs; it does *not*
|
||||
// dereference the pointers.
|
||||
template <typename T, std::ptrdiff_t Size1, std::ptrdiff_t Size2>
|
||||
bool operator==(const ArrayView<T, Size1>& a, const ArrayView<T, Size2>& b) {
|
||||
return a.data() == b.data() && a.size() == b.size();
|
||||
}
|
||||
template <typename T, std::ptrdiff_t Size1, std::ptrdiff_t Size2>
|
||||
bool operator!=(const ArrayView<T, Size1>& a, const ArrayView<T, Size2>& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
// Variable-size ArrayViews are the size of two pointers; fixed-size ArrayViews
|
||||
// are the size of one pointer. (And as a special case, fixed-size ArrayViews
|
||||
// of size 0 require no storage.)
|
||||
static_assert(sizeof(ArrayView<int>) == 2 * sizeof(int*), "");
|
||||
static_assert(sizeof(ArrayView<int, 17>) == sizeof(int*), "");
|
||||
static_assert(std::is_empty<ArrayView<int, 0>>::value, "");
|
||||
|
||||
template <typename T>
|
||||
inline ArrayView<T> MakeArrayView(T* data, size_t size) {
|
||||
return ArrayView<T>(data, size);
|
||||
}
|
||||
|
||||
// Only for primitive types that have the same size and aligment.
|
||||
// Allow reinterpret cast of the array view to another primitive type of the
|
||||
// same size.
|
||||
// Template arguments order is (U, T, Size) to allow deduction of the template
|
||||
// arguments in client calls: reinterpret_array_view<target_type>(array_view).
|
||||
template <typename U, typename T, std::ptrdiff_t Size>
|
||||
inline ArrayView<U, Size> reinterpret_array_view(ArrayView<T, Size> view) {
|
||||
static_assert(sizeof(U) == sizeof(T) && alignof(U) == alignof(T),
|
||||
"ArrayView reinterpret_cast is only supported for casting "
|
||||
"between views that represent the same chunk of memory.");
|
||||
static_assert(
|
||||
std::is_fundamental<T>::value && std::is_fundamental<U>::value,
|
||||
"ArrayView reinterpret_cast is only supported for casting between "
|
||||
"fundamental types.");
|
||||
return ArrayView<U, Size>(reinterpret_cast<U*>(view.data()), view.size());
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif // API_ARRAY_VIEW_H_
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/clock/clock.h"
|
||||
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
int64_t NtpOffsetUsCalledOnce() {
|
||||
constexpr int64_t kNtpJan1970Sec = 2208988800;
|
||||
int64_t clock_time = rtc::TimeMicros();
|
||||
int64_t utc_time = rtc::TimeUTCMicros();
|
||||
return utc_time - clock_time + kNtpJan1970Sec * rtc::kNumMicrosecsPerSec;
|
||||
}
|
||||
|
||||
NtpTime TimeMicrosToNtp(int64_t time_us) {
|
||||
static int64_t ntp_offset_us = NtpOffsetUsCalledOnce();
|
||||
|
||||
int64_t time_ntp_us = time_us + ntp_offset_us;
|
||||
|
||||
// Convert seconds to uint32 through uint64 for a well-defined cast.
|
||||
// A wrap around, which will happen in 2036, is expected for NTP time.
|
||||
uint32_t ntp_seconds =
|
||||
static_cast<uint64_t>(time_ntp_us / rtc::kNumMicrosecsPerSec);
|
||||
|
||||
// Scale fractions of the second to NTP resolution.
|
||||
constexpr int64_t kNtpFractionsInSecond = 1LL << 32;
|
||||
int64_t us_fractions = time_ntp_us % rtc::kNumMicrosecsPerSec;
|
||||
uint32_t ntp_fractions =
|
||||
us_fractions * kNtpFractionsInSecond / rtc::kNumMicrosecsPerSec;
|
||||
|
||||
return NtpTime(ntp_seconds, ntp_fractions);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class WebrtcClock : public Clock {
|
||||
public:
|
||||
WebrtcClock(std::shared_ptr<SystemClock> system_clock)
|
||||
: system_clock_(system_clock) {}
|
||||
WebrtcClock() = delete;
|
||||
|
||||
Timestamp CurrentTime() override {
|
||||
return Timestamp::Micros(system_clock_->CurrentTimeUs());
|
||||
}
|
||||
|
||||
NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) override {
|
||||
int64_t time_us = timestamp.us();
|
||||
constexpr int64_t kNtpJan1970Sec = 2208988800;
|
||||
int64_t clock_time = system_clock_->CurrentTimeUs();
|
||||
int64_t utc_time = system_clock_->CurrentUtcTimeUs();
|
||||
static int64_t ntp_offset_us =
|
||||
utc_time - clock_time + kNtpJan1970Sec * rtc::kNumMicrosecsPerSec;
|
||||
|
||||
int64_t time_ntp_us = time_us + ntp_offset_us;
|
||||
|
||||
// Convert seconds to uint32 through uint64 for a well-defined cast.
|
||||
// A wrap around, which will happen in 2036, is expected for NTP time.
|
||||
uint32_t ntp_seconds =
|
||||
static_cast<uint64_t>(time_ntp_us / rtc::kNumMicrosecsPerSec);
|
||||
|
||||
// Scale fractions of the second to NTP resolution.
|
||||
constexpr int64_t kNtpFractionsInSecond = 1LL << 32;
|
||||
int64_t us_fractions = time_ntp_us % rtc::kNumMicrosecsPerSec;
|
||||
uint32_t ntp_fractions =
|
||||
us_fractions * kNtpFractionsInSecond / rtc::kNumMicrosecsPerSec;
|
||||
|
||||
return NtpTime(ntp_seconds, ntp_fractions);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SystemClock> system_clock_;
|
||||
};
|
||||
|
||||
Clock* Clock::GetWebrtcClock(std::shared_ptr<SystemClock> system_clock) {
|
||||
static Clock* const clock = new WebrtcClock(system_clock);
|
||||
return clock;
|
||||
}
|
||||
|
||||
std::shared_ptr<Clock> Clock::GetWebrtcClockShared(
|
||||
std::shared_ptr<SystemClock> system_clock) {
|
||||
return std::make_shared<WebrtcClock>(system_clock);
|
||||
}
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef SYSTEM_WRAPPERS_INCLUDE_CLOCK_H_
|
||||
#define SYSTEM_WRAPPERS_INCLUDE_CLOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "api/ntp/ntp_time.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "clock/system_clock.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// January 1970, in NTP seconds.
|
||||
const uint32_t kNtpJan1970 = 2208988800UL;
|
||||
|
||||
// Magic NTP fractional unit.
|
||||
const double kMagicNtpFractionalUnit = 4.294967296E+9;
|
||||
|
||||
// A clock interface that allows reading of absolute and relative timestamps.
|
||||
class Clock {
|
||||
public:
|
||||
virtual ~Clock() {}
|
||||
|
||||
// Return a timestamp relative to an unspecified epoch.
|
||||
virtual Timestamp CurrentTime() = 0;
|
||||
int64_t TimeInMilliseconds() { return CurrentTime().ms(); }
|
||||
int64_t TimeInMicroseconds() { return CurrentTime().us(); }
|
||||
|
||||
// Retrieve an NTP absolute timestamp (with an epoch of Jan 1, 1900).
|
||||
NtpTime CurrentNtpTime() { return ConvertTimestampToNtpTime(CurrentTime()); }
|
||||
int64_t CurrentNtpInMilliseconds() { return CurrentNtpTime().ToMs(); }
|
||||
|
||||
// Converts between a relative timestamp returned by this clock, to NTP time.
|
||||
virtual NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) = 0;
|
||||
int64_t ConvertTimestampToNtpTimeInMilliseconds(int64_t timestamp_ms) {
|
||||
return ConvertTimestampToNtpTime(Timestamp::Millis(timestamp_ms)).ToMs();
|
||||
}
|
||||
|
||||
// Converts NtpTime to a Timestamp with UTC epoch.
|
||||
// A `Minus Infinity` Timestamp is returned if the NtpTime is invalid.
|
||||
static Timestamp NtpToUtc(NtpTime ntp_time) {
|
||||
if (!ntp_time.Valid()) {
|
||||
return Timestamp::MinusInfinity();
|
||||
}
|
||||
// Seconds since UTC epoch.
|
||||
int64_t time = ntp_time.seconds() - kNtpJan1970;
|
||||
// Microseconds since UTC epoch (not including NTP fraction)
|
||||
time = time * 1'000'000;
|
||||
// Fractions part of the NTP time, in microseconds.
|
||||
int64_t time_fraction =
|
||||
DivideRoundToNearest(int64_t{ntp_time.fractions()} * 1'000'000,
|
||||
NtpTime::kFractionsPerSecond);
|
||||
return Timestamp::Micros(time + time_fraction);
|
||||
}
|
||||
|
||||
// Returns an instance of the real-time system clock implementation.
|
||||
static Clock* GetWebrtcClock(std::shared_ptr<SystemClock> system_clock);
|
||||
static std::shared_ptr<Clock> GetWebrtcClockShared(
|
||||
std::shared_ptr<SystemClock> system_clock);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // SYSTEM_WRAPPERS_INCLUDE_CLOCK_H_
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_FUNCTION_VIEW_H_
|
||||
#define API_FUNCTION_VIEW_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// Just like std::function, FunctionView will wrap any callable and hide its
|
||||
// actual type, exposing only its signature. But unlike std::function,
|
||||
// FunctionView doesn't own its callable---it just points to it. Thus, it's a
|
||||
// good choice mainly as a function argument when the callable argument will
|
||||
// not be called again once the function has returned.
|
||||
//
|
||||
// Its constructors are implicit, so that callers won't have to convert lambdas
|
||||
// and other callables to FunctionView<Blah(Blah, Blah)> explicitly. This is
|
||||
// safe because FunctionView is only a reference to the real callable.
|
||||
//
|
||||
// Example use:
|
||||
//
|
||||
// void SomeFunction(rtc::FunctionView<int(int)> index_transform);
|
||||
// ...
|
||||
// SomeFunction([](int i) { return 2 * i + 1; });
|
||||
//
|
||||
// Note: FunctionView is tiny (essentially just two pointers) and trivially
|
||||
// copyable, so it's probably cheaper to pass it by value than by const
|
||||
// reference.
|
||||
|
||||
namespace rtc {
|
||||
|
||||
template <typename T>
|
||||
class FunctionView; // Undefined.
|
||||
|
||||
template <typename RetT, typename... ArgT>
|
||||
class FunctionView<RetT(ArgT...)> final {
|
||||
public:
|
||||
// Constructor for lambdas and other callables; it accepts every type of
|
||||
// argument except those noted in its enable_if call.
|
||||
template <
|
||||
typename F,
|
||||
typename std::enable_if<
|
||||
// Not for function pointers; we have another constructor for that
|
||||
// below.
|
||||
!std::is_function<typename std::remove_pointer<
|
||||
typename std::remove_reference<F>::type>::type>::value &&
|
||||
|
||||
// Not for nullptr; we have another constructor for that below.
|
||||
!std::is_same<std::nullptr_t,
|
||||
typename std::remove_cv<F>::type>::value &&
|
||||
|
||||
// Not for FunctionView objects; we have another constructor for that
|
||||
// (the implicitly declared copy constructor).
|
||||
!std::is_same<FunctionView,
|
||||
typename std::remove_cv<typename std::remove_reference<
|
||||
F>::type>::type>::value>::type* = nullptr>
|
||||
FunctionView(F&& f)
|
||||
: call_(CallVoidPtr<typename std::remove_reference<F>::type>) {
|
||||
f_.void_ptr = &f;
|
||||
}
|
||||
|
||||
// Constructor that accepts function pointers. If the argument is null, the
|
||||
// result is an empty FunctionView.
|
||||
template <
|
||||
typename F,
|
||||
typename std::enable_if<std::is_function<typename std::remove_pointer<
|
||||
typename std::remove_reference<F>::type>::type>::value>::type* =
|
||||
nullptr>
|
||||
FunctionView(F&& f)
|
||||
: call_(f ? CallFunPtr<typename std::remove_pointer<F>::type> : nullptr) {
|
||||
f_.fun_ptr = reinterpret_cast<void (*)()>(f);
|
||||
}
|
||||
|
||||
// Constructor that accepts nullptr. It creates an empty FunctionView.
|
||||
template <typename F, typename std::enable_if<std::is_same<
|
||||
std::nullptr_t, typename std::remove_cv<F>::type>::
|
||||
value>::type* = nullptr>
|
||||
FunctionView(F&& /* f */) : call_(nullptr) {}
|
||||
|
||||
// Default constructor. Creates an empty FunctionView.
|
||||
FunctionView() : call_(nullptr) {}
|
||||
|
||||
RetT operator()(ArgT... args) const {
|
||||
return call_(f_, std::forward<ArgT>(args)...);
|
||||
}
|
||||
|
||||
// Returns true if we have a function, false if we don't (i.e., we're null).
|
||||
explicit operator bool() const { return !!call_; }
|
||||
|
||||
private:
|
||||
union VoidUnion {
|
||||
void* void_ptr;
|
||||
void (*fun_ptr)();
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
static RetT CallVoidPtr(VoidUnion vu, ArgT... args) {
|
||||
return (*static_cast<F*>(vu.void_ptr))(std::forward<ArgT>(args)...);
|
||||
}
|
||||
template <typename F>
|
||||
static RetT CallFunPtr(VoidUnion vu, ArgT... args) {
|
||||
return (reinterpret_cast<typename std::add_pointer<F>::type>(vu.fun_ptr))(
|
||||
std::forward<ArgT>(args)...);
|
||||
}
|
||||
|
||||
// A pointer to the callable thing, with type information erased. It's a
|
||||
// union because we have to use separate types depending on if the callable
|
||||
// thing is a function pointer or something else.
|
||||
VoidUnion f_;
|
||||
|
||||
// Pointer to a dispatch function that knows the type of the callable thing
|
||||
// that's stored in f_, and how to call it. A FunctionView object is empty
|
||||
// (null) iff call_ is null.
|
||||
RetT (*call_)(VoidUnion, ArgT...);
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif // API_FUNCTION_VIEW_H_
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_MEDIA_TYPES_H_
|
||||
#define API_MEDIA_TYPES_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
// The cricket and webrtc have separate definitions for what a media type is.
|
||||
// They're not compatible. Watch out for this.
|
||||
|
||||
namespace cricket {
|
||||
|
||||
enum MediaType {
|
||||
MEDIA_TYPE_AUDIO,
|
||||
MEDIA_TYPE_VIDEO,
|
||||
MEDIA_TYPE_DATA,
|
||||
MEDIA_TYPE_UNSUPPORTED
|
||||
};
|
||||
|
||||
extern const char kMediaTypeAudio[];
|
||||
extern const char kMediaTypeVideo[];
|
||||
extern const char kMediaTypeData[];
|
||||
|
||||
std::string MediaTypeToString(MediaType type);
|
||||
|
||||
} // namespace cricket
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
enum class MediaType { ANY, AUDIO, VIDEO, DATA };
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_MEDIA_TYPES_H_
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_NETWORK_STATE_PREDICTOR_H_
|
||||
#define API_NETWORK_STATE_PREDICTOR_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "api/transport/bandwidth_usage.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// TODO(yinwa): work in progress. API in class NetworkStatePredictor should not
|
||||
// be used by other users until this comment is removed.
|
||||
|
||||
// NetworkStatePredictor predict network state based on current network metrics.
|
||||
// Usage:
|
||||
// Setup by calling Initialize.
|
||||
// For each update, call Update. Update returns network state
|
||||
// prediction.
|
||||
class NetworkStatePredictor {
|
||||
public:
|
||||
virtual ~NetworkStatePredictor() {}
|
||||
|
||||
// Returns current network state prediction.
|
||||
// Inputs: send_time_ms - packet send time.
|
||||
// arrival_time_ms - packet arrival time.
|
||||
// network_state - computed network state.
|
||||
virtual BandwidthUsage Update(int64_t send_time_ms,
|
||||
int64_t arrival_time_ms,
|
||||
BandwidthUsage network_state) = 0;
|
||||
};
|
||||
|
||||
class NetworkStatePredictorFactoryInterface {
|
||||
public:
|
||||
virtual std::unique_ptr<NetworkStatePredictor>
|
||||
CreateNetworkStatePredictor() = 0;
|
||||
virtual ~NetworkStatePredictorFactoryInterface() = default;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_NETWORK_STATE_PREDICTOR_H_
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_
|
||||
#define SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class NtpTime {
|
||||
public:
|
||||
static constexpr uint64_t kFractionsPerSecond = 0x100000000;
|
||||
NtpTime() : value_(0) {}
|
||||
explicit NtpTime(uint64_t value) : value_(value) {}
|
||||
NtpTime(uint32_t seconds, uint32_t fractions)
|
||||
: value_(seconds * kFractionsPerSecond + fractions) {}
|
||||
|
||||
NtpTime(const NtpTime&) = default;
|
||||
NtpTime& operator=(const NtpTime&) = default;
|
||||
explicit operator uint64_t() const { return value_; }
|
||||
|
||||
void Set(uint32_t seconds, uint32_t fractions) {
|
||||
value_ = seconds * kFractionsPerSecond + fractions;
|
||||
}
|
||||
void Reset() { value_ = 0; }
|
||||
|
||||
int64_t ToMs() const {
|
||||
static constexpr double kNtpFracPerMs = 4.294967296E6; // 2^32 / 1000.
|
||||
const double frac_ms = static_cast<double>(fractions()) / kNtpFracPerMs;
|
||||
return 1000 * static_cast<int64_t>(seconds()) +
|
||||
static_cast<int64_t>(frac_ms + 0.5);
|
||||
}
|
||||
// NTP standard (RFC1305, section 3.1) explicitly state value 0 is invalid.
|
||||
bool Valid() const { return value_ != 0; }
|
||||
|
||||
uint32_t seconds() const {
|
||||
return rtc::dchecked_cast<uint32_t>(value_ / kFractionsPerSecond);
|
||||
}
|
||||
uint32_t fractions() const {
|
||||
return rtc::dchecked_cast<uint32_t>(value_ % kFractionsPerSecond);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
inline bool operator==(const NtpTime& n1, const NtpTime& n2) {
|
||||
return static_cast<uint64_t>(n1) == static_cast<uint64_t>(n2);
|
||||
}
|
||||
inline bool operator!=(const NtpTime& n1, const NtpTime& n2) {
|
||||
return !(n1 == n2);
|
||||
}
|
||||
|
||||
// Converts `int64_t` milliseconds to Q32.32-formatted fixed-point seconds.
|
||||
// Performs clamping if the result overflows or underflows.
|
||||
inline int64_t Int64MsToQ32x32(int64_t milliseconds) {
|
||||
// TODO(bugs.webrtc.org/10893): Change to use `rtc::saturated_cast` once the
|
||||
// bug has been fixed.
|
||||
double result =
|
||||
std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0));
|
||||
|
||||
// Explicitly cast values to double to avoid implicit conversion warnings
|
||||
// The conversion of the std::numeric_limits<int64_t>::max() triggers
|
||||
// -Wimplicit-int-float-conversion warning in clang 10.0.0 without explicit
|
||||
// cast
|
||||
if (result <= static_cast<double>(std::numeric_limits<int64_t>::min())) {
|
||||
return std::numeric_limits<int64_t>::min();
|
||||
}
|
||||
|
||||
if (result >= static_cast<double>(std::numeric_limits<int64_t>::max())) {
|
||||
return std::numeric_limits<int64_t>::max();
|
||||
}
|
||||
|
||||
return rtc::dchecked_cast<int64_t>(result);
|
||||
}
|
||||
|
||||
// Converts `int64_t` milliseconds to UQ32.32-formatted fixed-point seconds.
|
||||
// Performs clamping if the result overflows or underflows.
|
||||
inline uint64_t Int64MsToUQ32x32(int64_t milliseconds) {
|
||||
// TODO(bugs.webrtc.org/10893): Change to use `rtc::saturated_cast` once the
|
||||
// bug has been fixed.
|
||||
double result =
|
||||
std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0));
|
||||
|
||||
// Explicitly cast values to double to avoid implicit conversion warnings
|
||||
// The conversion of the std::numeric_limits<int64_t>::max() triggers
|
||||
// -Wimplicit-int-float-conversion warning in clang 10.0.0 without explicit
|
||||
// cast
|
||||
if (result <= static_cast<double>(std::numeric_limits<uint64_t>::min())) {
|
||||
return std::numeric_limits<uint64_t>::min();
|
||||
}
|
||||
|
||||
if (result >= static_cast<double>(std::numeric_limits<uint64_t>::max())) {
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
return rtc::dchecked_cast<uint64_t>(result);
|
||||
}
|
||||
|
||||
// Converts Q32.32-formatted fixed-point seconds to `int64_t` milliseconds.
|
||||
inline int64_t Q32x32ToInt64Ms(int64_t q32x32) {
|
||||
return rtc::dchecked_cast<int64_t>(
|
||||
std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond)));
|
||||
}
|
||||
|
||||
// Converts UQ32.32-formatted fixed-point seconds to `int64_t` milliseconds.
|
||||
inline int64_t UQ32x32ToInt64Ms(uint64_t q32x32) {
|
||||
return rtc::dchecked_cast<int64_t>(
|
||||
std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond)));
|
||||
}
|
||||
|
||||
// Converts UQ32.32-formatted fixed-point seconds to `int64_t` microseconds.
|
||||
inline int64_t UQ32x32ToInt64Us(uint64_t q32x32) {
|
||||
return rtc::dchecked_cast<int64_t>(
|
||||
std::round(q32x32 * (1'000'000.0 / NtpTime::kFractionsPerSecond)));
|
||||
}
|
||||
|
||||
// Converts Q32.32-formatted fixed-point seconds to `int64_t` microseconds.
|
||||
inline int64_t Q32x32ToInt64Us(int64_t q32x32) {
|
||||
return rtc::dchecked_cast<int64_t>(
|
||||
std::round(q32x32 * (1'000'000.0 / NtpTime::kFractionsPerSecond)));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/ntp/ntp_time_util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
#include "rtc_base/numerics/divide_round.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
uint32_t SaturatedToCompactNtp(TimeDelta delta) {
|
||||
constexpr uint32_t kMaxCompactNtp = 0xFFFFFFFF;
|
||||
constexpr int kCompactNtpInSecond = 0x10000;
|
||||
if (delta <= TimeDelta::Zero()) return 0;
|
||||
if (delta.us() >=
|
||||
kMaxCompactNtp * rtc::kNumMicrosecsPerSec / kCompactNtpInSecond)
|
||||
return kMaxCompactNtp;
|
||||
// To convert to compact ntp need to divide by 1e6 to get seconds,
|
||||
// then multiply by 0x10000 to get the final result.
|
||||
// To avoid float operations, multiplication and division swapped.
|
||||
return DivideRoundToNearest(delta.us() * kCompactNtpInSecond,
|
||||
rtc::kNumMicrosecsPerSec);
|
||||
}
|
||||
|
||||
TimeDelta CompactNtpIntervalToTimeDelta(uint32_t compact_ntp_interval) {
|
||||
// Convert to 64bit value to avoid multiplication overflow.
|
||||
int64_t value = int64_t{compact_ntp_interval};
|
||||
if (compact_ntp_interval > 0x8000'0000) {
|
||||
value -= (int64_t{1} << 32);
|
||||
}
|
||||
// To convert to TimeDelta need to divide by 2^16 to get seconds,
|
||||
// then multiply by 1'000'000 to get microseconds. To avoid float operations,
|
||||
// multiplication and division are swapped.
|
||||
int64_t us = DivideRoundToNearest(value * rtc::kNumMicrosecsPerSec, 1 << 16);
|
||||
return TimeDelta::Micros(us);
|
||||
}
|
||||
|
||||
TimeDelta CompactNtpRttToTimeDelta(uint32_t compact_ntp_interval) {
|
||||
static constexpr TimeDelta kMinRtt = TimeDelta::Millis(1);
|
||||
// Interval to convert expected to be positive, e.g. RTT or delay.
|
||||
// Because interval can be derived from non-monotonic ntp clock,
|
||||
// it might become negative that is indistinguishable from very large values.
|
||||
// Since very large RTT/delay is less likely than non-monotonic ntp clock,
|
||||
// such value is considered negative and converted to minimum value of 1ms.
|
||||
// Small RTT value is considered too good to be true and increased to 1ms.
|
||||
return std::max(CompactNtpIntervalToTimeDelta(compact_ntp_interval), kMinRtt);
|
||||
}
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_RTP_RTCP_SOURCE_NTP_TIME_UTIL_H_
|
||||
#define MODULES_RTP_RTCP_SOURCE_NTP_TIME_UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api/ntp/ntp_time.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Helper function for compact ntp representation:
|
||||
// RFC 3550, Section 4. Time Format.
|
||||
// Wallclock time is represented using the timestamp format of
|
||||
// the Network Time Protocol (NTP).
|
||||
// ...
|
||||
// In some fields where a more compact representation is
|
||||
// appropriate, only the middle 32 bits are used; that is, the low 16
|
||||
// bits of the integer part and the high 16 bits of the fractional part.
|
||||
inline uint32_t CompactNtp(NtpTime ntp) {
|
||||
return (ntp.seconds() << 16) | (ntp.fractions() >> 16);
|
||||
}
|
||||
|
||||
// Converts interval to compact ntp (1/2^16 seconds) resolution.
|
||||
// Negative values converted to 0, Overlarge values converted to max uint32_t.
|
||||
uint32_t SaturatedToCompactNtp(TimeDelta delta);
|
||||
|
||||
// Convert interval to the NTP time resolution (1/2^32 seconds ~= 0.2 ns).
|
||||
// For deltas with absolute value larger than 35 minutes result is unspecified.
|
||||
inline constexpr int64_t ToNtpUnits(TimeDelta delta) {
|
||||
// For better precision `delta` is taken with best TimeDelta precision (us),
|
||||
// then multiplaction and conversion to seconds are swapped to avoid float
|
||||
// arithmetic.
|
||||
// 2^31 us ~= 35.8 minutes.
|
||||
return (rtc::saturated_cast<int32_t>(delta.us()) * (int64_t{1} << 32)) /
|
||||
1'000'000;
|
||||
}
|
||||
|
||||
// Converts interval from compact ntp (1/2^16 seconds) resolution to TimeDelta.
|
||||
// This interval can be up to ~9.1 hours (2^15 seconds).
|
||||
// Values close to 2^16 seconds are considered negative.
|
||||
TimeDelta CompactNtpIntervalToTimeDelta(uint32_t compact_ntp_interval);
|
||||
|
||||
// Converts interval from compact ntp (1/2^16 seconds) resolution to TimeDelta.
|
||||
// This interval can be up to ~9.1 hours (2^15 seconds).
|
||||
// Values close to 2^16 seconds are considered negative and are converted to
|
||||
// minimum value of 1ms.
|
||||
TimeDelta CompactNtpRttToTimeDelta(uint32_t compact_ntp_interval);
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // MODULES_RTP_RTCP_SOURCE_NTP_TIME_UTIL_H_
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2011 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef API_REF_COUNT_H_
|
||||
#define API_REF_COUNT_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Refcounted objects should implement the following informal interface:
|
||||
//
|
||||
// void AddRef() const ;
|
||||
// RefCountReleaseStatus Release() const;
|
||||
//
|
||||
// You may access members of a reference-counted object, including the AddRef()
|
||||
// and Release() methods, only if you already own a reference to it, or if
|
||||
// you're borrowing someone else's reference. (A newly created object is a
|
||||
// special case: the reference count is zero on construction, and the code that
|
||||
// creates the object should immediately call AddRef(), bringing the reference
|
||||
// count from zero to one, e.g., by constructing an rtc::scoped_refptr).
|
||||
//
|
||||
// AddRef() creates a new reference to the object.
|
||||
//
|
||||
// Release() releases a reference to the object; the caller now has one less
|
||||
// reference than before the call. Returns kDroppedLastRef if the number of
|
||||
// references dropped to zero because of this (in which case the object destroys
|
||||
// itself). Otherwise, returns kOtherRefsRemained, to signal that at the precise
|
||||
// time the caller's reference was dropped, other references still remained (but
|
||||
// if other threads own references, this may of course have changed by the time
|
||||
// Release() returns).
|
||||
//
|
||||
// The caller of Release() must treat it in the same way as a delete operation:
|
||||
// Regardless of the return value from Release(), the caller mustn't access the
|
||||
// object. The object might still be alive, due to references held by other
|
||||
// users of the object, but the object can go away at any time, e.g., as the
|
||||
// result of another thread calling Release().
|
||||
//
|
||||
// Calling AddRef() and Release() manually is discouraged. It's recommended to
|
||||
// use rtc::scoped_refptr to manage all pointers to reference counted objects.
|
||||
// Note that rtc::scoped_refptr depends on compile-time duck-typing; formally
|
||||
// implementing the below RefCountInterface is not required.
|
||||
|
||||
enum class RefCountReleaseStatus { kDroppedLastRef, kOtherRefsRemained };
|
||||
|
||||
// Interfaces where refcounting is part of the public api should
|
||||
// inherit this abstract interface. The implementation of these
|
||||
// methods is usually provided by the RefCountedObject template class,
|
||||
// applied as a leaf in the inheritance tree.
|
||||
class RefCountInterface {
|
||||
public:
|
||||
virtual void AddRef() const = 0;
|
||||
virtual RefCountReleaseStatus Release() const = 0;
|
||||
|
||||
// Non-public destructor, because Release() has exclusive responsibility for
|
||||
// destroying the object.
|
||||
protected:
|
||||
virtual ~RefCountInterface() {}
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_REF_COUNT_H_
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef API_REF_COUNTED_BASE_H_
|
||||
#define API_REF_COUNTED_BASE_H_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "api/ref_count.h"
|
||||
#include "rtc_base/ref_counter.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RefCountedBase {
|
||||
public:
|
||||
RefCountedBase() = default;
|
||||
|
||||
RefCountedBase(const RefCountedBase&) = delete;
|
||||
RefCountedBase& operator=(const RefCountedBase&) = delete;
|
||||
|
||||
void AddRef() const { ref_count_.IncRef(); }
|
||||
RefCountReleaseStatus Release() const {
|
||||
const auto status = ref_count_.DecRef();
|
||||
if (status == RefCountReleaseStatus::kDroppedLastRef) {
|
||||
delete this;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Provided for internal webrtc subclasses for corner cases where it's
|
||||
// necessary to know whether or not a reference is exclusively held.
|
||||
bool HasOneRef() const { return ref_count_.HasOneRef(); }
|
||||
|
||||
virtual ~RefCountedBase() = default;
|
||||
|
||||
private:
|
||||
mutable webrtc::webrtc_impl::RefCounter ref_count_{0};
|
||||
};
|
||||
|
||||
// Template based version of `RefCountedBase` for simple implementations that do
|
||||
// not need (or want) destruction via virtual destructor or the overhead of a
|
||||
// vtable.
|
||||
//
|
||||
// To use:
|
||||
// struct MyInt : public rtc::RefCountedNonVirtual<MyInt> {
|
||||
// int foo_ = 0;
|
||||
// };
|
||||
//
|
||||
// rtc::scoped_refptr<MyInt> my_int(new MyInt());
|
||||
//
|
||||
// sizeof(MyInt) on a 32 bit system would then be 8, int + refcount and no
|
||||
// vtable generated.
|
||||
template <typename T>
|
||||
class RefCountedNonVirtual {
|
||||
public:
|
||||
RefCountedNonVirtual() = default;
|
||||
|
||||
RefCountedNonVirtual(const RefCountedNonVirtual&) = delete;
|
||||
RefCountedNonVirtual& operator=(const RefCountedNonVirtual&) = delete;
|
||||
|
||||
void AddRef() const { ref_count_.IncRef(); }
|
||||
RefCountReleaseStatus Release() const {
|
||||
// If you run into this assert, T has virtual methods. There are two
|
||||
// options:
|
||||
// 1) The class doesn't actually need virtual methods, the type is complete
|
||||
// so the virtual attribute(s) can be removed.
|
||||
// 2) The virtual methods are a part of the design of the class. In this
|
||||
// case you can consider using `RefCountedBase` instead or alternatively
|
||||
// use `rtc::RefCountedObject`.
|
||||
static_assert(!std::is_polymorphic<T>::value,
|
||||
"T has virtual methods. RefCountedBase is a better fit.");
|
||||
const auto status = ref_count_.DecRef();
|
||||
if (status == RefCountReleaseStatus::kDroppedLastRef) {
|
||||
delete static_cast<const T*>(this);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Provided for internal webrtc subclasses for corner cases where it's
|
||||
// necessary to know whether or not a reference is exclusively held.
|
||||
bool HasOneRef() const { return ref_count_.HasOneRef(); }
|
||||
|
||||
~RefCountedNonVirtual() = default;
|
||||
|
||||
private:
|
||||
mutable webrtc::webrtc_impl::RefCounter ref_count_{0};
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
// Backwards compatibe aliases.
|
||||
// TODO: https://issues.webrtc.org/42225969 - deprecate and remove.
|
||||
namespace rtc {
|
||||
using RefCountedBase = webrtc::RefCountedBase;
|
||||
template <typename T>
|
||||
using RefCountedNonVirtual = webrtc::RefCountedNonVirtual<T>;
|
||||
} // namespace rtc
|
||||
|
||||
#endif // API_REF_COUNTED_BASE_H_
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef _RTP_RTCP_TYPEDEF_H_
|
||||
#define _RTP_RTCP_TYPEDEF_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "api/array_view.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
|
||||
#define RTCP_CNAME_SIZE 256 // RFC 3550 page 44, including null termination
|
||||
#define IP_PACKET_SIZE 1500 // we assume ethernet
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const int kVideoPayloadTypeFrequency = 90000;
|
||||
|
||||
// TODO(bugs.webrtc.org/6458): Remove this when all the depending projects are
|
||||
// updated to correctly set rtp rate for RtcpSender.
|
||||
const int kBogusRtpRateForAudioRtcp = 8000;
|
||||
|
||||
// Minimum RTP header size in bytes.
|
||||
const uint8_t kRtpHeaderSize = 12;
|
||||
|
||||
// This enum must not have any gaps, i.e., all integers between
|
||||
// kRtpExtensionNone and kRtpExtensionNumberOfExtensions must be valid enum
|
||||
// entries.
|
||||
enum RTPExtensionType : int {
|
||||
kRtpExtensionNone,
|
||||
kRtpExtensionTransmissionTimeOffset,
|
||||
kRtpExtensionAudioLevel,
|
||||
kRtpExtensionCsrcAudioLevel,
|
||||
kRtpExtensionInbandComfortNoise,
|
||||
kRtpExtensionAbsoluteSendTime,
|
||||
kRtpExtensionAbsoluteCaptureTime,
|
||||
kRtpExtensionVideoRotation,
|
||||
kRtpExtensionTransportSequenceNumber,
|
||||
kRtpExtensionTransportSequenceNumber02,
|
||||
kRtpExtensionPlayoutDelay,
|
||||
kRtpExtensionVideoContentType,
|
||||
kRtpExtensionVideoLayersAllocation,
|
||||
kRtpExtensionVideoTiming,
|
||||
kRtpExtensionRtpStreamId,
|
||||
kRtpExtensionRepairedRtpStreamId,
|
||||
kRtpExtensionMid,
|
||||
kRtpExtensionGenericFrameDescriptor,
|
||||
kRtpExtensionGenericFrameDescriptor00 [[deprecated]] =
|
||||
kRtpExtensionGenericFrameDescriptor,
|
||||
kRtpExtensionDependencyDescriptor,
|
||||
kRtpExtensionGenericFrameDescriptor02 [[deprecated]] =
|
||||
kRtpExtensionDependencyDescriptor,
|
||||
kRtpExtensionColorSpace,
|
||||
kRtpExtensionVideoFrameTrackingId,
|
||||
kRtpExtensionCorruptionDetection,
|
||||
kRtpExtensionNumberOfExtensions // Must be the last entity in the enum.
|
||||
};
|
||||
|
||||
enum RTCPAppSubTypes { kAppSubtypeBwe = 0x00 };
|
||||
|
||||
// TODO(sprang): Make this an enum class once rtcp_receiver has been cleaned up.
|
||||
enum RTCPPacketType : uint32_t {
|
||||
kRtcpReport = 0x0001,
|
||||
kRtcpSr = 0x0002,
|
||||
kRtcpRr = 0x0004,
|
||||
kRtcpSdes = 0x0008,
|
||||
kRtcpBye = 0x0010,
|
||||
kRtcpPli = 0x0020,
|
||||
kRtcpNack = 0x0040,
|
||||
kRtcpFir = 0x0080,
|
||||
kRtcpTmmbr = 0x0100,
|
||||
kRtcpTmmbn = 0x0200,
|
||||
kRtcpSrReq = 0x0400,
|
||||
kRtcpLossNotification = 0x2000,
|
||||
kRtcpRemb = 0x10000,
|
||||
kRtcpTransmissionTimeOffset = 0x20000,
|
||||
kRtcpXrReceiverReferenceTime = 0x40000,
|
||||
kRtcpXrDlrrReportBlock = 0x80000,
|
||||
kRtcpTransportFeedback = 0x100000,
|
||||
kRtcpXrTargetBitrate = 0x200000,
|
||||
};
|
||||
|
||||
enum class KeyFrameReqMethod : uint8_t {
|
||||
kNone, // Don't request keyframes.
|
||||
kPliRtcp, // Request keyframes through Picture Loss Indication.
|
||||
kFirRtcp // Request keyframes through Full Intra-frame Request.
|
||||
};
|
||||
|
||||
enum RtxMode {
|
||||
kRtxOff = 0x0,
|
||||
kRtxRetransmitted = 0x1, // Only send retransmissions over RTX.
|
||||
kRtxRedundantPayloads = 0x2 // Preventively send redundant payloads
|
||||
// instead of padding.
|
||||
};
|
||||
|
||||
const size_t kRtxHeaderSize = 2;
|
||||
|
||||
// NOTE! `kNumMediaTypes` must be kept in sync with RtpPacketMediaType!
|
||||
static constexpr size_t kNumMediaTypes = 5;
|
||||
enum class RtpPacketMediaType : size_t {
|
||||
kAudio, // Audio media packets.
|
||||
kVideo, // Video media packets.
|
||||
kRetransmission, // Retransmisions, sent as response to NACK.
|
||||
kForwardErrorCorrection, // FEC packets.
|
||||
kPadding = kNumMediaTypes - 1, // RTX or plain padding sent to maintain BWE.
|
||||
// Again, don't forget to update `kNumMediaTypes` if you add another value!
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // _RTP_RTCP_TYPEDEF_H_
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2011 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// Originally these classes are from Chromium.
|
||||
// http://src.chromium.org/viewvc/chrome/trunk/src/base/memory/ref_counted.h?view=markup
|
||||
|
||||
//
|
||||
// A smart pointer class for reference counted objects. Use this class instead
|
||||
// of calling AddRef and Release manually on a reference counted object to
|
||||
// avoid common memory leaks caused by forgetting to Release an object
|
||||
// reference. Sample usage:
|
||||
//
|
||||
// class MyFoo : public RefCounted<MyFoo> {
|
||||
// ...
|
||||
// };
|
||||
//
|
||||
// void some_function() {
|
||||
// scoped_refptr<MyFoo> foo = make_ref_counted<MyFoo>();
|
||||
// foo->Method(param);
|
||||
// // `foo` is released when this function returns
|
||||
// }
|
||||
//
|
||||
// void some_other_function() {
|
||||
// scoped_refptr<MyFoo> foo = make_ref_counted<MyFoo>();
|
||||
// ...
|
||||
// foo = nullptr; // explicitly releases `foo`
|
||||
// ...
|
||||
// if (foo)
|
||||
// foo->Method(param);
|
||||
// }
|
||||
//
|
||||
// The above examples show how scoped_refptr<T> acts like a pointer to T.
|
||||
// Given two scoped_refptr<T> classes, it is also possible to exchange
|
||||
// references between the two objects, like so:
|
||||
//
|
||||
// {
|
||||
// scoped_refptr<MyFoo> a = make_ref_counted<MyFoo>();
|
||||
// scoped_refptr<MyFoo> b;
|
||||
//
|
||||
// b.swap(a);
|
||||
// // now, `b` references the MyFoo object, and `a` references null.
|
||||
// }
|
||||
//
|
||||
// To make both `a` and `b` in the above example reference the same MyFoo
|
||||
// object, simply use the assignment operator:
|
||||
//
|
||||
// {
|
||||
// scoped_refptr<MyFoo> a = make_ref_counted<MyFoo>();
|
||||
// scoped_refptr<MyFoo> b;
|
||||
//
|
||||
// b = a;
|
||||
// // now, `a` and `b` each own a reference to the same MyFoo object.
|
||||
// }
|
||||
//
|
||||
|
||||
#ifndef API_SCOPED_REFPTR_H_
|
||||
#define API_SCOPED_REFPTR_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
template <class T>
|
||||
class scoped_refptr {
|
||||
public:
|
||||
using element_type = T;
|
||||
|
||||
scoped_refptr() : ptr_(nullptr) {}
|
||||
scoped_refptr(std::nullptr_t) : ptr_(nullptr) {} // NOLINT(runtime/explicit)
|
||||
|
||||
explicit scoped_refptr(T* p) : ptr_(p) {}
|
||||
|
||||
scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {}
|
||||
|
||||
template <typename U>
|
||||
scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {}
|
||||
|
||||
// Move constructors.
|
||||
scoped_refptr(scoped_refptr<T>&& r) noexcept : ptr_(std::move(r.ptr_)) {}
|
||||
|
||||
template <typename U>
|
||||
scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(std::move(r.ptr_)) {}
|
||||
|
||||
~scoped_refptr() = default;
|
||||
|
||||
T* get() const { return ptr_.get(); }
|
||||
explicit operator bool() const { return ptr_ != nullptr; }
|
||||
T& operator*() const { return *ptr_; }
|
||||
T* operator->() const { return ptr_.get(); }
|
||||
|
||||
T* release() {
|
||||
T* retVal = ptr_.get();
|
||||
ptr_.reset();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
scoped_refptr<T>& operator=(T* p) {
|
||||
ptr_.reset(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
|
||||
ptr_ = r.ptr_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
|
||||
ptr_ = r.ptr_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
scoped_refptr<T>& operator=(scoped_refptr<T>&& r) noexcept {
|
||||
ptr_ = std::move(r.ptr_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
scoped_refptr<T>& operator=(scoped_refptr<U>&& r) noexcept {
|
||||
ptr_ = std::move(r.ptr_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(T** pp) noexcept { std::swap(ptr_, *pp); }
|
||||
|
||||
void swap(scoped_refptr<T>& r) noexcept { std::swap(ptr_, r.ptr_); }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<T> ptr_;
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
bool operator==(const scoped_refptr<T>& a, const scoped_refptr<U>& b) {
|
||||
return a.get() == b.get();
|
||||
}
|
||||
template <typename T, typename U>
|
||||
bool operator!=(const scoped_refptr<T>& a, const scoped_refptr<U>& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator==(const scoped_refptr<T>& a, std::nullptr_t) {
|
||||
return a.get() == nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator!=(const scoped_refptr<T>& a, std::nullptr_t) {
|
||||
return !(a == nullptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator==(std::nullptr_t, const scoped_refptr<T>& a) {
|
||||
return a.get() == nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator!=(std::nullptr_t, const scoped_refptr<T>& a) {
|
||||
return !(a == nullptr);
|
||||
}
|
||||
|
||||
// Comparison with raw pointer.
|
||||
template <typename T, typename U>
|
||||
bool operator==(const scoped_refptr<T>& a, const U* b) {
|
||||
return a.get() == b;
|
||||
}
|
||||
template <typename T, typename U>
|
||||
bool operator!=(const scoped_refptr<T>& a, const U* b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
bool operator==(const T* a, const scoped_refptr<U>& b) {
|
||||
return a == b.get();
|
||||
}
|
||||
template <typename T, typename U>
|
||||
bool operator!=(const T* a, const scoped_refptr<U>& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
// Ordered comparison, needed for use as a std::map key.
|
||||
template <typename T, typename U>
|
||||
bool operator<(const scoped_refptr<T>& a, const scoped_refptr<U>& b) {
|
||||
return a.get() < b.get();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
namespace rtc {
|
||||
// Backwards compatible alias.
|
||||
// TODO: bugs.webrtc.org/42225969 - Deprecate and remove.
|
||||
using ::webrtc::scoped_refptr;
|
||||
} // namespace rtc
|
||||
|
||||
#endif // API_SCOPED_REFPTR_H_
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-01-15
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _BANDWIDTH_USAGE_H_
|
||||
#define _BANDWIDTH_USAGE_H_
|
||||
|
||||
enum class BandwidthUsage {
|
||||
kBwNormal = 0,
|
||||
kBwUnderusing = 1,
|
||||
kBwOverusing = 2,
|
||||
kLast
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2024 The WebRTC Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_TRANSPORT_ECN_MARKING_H_
|
||||
#define API_TRANSPORT_ECN_MARKING_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// TODO: bugs.webrtc.org/42225697 - L4S support is slowly being developed.
|
||||
// Help is appreciated.
|
||||
|
||||
// L4S Explicit Congestion Notification (ECN) .
|
||||
// https://www.rfc-editor.org/rfc/rfc9331.html ECT stands for ECN-Capable
|
||||
// Transport and CE stands for Congestion Experienced.
|
||||
|
||||
// RFC-3168, Section 5
|
||||
// +-----+-----+
|
||||
// | ECN FIELD |
|
||||
// +-----+-----+
|
||||
// ECT CE [Obsolete] RFC 2481 names for the ECN bits.
|
||||
// 0 0 Not-ECT
|
||||
// 0 1 ECT(1)
|
||||
// 1 0 ECT(0)
|
||||
// 1 1 CE
|
||||
|
||||
enum class EcnMarking {
|
||||
kNotEct = 0, // Not ECN-Capable Transport
|
||||
kEct1 = 1, // ECN-Capable Transport
|
||||
kEct0 = 2, // Not used by L4s (or webrtc.)
|
||||
kCe = 3, // Congestion experienced
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_TRANSPORT_ECN_MARKING_H_
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_TRANSPORT_NETWORK_CONTROL_H_
|
||||
#define API_TRANSPORT_NETWORK_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class TargetTransferRateObserver {
|
||||
public:
|
||||
virtual ~TargetTransferRateObserver() = default;
|
||||
// Called to indicate target transfer rate as well as giving information about
|
||||
// the current estimate of network parameters.
|
||||
virtual void OnTargetTransferRate(TargetTransferRate) = 0;
|
||||
// Called to provide updates to the expected target rate in case it changes
|
||||
// before the first call to OnTargetTransferRate.
|
||||
virtual void OnStartRateUpdate(DataRate) {}
|
||||
};
|
||||
|
||||
// Configuration sent to factory create function. The parameters here are
|
||||
// optional to use for a network controller implementation.
|
||||
struct NetworkControllerConfig {
|
||||
explicit NetworkControllerConfig() {}
|
||||
|
||||
// The initial constraints to start with, these can be changed at any later
|
||||
// time by calls to OnTargetRateConstraints. Note that the starting rate
|
||||
// has to be set initially to provide a starting state for the network
|
||||
// controller, even though the field is marked as optional.
|
||||
TargetRateConstraints constraints;
|
||||
// Initial stream specific configuration, these are changed at any later time
|
||||
// by calls to OnStreamsConfig.
|
||||
StreamsConfig stream_based_config;
|
||||
};
|
||||
|
||||
// NetworkControllerInterface is implemented by network controllers. A network
|
||||
// controller is a class that uses information about network state and traffic
|
||||
// to estimate network parameters such as round trip time and bandwidth. Network
|
||||
// controllers does not guarantee thread safety, the interface must be used in a
|
||||
// non-concurrent fashion.
|
||||
class NetworkControllerInterface {
|
||||
public:
|
||||
virtual ~NetworkControllerInterface() = default;
|
||||
|
||||
// Called when network availabilty changes.
|
||||
virtual NetworkControlUpdate OnNetworkAvailability(NetworkAvailability) = 0;
|
||||
// Called when the receiving or sending endpoint changes address.
|
||||
virtual NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange) = 0;
|
||||
// Called periodically with a periodicy as specified by
|
||||
// NetworkControllerFactoryInterface::GetProcessInterval.
|
||||
virtual NetworkControlUpdate OnProcessInterval(ProcessInterval) = 0;
|
||||
// Called when remotely calculated bitrate is received.
|
||||
virtual NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport) = 0;
|
||||
// Called round trip time has been calculated by protocol specific mechanisms.
|
||||
virtual NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate) = 0;
|
||||
// Called when a packet is sent on the network.
|
||||
virtual NetworkControlUpdate OnSentPacket(SentPacket) = 0;
|
||||
// Called when a packet is received from the remote client.
|
||||
virtual NetworkControlUpdate OnReceivedPacket(ReceivedPacket) = 0;
|
||||
// Called when the stream specific configuration has been updated.
|
||||
virtual NetworkControlUpdate OnStreamsConfig(StreamsConfig) = 0;
|
||||
// Called when target transfer rate constraints has been changed.
|
||||
virtual NetworkControlUpdate OnTargetRateConstraints(
|
||||
TargetRateConstraints) = 0;
|
||||
// Called when a protocol specific calculation of packet loss has been made.
|
||||
virtual NetworkControlUpdate OnTransportLossReport(TransportLossReport) = 0;
|
||||
// Called with per packet feedback regarding receive time.
|
||||
virtual NetworkControlUpdate OnTransportPacketsFeedback(
|
||||
TransportPacketsFeedback) = 0;
|
||||
// Called with network state estimate updates.
|
||||
virtual NetworkControlUpdate OnNetworkStateEstimate(NetworkStateEstimate) = 0;
|
||||
};
|
||||
|
||||
// NetworkControllerFactoryInterface is an interface for creating a network
|
||||
// controller.
|
||||
class NetworkControllerFactoryInterface {
|
||||
public:
|
||||
virtual ~NetworkControllerFactoryInterface() = default;
|
||||
|
||||
// Used to create a new network controller, requires an observer to be
|
||||
// provided to handle callbacks.
|
||||
virtual std::unique_ptr<NetworkControllerInterface> Create(
|
||||
NetworkControllerConfig config) = 0;
|
||||
// Returns the interval by which the network controller expects
|
||||
// OnProcessInterval calls.
|
||||
virtual TimeDelta GetProcessInterval() const = 0;
|
||||
};
|
||||
|
||||
// Under development, subject to change without notice.
|
||||
class NetworkStateEstimator {
|
||||
public:
|
||||
// Gets the current best estimate according to the estimator.
|
||||
virtual std::optional<NetworkStateEstimate> GetCurrentEstimate() = 0;
|
||||
// Called with per packet feedback regarding receive time.
|
||||
// Used when the NetworkStateEstimator runs in the sending endpoint.
|
||||
virtual void OnTransportPacketsFeedback(const TransportPacketsFeedback&) = 0;
|
||||
// Called with per packet feedback regarding receive time.
|
||||
// Used when the NetworkStateEstimator runs in the receiving endpoint.
|
||||
virtual void OnReceivedPacket(const PacketResult&) {}
|
||||
// Called when the receiving or sending endpoint changes address.
|
||||
virtual void OnRouteChange(const NetworkRouteChange&) = 0;
|
||||
virtual ~NetworkStateEstimator() = default;
|
||||
};
|
||||
class NetworkStateEstimatorFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<NetworkStateEstimator> Create() = 0;
|
||||
virtual ~NetworkStateEstimatorFactory() = default;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_TRANSPORT_NETWORK_CONTROL_H_
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/transport/network_types.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace webrtc {
|
||||
StreamsConfig::StreamsConfig() = default;
|
||||
StreamsConfig::StreamsConfig(const StreamsConfig&) = default;
|
||||
StreamsConfig::~StreamsConfig() = default;
|
||||
|
||||
TargetRateConstraints::TargetRateConstraints() = default;
|
||||
TargetRateConstraints::TargetRateConstraints(const TargetRateConstraints&) =
|
||||
default;
|
||||
TargetRateConstraints::~TargetRateConstraints() = default;
|
||||
|
||||
NetworkRouteChange::NetworkRouteChange() = default;
|
||||
NetworkRouteChange::NetworkRouteChange(const NetworkRouteChange&) = default;
|
||||
NetworkRouteChange::~NetworkRouteChange() = default;
|
||||
|
||||
PacketResult::PacketResult() = default;
|
||||
PacketResult::PacketResult(const PacketResult& other) = default;
|
||||
PacketResult::~PacketResult() = default;
|
||||
|
||||
bool PacketResult::ReceiveTimeOrder::operator()(const PacketResult& lhs,
|
||||
const PacketResult& rhs) {
|
||||
if (lhs.receive_time != rhs.receive_time)
|
||||
return lhs.receive_time < rhs.receive_time;
|
||||
if (lhs.sent_packet.send_time != rhs.sent_packet.send_time)
|
||||
return lhs.sent_packet.send_time < rhs.sent_packet.send_time;
|
||||
return lhs.sent_packet.sequence_number < rhs.sent_packet.sequence_number;
|
||||
}
|
||||
|
||||
TransportPacketsFeedback::TransportPacketsFeedback() = default;
|
||||
TransportPacketsFeedback::TransportPacketsFeedback(
|
||||
const TransportPacketsFeedback& other) = default;
|
||||
TransportPacketsFeedback::~TransportPacketsFeedback() = default;
|
||||
|
||||
std::vector<PacketResult> TransportPacketsFeedback::ReceivedWithSendInfo()
|
||||
const {
|
||||
std::vector<PacketResult> res;
|
||||
for (const PacketResult& fb : packet_feedbacks) {
|
||||
if (fb.IsReceived()) {
|
||||
res.push_back(fb);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<PacketResult> TransportPacketsFeedback::LostWithSendInfo() const {
|
||||
std::vector<PacketResult> res;
|
||||
for (const PacketResult& fb : packet_feedbacks) {
|
||||
if (!fb.IsReceived()) {
|
||||
res.push_back(fb);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<PacketResult> TransportPacketsFeedback::PacketsWithFeedback()
|
||||
const {
|
||||
return packet_feedbacks;
|
||||
}
|
||||
|
||||
std::vector<PacketResult> TransportPacketsFeedback::SortedByReceiveTime()
|
||||
const {
|
||||
std::vector<PacketResult> res;
|
||||
for (const PacketResult& fb : packet_feedbacks) {
|
||||
if (fb.IsReceived()) {
|
||||
res.push_back(fb);
|
||||
}
|
||||
}
|
||||
std::sort(res.begin(), res.end(), PacketResult::ReceiveTimeOrder());
|
||||
return res;
|
||||
}
|
||||
|
||||
NetworkControlUpdate::NetworkControlUpdate() = default;
|
||||
NetworkControlUpdate::NetworkControlUpdate(const NetworkControlUpdate&) =
|
||||
default;
|
||||
NetworkControlUpdate::~NetworkControlUpdate() = default;
|
||||
|
||||
PacedPacketInfo::PacedPacketInfo() = default;
|
||||
|
||||
PacedPacketInfo::PacedPacketInfo(int probe_cluster_id,
|
||||
int probe_cluster_min_probes,
|
||||
int probe_cluster_min_bytes)
|
||||
: probe_cluster_id(probe_cluster_id),
|
||||
probe_cluster_min_probes(probe_cluster_min_probes),
|
||||
probe_cluster_min_bytes(probe_cluster_min_bytes) {}
|
||||
|
||||
bool PacedPacketInfo::operator==(const PacedPacketInfo& rhs) const {
|
||||
return send_bitrate == rhs.send_bitrate &&
|
||||
probe_cluster_id == rhs.probe_cluster_id &&
|
||||
probe_cluster_min_probes == rhs.probe_cluster_min_probes &&
|
||||
probe_cluster_min_bytes == rhs.probe_cluster_min_bytes;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_TRANSPORT_NETWORK_TYPES_H_
|
||||
#define API_TRANSPORT_NETWORK_TYPES_H_
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "api/transport/ecn_marking.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Configuration
|
||||
|
||||
// Represents constraints and rates related to the currently enabled streams.
|
||||
// This is used as input to the congestion controller via the StreamsConfig
|
||||
// struct.
|
||||
struct BitrateAllocationLimits {
|
||||
// The total minimum send bitrate required by all sending streams.
|
||||
DataRate min_allocatable_rate = DataRate::Zero();
|
||||
// The total maximum allocatable bitrate for all currently available streams.
|
||||
DataRate max_allocatable_rate = DataRate::Zero();
|
||||
// The max bitrate to use for padding. The sum of the per-stream max padding
|
||||
// rate.
|
||||
DataRate max_padding_rate = DataRate::Zero();
|
||||
};
|
||||
|
||||
// Use StreamsConfig for information about streams that is required for specific
|
||||
// adjustments to the algorithms in network controllers. Especially useful
|
||||
// for experiments.
|
||||
struct StreamsConfig {
|
||||
StreamsConfig();
|
||||
StreamsConfig(const StreamsConfig&);
|
||||
~StreamsConfig();
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
std::optional<bool> requests_alr_probing;
|
||||
// If `enable_repeated_initial_probing` is set to true, Probes are sent
|
||||
// periodically every 1s during the first 5s after the network becomes
|
||||
// available. The probes ignores max_total_allocated_bitrate.
|
||||
std::optional<bool> enable_repeated_initial_probing;
|
||||
std::optional<double> pacing_factor;
|
||||
|
||||
// TODO(srte): Use BitrateAllocationLimits here.
|
||||
std::optional<DataRate> min_total_allocated_bitrate;
|
||||
std::optional<DataRate> max_padding_rate;
|
||||
std::optional<DataRate> max_total_allocated_bitrate;
|
||||
};
|
||||
|
||||
struct TargetRateConstraints {
|
||||
TargetRateConstraints();
|
||||
TargetRateConstraints(const TargetRateConstraints&);
|
||||
~TargetRateConstraints();
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
std::optional<DataRate> min_data_rate;
|
||||
std::optional<DataRate> max_data_rate;
|
||||
// The initial bandwidth estimate to base target rate on. This should be used
|
||||
// as the basis for initial OnTargetTransferRate and OnPacerConfig callbacks.
|
||||
std::optional<DataRate> starting_rate;
|
||||
};
|
||||
|
||||
// Send side information
|
||||
|
||||
struct NetworkAvailability {
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
bool network_available = false;
|
||||
};
|
||||
|
||||
struct NetworkRouteChange {
|
||||
NetworkRouteChange();
|
||||
NetworkRouteChange(const NetworkRouteChange&);
|
||||
~NetworkRouteChange();
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
// The TargetRateConstraints are set here so they can be changed synchronously
|
||||
// when network route changes.
|
||||
TargetRateConstraints constraints;
|
||||
};
|
||||
|
||||
struct PacedPacketInfo {
|
||||
PacedPacketInfo();
|
||||
PacedPacketInfo(int probe_cluster_id, int probe_cluster_min_probes,
|
||||
int probe_cluster_min_bytes);
|
||||
|
||||
bool operator==(const PacedPacketInfo& rhs) const;
|
||||
|
||||
// TODO(srte): Move probing info to a separate, optional struct.
|
||||
static constexpr int kNotAProbe = -1;
|
||||
DataRate send_bitrate = DataRate::BitsPerSec(0);
|
||||
int probe_cluster_id = kNotAProbe;
|
||||
int probe_cluster_min_probes = -1;
|
||||
int probe_cluster_min_bytes = -1;
|
||||
int probe_cluster_bytes_sent = 0;
|
||||
};
|
||||
|
||||
struct SentPacket {
|
||||
Timestamp send_time = Timestamp::PlusInfinity();
|
||||
// Size of packet with overhead up to IP layer.
|
||||
DataSize size = DataSize::Zero();
|
||||
// Size of preceeding packets that are not part of feedback.
|
||||
DataSize prior_unacked_data = DataSize::Zero();
|
||||
// Probe cluster id and parameters including bitrate, number of packets and
|
||||
// number of bytes.
|
||||
PacedPacketInfo pacing_info;
|
||||
// True if the packet is an audio packet, false for video, padding, RTX etc.
|
||||
bool audio = false;
|
||||
// Transport independent sequence number, any tracked packet should have a
|
||||
// sequence number that is unique over the whole call and increasing by 1 for
|
||||
// each packet.
|
||||
int64_t sequence_number;
|
||||
// Tracked data in flight when the packet was sent, excluding unacked data.
|
||||
DataSize data_in_flight = DataSize::Zero();
|
||||
};
|
||||
|
||||
struct ReceivedPacket {
|
||||
Timestamp send_time = Timestamp::MinusInfinity();
|
||||
Timestamp receive_time = Timestamp::PlusInfinity();
|
||||
DataSize size = DataSize::Zero();
|
||||
};
|
||||
|
||||
// Transport level feedback
|
||||
|
||||
struct RemoteBitrateReport {
|
||||
Timestamp receive_time = Timestamp::PlusInfinity();
|
||||
DataRate bandwidth = DataRate::Infinity();
|
||||
};
|
||||
|
||||
struct RoundTripTimeUpdate {
|
||||
Timestamp receive_time = Timestamp::PlusInfinity();
|
||||
TimeDelta round_trip_time = TimeDelta::PlusInfinity();
|
||||
bool smoothed = false;
|
||||
};
|
||||
|
||||
struct TransportLossReport {
|
||||
Timestamp receive_time = Timestamp::PlusInfinity();
|
||||
Timestamp start_time = Timestamp::PlusInfinity();
|
||||
Timestamp end_time = Timestamp::PlusInfinity();
|
||||
uint64_t packets_lost_delta = 0;
|
||||
uint64_t packets_received_delta = 0;
|
||||
};
|
||||
|
||||
// Packet level feedback
|
||||
|
||||
struct PacketResult {
|
||||
class ReceiveTimeOrder {
|
||||
public:
|
||||
bool operator()(const PacketResult& lhs, const PacketResult& rhs);
|
||||
};
|
||||
|
||||
PacketResult();
|
||||
PacketResult(const PacketResult&);
|
||||
~PacketResult();
|
||||
|
||||
inline bool IsReceived() const { return !receive_time.IsPlusInfinity(); }
|
||||
|
||||
SentPacket sent_packet;
|
||||
Timestamp receive_time = Timestamp::PlusInfinity();
|
||||
EcnMarking ecn = EcnMarking::kNotEct;
|
||||
};
|
||||
|
||||
struct TransportPacketsFeedback {
|
||||
TransportPacketsFeedback();
|
||||
TransportPacketsFeedback(const TransportPacketsFeedback& other);
|
||||
~TransportPacketsFeedback();
|
||||
|
||||
Timestamp feedback_time = Timestamp::PlusInfinity();
|
||||
DataSize data_in_flight = DataSize::Zero();
|
||||
bool transport_supports_ecn = false;
|
||||
std::vector<PacketResult> packet_feedbacks;
|
||||
|
||||
// Arrival times for messages without send time information.
|
||||
std::vector<Timestamp> sendless_arrival_times;
|
||||
|
||||
std::vector<PacketResult> ReceivedWithSendInfo() const;
|
||||
std::vector<PacketResult> LostWithSendInfo() const;
|
||||
std::vector<PacketResult> PacketsWithFeedback() const;
|
||||
std::vector<PacketResult> SortedByReceiveTime() const;
|
||||
};
|
||||
|
||||
// Network estimation
|
||||
|
||||
struct NetworkEstimate {
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
// Deprecated, use TargetTransferRate::target_rate instead.
|
||||
DataRate bandwidth = DataRate::Infinity();
|
||||
TimeDelta round_trip_time = TimeDelta::PlusInfinity();
|
||||
TimeDelta bwe_period = TimeDelta::PlusInfinity();
|
||||
|
||||
float loss_rate_ratio = 0;
|
||||
};
|
||||
|
||||
// Network control
|
||||
|
||||
struct PacerConfig {
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
// Pacer should send at most data_window data over time_window duration.
|
||||
DataSize data_window = DataSize::Infinity();
|
||||
TimeDelta time_window = TimeDelta::PlusInfinity();
|
||||
// Pacer should send at least pad_window data over time_window duration.
|
||||
DataSize pad_window = DataSize::Zero();
|
||||
DataRate data_rate() const { return data_window / time_window; }
|
||||
DataRate pad_rate() const { return pad_window / time_window; }
|
||||
};
|
||||
|
||||
struct ProbeClusterConfig {
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
DataRate target_data_rate = DataRate::Zero();
|
||||
// Duration of a probe.
|
||||
TimeDelta target_duration = TimeDelta::Zero();
|
||||
// Delta time between sent bursts of packets during probe.
|
||||
TimeDelta min_probe_delta = TimeDelta::Millis(2);
|
||||
int32_t target_probe_count = 0;
|
||||
int32_t id = 0;
|
||||
};
|
||||
|
||||
struct TargetTransferRate {
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
// The estimate on which the target rate is based on.
|
||||
NetworkEstimate network_estimate;
|
||||
DataRate target_rate = DataRate::Zero();
|
||||
DataRate stable_target_rate = DataRate::Zero();
|
||||
double cwnd_reduce_ratio = 0;
|
||||
};
|
||||
|
||||
// Contains updates of network controller comand state. Using optionals to
|
||||
// indicate whether a member has been updated. The array of probe clusters
|
||||
// should be used to send out probes if not empty.
|
||||
struct NetworkControlUpdate {
|
||||
NetworkControlUpdate();
|
||||
NetworkControlUpdate(const NetworkControlUpdate&);
|
||||
~NetworkControlUpdate();
|
||||
|
||||
bool has_updates() const {
|
||||
return congestion_window.has_value() || pacer_config.has_value() ||
|
||||
!probe_cluster_configs.empty() || target_rate.has_value();
|
||||
}
|
||||
|
||||
std::optional<DataSize> congestion_window;
|
||||
std::optional<PacerConfig> pacer_config;
|
||||
std::vector<ProbeClusterConfig> probe_cluster_configs;
|
||||
std::optional<TargetTransferRate> target_rate;
|
||||
};
|
||||
|
||||
// Process control
|
||||
struct ProcessInterval {
|
||||
Timestamp at_time = Timestamp::PlusInfinity();
|
||||
std::optional<DataSize> pacer_queue;
|
||||
};
|
||||
|
||||
// Under development, subject to change without notice.
|
||||
struct NetworkStateEstimate {
|
||||
double confidence = NAN;
|
||||
// The time the estimate was received/calculated.
|
||||
Timestamp update_time = Timestamp::MinusInfinity();
|
||||
Timestamp last_receive_time = Timestamp::MinusInfinity();
|
||||
Timestamp last_send_time = Timestamp::MinusInfinity();
|
||||
|
||||
// Total estimated link capacity.
|
||||
DataRate link_capacity = DataRate::MinusInfinity();
|
||||
// Used as a safe measure of available capacity.
|
||||
DataRate link_capacity_lower = DataRate::MinusInfinity();
|
||||
// Used as limit for increasing bitrate.
|
||||
DataRate link_capacity_upper = DataRate::MinusInfinity();
|
||||
|
||||
TimeDelta pre_link_buffer_delay = TimeDelta::MinusInfinity();
|
||||
TimeDelta post_link_buffer_delay = TimeDelta::MinusInfinity();
|
||||
TimeDelta propagation_delay = TimeDelta::MinusInfinity();
|
||||
|
||||
// Only for debugging
|
||||
TimeDelta time_delta = TimeDelta::MinusInfinity();
|
||||
Timestamp last_feed_time = Timestamp::MinusInfinity();
|
||||
double cross_delay_rate = NAN;
|
||||
double spike_delay_rate = NAN;
|
||||
DataRate link_capacity_std_dev = DataRate::MinusInfinity();
|
||||
DataRate link_capacity_min = DataRate::MinusInfinity();
|
||||
double cross_traffic_ratio = NAN;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_TRANSPORT_NETWORK_TYPES_H_
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/units/data_rate.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::string ToString(DataRate value) {
|
||||
if (value.IsPlusInfinity()) {
|
||||
return "+inf bps";
|
||||
} else if (value.IsMinusInfinity()) {
|
||||
return "-inf bps";
|
||||
} else {
|
||||
if (value.bps() == 0 || value.bps() % 1000 != 0) {
|
||||
return std::to_string(value.bps()) + " bps";
|
||||
} else {
|
||||
return std::to_string(value.kbps()) + " kbps";
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_UNITS_DATA_RATE_H_
|
||||
#define API_UNITS_DATA_RATE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/frequency.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "unit_base.h"
|
||||
|
||||
namespace webrtc {
|
||||
// DataRate is a class that represents a given data rate. This can be used to
|
||||
// represent bandwidth, encoding bitrate, etc. The internal storage is bits per
|
||||
// second (bps).
|
||||
class DataRate final : public rtc_units_impl::RelativeUnit<DataRate> {
|
||||
public:
|
||||
template <typename T>
|
||||
static constexpr DataRate BitsPerSec(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromValue(value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr DataRate BytesPerSec(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(8, value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr DataRate KilobitsPerSec(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(1000, value);
|
||||
}
|
||||
static constexpr DataRate Infinity() { return PlusInfinity(); }
|
||||
|
||||
constexpr DataRate() = default;
|
||||
|
||||
template <typename Sink>
|
||||
friend void AbslStringify(Sink& sink, DataRate value);
|
||||
|
||||
template <typename T = int64_t>
|
||||
constexpr T bps() const {
|
||||
return ToValue<T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T bytes_per_sec() const {
|
||||
return ToFraction<8, T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T kbps() const {
|
||||
return ToFraction<1000, T>();
|
||||
}
|
||||
constexpr int64_t bps_or(int64_t fallback_value) const {
|
||||
return ToValueOr(fallback_value);
|
||||
}
|
||||
constexpr int64_t kbps_or(int64_t fallback_value) const {
|
||||
return ToFractionOr<1000>(fallback_value);
|
||||
}
|
||||
|
||||
private:
|
||||
// Bits per second used internally to simplify debugging by making the value
|
||||
// more recognizable.
|
||||
friend class rtc_units_impl::UnitBase<DataRate>;
|
||||
using RelativeUnit::RelativeUnit;
|
||||
static constexpr bool one_sided = true;
|
||||
};
|
||||
|
||||
namespace data_rate_impl {
|
||||
inline constexpr int64_t Microbits(const DataSize& size) {
|
||||
constexpr int64_t kMaxBeforeConversion =
|
||||
std::numeric_limits<int64_t>::max() / 8000000;
|
||||
return size.bytes() * 8000000;
|
||||
}
|
||||
|
||||
inline constexpr int64_t MillibytePerSec(const DataRate& size) {
|
||||
constexpr int64_t kMaxBeforeConversion =
|
||||
std::numeric_limits<int64_t>::max() / (1000 / 8);
|
||||
return size.bps() * (1000 / 8);
|
||||
}
|
||||
} // namespace data_rate_impl
|
||||
|
||||
inline constexpr DataRate operator/(const DataSize size,
|
||||
const TimeDelta duration) {
|
||||
return DataRate::BitsPerSec(data_rate_impl::Microbits(size) / duration.us());
|
||||
}
|
||||
inline constexpr TimeDelta operator/(const DataSize size, const DataRate rate) {
|
||||
return TimeDelta::Micros(data_rate_impl::Microbits(size) / rate.bps());
|
||||
}
|
||||
inline constexpr DataSize operator*(const DataRate rate,
|
||||
const TimeDelta duration) {
|
||||
int64_t microbits = rate.bps() * duration.us();
|
||||
return DataSize::Bytes((microbits + 4000000) / 8000000);
|
||||
}
|
||||
inline constexpr DataSize operator*(const TimeDelta duration,
|
||||
const DataRate rate) {
|
||||
return rate * duration;
|
||||
}
|
||||
|
||||
inline constexpr DataSize operator/(const DataRate rate,
|
||||
const Frequency frequency) {
|
||||
int64_t millihertz = frequency.millihertz<int64_t>();
|
||||
// Note that the value is truncated here reather than rounded, potentially
|
||||
// introducing an error of .5 bytes if rounding were expected.
|
||||
return DataSize::Bytes(data_rate_impl::MillibytePerSec(rate) / millihertz);
|
||||
}
|
||||
inline constexpr Frequency operator/(const DataRate rate, const DataSize size) {
|
||||
return Frequency::MilliHertz(data_rate_impl::MillibytePerSec(rate) /
|
||||
size.bytes());
|
||||
}
|
||||
inline constexpr DataRate operator*(const DataSize size,
|
||||
const Frequency frequency) {
|
||||
int64_t millibits_per_second =
|
||||
size.bytes() * 8 * frequency.millihertz<int64_t>();
|
||||
return DataRate::BitsPerSec((millibits_per_second + 500) / 1000);
|
||||
}
|
||||
inline constexpr DataRate operator*(const Frequency frequency,
|
||||
const DataSize size) {
|
||||
return size * frequency;
|
||||
}
|
||||
|
||||
std::string ToString(DataRate value);
|
||||
|
||||
template <typename Sink>
|
||||
void AbslStringify(Sink& sink, DataRate value) {
|
||||
sink.Append(ToString(value));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_UNITS_DATA_RATE_H_
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/units/data_size.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::string ToString(DataSize value) {
|
||||
if (value.IsPlusInfinity()) {
|
||||
return "+inf bytes";
|
||||
} else if (value.IsMinusInfinity()) {
|
||||
return "-inf bytes";
|
||||
} else {
|
||||
return std::to_string(value.bytes()) + " bytes";
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_UNITS_DATA_SIZE_H_
|
||||
#define API_UNITS_DATA_SIZE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "unit_base.h" // IWYU pragma: export
|
||||
|
||||
namespace webrtc {
|
||||
// DataSize is a class represeting a count of bytes.
|
||||
class DataSize final : public rtc_units_impl::RelativeUnit<DataSize> {
|
||||
public:
|
||||
template <typename T>
|
||||
static constexpr DataSize Bytes(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromValue(value);
|
||||
}
|
||||
static constexpr DataSize Infinity() { return PlusInfinity(); }
|
||||
|
||||
constexpr DataSize() = default;
|
||||
|
||||
template <typename Sink>
|
||||
friend void AbslStringify(Sink& sink, DataSize value);
|
||||
|
||||
template <typename T = int64_t>
|
||||
constexpr T bytes() const {
|
||||
return ToValue<T>();
|
||||
}
|
||||
|
||||
constexpr int64_t bytes_or(int64_t fallback_value) const {
|
||||
return ToValueOr(fallback_value);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class rtc_units_impl::UnitBase<DataSize>;
|
||||
using RelativeUnit::RelativeUnit;
|
||||
static constexpr bool one_sided = true;
|
||||
};
|
||||
|
||||
std::string ToString(DataSize value);
|
||||
|
||||
template <typename Sink>
|
||||
void AbslStringify(Sink& sink, DataSize value) {
|
||||
sink.Append(ToString(value));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_UNITS_DATA_SIZE_H_
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#include "frequency.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace webrtc {
|
||||
std::string ToString(Frequency value) {
|
||||
if (value.IsPlusInfinity()) {
|
||||
return "+inf Hz";
|
||||
} else if (value.IsMinusInfinity()) {
|
||||
return "-inf Hz";
|
||||
} else if (value.millihertz<int64_t>() % 1000 != 0) {
|
||||
return std::to_string(value.hertz<double>()) + " Hz";
|
||||
} else {
|
||||
return std::to_string(value.hertz<int64_t>()) + " Hz";
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef API_UNITS_FREQUENCY_H_
|
||||
#define API_UNITS_FREQUENCY_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
#include "unit_base.h" // IWYU pragma: export
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class Frequency final : public rtc_units_impl::RelativeUnit<Frequency> {
|
||||
public:
|
||||
template <typename T>
|
||||
static constexpr Frequency MilliHertz(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromValue(value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr Frequency Hertz(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(1'000, value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr Frequency KiloHertz(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(1'000'000, value);
|
||||
}
|
||||
|
||||
constexpr Frequency() = default;
|
||||
|
||||
template <typename Sink>
|
||||
friend void AbslStringify(Sink& sink, Frequency value);
|
||||
|
||||
template <typename T = int64_t>
|
||||
constexpr T hertz() const {
|
||||
return ToFraction<1000, T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T millihertz() const {
|
||||
return ToValue<T>();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class rtc_units_impl::UnitBase<Frequency>;
|
||||
using RelativeUnit::RelativeUnit;
|
||||
static constexpr bool one_sided = true;
|
||||
};
|
||||
|
||||
inline constexpr Frequency operator/(int64_t nominator,
|
||||
const TimeDelta& interval) {
|
||||
constexpr int64_t kKiloPerMicro = 1000 * 1000000;
|
||||
return Frequency::MilliHertz(nominator * kKiloPerMicro / interval.us());
|
||||
}
|
||||
|
||||
inline constexpr TimeDelta operator/(int64_t nominator,
|
||||
const Frequency& frequency) {
|
||||
constexpr int64_t kMegaPerMilli = 1000000 * 1000;
|
||||
return TimeDelta::Micros(nominator * kMegaPerMilli / frequency.millihertz());
|
||||
}
|
||||
|
||||
inline constexpr double operator*(Frequency frequency, TimeDelta time_delta) {
|
||||
return frequency.hertz<double>() * time_delta.seconds<double>();
|
||||
}
|
||||
inline constexpr double operator*(TimeDelta time_delta, Frequency frequency) {
|
||||
return frequency * time_delta;
|
||||
}
|
||||
|
||||
std::string ToString(Frequency value);
|
||||
|
||||
template <typename Sink>
|
||||
void AbslStringify(Sink& sink, Frequency value) {
|
||||
sink.Append(ToString(value));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // API_UNITS_FREQUENCY_H_
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::string ToString(TimeDelta value) {
|
||||
if (value.IsPlusInfinity()) {
|
||||
return "+inf ms";
|
||||
} else if (value.IsMinusInfinity()) {
|
||||
return "-inf ms";
|
||||
} else {
|
||||
if (value.us() == 0 || (value.us() % 1000) != 0)
|
||||
return std::to_string(value.us()) + " us";
|
||||
else if (value.ms() % 1000 != 0)
|
||||
return std::to_string(value.ms()) + " ms";
|
||||
else
|
||||
return std::to_string(value.seconds()) + " s";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_UNITS_TIME_DELTA_H_
|
||||
#define API_UNITS_TIME_DELTA_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "unit_base.h" // IWYU pragma: export
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// TimeDelta represents the difference between two timestamps. Commonly this can
|
||||
// be a duration. However since two Timestamps are not guaranteed to have the
|
||||
// same epoch (they might come from different computers, making exact
|
||||
// synchronisation infeasible), the duration covered by a TimeDelta can be
|
||||
// undefined. To simplify usage, it can be constructed and converted to
|
||||
// different units, specifically seconds (s), milliseconds (ms) and
|
||||
// microseconds (us).
|
||||
class TimeDelta final : public rtc_units_impl::RelativeUnit<TimeDelta> {
|
||||
public:
|
||||
template <typename T>
|
||||
static constexpr TimeDelta Minutes(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return Seconds(value * 60);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr TimeDelta Seconds(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(1'000'000, value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr TimeDelta Millis(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(1'000, value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr TimeDelta Micros(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromValue(value);
|
||||
}
|
||||
|
||||
constexpr TimeDelta() = default;
|
||||
|
||||
template <typename Sink>
|
||||
friend void AbslStringify(Sink& sink, TimeDelta value);
|
||||
|
||||
template <typename T = int64_t>
|
||||
constexpr T seconds() const {
|
||||
return ToFraction<1000000, T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T ms() const {
|
||||
return ToFraction<1000, T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T us() const {
|
||||
return ToValue<T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T ns() const {
|
||||
return ToMultiple<1000, T>();
|
||||
}
|
||||
|
||||
constexpr int64_t seconds_or(int64_t fallback_value) const {
|
||||
return ToFractionOr<1000000>(fallback_value);
|
||||
}
|
||||
constexpr int64_t ms_or(int64_t fallback_value) const {
|
||||
return ToFractionOr<1000>(fallback_value);
|
||||
}
|
||||
constexpr int64_t us_or(int64_t fallback_value) const {
|
||||
return ToValueOr(fallback_value);
|
||||
}
|
||||
|
||||
constexpr TimeDelta Abs() const {
|
||||
return us() < 0 ? TimeDelta::Micros(-us()) : *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class rtc_units_impl::UnitBase<TimeDelta>;
|
||||
using RelativeUnit::RelativeUnit;
|
||||
static constexpr bool one_sided = false;
|
||||
};
|
||||
|
||||
std::string ToString(TimeDelta value);
|
||||
|
||||
template <typename Sink>
|
||||
void AbslStringify(Sink& sink, TimeDelta value) {
|
||||
sink.Append(ToString(value));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_UNITS_TIME_DELTA_H_
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/units/timestamp.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace webrtc {
|
||||
std::string ToString(Timestamp value) {
|
||||
if (value.IsPlusInfinity()) {
|
||||
return "+inf ms";
|
||||
} else if (value.IsMinusInfinity()) {
|
||||
return "-inf ms";
|
||||
} else {
|
||||
if (value.us() == 0 || (value.us() % 1000) != 0)
|
||||
return std::to_string(value.us()) + " us";
|
||||
else if (value.ms() % 1000 != 0)
|
||||
return std::to_string(value.ms()) + " ms";
|
||||
else
|
||||
return std::to_string(value.seconds()) + " s";
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_UNITS_TIMESTAMP_H_
|
||||
#define API_UNITS_TIMESTAMP_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "time_delta.h"
|
||||
#include "unit_base.h" // IWYU pragma: export
|
||||
|
||||
namespace webrtc {
|
||||
// Timestamp represents the time that has passed since some unspecified epoch.
|
||||
// The epoch is assumed to be before any represented timestamps, this means that
|
||||
// negative values are not valid. The most notable feature is that the
|
||||
// difference of two Timestamps results in a TimeDelta.
|
||||
class Timestamp final : public rtc_units_impl::UnitBase<Timestamp> {
|
||||
public:
|
||||
template <typename T>
|
||||
static constexpr Timestamp Seconds(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(1'000'000, value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr Timestamp Millis(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromFraction(1'000, value);
|
||||
}
|
||||
template <typename T>
|
||||
static constexpr Timestamp Micros(T value) {
|
||||
static_assert(std::is_arithmetic<T>::value, "");
|
||||
return FromValue(value);
|
||||
}
|
||||
|
||||
Timestamp() = delete;
|
||||
|
||||
template <typename Sink>
|
||||
friend void AbslStringify(Sink& sink, Timestamp value);
|
||||
|
||||
template <typename T = int64_t>
|
||||
constexpr T seconds() const {
|
||||
return ToFraction<1000000, T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T ms() const {
|
||||
return ToFraction<1000, T>();
|
||||
}
|
||||
template <typename T = int64_t>
|
||||
constexpr T us() const {
|
||||
return ToValue<T>();
|
||||
}
|
||||
|
||||
constexpr int64_t seconds_or(int64_t fallback_value) const {
|
||||
return ToFractionOr<1000000>(fallback_value);
|
||||
}
|
||||
constexpr int64_t ms_or(int64_t fallback_value) const {
|
||||
return ToFractionOr<1000>(fallback_value);
|
||||
}
|
||||
constexpr int64_t us_or(int64_t fallback_value) const {
|
||||
return ToValueOr(fallback_value);
|
||||
}
|
||||
|
||||
constexpr Timestamp operator+(const TimeDelta delta) const {
|
||||
if (IsPlusInfinity() || delta.IsPlusInfinity()) {
|
||||
return PlusInfinity();
|
||||
} else if (IsMinusInfinity() || delta.IsMinusInfinity()) {
|
||||
return MinusInfinity();
|
||||
}
|
||||
return Timestamp::Micros(us() + delta.us());
|
||||
}
|
||||
constexpr Timestamp operator-(const TimeDelta delta) const {
|
||||
if (IsPlusInfinity() || delta.IsMinusInfinity()) {
|
||||
return PlusInfinity();
|
||||
} else if (IsMinusInfinity() || delta.IsPlusInfinity()) {
|
||||
return MinusInfinity();
|
||||
}
|
||||
return Timestamp::Micros(us() - delta.us());
|
||||
}
|
||||
constexpr TimeDelta operator-(const Timestamp other) const {
|
||||
if (IsPlusInfinity() || other.IsMinusInfinity()) {
|
||||
return TimeDelta::PlusInfinity();
|
||||
} else if (IsMinusInfinity() || other.IsPlusInfinity()) {
|
||||
return TimeDelta::MinusInfinity();
|
||||
}
|
||||
return TimeDelta::Micros(us() - other.us());
|
||||
}
|
||||
constexpr Timestamp& operator-=(const TimeDelta delta) {
|
||||
*this = *this - delta;
|
||||
return *this;
|
||||
}
|
||||
constexpr Timestamp& operator+=(const TimeDelta delta) {
|
||||
*this = *this + delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class rtc_units_impl::UnitBase<Timestamp>;
|
||||
using UnitBase::UnitBase;
|
||||
static constexpr bool one_sided = true;
|
||||
};
|
||||
|
||||
std::string ToString(Timestamp value);
|
||||
|
||||
template <typename Sink>
|
||||
void AbslStringify(Sink& sink, Timestamp value) {
|
||||
sink.Append(ToString(value));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_UNITS_TIMESTAMP_H_
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef RTC_BASE_UNITS_UNIT_BASE_H_
|
||||
#define RTC_BASE_UNITS_UNIT_BASE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "rtc_base/numerics/divide_round.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace rtc_units_impl {
|
||||
|
||||
// UnitBase is a base class for implementing custom value types with a specific
|
||||
// unit. It provides type safety and commonly useful operations. The underlying
|
||||
// storage is always an int64_t, it's up to the unit implementation to choose
|
||||
// what scale it represents.
|
||||
//
|
||||
// It's used like:
|
||||
// class MyUnit: public UnitBase<MyUnit> {...};
|
||||
//
|
||||
// Unit_T is the subclass representing the specific unit.
|
||||
template <class Unit_T>
|
||||
class UnitBase {
|
||||
public:
|
||||
UnitBase() = delete;
|
||||
static constexpr Unit_T Zero() { return Unit_T(0); }
|
||||
static constexpr Unit_T PlusInfinity() { return Unit_T(PlusInfinityVal()); }
|
||||
static constexpr Unit_T MinusInfinity() { return Unit_T(MinusInfinityVal()); }
|
||||
|
||||
constexpr bool IsZero() const { return value_ == 0; }
|
||||
constexpr bool IsFinite() const { return !IsInfinite(); }
|
||||
constexpr bool IsInfinite() const {
|
||||
return value_ == PlusInfinityVal() || value_ == MinusInfinityVal();
|
||||
}
|
||||
constexpr bool IsPlusInfinity() const { return value_ == PlusInfinityVal(); }
|
||||
constexpr bool IsMinusInfinity() const {
|
||||
return value_ == MinusInfinityVal();
|
||||
}
|
||||
|
||||
constexpr bool operator==(const UnitBase<Unit_T>& other) const {
|
||||
return value_ == other.value_;
|
||||
}
|
||||
constexpr bool operator!=(const UnitBase<Unit_T>& other) const {
|
||||
return value_ != other.value_;
|
||||
}
|
||||
constexpr bool operator<=(const UnitBase<Unit_T>& other) const {
|
||||
return value_ <= other.value_;
|
||||
}
|
||||
constexpr bool operator>=(const UnitBase<Unit_T>& other) const {
|
||||
return value_ >= other.value_;
|
||||
}
|
||||
constexpr bool operator>(const UnitBase<Unit_T>& other) const {
|
||||
return value_ > other.value_;
|
||||
}
|
||||
constexpr bool operator<(const UnitBase<Unit_T>& other) const {
|
||||
return value_ < other.value_;
|
||||
}
|
||||
constexpr Unit_T RoundTo(const Unit_T& resolution) const {
|
||||
return Unit_T((value_ + resolution.value_ / 2) / resolution.value_) *
|
||||
resolution.value_;
|
||||
}
|
||||
constexpr Unit_T RoundUpTo(const Unit_T& resolution) const {
|
||||
return Unit_T((value_ + resolution.value_ - 1) / resolution.value_) *
|
||||
resolution.value_;
|
||||
}
|
||||
constexpr Unit_T RoundDownTo(const Unit_T& resolution) const {
|
||||
return Unit_T(value_ / resolution.value_) * resolution.value_;
|
||||
}
|
||||
|
||||
protected:
|
||||
template <typename T, typename std::enable_if<
|
||||
std::is_integral<T>::value>::type* = nullptr>
|
||||
static constexpr Unit_T FromValue(T value) {
|
||||
return Unit_T(rtc::dchecked_cast<int64_t>(value));
|
||||
}
|
||||
template <typename T, typename std::enable_if<
|
||||
std::is_floating_point<T>::value>::type* = nullptr>
|
||||
static constexpr Unit_T FromValue(T value) {
|
||||
if (value == std::numeric_limits<T>::infinity()) {
|
||||
return PlusInfinity();
|
||||
} else if (value == -std::numeric_limits<T>::infinity()) {
|
||||
return MinusInfinity();
|
||||
} else {
|
||||
return FromValue(rtc::dchecked_cast<int64_t>(value));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename std::enable_if<
|
||||
std::is_integral<T>::value>::type* = nullptr>
|
||||
static constexpr Unit_T FromFraction(int64_t denominator, T value) {
|
||||
return Unit_T(rtc::dchecked_cast<int64_t>(value * denominator));
|
||||
}
|
||||
template <typename T, typename std::enable_if<
|
||||
std::is_floating_point<T>::value>::type* = nullptr>
|
||||
static constexpr Unit_T FromFraction(int64_t denominator, T value) {
|
||||
return FromValue(value * denominator);
|
||||
}
|
||||
|
||||
template <typename T = int64_t>
|
||||
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type
|
||||
ToValue() const {
|
||||
return rtc::dchecked_cast<T>(value_);
|
||||
}
|
||||
template <typename T>
|
||||
constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type
|
||||
ToValue() const {
|
||||
return IsPlusInfinity() ? std::numeric_limits<T>::infinity()
|
||||
: IsMinusInfinity() ? -std::numeric_limits<T>::infinity()
|
||||
: value_;
|
||||
}
|
||||
template <typename T>
|
||||
constexpr T ToValueOr(T fallback_value) const {
|
||||
return IsFinite() ? value_ : fallback_value;
|
||||
}
|
||||
|
||||
template <int64_t Denominator, typename T = int64_t>
|
||||
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type
|
||||
ToFraction() const {
|
||||
return rtc::dchecked_cast<T>(DivideRoundToNearest(value_, Denominator));
|
||||
}
|
||||
template <int64_t Denominator, typename T>
|
||||
constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type
|
||||
ToFraction() const {
|
||||
return ToValue<T>() * (1 / static_cast<T>(Denominator));
|
||||
}
|
||||
|
||||
template <int64_t Denominator>
|
||||
constexpr int64_t ToFractionOr(int64_t fallback_value) const {
|
||||
return IsFinite() ? DivideRoundToNearest(value_, Denominator)
|
||||
: fallback_value;
|
||||
}
|
||||
|
||||
template <int64_t Factor, typename T = int64_t>
|
||||
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type
|
||||
ToMultiple() const {
|
||||
return rtc::dchecked_cast<T>(ToValue() * Factor);
|
||||
}
|
||||
template <int64_t Factor, typename T>
|
||||
constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type
|
||||
ToMultiple() const {
|
||||
return ToValue<T>() * Factor;
|
||||
}
|
||||
|
||||
explicit constexpr UnitBase(int64_t value) : value_(value) {}
|
||||
|
||||
private:
|
||||
template <class RelativeUnit_T>
|
||||
friend class RelativeUnit;
|
||||
|
||||
static inline constexpr int64_t PlusInfinityVal() {
|
||||
return std::numeric_limits<int64_t>::max();
|
||||
}
|
||||
static inline constexpr int64_t MinusInfinityVal() {
|
||||
return std::numeric_limits<int64_t>::min();
|
||||
}
|
||||
|
||||
constexpr Unit_T& AsSubClassRef() { return static_cast<Unit_T&>(*this); }
|
||||
constexpr const Unit_T& AsSubClassRef() const {
|
||||
return static_cast<const Unit_T&>(*this);
|
||||
}
|
||||
|
||||
int64_t value_;
|
||||
};
|
||||
|
||||
// Extends UnitBase to provide operations for relative units, that is, units
|
||||
// that have a meaningful relation between values such that a += b is a
|
||||
// sensible thing to do. For a,b <- same unit.
|
||||
template <class Unit_T>
|
||||
class RelativeUnit : public UnitBase<Unit_T> {
|
||||
public:
|
||||
constexpr Unit_T Clamped(Unit_T min_value, Unit_T max_value) const {
|
||||
return std::max(min_value,
|
||||
std::min(UnitBase<Unit_T>::AsSubClassRef(), max_value));
|
||||
}
|
||||
constexpr void Clamp(Unit_T min_value, Unit_T max_value) {
|
||||
*this = Clamped(min_value, max_value);
|
||||
}
|
||||
constexpr Unit_T operator+(const Unit_T other) const {
|
||||
if (this->IsPlusInfinity() || other.IsPlusInfinity()) {
|
||||
return this->PlusInfinity();
|
||||
} else if (this->IsMinusInfinity() || other.IsMinusInfinity()) {
|
||||
return this->MinusInfinity();
|
||||
}
|
||||
return UnitBase<Unit_T>::FromValue(this->ToValue() + other.ToValue());
|
||||
}
|
||||
constexpr Unit_T operator-(const Unit_T other) const {
|
||||
if (this->IsPlusInfinity() || other.IsMinusInfinity()) {
|
||||
return this->PlusInfinity();
|
||||
} else if (this->IsMinusInfinity() || other.IsPlusInfinity()) {
|
||||
return this->MinusInfinity();
|
||||
}
|
||||
return UnitBase<Unit_T>::FromValue(this->ToValue() - other.ToValue());
|
||||
}
|
||||
constexpr Unit_T& operator+=(const Unit_T other) {
|
||||
*this = *this + other;
|
||||
return this->AsSubClassRef();
|
||||
}
|
||||
constexpr Unit_T& operator-=(const Unit_T other) {
|
||||
*this = *this - other;
|
||||
return this->AsSubClassRef();
|
||||
}
|
||||
constexpr double operator/(const Unit_T other) const {
|
||||
return UnitBase<Unit_T>::template ToValue<double>() /
|
||||
other.template ToValue<double>();
|
||||
}
|
||||
template <typename T,
|
||||
typename std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
|
||||
constexpr Unit_T operator/(T scalar) const {
|
||||
return UnitBase<Unit_T>::FromValue(std::llround(this->ToValue() / scalar));
|
||||
}
|
||||
template <typename T,
|
||||
typename std::enable_if_t<std::is_integral_v<T>>* = nullptr>
|
||||
constexpr Unit_T operator/(T scalar) const {
|
||||
return UnitBase<Unit_T>::FromValue(this->ToValue() / scalar);
|
||||
}
|
||||
constexpr Unit_T operator*(double scalar) const {
|
||||
return UnitBase<Unit_T>::FromValue(std::llround(this->ToValue() * scalar));
|
||||
}
|
||||
constexpr Unit_T operator*(int64_t scalar) const {
|
||||
return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);
|
||||
}
|
||||
constexpr Unit_T operator*(int32_t scalar) const {
|
||||
return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);
|
||||
}
|
||||
constexpr Unit_T operator*(size_t scalar) const {
|
||||
return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);
|
||||
}
|
||||
|
||||
protected:
|
||||
using UnitBase<Unit_T>::UnitBase;
|
||||
constexpr RelativeUnit() : UnitBase<Unit_T>(0) {}
|
||||
};
|
||||
|
||||
template <class Unit_T>
|
||||
inline constexpr Unit_T operator*(double scalar, RelativeUnit<Unit_T> other) {
|
||||
return other * scalar;
|
||||
}
|
||||
template <class Unit_T>
|
||||
inline constexpr Unit_T operator*(int64_t scalar, RelativeUnit<Unit_T> other) {
|
||||
return other * scalar;
|
||||
}
|
||||
template <class Unit_T>
|
||||
inline constexpr Unit_T operator*(int32_t scalar, RelativeUnit<Unit_T> other) {
|
||||
return other * scalar;
|
||||
}
|
||||
template <class Unit_T>
|
||||
inline constexpr Unit_T operator*(size_t scalar, RelativeUnit<Unit_T> other) {
|
||||
return other * scalar;
|
||||
}
|
||||
|
||||
template <class Unit_T>
|
||||
inline constexpr Unit_T operator-(RelativeUnit<Unit_T> other) {
|
||||
if (other.IsPlusInfinity()) return UnitBase<Unit_T>::MinusInfinity();
|
||||
if (other.IsMinusInfinity()) return UnitBase<Unit_T>::PlusInfinity();
|
||||
return -1 * other;
|
||||
}
|
||||
|
||||
} // namespace rtc_units_impl
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // RTC_BASE_UNITS_UNIT_BASE_H_
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "api/video/video_timing.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "api/array_view.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "log.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
uint16_t VideoSendTiming::GetDeltaCappedMs(int64_t base_ms, int64_t time_ms) {
|
||||
if (time_ms < base_ms) {
|
||||
LOG_ERROR("Delta {} ms expected to be positive", (time_ms - base_ms));
|
||||
}
|
||||
return rtc::saturated_cast<uint16_t>(time_ms - base_ms);
|
||||
}
|
||||
|
||||
uint16_t VideoSendTiming::GetDeltaCappedMs(TimeDelta delta) {
|
||||
if (delta < TimeDelta::Zero()) {
|
||||
LOG_ERROR("Delta {} ms expected to be positive", delta.ms());
|
||||
}
|
||||
return rtc::saturated_cast<uint16_t>(delta.ms());
|
||||
}
|
||||
|
||||
TimingFrameInfo::TimingFrameInfo()
|
||||
: rtp_timestamp(0),
|
||||
capture_time_ms(-1),
|
||||
encode_start_ms(-1),
|
||||
encode_finish_ms(-1),
|
||||
packetization_finish_ms(-1),
|
||||
pacer_exit_ms(-1),
|
||||
network_timestamp_ms(-1),
|
||||
network2_timestamp_ms(-1),
|
||||
receive_start_ms(-1),
|
||||
receive_finish_ms(-1),
|
||||
decode_start_ms(-1),
|
||||
decode_finish_ms(-1),
|
||||
render_time_ms(-1),
|
||||
flags(VideoSendTiming::kNotTriggered) {}
|
||||
|
||||
int64_t TimingFrameInfo::EndToEndDelay() const {
|
||||
return capture_time_ms >= 0 ? decode_finish_ms - capture_time_ms : -1;
|
||||
}
|
||||
|
||||
bool TimingFrameInfo::IsLongerThan(const TimingFrameInfo& other) const {
|
||||
int64_t other_delay = other.EndToEndDelay();
|
||||
return other_delay == -1 || EndToEndDelay() > other_delay;
|
||||
}
|
||||
|
||||
bool TimingFrameInfo::operator<(const TimingFrameInfo& other) const {
|
||||
return other.IsLongerThan(*this);
|
||||
}
|
||||
|
||||
bool TimingFrameInfo::operator<=(const TimingFrameInfo& other) const {
|
||||
return !IsLongerThan(other);
|
||||
}
|
||||
|
||||
bool TimingFrameInfo::IsOutlier() const {
|
||||
return !IsInvalid() && (flags & VideoSendTiming::kTriggeredBySize);
|
||||
}
|
||||
|
||||
bool TimingFrameInfo::IsTimerTriggered() const {
|
||||
return !IsInvalid() && (flags & VideoSendTiming::kTriggeredByTimer);
|
||||
}
|
||||
|
||||
bool TimingFrameInfo::IsInvalid() const {
|
||||
return flags == VideoSendTiming::kInvalid;
|
||||
}
|
||||
|
||||
std::string TimingFrameInfo::ToString() const {
|
||||
if (IsInvalid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << rtp_timestamp << ',' << capture_time_ms << ',' << encode_start_ms
|
||||
<< ',' << encode_finish_ms << ',' << packetization_finish_ms << ','
|
||||
<< pacer_exit_ms << ',' << network_timestamp_ms << ','
|
||||
<< network2_timestamp_ms << ',' << receive_start_ms << ','
|
||||
<< receive_finish_ms << ',' << decode_start_ms << ',' << decode_finish_ms
|
||||
<< ',' << render_time_ms << ',' << IsOutlier() << ','
|
||||
<< IsTimerTriggered();
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
VideoPlayoutDelay::VideoPlayoutDelay(TimeDelta min, TimeDelta max)
|
||||
: min_(std::clamp(min, TimeDelta::Zero(), kMax)),
|
||||
max_(std::clamp(max, min_, kMax)) {
|
||||
if (!(TimeDelta::Zero() <= min && min <= max && max <= kMax)) {
|
||||
LOG_ERROR("Invalid video playout delay: [{},{}]. Clamped to [{},{}]", min,
|
||||
max, this->min(), this->max());
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoPlayoutDelay::Set(TimeDelta min, TimeDelta max) {
|
||||
if (TimeDelta::Zero() <= min && min <= max && max <= kMax) {
|
||||
min_ = min;
|
||||
max_ = max;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef API_VIDEO_VIDEO_TIMING_H_
|
||||
#define API_VIDEO_VIDEO_TIMING_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "api/units/time_delta.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Video timing timestamps in ms counted from capture_time_ms of a frame.
|
||||
// This structure represents data sent in video-timing RTP header extension.
|
||||
struct VideoSendTiming {
|
||||
enum TimingFrameFlags : uint8_t {
|
||||
kNotTriggered = 0, // Timing info valid, but not to be transmitted.
|
||||
// Used on send-side only.
|
||||
kTriggeredByTimer = 1 << 0, // Frame marked for tracing by periodic timer.
|
||||
kTriggeredBySize = 1 << 1, // Frame marked for tracing due to size.
|
||||
kInvalid = std::numeric_limits<uint8_t>::max() // Invalid, ignore!
|
||||
};
|
||||
|
||||
// Returns |time_ms - base_ms| capped at max 16-bit value.
|
||||
// Used to fill this data structure as per
|
||||
// https://webrtc.org/experiments/rtp-hdrext/video-timing/ extension stores
|
||||
// 16-bit deltas of timestamps from packet capture time.
|
||||
static uint16_t GetDeltaCappedMs(int64_t base_ms, int64_t time_ms);
|
||||
static uint16_t GetDeltaCappedMs(TimeDelta delta);
|
||||
|
||||
uint16_t encode_start_delta_ms;
|
||||
uint16_t encode_finish_delta_ms;
|
||||
uint16_t packetization_finish_delta_ms;
|
||||
uint16_t pacer_exit_delta_ms;
|
||||
uint16_t network_timestamp_delta_ms;
|
||||
uint16_t network2_timestamp_delta_ms;
|
||||
uint8_t flags = TimingFrameFlags::kInvalid;
|
||||
};
|
||||
|
||||
// Used to report precise timings of a 'timing frames'. Contains all important
|
||||
// timestamps for a lifetime of that specific frame. Reported as a string via
|
||||
// GetStats(). Only frame which took the longest between two GetStats calls is
|
||||
// reported.
|
||||
struct TimingFrameInfo {
|
||||
TimingFrameInfo();
|
||||
|
||||
// Returns end-to-end delay of a frame, if sender and receiver timestamps are
|
||||
// synchronized, -1 otherwise.
|
||||
int64_t EndToEndDelay() const;
|
||||
|
||||
// Returns true if current frame took longer to process than `other` frame.
|
||||
// If other frame's clocks are not synchronized, current frame is always
|
||||
// preferred.
|
||||
bool IsLongerThan(const TimingFrameInfo& other) const;
|
||||
|
||||
// Returns true if flags are set to indicate this frame was marked for tracing
|
||||
// due to the size being outside some limit.
|
||||
bool IsOutlier() const;
|
||||
|
||||
// Returns true if flags are set to indicate this frame was marked fro tracing
|
||||
// due to cyclic timer.
|
||||
bool IsTimerTriggered() const;
|
||||
|
||||
// Returns true if the timing data is marked as invalid, in which case it
|
||||
// should be ignored.
|
||||
bool IsInvalid() const;
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
bool operator<(const TimingFrameInfo& other) const;
|
||||
|
||||
bool operator<=(const TimingFrameInfo& other) const;
|
||||
|
||||
uint32_t rtp_timestamp; // Identifier of a frame.
|
||||
// All timestamps below are in local monotonous clock of a receiver.
|
||||
// If sender clock is not yet estimated, sender timestamps
|
||||
// (capture_time_ms ... pacer_exit_ms) are negative values, still
|
||||
// relatively correct.
|
||||
int64_t capture_time_ms; // Captrue time of a frame.
|
||||
int64_t encode_start_ms; // Encode start time.
|
||||
int64_t encode_finish_ms; // Encode completion time.
|
||||
int64_t packetization_finish_ms; // Time when frame was passed to pacer.
|
||||
int64_t pacer_exit_ms; // Time when last packet was pushed out of pacer.
|
||||
// Two in-network RTP processor timestamps: meaning is application specific.
|
||||
int64_t network_timestamp_ms;
|
||||
int64_t network2_timestamp_ms;
|
||||
int64_t receive_start_ms; // First received packet time.
|
||||
int64_t receive_finish_ms; // Last received packet time.
|
||||
int64_t decode_start_ms; // Decode start time.
|
||||
int64_t decode_finish_ms; // Decode completion time.
|
||||
int64_t render_time_ms; // Proposed render time to insure smooth playback.
|
||||
|
||||
uint8_t flags; // Flags indicating validity and/or why tracing was triggered.
|
||||
};
|
||||
|
||||
// Minimum and maximum playout delay values from capture to render.
|
||||
// These are best effort values.
|
||||
//
|
||||
// min = max = 0 indicates that the receiver should try and render
|
||||
// frame as soon as possible.
|
||||
//
|
||||
// min = x, max = y indicates that the receiver is free to adapt
|
||||
// in the range (x, y) based on network jitter.
|
||||
// This class ensures invariant 0 <= min <= max <= kMax.
|
||||
class VideoPlayoutDelay {
|
||||
public:
|
||||
// Maximum supported value for the delay limit.
|
||||
static constexpr TimeDelta kMax = TimeDelta::Millis(10) * 0xFFF;
|
||||
|
||||
// Creates delay limits that indicates receiver should try to render frame
|
||||
// as soon as possible.
|
||||
static VideoPlayoutDelay Minimal() {
|
||||
return VideoPlayoutDelay(TimeDelta::Zero(), TimeDelta::Zero());
|
||||
}
|
||||
|
||||
// Creates valid, but unspecified limits.
|
||||
VideoPlayoutDelay() = default;
|
||||
VideoPlayoutDelay(const VideoPlayoutDelay&) = default;
|
||||
VideoPlayoutDelay& operator=(const VideoPlayoutDelay&) = default;
|
||||
VideoPlayoutDelay(TimeDelta min, TimeDelta max);
|
||||
|
||||
bool Set(TimeDelta min, TimeDelta max);
|
||||
|
||||
TimeDelta min() const { return min_; }
|
||||
TimeDelta max() const { return max_; }
|
||||
|
||||
friend bool operator==(const VideoPlayoutDelay& lhs,
|
||||
const VideoPlayoutDelay& rhs) {
|
||||
return lhs.min_ == rhs.min_ && lhs.max_ == rhs.max_;
|
||||
}
|
||||
|
||||
private:
|
||||
TimeDelta min_ = TimeDelta::Zero();
|
||||
TimeDelta max_ = kMax;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_VIDEO_VIDEO_TIMING_H_
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-12-18
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _BYTE_IO_H_
|
||||
#define _BYTE_IO_H_
|
||||
|
||||
// This file contains classes for reading and writing integer types from/to
|
||||
// byte array representations. Signed/unsigned, partial (whole byte) sizes,
|
||||
// and big/little endian byte order is all supported.
|
||||
//
|
||||
// Usage examples:
|
||||
//
|
||||
// uint8_t* buffer = ...;
|
||||
//
|
||||
// // Read an unsigned 4 byte integer in big endian format
|
||||
// uint32_t val = ByteReader<uint32_t>::ReadBigEndian(buffer);
|
||||
//
|
||||
// // Read a signed 24-bit (3 byte) integer in little endian format
|
||||
// int32_t val = ByteReader<int32_t, 3>::ReadLittle(buffer);
|
||||
//
|
||||
// // Write an unsigned 8 byte integer in little endian format
|
||||
// ByteWriter<uint64_t>::WriteLittleEndian(buffer, val);
|
||||
//
|
||||
// Write an unsigned 40-bit (5 byte) integer in big endian format
|
||||
// ByteWriter<uint64_t, 5>::WriteBigEndian(buffer, val);
|
||||
//
|
||||
// These classes are implemented as recursive templetizations, intended to make
|
||||
// it easy for the compiler to completely inline the reading/writing.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
// According to ISO C standard ISO/IEC 9899, section 6.2.6.2 (2), the three
|
||||
// representations of signed integers allowed are two's complement, one's
|
||||
// complement and sign/magnitude. We can detect which is used by looking at
|
||||
// the two last bits of -1, which will be 11 in two's complement, 10 in one's
|
||||
// complement and 01 in sign/magnitude.
|
||||
// TODO(sprang): In the unlikely event that we actually need to support a
|
||||
// platform that doesn't use two's complement, implement conversion to/from
|
||||
// wire format.
|
||||
|
||||
// Assume the if any one signed integer type is two's complement, then all
|
||||
// other will be too.
|
||||
static_assert(
|
||||
(-1 & 0x03) == 0x03,
|
||||
"Only two's complement representation of signed integers supported.");
|
||||
|
||||
// Plain const char* won't work for static_assert, use #define instead.
|
||||
#define kSizeErrorMsg "Byte size must be less than or equal to data type size."
|
||||
|
||||
// Utility class for getting the unsigned equivalent of a signed type.
|
||||
template <typename T>
|
||||
struct UnsignedOf;
|
||||
|
||||
// Class for reading integers from a sequence of bytes.
|
||||
// T = type of integer, B = bytes to read, is_signed = true if signed integer.
|
||||
// If is_signed is true and B < sizeof(T), sign extension might be needed.
|
||||
template <typename T, unsigned int B = sizeof(T),
|
||||
bool is_signed = std::numeric_limits<T>::is_signed>
|
||||
class ByteReader;
|
||||
|
||||
// Specialization of ByteReader for unsigned types.
|
||||
template <typename T, unsigned int B>
|
||||
class ByteReader<T, B, false> {
|
||||
public:
|
||||
static T ReadBigEndian(const uint8_t* data) {
|
||||
static_assert(B <= sizeof(T), kSizeErrorMsg);
|
||||
return InternalReadBigEndian(data);
|
||||
}
|
||||
|
||||
static T ReadLittleEndian(const uint8_t* data) {
|
||||
static_assert(B <= sizeof(T), kSizeErrorMsg);
|
||||
return InternalReadLittleEndian(data);
|
||||
}
|
||||
|
||||
private:
|
||||
static T InternalReadBigEndian(const uint8_t* data) {
|
||||
T val(0);
|
||||
for (unsigned int i = 0; i < B; ++i)
|
||||
val |= static_cast<T>(data[i]) << ((B - 1 - i) * 8);
|
||||
return val;
|
||||
}
|
||||
|
||||
static T InternalReadLittleEndian(const uint8_t* data) {
|
||||
T val(0);
|
||||
for (unsigned int i = 0; i < B; ++i)
|
||||
val |= static_cast<T>(data[i]) << (i * 8);
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization of ByteReader for signed types.
|
||||
template <typename T, unsigned int B>
|
||||
class ByteReader<T, B, true> {
|
||||
public:
|
||||
typedef typename UnsignedOf<T>::Type U;
|
||||
|
||||
static T ReadBigEndian(const uint8_t* data) {
|
||||
U unsigned_val = ByteReader<T, B, false>::ReadBigEndian(data);
|
||||
if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val);
|
||||
return ReinterpretAsSigned(unsigned_val);
|
||||
}
|
||||
|
||||
static T ReadLittleEndian(const uint8_t* data) {
|
||||
U unsigned_val = ByteReader<T, B, false>::ReadLittleEndian(data);
|
||||
if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val);
|
||||
return ReinterpretAsSigned(unsigned_val);
|
||||
}
|
||||
|
||||
private:
|
||||
// As a hack to avoid implementation-specific or undefined behavior when
|
||||
// bit-shifting or casting signed integers, read as a signed equivalent
|
||||
// instead and convert to signed. This is safe since we have asserted that
|
||||
// two's complement for is used.
|
||||
static T ReinterpretAsSigned(U unsigned_val) {
|
||||
// An unsigned value with only the highest order bit set (ex 0x80).
|
||||
const U kUnsignedHighestBitMask = static_cast<U>(1)
|
||||
<< ((sizeof(U) * 8) - 1);
|
||||
// A signed value with only the highest bit set. Since this is two's
|
||||
// complement form, we can use the min value from std::numeric_limits.
|
||||
const T kSignedHighestBitMask = std::numeric_limits<T>::min();
|
||||
|
||||
T val;
|
||||
if ((unsigned_val & kUnsignedHighestBitMask) != 0) {
|
||||
// Casting is only safe when unsigned value can be represented in the
|
||||
// signed target type, so mask out highest bit and mask it back manually.
|
||||
val = static_cast<T>(unsigned_val & ~kUnsignedHighestBitMask);
|
||||
val |= kSignedHighestBitMask;
|
||||
} else {
|
||||
val = static_cast<T>(unsigned_val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// If number of bytes is less than native data type (eg 24 bit, in int32_t),
|
||||
// and the most significant bit of the actual data is set, we must sign
|
||||
// extend the remaining byte(s) with ones so that the correct negative
|
||||
// number is retained.
|
||||
// Ex: 0x810A0B -> 0xFF810A0B, but 0x710A0B -> 0x00710A0B
|
||||
static U SignExtend(const U val) {
|
||||
const uint8_t kMsb = static_cast<uint8_t>(val >> ((B - 1) * 8));
|
||||
if ((kMsb & 0x80) != 0) {
|
||||
// Create a mask where all bits used by the B bytes are set to one,
|
||||
// for instance 0x00FFFFFF for B = 3. Bit-wise invert that mask (to
|
||||
// (0xFF000000 in the example above) and add it to the input value.
|
||||
// The "B % sizeof(T)" is a workaround to undefined values warnings for
|
||||
// B == sizeof(T), in which case this code won't be called anyway.
|
||||
const U kUsedBitsMask = (1 << ((B % sizeof(T)) * 8)) - 1;
|
||||
return ~kUsedBitsMask | val;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
// Class for writing integers to a sequence of bytes
|
||||
// T = type of integer, B = bytes to write
|
||||
template <typename T, unsigned int B = sizeof(T),
|
||||
bool is_signed = std::numeric_limits<T>::is_signed>
|
||||
class ByteWriter;
|
||||
|
||||
// Specialization of ByteWriter for unsigned types.
|
||||
template <typename T, unsigned int B>
|
||||
class ByteWriter<T, B, false> {
|
||||
public:
|
||||
static void WriteBigEndian(uint8_t* data, T val) {
|
||||
static_assert(B <= sizeof(T), kSizeErrorMsg);
|
||||
for (unsigned int i = 0; i < B; ++i) {
|
||||
data[i] = val >> ((B - 1 - i) * 8);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteLittleEndian(uint8_t* data, T val) {
|
||||
static_assert(B <= sizeof(T), kSizeErrorMsg);
|
||||
for (unsigned int i = 0; i < B; ++i) {
|
||||
data[i] = val >> (i * 8);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization of ByteWriter for signed types.
|
||||
template <typename T, unsigned int B>
|
||||
class ByteWriter<T, B, true> {
|
||||
public:
|
||||
typedef typename UnsignedOf<T>::Type U;
|
||||
|
||||
static void WriteBigEndian(uint8_t* data, T val) {
|
||||
ByteWriter<U, B, false>::WriteBigEndian(data, ReinterpretAsUnsigned(val));
|
||||
}
|
||||
|
||||
static void WriteLittleEndian(uint8_t* data, T val) {
|
||||
ByteWriter<U, B, false>::WriteLittleEndian(data,
|
||||
ReinterpretAsUnsigned(val));
|
||||
}
|
||||
|
||||
private:
|
||||
static U ReinterpretAsUnsigned(T val) {
|
||||
// According to ISO C standard ISO/IEC 9899, section 6.3.1.3 (1, 2) a
|
||||
// conversion from signed to unsigned keeps the value if the new type can
|
||||
// represent it, and otherwise adds one more than the max value of T until
|
||||
// the value is in range. For two's complement, this fortunately means
|
||||
// that the bit-wise value will be intact. Thus, since we have asserted that
|
||||
// two's complement form is actually used, a simple cast is sufficient.
|
||||
return static_cast<U>(val);
|
||||
}
|
||||
};
|
||||
|
||||
// ----- Below follows specializations of UnsignedOf utility class -----
|
||||
|
||||
template <>
|
||||
struct UnsignedOf<int8_t> {
|
||||
typedef uint8_t Type;
|
||||
};
|
||||
template <>
|
||||
struct UnsignedOf<int16_t> {
|
||||
typedef uint16_t Type;
|
||||
};
|
||||
template <>
|
||||
struct UnsignedOf<int32_t> {
|
||||
typedef uint32_t Type;
|
||||
};
|
||||
template <>
|
||||
struct UnsignedOf<int64_t> {
|
||||
typedef uint64_t Type;
|
||||
};
|
||||
|
||||
// ----- Below follows specializations for unsigned, B in { 1, 2, 4, 8 } -----
|
||||
|
||||
// TODO(sprang): Check if these actually help or if generic cases will be
|
||||
// unrolled to and optimized to similar performance.
|
||||
|
||||
// Specializations for single bytes
|
||||
template <typename T>
|
||||
class ByteReader<T, 1, false> {
|
||||
public:
|
||||
static T ReadBigEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) == 1, kSizeErrorMsg);
|
||||
return data[0];
|
||||
}
|
||||
|
||||
static T ReadLittleEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) == 1, kSizeErrorMsg);
|
||||
return data[0];
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ByteWriter<T, 1, false> {
|
||||
public:
|
||||
static void WriteBigEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) == 1, kSizeErrorMsg);
|
||||
data[0] = val;
|
||||
}
|
||||
|
||||
static void WriteLittleEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) == 1, kSizeErrorMsg);
|
||||
data[0] = val;
|
||||
}
|
||||
};
|
||||
|
||||
// Specializations for two byte words
|
||||
template <typename T>
|
||||
class ByteReader<T, 2, false> {
|
||||
public:
|
||||
static T ReadBigEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
|
||||
return (data[0] << 8) | data[1];
|
||||
}
|
||||
|
||||
static T ReadLittleEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
|
||||
return data[0] | (data[1] << 8);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ByteWriter<T, 2, false> {
|
||||
public:
|
||||
static void WriteBigEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
|
||||
data[0] = val >> 8;
|
||||
data[1] = val;
|
||||
}
|
||||
|
||||
static void WriteLittleEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
|
||||
data[0] = val;
|
||||
data[1] = val >> 8;
|
||||
}
|
||||
};
|
||||
|
||||
// Specializations for four byte words.
|
||||
template <typename T>
|
||||
class ByteReader<T, 4, false> {
|
||||
public:
|
||||
static T ReadBigEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
|
||||
return (Get(data, 0) << 24) | (Get(data, 1) << 16) | (Get(data, 2) << 8) |
|
||||
Get(data, 3);
|
||||
}
|
||||
|
||||
static T ReadLittleEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
|
||||
return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) |
|
||||
(Get(data, 3) << 24);
|
||||
}
|
||||
|
||||
private:
|
||||
inline static T Get(const uint8_t* data, unsigned int index) {
|
||||
return static_cast<T>(data[index]);
|
||||
}
|
||||
};
|
||||
|
||||
// Specializations for four byte words.
|
||||
template <typename T>
|
||||
class ByteWriter<T, 4, false> {
|
||||
public:
|
||||
static void WriteBigEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
|
||||
data[0] = val >> 24;
|
||||
data[1] = val >> 16;
|
||||
data[2] = val >> 8;
|
||||
data[3] = val;
|
||||
}
|
||||
|
||||
static void WriteLittleEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
|
||||
data[0] = val;
|
||||
data[1] = val >> 8;
|
||||
data[2] = val >> 16;
|
||||
data[3] = val >> 24;
|
||||
}
|
||||
};
|
||||
|
||||
// Specializations for eight byte words.
|
||||
template <typename T>
|
||||
class ByteReader<T, 8, false> {
|
||||
public:
|
||||
static T ReadBigEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
|
||||
return (Get(data, 0) << 56) | (Get(data, 1) << 48) | (Get(data, 2) << 40) |
|
||||
(Get(data, 3) << 32) | (Get(data, 4) << 24) | (Get(data, 5) << 16) |
|
||||
(Get(data, 6) << 8) | Get(data, 7);
|
||||
}
|
||||
|
||||
static T ReadLittleEndian(const uint8_t* data) {
|
||||
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
|
||||
return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) |
|
||||
(Get(data, 3) << 24) | (Get(data, 4) << 32) | (Get(data, 5) << 40) |
|
||||
(Get(data, 6) << 48) | (Get(data, 7) << 56);
|
||||
}
|
||||
|
||||
private:
|
||||
inline static T Get(const uint8_t* data, unsigned int index) {
|
||||
return static_cast<T>(data[index]);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ByteWriter<T, 8, false> {
|
||||
public:
|
||||
static void WriteBigEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
|
||||
data[0] = val >> 56;
|
||||
data[1] = val >> 48;
|
||||
data[2] = val >> 40;
|
||||
data[3] = val >> 32;
|
||||
data[4] = val >> 24;
|
||||
data[5] = val >> 16;
|
||||
data[6] = val >> 8;
|
||||
data[7] = val;
|
||||
}
|
||||
|
||||
static void WriteLittleEndian(uint8_t* data, T val) {
|
||||
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
|
||||
data[0] = val;
|
||||
data[1] = val >> 8;
|
||||
data[2] = val >> 16;
|
||||
data[3] = val >> 24;
|
||||
data[4] = val >> 32;
|
||||
data[5] = val >> 40;
|
||||
data[6] = val >> 48;
|
||||
data[7] = val >> 56;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "system_clock.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#if defined(__POSIX__)
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
int64_t SystemClock::ConvertToNtpTime(int64_t time_us) {
|
||||
constexpr int64_t kMicrosecondsPerSecond = 1000000;
|
||||
constexpr uint64_t kNtpFractionalUnit = 0x100000000; // 2^32
|
||||
uint32_t seconds = static_cast<uint32_t>(time_us / kMicrosecondsPerSecond);
|
||||
uint32_t fractions =
|
||||
static_cast<uint32_t>((time_us % kMicrosecondsPerSecond) *
|
||||
kNtpFractionalUnit / kMicrosecondsPerSecond);
|
||||
|
||||
return seconds * kNtpFractionalUnit + fractions;
|
||||
}
|
||||
|
||||
int64_t SystemClock::CurrentTimeNs() {
|
||||
int64_t ticks = -1; // Default to error case
|
||||
|
||||
#if defined(__APPLE__)
|
||||
static mach_timebase_info_data_t timebase;
|
||||
if (timebase.denom == 0 && mach_timebase_info(&timebase) != KERN_SUCCESS) {
|
||||
return -1; // Error case for macOS timebase info retrieval
|
||||
}
|
||||
ticks = static_cast<int64_t>(mach_absolute_time() * timebase.numer) /
|
||||
timebase.denom;
|
||||
|
||||
#elif defined(__POSIX__)
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
|
||||
return -1; // Error case for POSIX clock retrieval
|
||||
}
|
||||
ticks = static_cast<int64_t>(ts.tv_sec) * kNumNanosecsPerSec +
|
||||
static_cast<int64_t>(ts.tv_nsec);
|
||||
|
||||
#elif defined(_WIN32)
|
||||
static volatile LONG last_timegettime = 0;
|
||||
static volatile int64_t num_wrap_timegettime = 0;
|
||||
volatile LONG* last_timegettime_ptr = &last_timegettime;
|
||||
|
||||
DWORD now = timeGetTime();
|
||||
DWORD old = InterlockedExchange(last_timegettime_ptr, now);
|
||||
|
||||
if (now < old) {
|
||||
// Handle wraparound (when timeGetTime() wraps around after ~49.7 days)
|
||||
if (old > 0xf0000000 && now < 0x0fffffff) {
|
||||
num_wrap_timegettime++;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert milliseconds to nanoseconds and add wraparound offset
|
||||
ticks = static_cast<int64_t>(now) + (num_wrap_timegettime << 32);
|
||||
ticks *= 1000000;
|
||||
#endif
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
int64_t SystemClock::CurrentTime() { return CurrentTimeNs() / 1000LL; }
|
||||
|
||||
int64_t SystemClock::CurrentTimeUs() { return CurrentTimeNs() / 1000LL; }
|
||||
|
||||
int64_t SystemClock::CurrentTimeMs() { return CurrentTimeNs() / 1000000LL; }
|
||||
|
||||
int64_t SystemClock::CurrentNtpTime() {
|
||||
return ConvertToNtpTime(CurrentTimeNs());
|
||||
}
|
||||
|
||||
int64_t SystemClock::CurrentNtpTimeMs() {
|
||||
int64_t ntp_ts = ConvertToNtpTime(CurrentTimeNs());
|
||||
uint32_t seconds = static_cast<uint32_t>(ntp_ts / 1000000000);
|
||||
uint32_t fractions = static_cast<uint32_t>(ntp_ts % 1000000000);
|
||||
|
||||
static constexpr double kNtpFracPerMs = 4.294967296E6; // 2^32 / 1000.
|
||||
const double frac_ms = static_cast<double>(fractions) / kNtpFracPerMs;
|
||||
return 1000 * static_cast<int64_t>(seconds) +
|
||||
static_cast<int64_t>(frac_ms + 0.5);
|
||||
}
|
||||
|
||||
int64_t SystemClock::CurrentUtcTimeNs() {
|
||||
#if defined(__POSIX__)
|
||||
struct timeval time;
|
||||
gettimeofday(&time, nullptr);
|
||||
return (static_cast<int64_t>(time.tv_sec) * 1000000000 + time.tv_usec * 1000);
|
||||
#elif defined(_WIN32)
|
||||
FILETIME file_time;
|
||||
GetSystemTimeAsFileTime(&file_time);
|
||||
int64_t file_time_100ns =
|
||||
((int64_t)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime;
|
||||
constexpr int64_t kUnixEpochFileTimeOffsetIn100ns = 116444736000000000LL;
|
||||
return (file_time_100ns - kUnixEpochFileTimeOffsetIn100ns) * 100;
|
||||
#elif defined(__APPLE__)
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
|
||||
return -1; // Error case for macOS clock retrieval
|
||||
}
|
||||
return static_cast<int64_t>(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t SystemClock::CurrentUtcTimeUs() { return CurrentUtcTimeNs() / 1000LL; }
|
||||
|
||||
int64_t SystemClock::CurrentUtcTimeMs() {
|
||||
return CurrentUtcTimeNs() / 1000000LL;
|
||||
}
|
||||
|
||||
int64_t SystemClock::CurrentUtcTime() {
|
||||
return CurrentUtcTimeNs() / 1000000000LL;
|
||||
}
|
||||
|
||||
int64_t SystemClock::NtpToUtc(int64_t ntp_time) {
|
||||
constexpr int64_t kNtpEpochOffset =
|
||||
2208988800LL; // NTP epoch starts at 1900-01-01, Unix epoch starts at
|
||||
// 1970-01-01
|
||||
constexpr int64_t kMicrosecondsPerSecond = 1000000;
|
||||
constexpr uint64_t kNtpFractionalUnit = 0x100000000; // 2^32
|
||||
|
||||
uint32_t seconds = static_cast<uint32_t>(ntp_time / kNtpFractionalUnit);
|
||||
uint32_t fractions = static_cast<uint32_t>(ntp_time % kNtpFractionalUnit);
|
||||
|
||||
int64_t unix_seconds = static_cast<int64_t>(seconds) - kNtpEpochOffset;
|
||||
int64_t microseconds =
|
||||
(static_cast<int64_t>(fractions) * kMicrosecondsPerSecond) /
|
||||
kNtpFractionalUnit;
|
||||
|
||||
return unix_seconds * kMicrosecondsPerSecond + microseconds;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-02-19
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SYSTEM_CLOCK_H_
|
||||
#define _SYSTEM_CLOCK_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
static const int64_t kNtpEpochOffset = 2208988800LL;
|
||||
|
||||
class SystemClock {
|
||||
public:
|
||||
SystemClock() = default;
|
||||
~SystemClock() = default;
|
||||
|
||||
int64_t CurrentTime();
|
||||
int64_t CurrentTimeUs();
|
||||
int64_t CurrentTimeMs();
|
||||
int64_t CurrentTimeNs();
|
||||
|
||||
int64_t CurrentNtpTime();
|
||||
int64_t CurrentNtpTimeMs();
|
||||
|
||||
int64_t CurrentUtcTime();
|
||||
int64_t CurrentUtcTimeMs();
|
||||
int64_t CurrentUtcTimeUs();
|
||||
int64_t CurrentUtcTimeNs();
|
||||
|
||||
int64_t ConvertToNtpTime(int64_t time_us);
|
||||
|
||||
int64_t NtpToUtc(int64_t ntp_time);
|
||||
|
||||
int64_t CurrentNtpInMilliseconds() { return CurrentNtpTimeMs(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "common.h"
|
||||
|
||||
int CommonDummy()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef _COMMON_H_
|
||||
#define _COMMON_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <unordered_set>
|
||||
|
||||
int CommonDummy();
|
||||
|
||||
constexpr size_t HASH_STRING_PIECE(const char *string_piece) {
|
||||
std::size_t result = 0;
|
||||
while (*string_piece) {
|
||||
result = (result * 131) + *string_piece++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr size_t operator"" _H(const char *string_piece, size_t) {
|
||||
return HASH_STRING_PIECE(string_piece);
|
||||
}
|
||||
|
||||
inline const std::string GetIceUsername(const std::string &sdp) {
|
||||
std::string result = "";
|
||||
|
||||
std::string start = "ice-ufrag:";
|
||||
std::string end = "\r\n";
|
||||
size_t startPos = sdp.find(start);
|
||||
size_t endPos = sdp.find(end);
|
||||
|
||||
if (startPos != std::string::npos && endPos != std::string::npos) {
|
||||
result = sdp.substr(startPos + start.length(),
|
||||
endPos - startPos - start.length());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// SSRCManager is used to manage the SSRCs that have been used.
|
||||
|
||||
class SSRCManager {
|
||||
public:
|
||||
static SSRCManager &Instance() {
|
||||
static SSRCManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void AddSsrc(uint32_t ssrc) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
ssrcs_.insert(ssrc);
|
||||
}
|
||||
|
||||
void DeleteSsrc(uint32_t ssrc) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
ssrcs_.erase(ssrc);
|
||||
}
|
||||
|
||||
bool Contains(uint32_t ssrc) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return ssrcs_.count(ssrc) > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
SSRCManager() = default;
|
||||
~SSRCManager() = default;
|
||||
SSRCManager(const SSRCManager &) = delete;
|
||||
SSRCManager &operator=(const SSRCManager &) = delete;
|
||||
|
||||
std::unordered_set<uint32_t> ssrcs_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
inline uint32_t GenerateRandomSSRC() {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<uint32_t> dis(1, 0xFFFFFFFF);
|
||||
return dis(gen);
|
||||
}
|
||||
|
||||
inline uint32_t GenerateUniqueSsrc() {
|
||||
uint32_t new_ssrc;
|
||||
do {
|
||||
new_ssrc = GenerateRandomSSRC();
|
||||
} while (SSRCManager::Instance().Contains(new_ssrc));
|
||||
SSRCManager::Instance().AddSsrc(new_ssrc);
|
||||
return new_ssrc;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-01-22
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _COPY_ON_WRITE_BUFFER_H_
|
||||
#define _COPY_ON_WRITE_BUFFER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class CopyOnWriteBuffer {
|
||||
public:
|
||||
CopyOnWriteBuffer() = default;
|
||||
CopyOnWriteBuffer(size_t size) {
|
||||
buffer_ = std::make_shared<std::vector<uint8_t>>(size);
|
||||
}
|
||||
CopyOnWriteBuffer(const uint8_t* data, size_t size) {
|
||||
buffer_ = std::make_shared<std::vector<uint8_t>>(data, data + size);
|
||||
}
|
||||
|
||||
CopyOnWriteBuffer(const CopyOnWriteBuffer& other) = default;
|
||||
CopyOnWriteBuffer(CopyOnWriteBuffer&& other) noexcept = default;
|
||||
CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& other) = default;
|
||||
CopyOnWriteBuffer& operator=(CopyOnWriteBuffer&& other) noexcept = default;
|
||||
|
||||
void SetData(const uint8_t* data, size_t size) {
|
||||
buffer_ = std::make_shared<std::vector<uint8_t>>(data, data + size);
|
||||
}
|
||||
|
||||
void InsertDataAt(size_t offset, const uint8_t* data, size_t size) {
|
||||
EnsureUnique();
|
||||
buffer_->insert(buffer_->begin() + offset, data, data + size);
|
||||
}
|
||||
|
||||
const uint8_t* data() const { return buffer_ ? buffer_->data() : nullptr; }
|
||||
|
||||
size_t size() const { return buffer_ ? buffer_->size() : 0; }
|
||||
|
||||
uint8_t& operator[](size_t index) {
|
||||
EnsureUnique();
|
||||
return (*buffer_)[index];
|
||||
}
|
||||
|
||||
const uint8_t& operator[](size_t index) const { return (*buffer_)[index]; }
|
||||
|
||||
private:
|
||||
void EnsureUnique() {
|
||||
if (buffer_.use_count() != 1) {
|
||||
buffer_ = std::make_shared<std::vector<uint8_t>>(*buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> buffer_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-05-15
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DISPLAY_INFO_H_
|
||||
#define _DISPLAY_INFO_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class DisplayInfo {
|
||||
public:
|
||||
DisplayInfo(std::string name, int left, int top, int right, int bottom)
|
||||
: name(name), left(left), top(top), right(right), bottom(bottom) {
|
||||
width = right - left;
|
||||
height = bottom - top;
|
||||
}
|
||||
DisplayInfo(void* handle, std::string name, bool is_primary, int left,
|
||||
int top, int right, int bottom)
|
||||
: handle(handle),
|
||||
name(name),
|
||||
is_primary(is_primary),
|
||||
left(left),
|
||||
top(top),
|
||||
right(right),
|
||||
bottom(bottom) {
|
||||
width = right - left;
|
||||
height = bottom - top;
|
||||
}
|
||||
~DisplayInfo() {}
|
||||
|
||||
void* handle = nullptr;
|
||||
std::string name = "";
|
||||
bool is_primary = false;
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
int right = 0;
|
||||
int bottom = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef CROSSDESK_COMMON_FILESYSTEM_UTF8_H_
|
||||
#define CROSSDESK_COMMON_FILESYSTEM_UTF8_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
inline std::filesystem::path PathFromUtf8(std::string_view value) {
|
||||
#if defined(__cpp_char8_t)
|
||||
const auto *begin = reinterpret_cast<const char8_t *>(value.data());
|
||||
return std::filesystem::path(std::u8string(begin, begin + value.size()));
|
||||
#else
|
||||
return std::filesystem::u8path(value.begin(), value.end());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif // CROSSDESK_COMMON_FILESYSTEM_UTF8_H_
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-03-12
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _INLINED_VECTOR_H_
|
||||
#define _INLINED_VECTOR_H_
|
||||
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
template <typename T, size_t N>
|
||||
class InlinedVector {
|
||||
public:
|
||||
InlinedVector() : size_(0), use_heap_(false) {}
|
||||
|
||||
void push_back(const T& value) {
|
||||
if (!use_heap_ && size_ < N) {
|
||||
stack_data_[size_] = value;
|
||||
} else {
|
||||
if (!use_heap_) {
|
||||
heap_data_.reserve(N * 2);
|
||||
for (size_t i = 0; i < size_; ++i) {
|
||||
heap_data_.push_back(stack_data_[i]);
|
||||
}
|
||||
use_heap_ = true;
|
||||
}
|
||||
heap_data_.push_back(value);
|
||||
}
|
||||
++size_;
|
||||
}
|
||||
|
||||
void assign(size_t n, const T& value) {
|
||||
clear();
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
T& operator[](size_t index) {
|
||||
return use_heap_ ? heap_data_[index] : stack_data_[index];
|
||||
}
|
||||
|
||||
const T& operator[](size_t index) const {
|
||||
return use_heap_ ? heap_data_[index] : stack_data_[index];
|
||||
}
|
||||
|
||||
private:
|
||||
void clear() {
|
||||
size_ = 0;
|
||||
use_heap_ = false;
|
||||
heap_data_.clear();
|
||||
}
|
||||
|
||||
size_t size_;
|
||||
bool use_heap_;
|
||||
std::array<T, N> stack_data_;
|
||||
std::vector<T> heap_data_;
|
||||
};
|
||||
|
||||
#endif // _INLINED_VECTOR_H_
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-01-14
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _LIMITS_BASE_H_
|
||||
#define _LIMITS_BASE_H_
|
||||
|
||||
#include <limits>
|
||||
|
||||
template <typename T>
|
||||
bool IsInfinite(const T& value) {
|
||||
return value == std::numeric_limits<T>::min() ||
|
||||
value == std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsFinite(const T& value) {
|
||||
return !IsInfinite(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsPlusFinite(const T& value) {
|
||||
return value == std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsMinusFinite(const T& value) {
|
||||
return value == std::numeric_limits<T>::min();
|
||||
}
|
||||
|
||||
#define INT64_T_MAX std::numeric_limits<int64_t>::max()
|
||||
#define INT64_T_MIN std::numeric_limits<int64_t>::min()
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-01-08
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MOD_OPS_H_
|
||||
#define _MOD_OPS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
template <unsigned long M> // NOLINT
|
||||
inline unsigned long Add(unsigned long a, unsigned long b) { // NOLINT
|
||||
// RTC_DCHECK_LT(a, M);
|
||||
unsigned long t = M - b % M; // NOLINT
|
||||
unsigned long res = a - t; // NOLINT
|
||||
if (t > a) return res + M;
|
||||
return res;
|
||||
}
|
||||
|
||||
template <unsigned long M> // NOLINT
|
||||
inline unsigned long Subtract(unsigned long a, unsigned long b) { // NOLINT
|
||||
// RTC_DCHECK_LT(a, M);
|
||||
unsigned long sub = b % M; // NOLINT
|
||||
if (a < sub) return M - (sub - a);
|
||||
return a - sub;
|
||||
}
|
||||
|
||||
// Calculates the forward difference between two wrapping numbers.
|
||||
//
|
||||
// Example:
|
||||
// uint8_t x = 253;
|
||||
// uint8_t y = 2;
|
||||
//
|
||||
// ForwardDiff(x, y) == 5
|
||||
//
|
||||
// 252 253 254 255 0 1 2 3
|
||||
// #################################################
|
||||
// | | x | | | | | y | |
|
||||
// #################################################
|
||||
// |----->----->----->----->----->
|
||||
//
|
||||
// ForwardDiff(y, x) == 251
|
||||
//
|
||||
// 252 253 254 255 0 1 2 3
|
||||
// #################################################
|
||||
// | | x | | | | | y | |
|
||||
// #################################################
|
||||
// -->-----> |----->---
|
||||
//
|
||||
// If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the
|
||||
// largest value representable by T.
|
||||
template <typename T, T M>
|
||||
inline typename std::enable_if<(M > 0), T>::type ForwardDiff(T a, T b) {
|
||||
static_assert(std::is_unsigned<T>::value,
|
||||
"Type must be an unsigned integer.");
|
||||
// RTC_DCHECK_LT(a, M);
|
||||
// RTC_DCHECK_LT(b, M);
|
||||
return a <= b ? b - a : M - (a - b);
|
||||
}
|
||||
|
||||
template <typename T, T M>
|
||||
inline typename std::enable_if<(M == 0), T>::type ForwardDiff(T a, T b) {
|
||||
static_assert(std::is_unsigned<T>::value,
|
||||
"Type must be an unsigned integer.");
|
||||
return b - a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T ForwardDiff(T a, T b) {
|
||||
return ForwardDiff<T, 0>(a, b);
|
||||
}
|
||||
|
||||
// Calculates the reverse difference between two wrapping numbers.
|
||||
//
|
||||
// Example:
|
||||
// uint8_t x = 253;
|
||||
// uint8_t y = 2;
|
||||
//
|
||||
// ReverseDiff(y, x) == 5
|
||||
//
|
||||
// 252 253 254 255 0 1 2 3
|
||||
// #################################################
|
||||
// | | x | | | | | y | |
|
||||
// #################################################
|
||||
// <-----<-----<-----<-----<-----|
|
||||
//
|
||||
// ReverseDiff(x, y) == 251
|
||||
//
|
||||
// 252 253 254 255 0 1 2 3
|
||||
// #################################################
|
||||
// | | x | | | | | y | |
|
||||
// #################################################
|
||||
// ---<-----| |<-----<--
|
||||
//
|
||||
// If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the
|
||||
// largest value representable by T.
|
||||
template <typename T, T M>
|
||||
inline typename std::enable_if<(M > 0), T>::type ReverseDiff(T a, T b) {
|
||||
static_assert(std::is_unsigned<T>::value,
|
||||
"Type must be an unsigned integer.");
|
||||
// RTC_DCHECK_LT(a, M);
|
||||
// RTC_DCHECK_LT(b, M);
|
||||
return b <= a ? a - b : M - (b - a);
|
||||
}
|
||||
|
||||
template <typename T, T M>
|
||||
inline typename std::enable_if<(M == 0), T>::type ReverseDiff(T a, T b) {
|
||||
static_assert(std::is_unsigned<T>::value,
|
||||
"Type must be an unsigned integer.");
|
||||
return a - b;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T ReverseDiff(T a, T b) {
|
||||
return ReverseDiff<T, 0>(a, b);
|
||||
}
|
||||
|
||||
// Calculates the minimum distance between to wrapping numbers.
|
||||
//
|
||||
// The minimum distance is defined as min(ForwardDiff(a, b), ReverseDiff(a, b))
|
||||
template <typename T, T M = 0>
|
||||
inline T MinDiff(T a, T b) {
|
||||
static_assert(std::is_unsigned<T>::value,
|
||||
"Type must be an unsigned integer.");
|
||||
return (std::min)(ForwardDiff<T, M>(a, b), ReverseDiff<T, M>(a, b));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_
|
||||
#define MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_
|
||||
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
template <typename U>
|
||||
inline bool IsNewer(U value, U prev_value) {
|
||||
static_assert(!std::numeric_limits<U>::is_signed, "U must be unsigned");
|
||||
// kBreakpoint is the half-way mark for the type U. For instance, for a
|
||||
// uint16_t it will be 0x8000, and for a uint32_t, it will be 0x8000000.
|
||||
constexpr U kBreakpoint = (std::numeric_limits<U>::max() >> 1) + 1;
|
||||
// Distinguish between elements that are exactly kBreakpoint apart.
|
||||
// If t1>t2 and |t1-t2| = kBreakpoint: IsNewer(t1,t2)=true,
|
||||
// IsNewer(t2,t1)=false
|
||||
// rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false.
|
||||
if (value - prev_value == kBreakpoint) {
|
||||
return value > prev_value;
|
||||
}
|
||||
return value != prev_value &&
|
||||
static_cast<U>(value - prev_value) < kBreakpoint;
|
||||
}
|
||||
|
||||
// NB: Doesn't fulfill strict weak ordering requirements.
|
||||
// Mustn't be used as std::map Compare function.
|
||||
inline bool IsNewerSequenceNumber(uint16_t sequence_number,
|
||||
uint16_t prev_sequence_number) {
|
||||
return IsNewer(sequence_number, prev_sequence_number);
|
||||
}
|
||||
|
||||
// NB: Doesn't fulfill strict weak ordering requirements.
|
||||
// Mustn't be used as std::map Compare function.
|
||||
inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) {
|
||||
return IsNewer(timestamp, prev_timestamp);
|
||||
}
|
||||
|
||||
inline uint16_t LatestSequenceNumber(uint16_t sequence_number1,
|
||||
uint16_t sequence_number2) {
|
||||
return IsNewerSequenceNumber(sequence_number1, sequence_number2)
|
||||
? sequence_number1
|
||||
: sequence_number2;
|
||||
}
|
||||
|
||||
inline uint32_t LatestTimestamp(uint32_t timestamp1, uint32_t timestamp2) {
|
||||
return IsNewerTimestamp(timestamp1, timestamp2) ? timestamp1 : timestamp2;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_
|
||||
@@ -1,152 +0,0 @@
|
||||
#include "platform.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
#elif __APPLE__
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#elif __linux__
|
||||
#include <fcntl.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
std::string GetMac() {
|
||||
char mac_addr[16];
|
||||
int len = 0;
|
||||
#ifdef _WIN32
|
||||
IP_ADAPTER_INFO adapterInfo[16];
|
||||
DWORD bufferSize = sizeof(adapterInfo);
|
||||
DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize);
|
||||
if (result == ERROR_SUCCESS) {
|
||||
PIP_ADAPTER_INFO adapter = adapterInfo;
|
||||
while (adapter) {
|
||||
for (UINT i = 0; i < adapter->AddressLength; i++) {
|
||||
len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X",
|
||||
adapter->Address[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif __APPLE__
|
||||
std::string if_name = "en0";
|
||||
|
||||
struct ifaddrs* addrs;
|
||||
struct ifaddrs* cursor;
|
||||
const struct sockaddr_dl* dlAddr;
|
||||
|
||||
if (!getifaddrs(&addrs)) {
|
||||
cursor = addrs;
|
||||
while (cursor != 0) {
|
||||
const struct sockaddr_dl* socAddr =
|
||||
(const struct sockaddr_dl*)cursor->ifa_addr;
|
||||
if ((cursor->ifa_addr->sa_family == AF_LINK) &&
|
||||
(socAddr->sdl_type == IFT_ETHER) &&
|
||||
strcmp(if_name.c_str(), cursor->ifa_name) == 0) {
|
||||
dlAddr = (const struct sockaddr_dl*)cursor->ifa_addr;
|
||||
const unsigned char* base =
|
||||
(const unsigned char*)&dlAddr->sdl_data[dlAddr->sdl_nlen];
|
||||
for (int i = 0; i < dlAddr->sdl_alen; i++) {
|
||||
len +=
|
||||
snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]);
|
||||
}
|
||||
}
|
||||
cursor = cursor->ifa_next;
|
||||
}
|
||||
freeifaddrs(addrs);
|
||||
}
|
||||
#elif __linux__
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock < 0) {
|
||||
return "";
|
||||
}
|
||||
struct ifreq ifr;
|
||||
struct ifconf ifc;
|
||||
char buf[1024];
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) {
|
||||
close(sock);
|
||||
return "";
|
||||
}
|
||||
struct ifreq* it = ifc.ifc_req;
|
||||
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
|
||||
for (; it != end; ++it) {
|
||||
std::strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (ifr.ifr_flags & IFF_LOOPBACK) {
|
||||
continue;
|
||||
}
|
||||
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
|
||||
continue;
|
||||
}
|
||||
std::string mac_address;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
len += sprintf(mac_addr + len, "%.2X", ifr.ifr_hwaddr.sa_data[i] & 0xff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
close(sock);
|
||||
#endif
|
||||
return mac_addr;
|
||||
}
|
||||
|
||||
std::string GetHostName() {
|
||||
char hostname[256];
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
LOG_ERROR("WSAStartup failed");
|
||||
return "";
|
||||
}
|
||||
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
|
||||
LOG_ERROR("gethostname failed: {}", WSAGetLastError());
|
||||
WSACleanup();
|
||||
return "";
|
||||
}
|
||||
WSACleanup();
|
||||
#else
|
||||
if (gethostname(hostname, sizeof(hostname)) == -1) {
|
||||
LOG_ERROR("gethostname failed");
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
return hostname;
|
||||
}
|
||||
|
||||
bool IsWaylandSession() {
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
const char* session_type = std::getenv("XDG_SESSION_TYPE");
|
||||
if (session_type) {
|
||||
if (std::strcmp(session_type, "wayland") == 0 ||
|
||||
std::strcmp(session_type, "Wayland") == 0) {
|
||||
return true;
|
||||
}
|
||||
if (std::strcmp(session_type, "x11") == 0 ||
|
||||
std::strcmp(session_type, "X11") == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char* wayland_display = std::getenv("WAYLAND_DISPLAY");
|
||||
return wayland_display && wayland_display[0] != '\0';
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
} // namespace crossdesk
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-18
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#define _PLATFORM_H_
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
std::string GetMac();
|
||||
std::string GetHostName();
|
||||
bool IsWaylandSession();
|
||||
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "rounded_corner_button.h"
|
||||
|
||||
namespace crossdesk {
|
||||
bool RoundedCornerButton(const char* label, const ImVec2& size, float rounding,
|
||||
ImDrawFlags round_flags, bool enabled,
|
||||
ImU32 normal_col, ImU32 hover_col, ImU32 active_col,
|
||||
ImU32 border_col) {
|
||||
ImGuiWindow* current_window = ImGui::GetCurrentWindow();
|
||||
if (current_window->SkipItems) return false;
|
||||
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
ImGuiID button_id = current_window->GetID(label);
|
||||
ImVec2 cursor_pos = current_window->DC.CursorPos;
|
||||
ImVec2 button_size = ImGui::CalcItemSize(size, 0.0f, 0.0f);
|
||||
ImRect button_rect(cursor_pos, ImVec2(cursor_pos.x + button_size.x,
|
||||
cursor_pos.y + button_size.y));
|
||||
ImGui::ItemSize(button_rect);
|
||||
if (!ImGui::ItemAdd(button_rect, button_id)) return false;
|
||||
|
||||
bool is_hovered = false, is_held = false;
|
||||
bool is_pressed = false;
|
||||
if (enabled) {
|
||||
is_pressed =
|
||||
ImGui::ButtonBehavior(button_rect, button_id, &is_hovered, &is_held);
|
||||
}
|
||||
|
||||
if (normal_col == 0) normal_col = ImGui::GetColorU32(ImGuiCol_Button);
|
||||
if (hover_col == 0) hover_col = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
|
||||
if (active_col == 0) active_col = ImGui::GetColorU32(ImGuiCol_ButtonActive);
|
||||
if (border_col == 0) border_col = ImGui::GetColorU32(ImGuiCol_Border);
|
||||
|
||||
ImU32 fill_color = normal_col;
|
||||
if (is_held && is_hovered)
|
||||
fill_color = active_col;
|
||||
else if (is_hovered)
|
||||
fill_color = hover_col;
|
||||
|
||||
if (!enabled) fill_color = IM_COL32(120, 120, 120, 180);
|
||||
|
||||
ImDrawList* window_draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
window_draw_list->AddRectFilled(button_rect.Min, button_rect.Max, fill_color,
|
||||
rounding, round_flags);
|
||||
|
||||
if (style.FrameBorderSize > 0.0f) {
|
||||
window_draw_list->AddRect(button_rect.Min, button_rect.Max, border_col,
|
||||
rounding, round_flags, style.FrameBorderSize);
|
||||
}
|
||||
|
||||
ImU32 text_color =
|
||||
ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled);
|
||||
|
||||
const char* label_end = ImGui::FindRenderedTextEnd(label);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,
|
||||
ImGui::ColorConvertU32ToFloat4(text_color));
|
||||
ImGui::RenderTextClipped(button_rect.Min, button_rect.Max, label, label_end,
|
||||
nullptr, ImVec2(0.5f, 0.5f), &button_rect);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
return is_pressed;
|
||||
}
|
||||
} // namespace crossdesk
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-02-26
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _ROUNDED_CORNER_BUTTON_H_
|
||||
#define _ROUNDED_CORNER_BUTTON_H_
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
|
||||
namespace crossdesk {
|
||||
bool RoundedCornerButton(const char* label, const ImVec2& size, float rounding,
|
||||
ImDrawFlags round_flags, bool enabled = true,
|
||||
ImU32 normal_col = 0, ImU32 hover_col = 0,
|
||||
ImU32 active_col = 0, ImU32 border_col = 0);
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef RTC_BASE_ARRAYSIZE_H_
|
||||
#define RTC_BASE_ARRAYSIZE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// This file defines the arraysize() macro and is derived from Chromium's
|
||||
// base/macros.h.
|
||||
|
||||
// The arraysize(arr) macro returns the # of elements in an array arr.
|
||||
// The expression is a compile-time constant, and therefore can be
|
||||
// used in defining new arrays, for example. If you use arraysize on
|
||||
// a pointer by mistake, you will get a compile-time error.
|
||||
|
||||
// This template function declaration is used in defining arraysize.
|
||||
// Note that the function doesn't need an implementation, as we only
|
||||
// use its type.
|
||||
template <typename T, size_t N>
|
||||
char (&ArraySizeHelper(T (&array)[N]))[N];
|
||||
|
||||
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
|
||||
|
||||
#endif // RTC_BASE_ARRAYSIZE_H_
|
||||