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,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,533 +0,0 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
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-22.04
|
||||
image: crossdesk/ubuntu20.04:latest
|
||||
package_script: ./scripts/linux/pkg_amd64.sh
|
||||
|
||||
- arch: arm64
|
||||
runner: ubuntu-22.04-arm
|
||||
image: crossdesk/ubuntu20.04-arm64v8:latest
|
||||
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]{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}-${BUILD_DATE}-${PATCH_NUMBER}"
|
||||
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: 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: 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]{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}-${BUILD_DATE}-${PATCH_NUMBER}"
|
||||
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: 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]{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-$BUILD_DATE-$PATCH_NUMBER"
|
||||
} 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 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: |
|
||||
cd "${{ github.workspace }}\scripts\windows"
|
||||
makensis /DVERSION=$env:VERSION_NUM nsis_script.nsi
|
||||
|
||||
- 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]{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}-${BUILD_DATE}-${PATCH_NUMBER}"
|
||||
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
|
||||
{
|
||||
"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,80 +0,0 @@
|
||||
name: Close Inactive Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run every day at midnight
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
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@v6
|
||||
with:
|
||||
script: |
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const now = new Date().getTime();
|
||||
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// fetch comments for this issue
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
// determine the "last activity" time
|
||||
let lastActivityTime;
|
||||
if (comments.length > 0) {
|
||||
const lastComment = comments[comments.length - 1];
|
||||
lastActivityTime = new Date(lastComment.updated_at).getTime();
|
||||
} else {
|
||||
lastActivityTime = new Date(issue.created_at).getTime();
|
||||
}
|
||||
|
||||
// check inactivity
|
||||
if (now - lastActivityTime > inactivePeriod) {
|
||||
console.log(`Closing inactive issue: #${issue.number} (No recent replies 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).`);
|
||||
}
|
||||
}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,158 +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-20251113-1.
|
||||
if [[ "${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
|
||||
{
|
||||
"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,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser 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
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,292 +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/lgpl-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环境下需安装以下包:
|
||||
|
||||
```
|
||||
sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxfixes-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
|
||||
```
|
||||
|
||||
编译
|
||||
```
|
||||
git clone https://github.com/kunkundi/crossdesk.git
|
||||
|
||||
cd crossdesk
|
||||
|
||||
git submodule init
|
||||
|
||||
git submodule update
|
||||
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
编译选项
|
||||
```
|
||||
--USE_CUDA=true/false: 启用 CUDA 硬件编解码,默认不启用
|
||||
--CROSSDESK_VERSION=xxx: 指定 CrossDesk 的版本
|
||||
|
||||
# 示例
|
||||
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
|
||||
```
|
||||
运行
|
||||
```
|
||||
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,306 +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/lgpl-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/)
|
||||
|
||||
Following packages need to be installed on Linux:
|
||||
|
||||
```
|
||||
sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxfixes-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
|
||||
```
|
||||
|
||||
Build:
|
||||
```
|
||||
git clone https://github.com/kunkundi/crossdesk.git
|
||||
|
||||
cd crossdesk
|
||||
|
||||
git submodule init
|
||||
|
||||
git submodule update
|
||||
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
Build options:
|
||||
```
|
||||
--USE_CUDA=true/false: enable CUDA acceleration codec, default: false
|
||||
--CROSSDESK_VERSION=xxx: set the version number
|
||||
|
||||
# example:
|
||||
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
|
||||
```
|
||||
Run:
|
||||
```
|
||||
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,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)
|
||||
|
||||
---
|
||||
|
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,98 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PKG_NAME="crossdesk"
|
||||
APP_NAME="CrossDesk"
|
||||
|
||||
APP_VERSION="$1"
|
||||
ARCHITECTURE="amd64"
|
||||
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"
|
||||
|
||||
# Remove 'v' prefix from version for Debian package (Debian version must start with digit)
|
||||
DEB_VERSION="${APP_VERSION#v}"
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${DEB_VERSION}"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/bin"
|
||||
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$DESKTOP_DIR"
|
||||
|
||||
cp build/linux/x86_64/release/crossdesk "$BIN_DIR/$PKG_NAME"
|
||||
chmod +x "$BIN_DIR/$PKG_NAME"
|
||||
|
||||
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
|
||||
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
|
||||
cp "icons/linux/crossdesk_${size}x${size}.png" \
|
||||
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
|
||||
done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $DEB_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, 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: $PORTAL_RUNTIME_RECOMMENDS, nvidia-cuda-toolkit
|
||||
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
|
||||
|
||||
cat > "$DEBIAN_DIR/postrm" << EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
|
||||
rm -f /usr/bin/$PKG_NAME || true
|
||||
rm -f /usr/bin/$APP_NAME || true
|
||||
rm -f /usr/share/applications/$PKG_NAME.desktop || true
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
|
||||
done
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postrm"
|
||||
|
||||
cat > "$DEBIAN_DIR/postinst" << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x "$DEBIAN_DIR/postinst"
|
||||
|
||||
dpkg-deb --build "$DEB_DIR"
|
||||
|
||||
OUTPUT_FILE="${PKG_NAME}-linux-${ARCHITECTURE}-${APP_VERSION}.deb"
|
||||
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
echo "✅ Deb package created: $OUTPUT_FILE"
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PKG_NAME="crossdesk"
|
||||
APP_NAME="CrossDesk"
|
||||
|
||||
APP_VERSION="$1"
|
||||
ARCHITECTURE="arm64"
|
||||
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"
|
||||
|
||||
# Remove 'v' prefix from version for Debian package (Debian version must start with digit)
|
||||
DEB_VERSION="${APP_VERSION#v}"
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${DEB_VERSION}"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/bin"
|
||||
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$DESKTOP_DIR"
|
||||
|
||||
cp build/linux/arm64/release/crossdesk "$BIN_DIR"
|
||||
chmod +x "$BIN_DIR/$PKG_NAME"
|
||||
|
||||
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
|
||||
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
|
||||
cp "icons/linux/crossdesk_${size}x${size}.png" \
|
||||
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
|
||||
done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $DEB_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, 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: $PORTAL_RUNTIME_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
|
||||
|
||||
cat > "$DEBIAN_DIR/postrm" << EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
|
||||
rm -f /usr/bin/$PKG_NAME || true
|
||||
rm -f /usr/bin/$APP_NAME || true
|
||||
rm -f /usr/share/applications/$PKG_NAME.desktop || true
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
|
||||
done
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postrm"
|
||||
|
||||
cat > "$DEBIAN_DIR/postinst" << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x "$DEBIAN_DIR/postinst"
|
||||
|
||||
dpkg-deb --build "$DEB_DIR"
|
||||
|
||||
OUTPUT_FILE="crossdesk-linux-arm64-$APP_VERSION.deb"
|
||||
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
echo "✅ Deb package created: $OUTPUT_FILE"
|
||||
@@ -1,194 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
|
||||
APP_VERSION="$1"
|
||||
PLATFORM="macos"
|
||||
ARCH="arm64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
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,194 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
|
||||
APP_VERSION="$1"
|
||||
PLATFORM="macos"
|
||||
ARCH="x64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
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,338 +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);
|
||||
chdir("/");
|
||||
|
||||
// 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
|
||||
@@ -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_
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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 "rtc_base/bitrate_tracker.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/rate_statistics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
BitrateTracker::BitrateTracker(TimeDelta max_window_size)
|
||||
: impl_(max_window_size.ms(), RateStatistics::kBpsScale) {}
|
||||
|
||||
std::optional<DataRate> BitrateTracker::Rate(Timestamp now) const {
|
||||
if (std::optional<int64_t> rate = impl_.Rate(now.ms())) {
|
||||
return DataRate::BitsPerSec(*rate);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool BitrateTracker::SetWindowSize(TimeDelta window_size, Timestamp now) {
|
||||
return impl_.SetWindowSize(window_size.ms(), now.ms());
|
||||
}
|
||||
|
||||
void BitrateTracker::Update(int64_t bytes, Timestamp now) {
|
||||
impl_.Update(bytes, now.ms());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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_BITRATE_TRACKER_H_
|
||||
#define RTC_BASE_BITRATE_TRACKER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/rate_statistics.h"
|
||||
|
||||
namespace webrtc {
|
||||
// Class to estimate bitrates over running window.
|
||||
// Timestamps used in Update(), Rate() and SetWindowSize() must never
|
||||
// decrease for two consecutive calls.
|
||||
// This class is thread unsafe.
|
||||
class BitrateTracker {
|
||||
public:
|
||||
// max_window_sizes = Maximum window size for the rate estimation.
|
||||
// Initial window size is set to this, but may be changed
|
||||
// to something lower by calling SetWindowSize().
|
||||
explicit BitrateTracker(TimeDelta max_window_size);
|
||||
|
||||
BitrateTracker(const BitrateTracker&) = default;
|
||||
BitrateTracker(BitrateTracker&&) = default;
|
||||
BitrateTracker& operator=(const BitrateTracker&) = delete;
|
||||
BitrateTracker& operator=(BitrateTracker&&) = delete;
|
||||
|
||||
~BitrateTracker() = default;
|
||||
|
||||
// Resets instance to original state.
|
||||
void Reset() { impl_.Reset(); }
|
||||
|
||||
// Updates bitrate with a new data point, moving averaging window as needed.
|
||||
void Update(int64_t bytes, Timestamp now);
|
||||
void Update(DataSize size, Timestamp now) { Update(size.bytes(), now); }
|
||||
|
||||
// Returns bitrate, moving averaging window as needed.
|
||||
// Returns nullopt when bitrate can't be measured.
|
||||
std::optional<DataRate> Rate(Timestamp now) const;
|
||||
|
||||
// Update the size of the averaging window. The maximum allowed value for
|
||||
// `window_size` is `max_window_size` as supplied in the constructor.
|
||||
bool SetWindowSize(TimeDelta window_size, Timestamp now);
|
||||
|
||||
private:
|
||||
RateStatistics impl_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // RTC_BASE_BITRATE_TRACKER_H_
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2004 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_BYTE_ORDER_H_
|
||||
#define RTC_BASE_BYTE_ORDER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#if defined(__POSIX__) && !defined(__native_client__)
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#include "rtc_base/system/arch.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <libkern/OSByteOrder.h>
|
||||
|
||||
#define htobe16(v) OSSwapHostToBigInt16(v)
|
||||
#define htobe32(v) OSSwapHostToBigInt32(v)
|
||||
#define htobe64(v) OSSwapHostToBigInt64(v)
|
||||
#define be16toh(v) OSSwapBigToHostInt16(v)
|
||||
#define be32toh(v) OSSwapBigToHostInt32(v)
|
||||
#define be64toh(v) OSSwapBigToHostInt64(v)
|
||||
|
||||
#define htole16(v) OSSwapHostToLittleInt16(v)
|
||||
#define htole32(v) OSSwapHostToLittleInt32(v)
|
||||
#define htole64(v) OSSwapHostToLittleInt64(v)
|
||||
#define le16toh(v) OSSwapLittleToHostInt16(v)
|
||||
#define le32toh(v) OSSwapLittleToHostInt32(v)
|
||||
#define le64toh(v) OSSwapLittleToHostInt64(v)
|
||||
|
||||
#elif defined(_WIN32) || defined(__native_client__)
|
||||
#if defined(_WIN32)
|
||||
#include <stdlib.h>
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h> // no-presubmit-check
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(WEBRTC_ARCH_LITTLE_ENDIAN)
|
||||
#define htobe16(v) htons(v)
|
||||
#define htobe32(v) htonl(v)
|
||||
#define be16toh(v) ntohs(v)
|
||||
#define be32toh(v) ntohl(v)
|
||||
#define htole16(v) (v)
|
||||
#define htole32(v) (v)
|
||||
#define htole64(v) (v)
|
||||
#define le16toh(v) (v)
|
||||
#define le32toh(v) (v)
|
||||
#define le64toh(v) (v)
|
||||
#if defined(_WIN32)
|
||||
#define htobe64(v) _byteswap_uint64(v)
|
||||
#define be64toh(v) _byteswap_uint64(v)
|
||||
#endif // defined(_WIN32)
|
||||
#if defined(__native_client__)
|
||||
#define htobe64(v) __builtin_bswap64(v)
|
||||
#define be64toh(v) __builtin_bswap64(v)
|
||||
#endif // defined(__native_client__)
|
||||
|
||||
#elif defined(WEBRTC_ARCH_BIG_ENDIAN)
|
||||
#define htobe16(v) (v)
|
||||
#define htobe32(v) (v)
|
||||
#define be16toh(v) (v)
|
||||
#define be32toh(v) (v)
|
||||
#define htole16(v) __builtin_bswap16(v)
|
||||
#define htole32(v) __builtin_bswap32(v)
|
||||
#define htole64(v) __builtin_bswap64(v)
|
||||
#define le16toh(v) __builtin_bswap16(v)
|
||||
#define le32toh(v) __builtin_bswap32(v)
|
||||
#define le64toh(v) __builtin_bswap64(v)
|
||||
#if defined(_WIN32)
|
||||
#define htobe64(v) (v)
|
||||
#define be64toh(v) (v)
|
||||
#endif // defined(_WIN32)
|
||||
#if defined(__native_client__)
|
||||
#define htobe64(v) (v)
|
||||
#define be64toh(v) (v)
|
||||
#endif // defined(__native_client__)
|
||||
#else
|
||||
#error WEBRTC_ARCH_BIG_ENDIAN or WEBRTC_ARCH_LITTLE_ENDIAN must be defined.
|
||||
#endif // defined(WEBRTC_ARCH_LITTLE_ENDIAN)
|
||||
|
||||
#elif defined(__POSIX__)
|
||||
#include <endian.h>
|
||||
#else
|
||||
#error "Missing byte order functions for this arch."
|
||||
#endif // defined(__APPLE__)
|
||||
|
||||
namespace rtc {
|
||||
|
||||
// Reading and writing of little and big-endian numbers from memory
|
||||
|
||||
inline void Set8(void* memory, size_t offset, uint8_t v) {
|
||||
static_cast<uint8_t*>(memory)[offset] = v;
|
||||
}
|
||||
|
||||
inline uint8_t Get8(const void* memory, size_t offset) {
|
||||
return static_cast<const uint8_t*>(memory)[offset];
|
||||
}
|
||||
|
||||
inline void SetBE16(void* memory, uint16_t v) {
|
||||
uint16_t val = htobe16(v);
|
||||
memcpy(memory, &val, sizeof(val));
|
||||
}
|
||||
|
||||
inline void SetBE32(void* memory, uint32_t v) {
|
||||
uint32_t val = htobe32(v);
|
||||
memcpy(memory, &val, sizeof(val));
|
||||
}
|
||||
|
||||
inline void SetBE64(void* memory, uint64_t v) {
|
||||
uint64_t val = htobe64(v);
|
||||
memcpy(memory, &val, sizeof(val));
|
||||
}
|
||||
|
||||
inline uint16_t GetBE16(const void* memory) {
|
||||
uint16_t val;
|
||||
memcpy(&val, memory, sizeof(val));
|
||||
return be16toh(val);
|
||||
}
|
||||
|
||||
inline uint32_t GetBE32(const void* memory) {
|
||||
uint32_t val;
|
||||
memcpy(&val, memory, sizeof(val));
|
||||
return be32toh(val);
|
||||
}
|
||||
|
||||
inline uint64_t GetBE64(const void* memory) {
|
||||
uint64_t val;
|
||||
memcpy(&val, memory, sizeof(val));
|
||||
return be64toh(val);
|
||||
}
|
||||
|
||||
inline void SetLE16(void* memory, uint16_t v) {
|
||||
uint16_t val = htole16(v);
|
||||
memcpy(memory, &val, sizeof(val));
|
||||
}
|
||||
|
||||
inline void SetLE32(void* memory, uint32_t v) {
|
||||
uint32_t val = htole32(v);
|
||||
memcpy(memory, &val, sizeof(val));
|
||||
}
|
||||
|
||||
inline void SetLE64(void* memory, uint64_t v) {
|
||||
uint64_t val = htole64(v);
|
||||
memcpy(memory, &val, sizeof(val));
|
||||
}
|
||||
|
||||
inline uint16_t GetLE16(const void* memory) {
|
||||
uint16_t val;
|
||||
memcpy(&val, memory, sizeof(val));
|
||||
return le16toh(val);
|
||||
}
|
||||
|
||||
inline uint32_t GetLE32(const void* memory) {
|
||||
uint32_t val;
|
||||
memcpy(&val, memory, sizeof(val));
|
||||
return le32toh(val);
|
||||
}
|
||||
|
||||
inline uint64_t GetLE64(const void* memory) {
|
||||
uint64_t val;
|
||||
memcpy(&val, memory, sizeof(val));
|
||||
return le64toh(val);
|
||||
}
|
||||
|
||||
// Check if the current host is big endian.
|
||||
inline bool IsHostBigEndian() {
|
||||
#if defined(WEBRTC_ARCH_BIG_ENDIAN)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint16_t HostToNetwork16(uint16_t n) { return htobe16(n); }
|
||||
|
||||
inline uint32_t HostToNetwork32(uint32_t n) { return htobe32(n); }
|
||||
|
||||
inline uint64_t HostToNetwork64(uint64_t n) { return htobe64(n); }
|
||||
|
||||
inline uint16_t NetworkToHost16(uint16_t n) { return be16toh(n); }
|
||||
|
||||
inline uint32_t NetworkToHost32(uint32_t n) { return be32toh(n); }
|
||||
|
||||
inline uint64_t NetworkToHost64(uint64_t n) { return be64toh(n); }
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif // RTC_BASE_BYTE_ORDER_H_
|
||||
@@ -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_
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "rtc_base/network/sent_packet.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
PacketInfo::PacketInfo() = default;
|
||||
PacketInfo::PacketInfo(const PacketInfo& info) = default;
|
||||
PacketInfo::~PacketInfo() = default;
|
||||
|
||||
SentPacket::SentPacket() = default;
|
||||
SentPacket::SentPacket(int64_t packet_id, int64_t send_time_ms)
|
||||
: packet_id(packet_id), send_time_ms(send_time_ms) {}
|
||||
SentPacket::SentPacket(int64_t packet_id,
|
||||
int64_t send_time_ms,
|
||||
const rtc::PacketInfo& info)
|
||||
: packet_id(packet_id), send_time_ms(send_time_ms), info(info) {}
|
||||
|
||||
} // namespace rtc
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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_NETWORK_SENT_PACKET_H_
|
||||
#define RTC_BASE_NETWORK_SENT_PACKET_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
enum class PacketType {
|
||||
kUnknown,
|
||||
kData,
|
||||
kIceConnectivityCheck,
|
||||
kIceConnectivityCheckResponse,
|
||||
kStunMessage,
|
||||
kTurnMessage,
|
||||
};
|
||||
|
||||
enum class PacketInfoProtocolType {
|
||||
kUnknown,
|
||||
kUdp,
|
||||
kTcp,
|
||||
kSsltcp,
|
||||
kTls,
|
||||
};
|
||||
|
||||
struct PacketInfo {
|
||||
PacketInfo();
|
||||
PacketInfo(const PacketInfo& info);
|
||||
~PacketInfo();
|
||||
|
||||
bool included_in_feedback = false;
|
||||
bool included_in_allocation = false;
|
||||
// `is_media` is true if this is an audio or video packet, excluding
|
||||
// retransmissions.
|
||||
bool is_media = false;
|
||||
PacketType packet_type = PacketType::kUnknown;
|
||||
PacketInfoProtocolType protocol = PacketInfoProtocolType::kUnknown;
|
||||
// A unique id assigned by the network manager, and std::nullopt if not set.
|
||||
std::optional<uint16_t> network_id;
|
||||
size_t packet_size_bytes = 0;
|
||||
size_t turn_overhead_bytes = 0;
|
||||
size_t ip_overhead_bytes = 0;
|
||||
};
|
||||
|
||||
struct SentPacket {
|
||||
SentPacket();
|
||||
SentPacket(int64_t packet_id, int64_t send_time_ms);
|
||||
SentPacket(int64_t packet_id, int64_t send_time_ms,
|
||||
const rtc::PacketInfo& info);
|
||||
|
||||
int64_t packet_id = -1;
|
||||
int64_t send_time_ms = -1;
|
||||
rtc::PacketInfo info;
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif // RTC_BASE_NETWORK_SENT_PACKET_H_
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2020 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 "rtc_base/network_constants.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
std::string AdapterTypeToString(AdapterType type) {
|
||||
switch (type) {
|
||||
case ADAPTER_TYPE_ANY:
|
||||
return "Wildcard";
|
||||
case ADAPTER_TYPE_UNKNOWN:
|
||||
return "Unknown";
|
||||
case ADAPTER_TYPE_ETHERNET:
|
||||
return "Ethernet";
|
||||
case ADAPTER_TYPE_WIFI:
|
||||
return "Wifi";
|
||||
case ADAPTER_TYPE_CELLULAR:
|
||||
return "Cellular";
|
||||
case ADAPTER_TYPE_CELLULAR_2G:
|
||||
return "Cellular2G";
|
||||
case ADAPTER_TYPE_CELLULAR_3G:
|
||||
return "Cellular3G";
|
||||
case ADAPTER_TYPE_CELLULAR_4G:
|
||||
return "Cellular4G";
|
||||
case ADAPTER_TYPE_CELLULAR_5G:
|
||||
return "Cellular5G";
|
||||
case ADAPTER_TYPE_VPN:
|
||||
return "VPN";
|
||||
case ADAPTER_TYPE_LOOPBACK:
|
||||
return "Loopback";
|
||||
default:
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2004 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_NETWORK_CONSTANTS_H_
|
||||
#define RTC_BASE_NETWORK_CONSTANTS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace rtc {
|
||||
|
||||
constexpr uint16_t kNetworkCostMax = 999;
|
||||
constexpr uint16_t kNetworkCostCellular2G = 980;
|
||||
constexpr uint16_t kNetworkCostCellular3G = 910;
|
||||
constexpr uint16_t kNetworkCostCellular = 900;
|
||||
constexpr uint16_t kNetworkCostCellular4G = 500;
|
||||
constexpr uint16_t kNetworkCostCellular5G = 250;
|
||||
constexpr uint16_t kNetworkCostUnknown = 50;
|
||||
constexpr uint16_t kNetworkCostLow = 10;
|
||||
constexpr uint16_t kNetworkCostMin = 0;
|
||||
|
||||
// Add 1 to network cost of underlying network type
|
||||
// so that e.g a "plain" WIFI is prefered over a VPN over WIFI
|
||||
// everything else being equal.
|
||||
constexpr uint16_t kNetworkCostVpn = 1;
|
||||
|
||||
// alias
|
||||
constexpr uint16_t kNetworkCostHigh = kNetworkCostCellular;
|
||||
|
||||
enum AdapterType {
|
||||
// This enum resembles the one in Chromium net::ConnectionType.
|
||||
ADAPTER_TYPE_UNKNOWN = 0,
|
||||
ADAPTER_TYPE_ETHERNET = 1 << 0,
|
||||
ADAPTER_TYPE_WIFI = 1 << 1,
|
||||
ADAPTER_TYPE_CELLULAR = 1 << 2, // This is CELLULAR of unknown type.
|
||||
ADAPTER_TYPE_VPN = 1 << 3,
|
||||
ADAPTER_TYPE_LOOPBACK = 1 << 4,
|
||||
// ADAPTER_TYPE_ANY is used for a network, which only contains a single "any
|
||||
// address" IP address (INADDR_ANY for IPv4 or in6addr_any for IPv6), and can
|
||||
// use any/all network interfaces. Whereas ADAPTER_TYPE_UNKNOWN is used
|
||||
// when the network uses a specific interface/IP, but its interface type can
|
||||
// not be determined or not fit in this enum.
|
||||
ADAPTER_TYPE_ANY = 1 << 5,
|
||||
ADAPTER_TYPE_CELLULAR_2G = 1 << 6,
|
||||
ADAPTER_TYPE_CELLULAR_3G = 1 << 7,
|
||||
ADAPTER_TYPE_CELLULAR_4G = 1 << 8,
|
||||
ADAPTER_TYPE_CELLULAR_5G = 1 << 9
|
||||
};
|
||||
|
||||
std::string AdapterTypeToString(AdapterType type);
|
||||
|
||||
// Useful for testing!
|
||||
constexpr AdapterType kAllAdapterTypes[] = {
|
||||
ADAPTER_TYPE_UNKNOWN, ADAPTER_TYPE_ETHERNET,
|
||||
ADAPTER_TYPE_WIFI, ADAPTER_TYPE_CELLULAR,
|
||||
ADAPTER_TYPE_VPN, ADAPTER_TYPE_LOOPBACK,
|
||||
ADAPTER_TYPE_ANY, ADAPTER_TYPE_CELLULAR_2G,
|
||||
ADAPTER_TYPE_CELLULAR_3G, ADAPTER_TYPE_CELLULAR_4G,
|
||||
ADAPTER_TYPE_CELLULAR_5G,
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif // RTC_BASE_NETWORK_CONSTANTS_H_
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 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 "rtc_base/network_route.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
bool RouteEndpoint::operator==(const RouteEndpoint& other) const {
|
||||
return adapter_type_ == other.adapter_type_ &&
|
||||
adapter_id_ == other.adapter_id_ && network_id_ == other.network_id_ &&
|
||||
uses_turn_ == other.uses_turn_;
|
||||
}
|
||||
|
||||
bool NetworkRoute::operator==(const NetworkRoute& other) const {
|
||||
return connected == other.connected && local == other.local &&
|
||||
remote == other.remote && packet_overhead == other.packet_overhead &&
|
||||
last_sent_packet_id == other.last_sent_packet_id;
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 RTC_BASE_NETWORK_ROUTE_H_
|
||||
#define RTC_BASE_NETWORK_ROUTE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "rtc_base/network_constants.h"
|
||||
|
||||
// TODO(honghaiz): Make a directory that describes the interfaces and structs
|
||||
// the media code can rely on and the network code can implement, and both can
|
||||
// depend on that, but not depend on each other. Then, move this file to that
|
||||
// directory.
|
||||
namespace rtc {
|
||||
|
||||
class RouteEndpoint {
|
||||
public:
|
||||
RouteEndpoint() {} // Used by tests.
|
||||
RouteEndpoint(AdapterType adapter_type, uint16_t adapter_id,
|
||||
uint16_t network_id, bool uses_turn)
|
||||
: adapter_type_(adapter_type),
|
||||
adapter_id_(adapter_id),
|
||||
network_id_(network_id),
|
||||
uses_turn_(uses_turn) {}
|
||||
|
||||
RouteEndpoint(const RouteEndpoint&) = default;
|
||||
RouteEndpoint& operator=(const RouteEndpoint&) = default;
|
||||
|
||||
// Used by tests.
|
||||
static RouteEndpoint CreateWithNetworkId(uint16_t network_id) {
|
||||
return RouteEndpoint(ADAPTER_TYPE_UNKNOWN,
|
||||
/* adapter_id = */ 0, network_id,
|
||||
/* uses_turn = */ false);
|
||||
}
|
||||
RouteEndpoint CreateWithTurn(bool uses_turn) const {
|
||||
return RouteEndpoint(adapter_type_, adapter_id_, network_id_, uses_turn);
|
||||
}
|
||||
|
||||
AdapterType adapter_type() const { return adapter_type_; }
|
||||
uint16_t adapter_id() const { return adapter_id_; }
|
||||
uint16_t network_id() const { return network_id_; }
|
||||
bool uses_turn() const { return uses_turn_; }
|
||||
|
||||
bool operator==(const RouteEndpoint& other) const;
|
||||
|
||||
private:
|
||||
AdapterType adapter_type_ = ADAPTER_TYPE_UNKNOWN;
|
||||
uint16_t adapter_id_ = 0;
|
||||
uint16_t network_id_ = 0;
|
||||
bool uses_turn_ = false;
|
||||
};
|
||||
|
||||
struct NetworkRoute {
|
||||
bool connected = false;
|
||||
RouteEndpoint local;
|
||||
RouteEndpoint remote;
|
||||
// Last packet id sent on the PREVIOUS route.
|
||||
int last_sent_packet_id = -1;
|
||||
// The overhead in bytes from IP layer and above.
|
||||
// This is the maximum of any part of the route.
|
||||
int packet_overhead = 0;
|
||||
|
||||
std::string DebugString() const {
|
||||
std::ostringstream oss;
|
||||
oss << "[ connected: " << connected << " local: [ " << local.adapter_id()
|
||||
<< "/" << local.network_id() << " "
|
||||
<< AdapterTypeToString(local.adapter_type())
|
||||
<< " turn: " << local.uses_turn() << " ] remote: [ "
|
||||
<< remote.adapter_id() << "/" << remote.network_id() << " "
|
||||
<< AdapterTypeToString(remote.adapter_type())
|
||||
<< " turn: " << remote.uses_turn()
|
||||
<< " ] packet_overhead_bytes: " << packet_overhead << " ]";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool operator==(const NetworkRoute& other) const;
|
||||
bool operator!=(const NetworkRoute& other) { return !operator==(other); }
|
||||
};
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
#endif // RTC_BASE_NETWORK_ROUTE_H_
|
||||