Compare commits

...

10 Commits

Author SHA1 Message Date
dijunkun c0b880e1eb [fix] pass Windows resource compiler to Slint build 2026-08-01 02:08:05 +08:00
dijunkun c10f1499f2 [fix] encode web audio before RTP packetization, fixes #93 2026-07-31 23:58:56 +08:00
dijunkun d7a9c26ff9 [fix] scale local ID text to fit 2026-07-31 23:57:09 +08:00
dijunkun f809ad173c [fix] restore Linux keyboard injection 2026-07-31 23:57:09 +08:00
dijunkun 34155d0266 [fix] let SDL select available Linux audio driver 2026-07-31 23:57:09 +08:00
dijunkun 9d544788b0 [fix] use custom titlebar only with X11 2026-07-31 23:57:08 +08:00
dijunkun dd16e65b68 [fix] stabilize third-party dependency builds 2026-07-31 23:57:08 +08:00
dijunkun 91eac61a90 [docs] use GCC 10 toolchain for Linux builds 2026-07-31 23:57:08 +08:00
dijunkun 941cc79af5 [fix] use xmake-managed CMake for builds 2026-07-31 23:57:08 +08:00
dijunkun 0d6ecfc730 [ci] build Linux releases on Ubuntu 20.04 for compatibility 2026-07-31 23:57:08 +08:00
22 changed files with 420 additions and 75 deletions
+6 -4
View File
@@ -26,8 +26,9 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
env: env:
IMAGE_NAME: crossdesk/ubuntu22.04 IMAGE_NAME: crossdesk/ubuntu20.04
STACK_TAG: buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0 STACK_TAG: buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0
CMAKE_VERSION: 3.31.6
XMAKE_VERSION: 3.0.9 XMAKE_VERSION: 3.0.9
RUST_VERSION: 1.92.0 RUST_VERSION: 1.92.0
RUSTUP_VERSION: 1.28.2 RUSTUP_VERSION: 1.28.2
@@ -44,11 +45,11 @@ jobs:
- arch: amd64 - arch: amd64
platform: linux/amd64 platform: linux/amd64
runner: ubuntu-24.04 runner: ubuntu-24.04
base_image: nvidia/cuda:12.6.3-devel-ubuntu22.04 base_image: nvidia/cuda:12.6.3-devel-ubuntu20.04
- arch: arm64 - arch: arm64
platform: linux/arm64 platform: linux/arm64
runner: ubuntu-24.04-arm runner: ubuntu-24.04-arm
base_image: ubuntu:22.04 base_image: ubuntu:20.04
steps: steps:
- name: Set candidate tag - name: Set candidate tag
id: image id: image
@@ -86,6 +87,7 @@ jobs:
sbom: false sbom: false
build-args: | build-args: |
BASE_IMAGE=${{ matrix.base_image }} BASE_IMAGE=${{ matrix.base_image }}
CMAKE_VERSION=${{ env.CMAKE_VERSION }}
XMAKE_VERSION=${{ env.XMAKE_VERSION }} XMAKE_VERSION=${{ env.XMAKE_VERSION }}
RUST_VERSION=${{ env.RUST_VERSION }} RUST_VERSION=${{ env.RUST_VERSION }}
RUSTUP_VERSION=${{ env.RUSTUP_VERSION }} RUSTUP_VERSION=${{ env.RUSTUP_VERSION }}
+20 -10
View File
@@ -29,12 +29,12 @@ jobs:
include: include:
- arch: amd64 - arch: amd64
runner: ubuntu-24.04 runner: ubuntu-24.04
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0 image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0
package_script: ./scripts/linux/pkg_amd64.sh package_script: ./scripts/linux/pkg_amd64.sh
- arch: arm64 - arch: arm64
runner: ubuntu-24.04-arm runner: ubuntu-24.04-arm
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0 image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0
package_script: ./scripts/linux/pkg_arm64.sh package_script: ./scripts/linux/pkg_arm64.sh
container: container:
@@ -106,26 +106,30 @@ jobs:
- name: Ensure Wayland build dependencies - name: Ensure Wayland build dependencies
shell: bash shell: bash
run: | run: |
if pkg-config --exists dbus-1 libdrm libpipewire-0.3 libspa-0.2; then if pkg-config --exists dbus-1 libdrm && \
test -f /opt/crossdesk-pipewire-sdk/include/pipewire-0.3/pipewire/pipewire.h && \
test -f /opt/crossdesk-pipewire-sdk/include/spa-0.2/spa/param/video/format-utils.h; then
exit 0 exit 0
fi fi
apt-get update apt-get update
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
libdbus-1-dev \ libdbus-1-dev \
libdrm-dev \ libdrm-dev
libpipewire-0.3-dev \
libspa-0.2-dev
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
pkg-config --exists dbus-1 libdrm libpipewire-0.3 libspa-0.2 install-crossdesk-pipewire-sdk
pkg-config --exists dbus-1 libdrm
test -f /opt/crossdesk-pipewire-sdk/include/pipewire-0.3/pipewire/pipewire.h
test -f /opt/crossdesk-pipewire-sdk/include/spa-0.2/spa/param/video/format-utils.h
- name: Build CrossDesk - name: Build CrossDesk
env: env:
CUDA_PATH: /usr/local/cuda CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data XMAKE_GLOBALDIR: /data
run: | run: |
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --USE_WAYLAND=true --root -y xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --USE_WAYLAND=true \
--cc=gcc-10 --cxx=g++-10 --root -y
xmake b -vy --root crossdesk xmake b -vy --root crossdesk
- name: Package - name: Package
@@ -133,13 +137,19 @@ jobs:
chmod +x ${{ matrix.package_script }} chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${LEGAL_VERSION} ${{ matrix.package_script }} ${LEGAL_VERSION}
- name: Verify packaged Ubuntu 22.04 glibc baseline - name: Verify Ubuntu 20.04 package dependency resolution
run: |
PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb"
apt-get update
apt-get install --simulate "${PACKAGE_FILE}"
- name: Verify packaged Ubuntu 20.04 glibc baseline
run: | run: |
PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb" PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb"
VERIFY_DIR="${RUNNER_TEMP}/crossdesk-glibc-${{ matrix.arch }}" VERIFY_DIR="${RUNNER_TEMP}/crossdesk-glibc-${{ matrix.arch }}"
install -d "${VERIFY_DIR}" install -d "${VERIFY_DIR}"
dpkg-deb --extract "${PACKAGE_FILE}" "${VERIFY_DIR}" dpkg-deb --extract "${PACKAGE_FILE}" "${VERIFY_DIR}"
verify-crossdesk-glibc-baseline "${VERIFY_DIR}" 2.35 verify-crossdesk-glibc-baseline "${VERIFY_DIR}" 2.31
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
+30 -8
View File
@@ -32,7 +32,7 @@ CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频
|----------------|---------------------------| |----------------|---------------------------|
| **Windows** | Windows 10 及以上 (64 位) | | **Windows** | Windows 10 及以上 (64 位) |
| **macOS** | macOS Intel 15.0 及以上 ( 大于 14.0 小于 15.0 的版本可自行编译实现兼容 )<br> macOS Apple Silicon 14.0 及以上 | | **macOS** | macOS Intel 15.0 及以上 ( 大于 14.0 小于 15.0 的版本可自行编译实现兼容 )<br> macOS Apple Silicon 14.0 及以上 |
| **Linux** | Ubuntu 22.04 及以上 ( 低版本可自行编译实现兼容 ) | | **Linux** | Ubuntu 20.04 及以上 |
## 使用 ## 使用
@@ -80,16 +80,18 @@ Windows 安装包会自动打包 `crossdesk_service.exe` 和 `crossdesk_session_
依赖: 依赖:
- [xmake](https://xmake.io/#/guide/installation) - [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/) - [cmake](https://cmake.org/download/) 3.21 及以上(系统版本过低时由 xmake 自动安装)
### Linux ### Linux
Linux 构建目前支持 Ubuntu 22.04 及以上版本的 amd64 和 arm64。先安装基础编译依赖: Linux 构建支持 Ubuntu 20.04 及以上版本的 amd64 和 arm64。发布安装包以
Ubuntu 20.04/glibc 2.31 为兼容基线。先安装基础编译依赖:
```bash ```bash
sudo apt-get update sudo apt-get update
sudo apt-get install -y \ sudo apt-get install -y \
git curl unzip build-essential cmake pkg-config binutils dpkg-dev \ git curl unzip build-essential gcc-10 g++-10 python3-pip \
pkg-config binutils dpkg-dev \
libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \ libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \
libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \ libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \
libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \ libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \
@@ -97,10 +99,19 @@ sudo apt-get install -y \
libasound2-dev libsndio-dev libpulse-dev libasound2-dev libsndio-dev libpulse-dev
``` ```
Ubuntu 20.04 自带的 CMake 3.16 不满足要求时,xmake 会自动下载并使用
CMake 3.21 或更高版本,无需手动替换系统 CMake。
Ubuntu 20.04 默认的 GCC 9 缺少 Slint C++ API 所需的部分 C++20 标准库,
因此上述命令同时安装并在后续配置中指定 GCC 10。
需要启用 Wayland 捕获或 DRM 捕获时,再安装对应依赖: 需要启用 Wayland 捕获或 DRM 捕获时,再安装对应依赖:
```bash ```bash
sudo apt-get install -y libdbus-1-dev libpipewire-0.3-dev libdrm-dev sudo apt-get install -y libdbus-1-dev libdrm-dev
# Ubuntu 22.04 及以上也可以直接安装系统开发包
# sudo apt-get install -y libpipewire-0.3-dev libspa-0.2-dev
``` ```
下载子模块并编译 Release 版本: 下载子模块并编译 Release 版本:
@@ -112,16 +123,25 @@ cd crossdesk
# 已经克隆过仓库时执行这一行 # 已经克隆过仓库时执行这一行
git submodule update --init --recursive git submodule update --init --recursive
xmake f -m release --USE_CUDA=false -y # Ubuntu 20.04 没有 PipeWire 0.3 开发包,安装仅用于编译的头文件 SDK。
# 已安装发行版 PipeWire 0.3 开发包时可跳过此步骤。
sudo ./docker/linux-build/install-pipewire-sdk.sh
xmake f -c -m release --USE_CUDA=false --toolchain=gcc-10 -y
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
这里应使用 `--toolchain=gcc-10`,以确保 CrossDesk 及 xmake 在配置阶段构建的
libyuv 等第三方包都使用 GCC 10。只指定 `--cc`/`--cxx` 时,依赖安装可能仍回退到
系统默认的 `/bin/cc``/bin/c++``-c` 会清除旧配置,切换编译器时必须保留。
amd64 构建结果位于 `build/linux/x86_64/release/crossdesk`arm64 构建结果位于 `build/linux/arm64/release/crossdesk` amd64 构建结果位于 `build/linux/x86_64/release/crossdesk`arm64 构建结果位于 `build/linux/arm64/release/crossdesk`
启用 Wayland 和 DRM 的配置示例: 启用 Wayland 和 DRM 的配置示例:
```bash ```bash
xmake f -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false -y xmake f -c -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false \
--toolchain=gcc-10 -y
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
@@ -136,6 +156,8 @@ xmake b -vy crossdesk
``` ```
打包脚本会将 Slint 共享运行库安装到软件包私有目录 `/usr/lib/crossdesk`,无需用户另外安装 `libslint_cpp.so` 打包脚本会将 Slint 共享运行库安装到软件包私有目录 `/usr/lib/crossdesk`,无需用户另外安装 `libslint_cpp.so`
PipeWire 不属于强制运行时依赖:程序在运行时检测宿主系统的 PipeWire 0.3
没有该运行库时仍可使用 X11(以及构建时启用的 DRM)捕获。
### 通用编译选项 ### 通用编译选项
@@ -158,7 +180,7 @@ xmake r crossdesk
### 无 CUDA 环境下的开发支持 ### 无 CUDA 环境下的开发支持
对于**未安装 CUDA 环境的 Linux 开发者,如果希望编译后的成果物拥有硬件编解码能力**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目 对于**未安装 CUDA 环境的 Linux 开发者,如果希望编译后的成果物拥有硬件编解码能力**,这里提供了预配置的 [Ubuntu 20.04 兼容构建镜像](https://hub.docker.com/r/crossdesk/ubuntu20.04)。该镜像内置必要的构建依赖,可生成兼容 glibc 2.31 的单一 Linux 安装包
进入容器,下载工程后执行: 进入容器,下载工程后执行:
``` ```
+38 -9
View File
@@ -32,7 +32,7 @@ CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkund
|-----------|-----------------| |-----------|-----------------|
| **Windows** | Windows 10 or later (64-bit) | | **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 | | **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)* | | **Linux** | Ubuntu 20.04 or later |
## Usage ## Usage
@@ -85,16 +85,19 @@ If the remote Windows service is not installed, not running, or temporarily unav
Requirements: Requirements:
- [xmake](https://xmake.io/#/guide/installation) - [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/) - [cmake](https://cmake.org/download/) 3.21 or later (installed automatically by xmake when the system version is too old)
### Linux ### Linux
Linux builds currently support Ubuntu 22.04 or later on amd64 and arm64. Install the base build dependencies first: Linux builds support Ubuntu 20.04 or later on amd64 and arm64. Release packages
use Ubuntu 20.04/glibc 2.31 as their compatibility baseline. Install the base
build dependencies first:
```bash ```bash
sudo apt-get update sudo apt-get update
sudo apt-get install -y \ sudo apt-get install -y \
git curl unzip build-essential cmake pkg-config binutils dpkg-dev \ git curl unzip build-essential gcc-10 g++-10 python3-pip \
pkg-config binutils dpkg-dev \
libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \ libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \
libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \ libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \
libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \ libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \
@@ -102,10 +105,21 @@ sudo apt-get install -y \
libasound2-dev libsndio-dev libpulse-dev libasound2-dev libsndio-dev libpulse-dev
``` ```
When Ubuntu 20.04's bundled CMake 3.16 is too old, xmake automatically
downloads and uses CMake 3.21 or later; replacing the system CMake manually is
not required.
Ubuntu 20.04's default GCC 9 lacks parts of the C++20 standard library needed
by Slint's C++ API, so the commands above install GCC 10 and the configuration
below selects it explicitly.
Install the optional dependencies when Wayland capture or DRM capture is enabled: Install the optional dependencies when Wayland capture or DRM capture is enabled:
```bash ```bash
sudo apt-get install -y libdbus-1-dev libpipewire-0.3-dev libdrm-dev sudo apt-get install -y libdbus-1-dev libdrm-dev
# Ubuntu 22.04 and later can alternatively use the distribution packages.
# sudo apt-get install -y libpipewire-0.3-dev libspa-0.2-dev
``` ```
Clone the submodules and build a release binary: Clone the submodules and build a release binary:
@@ -117,16 +131,28 @@ cd crossdesk
# Run this when the repository has already been cloned # Run this when the repository has already been cloned
git submodule update --init --recursive git submodule update --init --recursive
xmake f -m release --USE_CUDA=false -y # Ubuntu 20.04 has no PipeWire 0.3 development package. Install the
# header-only SDK used during compilation. Skip this when the distribution's
# PipeWire 0.3 development packages are already installed.
sudo ./docker/linux-build/install-pipewire-sdk.sh
xmake f -c -m release --USE_CUDA=false --toolchain=gcc-10 -y
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
Use `--toolchain=gcc-10` so that both CrossDesk and third-party packages such
as libyuv, which xmake builds during configuration, use GCC 10. Specifying only
`--cc`/`--cxx` may still let dependency builds fall back to the system
`/bin/cc` and `/bin/c++`. Keep `-c` when switching compilers to clear the old
configuration.
The amd64 binary is written to `build/linux/x86_64/release/crossdesk`; the arm64 binary is written to `build/linux/arm64/release/crossdesk`. The amd64 binary is written to `build/linux/x86_64/release/crossdesk`; the arm64 binary is written to `build/linux/arm64/release/crossdesk`.
Example configuration with Wayland and DRM capture enabled: Example configuration with Wayland and DRM capture enabled:
```bash ```bash
xmake f -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false -y xmake f -c -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false \
--toolchain=gcc-10 -y
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
@@ -141,6 +167,9 @@ Build a Debian package after compiling a release binary on the matching architec
``` ```
The package scripts install the shared Slint runtime in the private `/usr/lib/crossdesk` directory, so users do not need to install `libslint_cpp.so` separately. The package scripts install the shared Slint runtime in the private `/usr/lib/crossdesk` directory, so users do not need to install `libslint_cpp.so` separately.
PipeWire is not a mandatory runtime dependency. CrossDesk detects the host's
PipeWire 0.3 runtime dynamically and can still use X11 capture (and DRM when
enabled at build time) when it is unavailable.
### Common build options ### Common build options
@@ -163,8 +192,8 @@ xmake r crossdesk
#### Development Without CUDA Environment #### 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. For **Linux developers who do not have a CUDA environment installed and want to enable hardware codec feature**, a preconfigured [Ubuntu 20.04 compatibility build image](https://hub.docker.com/r/crossdesk/ubuntu20.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. This image contains the required build dependencies and produces a single Linux package compatible with the glibc 2.31 baseline.
After entering the container, download the project and run: After entering the container, download the project and run:
``` ```
+25 -6
View File
@@ -1,15 +1,16 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.7
ARG BASE_IMAGE=nvidia/cuda:12.6.3-devel-ubuntu22.04 ARG BASE_IMAGE=nvidia/cuda:12.6.3-devel-ubuntu20.04
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
ARG TARGETARCH ARG TARGETARCH
ARG CMAKE_VERSION=3.31.6
ARG XMAKE_VERSION=3.0.9 ARG XMAKE_VERSION=3.0.9
ARG RUST_VERSION=1.92.0 ARG RUST_VERSION=1.92.0
ARG RUSTUP_VERSION=1.28.2 ARG RUSTUP_VERSION=1.28.2
LABEL org.opencontainers.image.title="CrossDesk Linux build environment" \ LABEL org.opencontainers.image.title="CrossDesk Linux build environment" \
org.opencontainers.image.description="Ubuntu 22.04 build environment for CrossDesk" \ org.opencontainers.image.description="Ubuntu 20.04 compatibility build environment for CrossDesk" \
org.opencontainers.image.source="https://github.com/kunkundi/crossdesk" \ org.opencontainers.image.source="https://github.com/kunkundi/crossdesk" \
org.opencontainers.image.licenses="GPL-3.0-only" org.opencontainers.image.licenses="GPL-3.0-only"
@@ -22,6 +23,8 @@ ENV DEBIAN_FRONTEND=noninteractive \
XMAKE_GLOBALDIR=/data \ XMAKE_GLOBALDIR=/data \
XMAKE_ROOT=y \ XMAKE_ROOT=y \
CUDA_PATH=/usr/local/cuda \ CUDA_PATH=/usr/local/cuda \
CC=/usr/bin/gcc-10 \
CXX=/usr/bin/g++-10 \
PATH=/root/.local/bin:/opt/cargo/bin:/usr/local/bin:${PATH} PATH=/root/.local/bin:/opt/cargo/bin:/usr/local/bin:${PATH}
SHELL ["/bin/bash", "-o", "pipefail", "-c"] SHELL ["/bin/bash", "-o", "pipefail", "-c"]
@@ -35,13 +38,14 @@ RUN apt-get update \
build-essential \ build-essential \
ca-certificates \ ca-certificates \
ccache \ ccache \
cmake \
curl \ curl \
dbus \ dbus \
dpkg-dev \ dpkg-dev \
file \ file \
flex \ flex \
gettext \ gettext \
gcc-10 \
g++-10 \
git \ git \
libasound2-dev \ libasound2-dev \
libdbus-1-dev \ libdbus-1-dev \
@@ -52,11 +56,9 @@ RUN apt-get update \
libfreetype6-dev \ libfreetype6-dev \
libgl1-mesa-dev \ libgl1-mesa-dev \
libmount-dev \ libmount-dev \
libpipewire-0.3-dev \
libpulse-dev \ libpulse-dev \
libreadline-dev \ libreadline-dev \
libsndio-dev \ libsndio-dev \
libspa-0.2-dev \
libssl-dev \ libssl-dev \
libtool \ libtool \
libx11-dev \ libx11-dev \
@@ -95,6 +97,22 @@ RUN apt-get update \
zlib1g-dev \ zlib1g-dev \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Slint 1.17 requires CMake 3.21 or newer, while Ubuntu 20.04 provides 3.16.
# PyPI publishes pinned manylinux2014 wheels for both amd64 and arm64.
RUN python3 -m pip install --no-cache-dir "cmake==${CMAKE_VERSION}" \
&& cmake --version | grep -F "cmake version ${CMAKE_VERSION}"
# Ubuntu 20.04 only ships PipeWire 0.2. Install the Ubuntu 22.04-era 0.3.48
# headers as a build-only SDK. CrossDesk loads the host's PipeWire 0.3 runtime
# with dlopen(), so no PipeWire shared library is copied into release packages.
COPY docker/linux-build/install-pipewire-sdk.sh \
/usr/local/bin/install-crossdesk-pipewire-sdk
RUN chmod 0755 /usr/local/bin/install-crossdesk-pipewire-sdk \
&& install-crossdesk-pipewire-sdk \
&& grep -F "#define PW_MICRO 48" \
/opt/crossdesk-pipewire-sdk/include/pipewire-0.3/pipewire/version.h
RUN export GITHUB_ACTIONS=true \ RUN export GITHUB_ACTIONS=true \
&& curl -fsSL https://xmake.io/shget.text \ && curl -fsSL https://xmake.io/shget.text \
| bash -s -- "v${XMAKE_VERSION}" \ | bash -s -- "v${XMAKE_VERSION}" \
@@ -136,7 +154,8 @@ COPY submodules/minirtc/thirdparty ./submodules/minirtc/thirdparty
# CrossDesk's exact dependency graph. Source files are intentionally excluded # CrossDesk's exact dependency graph. Source files are intentionally excluded
# from the Docker context; source-only changes therefore reuse this layer. # from the Docker context; source-only changes therefore reuse this layer.
RUN xmake repo -u --root \ RUN xmake repo -u --root \
&& xmake f -m release --USE_CUDA=true --USE_WAYLAND=true --root -y \ && xmake f -m release --USE_CUDA=true --USE_WAYLAND=true \
--cc=gcc-10 --cxx=g++-10 --root -y \
&& rm -rf /data/.xmake/cache /tmp/crossdesk-dependencies \ && rm -rf /data/.xmake/cache /tmp/crossdesk-dependencies \
&& mkdir -p /workspace && mkdir -p /workspace
+14 -8
View File
@@ -1,7 +1,8 @@
# CrossDesk Linux 构建镜像 # CrossDesk Linux 构建镜像
这个目录维护 CrossDesk 专用的 Ubuntu 22.04 构建环境。镜像包含 xmake 这个目录维护 CrossDesk 专用的 Ubuntu 20.04 兼容构建环境。镜像包含 GCC 10
Rust、Linux 开发库(包括 D-Bus、DRM、PipeWire 和 SPA)以及根据项目 CMake 3.31、xmake、Rust、Linux 开发库(包括 D-Bus、DRM,以及仅用于编译的
PipeWire 0.3/SPA 头文件 SDK)以及根据项目
`xmake.lua` 提前编译好的依赖。 `xmake.lua` 提前编译好的依赖。
## 发布方式 ## 发布方式
@@ -11,14 +12,19 @@ Rust、Linux 开发库(包括 D-Bus、DRM、PipeWire 和 SPA)以及根据项
全部通过后再合并为同一个 multi-arch 镜像: 全部通过后再合并为同一个 multi-arch 镜像:
```text ```text
crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0 crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0
``` ```
Docker 会根据运行机器自动选择正确架构。amd64 变体以 Docker 会根据运行机器自动选择正确架构。amd64 变体以
`nvidia/cuda:12.6.3-devel-ubuntu22.04` 为基础;arm64 变体不包含当前构建 `nvidia/cuda:12.6.3-devel-ubuntu20.04` 为基础;arm64 变体不包含当前构建
用不到的 CUDA SDK。两个变体都使用 Ubuntu 22.04/glibc 2.35。 用不到的 CUDA SDK。两个变体都使用 Ubuntu 20.04/glibc 2.31,因此生成的
因此该镜像生成的 Linux 安装包最低支持 Ubuntu 22.04,不再保证兼容 单一 Linux 安装包可运行在 Ubuntu 20.04 及更高版本。
Ubuntu 20.04。
Ubuntu 20.04 仓库只提供 PipeWire 0.2。镜像通过
`install-pipewire-sdk.sh` 安装固定版本的 PipeWire 0.3.48/SPA 头文件,但不
安装或打包 PipeWire 动态库。CrossDesk 在运行时通过 `dlopen()` 使用宿主系统
提供的 PipeWire 0.3Ubuntu 20.04 没有该运行库时仍可使用 X11(以及构建时
启用的 DRM),Ubuntu 22.04 及更高版本则可使用 Wayland 捕获。
每次成功发布还会生成 `sha-<完整提交 SHA>`,用于精确回滚;`latest` 和上面的 每次成功发布还会生成 `sha-<完整提交 SHA>`,用于精确回滚;`latest` 和上面的
工具链版本 tag 只会在两个架构都验证成功后更新。 工具链版本 tag 只会在两个架构都验证成功后更新。
@@ -27,7 +33,7 @@ Ubuntu 20.04。
在仓库的 Actions secrets 中配置: 在仓库的 Actions secrets 中配置:
- `DOCKERHUB_USERNAME`:有权推送 `crossdesk/ubuntu22.04` 的 Docker Hub 用户名 - `DOCKERHUB_USERNAME`:有权推送 `crossdesk/ubuntu20.04` 的 Docker Hub 用户名
- `DOCKERHUB_TOKEN`:该用户的 Docker Hub access token - `DOCKERHUB_TOKEN`:该用户的 Docker Hub access token
工作流文件进入默认分支后,也可以从 Actions 页面手动运行。为了避免首次发布 工作流文件进入默认分支后,也可以从 Actions 页面手动运行。为了避免首次发布
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
PIPEWIRE_VERSION="0.3.48"
PIPEWIRE_ARCHIVE_SHA256="0d218be86b3d2b548c06259c47ad8d110ee1c09f071e17c4393eeef5c880fa6f"
INSTALL_PREFIX="${1:-/opt/crossdesk-pipewire-sdk}"
IFS='.' read -r PIPEWIRE_VERSION_MAJOR PIPEWIRE_VERSION_MINOR \
PIPEWIRE_VERSION_MICRO <<< "${PIPEWIRE_VERSION}"
if [[ "${INSTALL_PREFIX}" != /* ]]; then
echo "Install prefix must be an absolute path: ${INSTALL_PREFIX}" >&2
exit 2
fi
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/crossdesk-pipewire-sdk.XXXXXX")"
trap 'rm -rf "${WORK_DIR}"' EXIT
ARCHIVE_PATH="${WORK_DIR}/pipewire-${PIPEWIRE_VERSION}.tar.gz"
SOURCE_DIR="${WORK_DIR}/pipewire-${PIPEWIRE_VERSION}"
ARCHIVE_URL="https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/${PIPEWIRE_VERSION}/pipewire-${PIPEWIRE_VERSION}.tar.gz"
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
--retry 3 "${ARCHIVE_URL}" -o "${ARCHIVE_PATH}"
echo "${PIPEWIRE_ARCHIVE_SHA256} ${ARCHIVE_PATH}" | sha256sum --check -
tar -xzf "${ARCHIVE_PATH}" -C "${WORK_DIR}"
PIPEWIRE_INCLUDE_DIR="${INSTALL_PREFIX}/include/pipewire-0.3/pipewire"
SPA_INCLUDE_DIR="${INSTALL_PREFIX}/include/spa-0.2/spa"
while IFS= read -r -d '' header_path; do
relative_path="${header_path#${SOURCE_DIR}/src/pipewire/}"
install -D -m 0644 "${header_path}" \
"${PIPEWIRE_INCLUDE_DIR}/${relative_path}"
done < <(find "${SOURCE_DIR}/src/pipewire" -type f -name '*.h' -print0)
while IFS= read -r -d '' header_path; do
relative_path="${header_path#${SOURCE_DIR}/spa/include/spa/}"
install -D -m 0644 "${header_path}" \
"${SPA_INCLUDE_DIR}/${relative_path}"
done < <(find "${SOURCE_DIR}/spa/include/spa" -type f -name '*.h' -print0)
sed \
-e "s/@PIPEWIRE_VERSION_MAJOR@/${PIPEWIRE_VERSION_MAJOR}/g" \
-e "s/@PIPEWIRE_VERSION_MINOR@/${PIPEWIRE_VERSION_MINOR}/g" \
-e "s/@PIPEWIRE_VERSION_MICRO@/${PIPEWIRE_VERSION_MICRO}/g" \
-e 's/@PIPEWIRE_API_VERSION@/"0.3"/g' \
"${SOURCE_DIR}/src/pipewire/version.h.in" \
> "${PIPEWIRE_INCLUDE_DIR}/version.h"
chmod 0644 "${PIPEWIRE_INCLUDE_DIR}/version.h"
test -f "${PIPEWIRE_INCLUDE_DIR}/pipewire.h"
test -f "${PIPEWIRE_INCLUDE_DIR}/version.h"
test -f "${SPA_INCLUDE_DIR}/param/video/format-utils.h"
echo "Installed PipeWire ${PIPEWIRE_VERSION} header SDK under ${INSTALL_PREFIX}"
+40 -8
View File
@@ -2,19 +2,22 @@
set -euo pipefail set -euo pipefail
EXPECTED_ARCH="${EXPECTED_ARCH:-}" EXPECTED_ARCH="${EXPECTED_ARCH:-}"
EXPECTED_CMAKE_VERSION="${EXPECTED_CMAKE_VERSION:-3.31.6}"
EXPECTED_XMAKE_VERSION="${EXPECTED_XMAKE_VERSION:-3.0.9}" EXPECTED_XMAKE_VERSION="${EXPECTED_XMAKE_VERSION:-3.0.9}"
EXPECTED_RUST_VERSION="${EXPECTED_RUST_VERSION:-1.92.0}" EXPECTED_RUST_VERSION="${EXPECTED_RUST_VERSION:-1.92.0}"
BUILD_CC="${CC:-/usr/bin/gcc-10}"
BUILD_CXX="${CXX:-/usr/bin/g++-10}"
# shellcheck disable=SC1091 # shellcheck disable=SC1091
source /etc/os-release source /etc/os-release
if [[ "${ID}" != "ubuntu" || "${VERSION_ID}" != "22.04" ]]; then if [[ "${ID}" != "ubuntu" || "${VERSION_ID}" != "20.04" ]]; then
echo "Expected Ubuntu 22.04, found ${ID:-unknown} ${VERSION_ID:-unknown}" >&2 echo "Expected Ubuntu 20.04, found ${ID:-unknown} ${VERSION_ID:-unknown}" >&2
exit 1 exit 1
fi fi
GLIBC_VERSION="$(getconf GNU_LIBC_VERSION | awk '{print $2}')" GLIBC_VERSION="$(getconf GNU_LIBC_VERSION | awk '{print $2}')"
if [[ "${GLIBC_VERSION}" != "2.35" ]]; then if [[ "${GLIBC_VERSION}" != "2.31" ]]; then
echo "Expected glibc 2.35, found ${GLIBC_VERSION}" >&2 echo "Expected glibc 2.31, found ${GLIBC_VERSION}" >&2
exit 1 exit 1
fi fi
@@ -28,13 +31,23 @@ XMAKE_VERSION_OUTPUT="$(xmake --version 2>&1)"
grep -F "v${EXPECTED_XMAKE_VERSION}" <<<"${XMAKE_VERSION_OUTPUT}" >/dev/null grep -F "v${EXPECTED_XMAKE_VERSION}" <<<"${XMAKE_VERSION_OUTPUT}" >/dev/null
rustc --version | grep -F "rustc ${EXPECTED_RUST_VERSION}" >/dev/null rustc --version | grep -F "rustc ${EXPECTED_RUST_VERSION}" >/dev/null
cargo --version >/dev/null cargo --version >/dev/null
cmake --version >/dev/null cmake --version | grep -F "cmake version ${EXPECTED_CMAKE_VERSION}" >/dev/null
g++ --version >/dev/null "${BUILD_CC}" --version | grep -F 'gcc-10' >/dev/null
"${BUILD_CXX}" --version | grep -F 'g++-10' >/dev/null
printf '%s\n' \
'#include <concepts>' \
'#include <span>' \
'template <typename T> concept TestConcept = true;' \
'int main() {' \
' int value = 0;' \
' std::span<int> values(&value, 1);' \
' static_assert(TestConcept<int>);' \
' return values.empty();' \
'}' \
| "${BUILD_CXX}" -std=c++20 -fsyntax-only -x c++ -
for pkg_config_package in \ for pkg_config_package in \
dbus-1 \ dbus-1 \
libdrm \ libdrm \
libpipewire-0.3 \
libspa-0.2 \
xft; do xft; do
if ! pkg-config --exists "${pkg_config_package}"; then if ! pkg-config --exists "${pkg_config_package}"; then
echo "Required pkg-config package is missing: ${pkg_config_package}" >&2 echo "Required pkg-config package is missing: ${pkg_config_package}" >&2
@@ -42,6 +55,25 @@ for pkg_config_package in \
fi fi
done done
for sdk_header in \
/opt/crossdesk-pipewire-sdk/include/pipewire-0.3/pipewire/pipewire.h \
/opt/crossdesk-pipewire-sdk/include/pipewire-0.3/pipewire/version.h \
/opt/crossdesk-pipewire-sdk/include/spa-0.2/spa/param/video/format-utils.h; do
if [[ ! -f "${sdk_header}" ]]; then
echo "Required PipeWire build SDK header is missing: ${sdk_header}" >&2
exit 1
fi
done
printf '%s\n' \
'#include <pipewire/pipewire.h>' \
'#include <spa/param/video/format-utils.h>' \
'static_assert(PW_CHECK_VERSION(0, 3, 48));' \
'int main() { return 0; }' \
| "${BUILD_CXX}" -std=c++17 -fsyntax-only -x c++ \
-I/opt/crossdesk-pipewire-sdk/include/pipewire-0.3 \
-I/opt/crossdesk-pipewire-sdk/include/spa-0.2 -
for package_path in \ for package_path in \
"s/slint" \ "s/slint" \
"l/libdatachannel" \ "l/libdatachannel" \
+1 -1
View File
@@ -7,7 +7,7 @@ if [[ $# -lt 1 || $# -gt 2 ]]; then
fi fi
INPUT_PATH="$1" INPUT_PATH="$1"
MAXIMUM_VERSION="${2:-2.35}" MAXIMUM_VERSION="${2:-2.31}"
verify_elf() { verify_elf() {
local elf_file="$1" local elf_file="$1"
+10 -1
View File
@@ -104,6 +104,15 @@ OUTPUT_FILE="$PROJECT_ROOT/${PKG_NAME}-linux-${DEBIAN_ARCH}-${APP_VERSION}.deb"
install -d "$DEBIAN_DIR" "$BIN_DIR" "$PRIVATE_DIR" "$DESKTOP_DIR" install -d "$DEBIAN_DIR" "$BIN_DIR" "$PRIVATE_DIR" "$DESKTOP_DIR"
install -m 0755 "$BUILD_BINARY" "$PRIVATE_DIR/crossdesk-bin" install -m 0755 "$BUILD_BINARY" "$PRIVATE_DIR/crossdesk-bin"
# PipeWire is deliberately optional. The Wayland capturer resolves PipeWire
# 0.3 with dlopen() so the same package can run on Ubuntu 20.04 without the
# runtime while using the host runtime on newer Ubuntu releases.
if readelf -d "$BUILD_BINARY" \
| grep -qE 'Shared library: \[libpipewire-0\.3\.so'; then
echo "CrossDesk must not link directly to the optional PipeWire runtime" >&2
exit 1
fi
# Slint is intentionally built as a shared library. Keep that runtime private # Slint is intentionally built as a shared library. Keep that runtime private
# to CrossDesk instead of relying on a non-existent distribution package. # to CrossDesk instead of relying on a non-existent distribution package.
SLINT_NEEDED="$(readelf -d "$BUILD_BINARY" | sed -n \ SLINT_NEEDED="$(readelf -d "$BUILD_BINARY" | sed -n \
@@ -147,7 +156,7 @@ Version: $DEB_VERSION
Architecture: $DEBIAN_ARCH Architecture: $DEBIAN_ARCH
Maintainer: $MAINTAINER Maintainer: $MAINTAINER
Description: $DESCRIPTION Description: $DESCRIPTION
Depends: libc6 (>= 2.35), libstdc++6 (>= 9), libx11-6, libxext6, Depends: libc6 (>= 2.31), libstdc++6 (>= 10), libx11-6, libxext6,
libxrender1, libxft2, libxrandr2, libxfixes3, libxcb1, libxcb-randr0, libxrender1, libxft2, libxrandr2, libxfixes3, libxcb1, libxcb-randr0,
libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, libxcb-xkb1, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, libxcb-xkb1,
libxcb-xfixes0, libxv1, libxtst6, $ALSA_RUNTIME_DEP, libsndio7.0, libxcb-xfixes0, libxv1, libxtst6, $ALSA_RUNTIME_DEP, libsndio7.0,
@@ -8,6 +8,7 @@
#include <poll.h> #include <poll.h>
#include "keyboard_converter.h" #include "keyboard_converter.h"
#include "linux_evdev_keycode.h"
#include "platform.h" #include "platform.h"
#include "rd_log.h" #include "rd_log.h"
#include "windows_key_metadata.h" #include "windows_key_metadata.h"
@@ -17,6 +18,40 @@ namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr; static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr; static void* g_user_ptr = nullptr;
static KeyCode ResolveX11Keycode(Display* display, int key_code,
uint32_t scan_code, bool extended) {
if (!display) {
return 0;
}
const auto key_it = vkCodeToX11KeySym.find(key_code);
if (key_it != vkCodeToX11KeySym.end()) {
const KeyCode x11_keycode =
XKeysymToKeycode(display, static_cast<KeySym>(key_it->second));
if (x11_keycode != 0) {
return x11_keycode;
}
}
// Some controllers can preserve the physical Windows scan code even when
// they cannot resolve a Windows virtual-key value. Xorg's evdev keycodes use
// the Linux input code plus the protocol-reserved offset of 8.
const int evdev_keycode =
ResolveLinuxEvdevKeycodeFromWindowsKey(key_code, scan_code, extended);
if (evdev_keycode < 0) {
return 0;
}
int min_keycode = 0;
int max_keycode = 0;
XDisplayKeycodes(display, &min_keycode, &max_keycode);
const int x11_keycode = evdev_keycode + 8;
if (x11_keycode < min_keycode || x11_keycode > max_keycode) {
return 0;
}
return static_cast<KeyCode>(x11_keycode);
}
static KeySym NormalizeKeySym(KeySym key_sym) { static KeySym NormalizeKeySym(KeySym key_sym) {
if (key_sym >= XK_a && key_sym <= XK_z) { if (key_sym >= XK_a && key_sym <= XK_z) {
return key_sym - XK_a + XK_A; return key_sym - XK_a + XK_A;
@@ -62,7 +97,16 @@ KeyboardCapturer::KeyboardCapturer()
display_ = XOpenDisplay(nullptr); display_ = XOpenDisplay(nullptr);
if (!display_) { if (!display_) {
LOG_ERROR("Failed to open X display."); LOG_ERROR("Failed to open X display.");
return;
} }
int event_base = 0;
int error_base = 0;
int major_version = 0;
int minor_version = 0;
x11_xtest_available_ =
XTestQueryExtension(display_, &event_base, &error_base, &major_version,
&minor_version) != 0;
} }
KeyboardCapturer::~KeyboardCapturer() { KeyboardCapturer::~KeyboardCapturer() {
@@ -70,8 +114,10 @@ KeyboardCapturer::~KeyboardCapturer() {
CleanupWaylandPortal(); CleanupWaylandPortal();
if (display_) { if (display_) {
std::lock_guard<std::mutex> lock(x11_injection_mutex_);
XCloseDisplay(display_); XCloseDisplay(display_);
display_ = nullptr; display_ = nullptr;
x11_xtest_available_ = false;
} }
} }
@@ -156,8 +202,6 @@ int KeyboardCapturer::Unhook() {
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
uint32_t scan_code, bool extended) { uint32_t scan_code, bool extended) {
(void)scan_code;
(void)extended;
if (IsWaylandSession()) { if (IsWaylandSession()) {
if (!use_wayland_portal_ && !wayland_init_attempted_) { if (!use_wayland_portal_ && !wayland_init_attempted_) {
wayland_init_attempted_ = true; wayland_init_attempted_ = true;
@@ -181,12 +225,33 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
return -1; return -1;
} }
if (vkCodeToX11KeySym.find(key_code) != vkCodeToX11KeySym.end()) { std::lock_guard<std::mutex> lock(x11_injection_mutex_);
int x11_key_code = vkCodeToX11KeySym[key_code]; if (!x11_xtest_available_) {
KeyCode keycode = XKeysymToKeycode(display_, x11_key_code); LOG_ERROR("XTest extension not available for keyboard injection");
XTestFakeKeyEvent(display_, keycode, is_down, CurrentTime); return -2;
XFlush(display_);
} }
const KeyCode x11_keycode =
ResolveX11Keycode(display_, key_code, scan_code, extended);
if (x11_keycode == 0) {
LOG_WARN(
"Cannot map remote keyboard event to X11 keycode, vk_code={}, "
"scan_code={}, extended={}",
key_code, scan_code, extended);
return -3;
}
if (!XTestFakeKeyEvent(display_, x11_keycode, is_down, CurrentTime)) {
LOG_ERROR(
"XTest keyboard injection failed, vk_code={}, scan_code={}, "
"extended={}, x11_keycode={}, is_down={}",
key_code, scan_code, extended, static_cast<int>(x11_keycode), is_down);
return -4;
}
// Complete the request before reporting success to the keyboard-state
// reconciler. This also makes X11 protocol errors observable immediately.
XSync(display_, False);
return 0; return 0;
} }
} // namespace crossdesk } // namespace crossdesk
@@ -10,6 +10,7 @@
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -49,6 +50,8 @@ class KeyboardCapturer : public DeviceController {
unsigned long root_; unsigned long root_;
std::atomic<bool> running_; std::atomic<bool> running_;
std::thread event_thread_; std::thread event_thread_;
std::mutex x11_injection_mutex_;
bool x11_xtest_available_ = false;
bool use_wayland_portal_ = false; bool use_wayland_portal_ = false;
bool wayland_init_attempted_ = false; bool wayland_init_attempted_ = false;
DBusConnection* dbus_connection_ = nullptr; DBusConnection* dbus_connection_ = nullptr;
@@ -611,6 +611,7 @@ int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down,
// Prefer keycode injection to preserve physical-key semantics and avoid // Prefer keycode injection to preserve physical-key semantics and avoid
// implicit Shift interpretation for uppercase keysyms. // implicit Shift interpretation for uppercase keysyms.
if (display_) { if (display_) {
std::lock_guard<std::mutex> lock(x11_injection_mutex_);
const int keysym = key_it->second; const int keysym = key_it->second;
const KeyCode x11_keycode = const KeyCode x11_keycode =
XKeysymToKeycode(display_, static_cast<KeySym>(keysym)); XKeysymToKeycode(display_, static_cast<KeySym>(keysym));
+10 -8
View File
@@ -943,10 +943,6 @@ void GuiApplication::InitializeSettings() {
bool GuiApplication::InitializeSDL() { bool GuiApplication::InitializeSDL() {
#if defined(__linux__) && !defined(__APPLE__) #if defined(__linux__) && !defined(__APPLE__)
if (!getenv("SDL_AUDIODRIVER")) {
setenv("SDL_AUDIODRIVER", "pulseaudio", 0);
}
// Standard Wayland top-level windows cannot choose an absolute screen // Standard Wayland top-level windows cannot choose an absolute screen
// position. Use the session's XWayland compatibility server for the GUI so // position. Use the session's XWayland compatibility server for the GUI so
// auxiliary windows such as the controlled-side status window can be // auxiliary windows such as the controlled-side status window can be
@@ -970,6 +966,14 @@ bool GuiApplication::InitializeSDL() {
LOG_ERROR("SDL initialization failed: {}", SDL_GetError()); LOG_ERROR("SDL initialization failed: {}", SDL_GetError());
return false; return false;
} }
#if defined(__linux__) && !defined(__APPLE__)
const char* active_video_driver = SDL_GetCurrentVideoDriver();
// X11 permits the manual move/position operations used by the frameless
// main window. Keep native Wayland decorations as a compatibility fallback.
use_x11_custom_titlebar_ =
active_video_driver != nullptr &&
std::strcmp(active_video_driver, "x11") == 0;
#endif
if (const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(0)) { if (const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(0)) {
screen_width_ = mode->w; screen_width_ = mode->w;
@@ -1004,10 +1008,8 @@ void GuiApplication::InitializeUi() {
#if _WIN32 #if _WIN32
ui_->main->set_custom_titlebar(true); ui_->main->set_custom_titlebar(true);
#elif defined(__linux__) #elif defined(__linux__)
if (use_xwayland_gui_) { ui_->main->set_custom_titlebar(use_x11_custom_titlebar_);
ui_->main->set_custom_titlebar(true); ui_->main->set_wayland_titlebar(use_xwayland_gui_);
ui_->main->set_wayland_titlebar(true);
}
#endif #endif
std::vector<ui::ReleaseNoteBlock> release_note_blocks; std::vector<ui::ReleaseNoteBlock> release_note_blocks;
for (const auto& parsed : ParseReleaseNotesMarkdownForSlint(release_notes_)) { for (const auto& parsed : ParseReleaseNotesMarkdownForSlint(release_notes_)) {
+1
View File
@@ -66,6 +66,7 @@ private:
std::unique_ptr<SlintUi> ui_; std::unique_ptr<SlintUi> ui_;
#if defined(__linux__) && !defined(__APPLE__) #if defined(__linux__) && !defined(__APPLE__)
bool use_xwayland_gui_ = false; bool use_xwayland_gui_ = false;
bool use_x11_custom_titlebar_ = false;
#endif #endif
}; };
+12 -1
View File
@@ -601,6 +601,15 @@ export component MainWindow inherits Window {
border-width: 1px; border-width: 1px;
border-color: ImGuiLineStyle.border; border-color: ImGuiLineStyle.border;
border-radius: 4px; border-radius: 4px;
// Measure the ID using the preferred field size.
// The visible text can then scale only on systems
// whose font metrics would otherwise clip it.
local-id-width-probe := Text {
visible: false;
text: root.local-id;
font-size: ImGuiFontStyle.field-value;
letter-spacing: 1px;
}
Text { Text {
x: 8px; x: 8px;
// Compensate for the font's ascender-heavy // Compensate for the font's ascender-heavy
@@ -610,7 +619,9 @@ export component MainWindow inherits Window {
height: parent.height - 4px; height: parent.height - 4px;
text: root.local-id; text: root.local-id;
color: #20242a; color: #20242a;
font-size: ImGuiFontStyle.field-value; font-size: max(18px,
ImGuiFontStyle.field-value *
(self.width / max(self.width, local-id-width-probe.preferred-width)));
letter-spacing: 1px; letter-spacing: 1px;
horizontal-alignment: left; horizontal-alignment: left;
vertical-alignment: center; vertical-alignment: center;
@@ -0,0 +1,52 @@
#include <X11/Xlib.h>
#include <chrono>
#include <cstdio>
#include <thread>
#include "keyboard_capturer.h"
int main() {
Display* receiver = XOpenDisplay(nullptr);
if (!receiver) {
std::fprintf(stderr, "open display failed\n");
return 1;
}
const int screen = DefaultScreen(receiver);
Window window = XCreateSimpleWindow(receiver, RootWindow(receiver, screen),
0, 0, 100, 100, 0, 0, 0);
XSelectInput(receiver, window, KeyPressMask | KeyReleaseMask);
XMapWindow(receiver, window);
XSync(receiver, False);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
XSetInputFocus(receiver, window, RevertToParent, CurrentTime);
XSync(receiver, False);
crossdesk::KeyboardCapturer keyboard;
// Exercise the scan-code fallback: the controller supplied no usable VK,
// but did preserve the set-1 scan code for the A key.
const int down = keyboard.SendKeyboardCommand(0, true, 0x1E, false);
const int up = keyboard.SendKeyboardCommand(0, false, 0x1E, false);
bool saw_down = false;
bool saw_up = false;
const auto deadline = std::chrono::steady_clock::now() +
std::chrono::seconds(2);
while (std::chrono::steady_clock::now() < deadline &&
(!saw_down || !saw_up)) {
while (XPending(receiver) > 0) {
XEvent event{};
XNextEvent(receiver, &event);
saw_down = saw_down || event.type == KeyPress;
saw_up = saw_up || event.type == KeyRelease;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
XDestroyWindow(receiver, window);
XCloseDisplay(receiver);
std::printf("down_ret=%d up_ret=%d saw_down=%d saw_up=%d\n", down, up,
saw_down, saw_up);
return down == 0 && up == 0 && saw_down && saw_up ? 0 : 2;
}
+17 -1
View File
@@ -9,10 +9,13 @@ package("slint")
add_configs("shared", {description = "Build the Slint runtime as a shared library", default = true, type = "boolean", readonly = true}) add_configs("shared", {description = "Build the Slint runtime as a shared library", default = true, type = "boolean", readonly = true})
add_deps("cmake") add_deps("cmake~slint >=3.21 <4.0", {host = true, private = true, system = false})
add_deps("rust 1.92.0", {host = true, private = true, system = false}) add_deps("rust 1.92.0", {host = true, private = true, system = false})
on_load(function(package) on_load(function(package)
if package:is_plat("linux") then
package:add("deps", "fontconfig", {private = true, system = false})
end
package:add("includedirs", "include/slint") package:add("includedirs", "include/slint")
if package:is_plat("windows") then if package:is_plat("windows") then
package:add("links", "slint_cpp.dll") package:add("links", "slint_cpp.dll")
@@ -22,6 +25,13 @@ package("slint")
end) end)
on_install("windows", "linux", "macosx", function(package) on_install("windows", "linux", "macosx", function(package)
for _, dep in ipairs(package:orderdeps()) do
if dep:name() == "cmake" and dep:label() == "slint" then
os.addenv("PATH", dep:installdir("bin"))
import("core.cache.detectcache"):set2("find_program", "cmake", nil)
break
end
end
local configs = { local configs = {
"-DSLINT_BUILD_TESTING=OFF", "-DSLINT_BUILD_TESTING=OFF",
"-DSLINT_BUILD_EXAMPLES=OFF", "-DSLINT_BUILD_EXAMPLES=OFF",
@@ -39,6 +49,12 @@ package("slint")
"-DSLINT_STYLE=fluent", "-DSLINT_STYLE=fluent",
"-DBUILD_SHARED_LIBS=ON" "-DBUILD_SHARED_LIBS=ON"
} }
if package:is_plat("windows") then
local rc = assert(package:build_getenv("mrc"),
"failed to find the Windows resource compiler")
rc = rc:gsub("\\", "/")
table.insert(configs, "-DCMAKE_RC_COMPILER=" .. rc)
end
import("package.tools.cmake").install(package, configs) import("package.tools.cmake").install(package, configs)
end) end)
+1 -1
View File
@@ -51,7 +51,7 @@ function setup_options_and_dependencies()
add_defines("CROSSDESK_DEBUG") add_defines("CROSSDESK_DEBUG")
end end
add_requireconfs("*.python", {version = "3.12", override = true, configs = {pgo = false}}) add_requireconfs("**.python", {version = "3.12", override = true, configs = {pgo = false}})
add_requires("spdlog 1.14.1", {system = false}) add_requires("spdlog 1.14.1", {system = false})
add_requires("slint 1.17.1", {configs = {shared = true}}) add_requires("slint 1.17.1", {configs = {shared = true}})
add_requires("libsdl3 3.2.26", {configs = {shared = false}}) add_requires("libsdl3 3.2.26", {configs = {shared = false}})
+3 -1
View File
@@ -64,10 +64,12 @@ function setup_platform_settings()
"/usr/local/include/pipewire-0.3", "/usr/local/include/pipewire-0.3",
"/usr/include/pipewire", "/usr/include/pipewire",
"/usr/local/include/pipewire", "/usr/local/include/pipewire",
"/opt/crossdesk-pipewire-sdk/include/pipewire-0.3",
"/usr/include/spa-0.2", "/usr/include/spa-0.2",
"/usr/local/include/spa-0.2", "/usr/local/include/spa-0.2",
"/usr/include/spa", "/usr/include/spa",
"/usr/local/include/spa" "/usr/local/include/spa",
"/opt/crossdesk-pipewire-sdk/include/spa-0.2"
}, {system = true}) }, {system = true})
for _, include_dir in ipairs(collect_dbus_arch_include_dirs()) do for _, include_dir in ipairs(collect_dbus_arch_include_dirs()) do
add_includedirs(include_dir, {system = true}) add_includedirs(include_dir, {system = true})
+8
View File
@@ -197,6 +197,14 @@ function setup_targets()
"src/device_controller/keyboard/linux", {public = true}) "src/device_controller/keyboard/linux", {public = true})
end end
if is_os("linux") then
target("linux_keyboard_x11_integration_test")
set_kind("binary")
set_default(false)
add_deps("device_controller")
add_files("tests/linux_keyboard_x11_integration_test.cpp")
end
target("thumbnail") target("thumbnail")
set_kind("object") set_kind("object")
add_packages("libyuv", "openssl3") add_packages("libyuv", "openssl3")