mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-12-18 04:56:45 +08:00
Compare commits
10 Commits
v1.1.0-bet
...
web-client
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd3c3e073 | ||
|
|
4ba4f17a6b | ||
|
|
f5d0291b5a | ||
|
|
1a64c1afef | ||
|
|
18f4973d0a | ||
|
|
37ede5861c | ||
|
|
497454ac51 | ||
|
|
4ebb7a6a4d | ||
|
|
7d1910df71 | ||
|
|
52e70a26f3 |
94
.github/workflows/build.yml
vendored
94
.github/workflows/build.yml
vendored
@@ -35,12 +35,14 @@ jobs:
|
||||
id: set_deb_version
|
||||
run: |
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
BUILD_DATE=$(date +%Y%m%d)
|
||||
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
||||
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
|
||||
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
|
||||
else
|
||||
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
|
||||
LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
|
||||
fi
|
||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -99,12 +101,14 @@ jobs:
|
||||
id: set_deb_version
|
||||
run: |
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
BUILD_DATE=$(date +%Y%m%d)
|
||||
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
||||
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
|
||||
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
|
||||
else
|
||||
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
|
||||
LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
|
||||
fi
|
||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -160,9 +164,11 @@ jobs:
|
||||
run: |
|
||||
VERSION="${GITHUB_REF##*/}"
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
|
||||
BUILD_DATE=$(date +%Y%m%d)
|
||||
VERSION_NUM="v${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||
echo "VERSION_NUM=${VERSION_NUM}"
|
||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache xmake dependencies
|
||||
uses: actions/cache@v4
|
||||
@@ -223,7 +229,9 @@ jobs:
|
||||
$version = $version -replace '^v', ''
|
||||
$version = $version -replace '/', '-'
|
||||
$SHORT_SHA = $env:GITHUB_SHA.Substring(0,7)
|
||||
echo "VERSION_NUM=$version-$SHORT_SHA" >> $env:GITHUB_ENV
|
||||
$BUILD_DATE = Get-Date -Format "yyyyMMdd"
|
||||
echo "VERSION_NUM=v$version-$BUILD_DATE-$SHORT_SHA" >> $env:GITHUB_ENV
|
||||
echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Cache xmake dependencies
|
||||
uses: actions/cache@v4
|
||||
@@ -292,7 +300,7 @@ jobs:
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} -y
|
||||
xmake b -vy crossdesk
|
||||
|
||||
|
||||
- name: Decode and save certificate
|
||||
shell: powershell
|
||||
run: |
|
||||
@@ -332,17 +340,25 @@ jobs:
|
||||
run: |
|
||||
VERSION="${GITHUB_REF##*/}"
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
|
||||
BUILD_DATE=$(date +%Y%m%d)
|
||||
BUILD_DATE_ISO=$(date +%Y-%m-%d)
|
||||
VERSION_NUM="${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}"
|
||||
VERSION_WITH_V="v${VERSION_NUM}"
|
||||
VERSION_ONLY="${VERSION#v}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION_ONLY=${VERSION_ONLY}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Rename artifacts
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}.pkg
|
||||
cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}.pkg
|
||||
cp artifacts/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}.deb
|
||||
cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}.deb
|
||||
cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}.exe
|
||||
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
|
||||
|
||||
- name: List release files
|
||||
run: ls -lh release/
|
||||
@@ -350,8 +366,8 @@ jobs:
|
||||
- name: Upload to Versioned GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ steps.version.outputs.VERSION_NUM }}
|
||||
name: Release v${{ steps.version.outputs.VERSION_NUM }}
|
||||
tag_name: ${{ steps.version.outputs.VERSION_WITH_V }}
|
||||
name: Release ${{ steps.version.outputs.VERSION_WITH_V }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: release/*
|
||||
@@ -387,17 +403,43 @@ jobs:
|
||||
remote_key: ${{ secrets.SERVER_KEY }}
|
||||
|
||||
- name: Generate version.json
|
||||
run: |
|
||||
echo "{" > version.json
|
||||
echo " \"latest_version\": \"${{ steps.version.outputs.VERSION_NUM }}\"" >> version.json
|
||||
echo "}" >> version.json cat version.json
|
||||
run: |
|
||||
cat > version.json << EOF
|
||||
{
|
||||
"version": "${{ steps.version.outputs.VERSION_ONLY }}",
|
||||
"releaseDate": "${{ steps.version.outputs.BUILD_DATE_ISO }}",
|
||||
"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"
|
||||
},
|
||||
"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
|
||||
- name: Upload version.json to server
|
||||
uses: burnett01/rsync-deployments@5.2
|
||||
with:
|
||||
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 }}
|
||||
path: version.json
|
||||
remote_path: /var/www/html/version/
|
||||
remote_host: ${{ secrets.SERVER_HOST }}
|
||||
remote_user: ${{ secrets.SERVER_USER }}
|
||||
remote_key: ${{ secrets.SERVER_KEY }}
|
||||
|
||||
51
.github/workflows/update-pages.yml
vendored
51
.github/workflows/update-pages.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Update GitHub Pages Downloads
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
update-pages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout CrossDesk repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set version number
|
||||
id: version
|
||||
run: |
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
VERSION_NUM="${GITHUB_REF##*/}"
|
||||
VERSION_NUM="${VERSION_NUM#v}-${SHORT_SHA}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout Pages repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: kunkundi/kunkundi.github.io
|
||||
token: ${{ secrets.GH_PAGES_PAT }}
|
||||
path: pages
|
||||
|
||||
- name: Update download links
|
||||
run: |
|
||||
cd pages
|
||||
echo "Updating download links to ${VERSION_NUM}"
|
||||
sed -E -i "s/crossdesk-win-x64-[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9._-]+)?\.exe/crossdesk-win-x64-${VERSION_NUM}.exe/g" index.html
|
||||
sed -E -i "s/crossdesk-macos-x64-[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9._-]+)?\.pkg/crossdesk-macos-x64-${VERSION_NUM}.pkg/g" index.html
|
||||
sed -E -i "s/crossdesk-macos-arm64-[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9._-]+)?\.pkg/crossdesk-macos-arm64-${VERSION_NUM}.pkg/g" index.html
|
||||
sed -E -i "s/crossdesk-linux-amd64-[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9._-]+)?\.deb/crossdesk-linux-amd64-${VERSION_NUM}.deb/g" index.html
|
||||
sed -E -i "s/crossdesk-linux-arm64-[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9._-]+)?\.deb/crossdesk-linux-arm64-${VERSION_NUM}.deb/g" index.html
|
||||
git diff index.html || true
|
||||
|
||||
- name: Commit & Push changes
|
||||
run: |
|
||||
cd pages
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add index.html
|
||||
git commit -m "Update download links to v${VERSION_NUM}" || echo "No changes to commit"
|
||||
git push origin main
|
||||
env:
|
||||
VERSION_NUM: ${{ env.VERSION_NUM }}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[]()
|
||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/web-client)
|
||||
[](https://github.com/kunkundi/crossdesk/actions)
|
||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||
[]()
|
||||
@@ -42,7 +42,9 @@ CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频
|
||||
|
||||
### Web 客户端
|
||||
浏览器访问 [CrossDesk Web Client](https://web.crossdesk.cn/)。
|
||||
输入 **远程设备 ID** 与 **密码**,点击连接即可接入远程设备。
|
||||
输入 **远程设备 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" />
|
||||
|
||||
## 如何编译
|
||||
|
||||
@@ -315,4 +317,4 @@ Generation complete. Deployment files::
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
||||
|
||||
# 常见问题
|
||||
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。
|
||||
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[]()
|
||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/web-client)
|
||||
[](https://github.com/kunkundi/crossdesk/actions)
|
||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||
[]()
|
||||
@@ -45,7 +45,9 @@ Before connecting, you can customize configuration options in the settings, such
|
||||
### 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.
|
||||
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" />
|
||||
|
||||
## How to build
|
||||
|
||||
@@ -323,4 +325,4 @@ Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/y
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
||||
|
||||
# FAQ
|
||||
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .
|
||||
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .
|
||||
|
||||
@@ -9,7 +9,10 @@ ARCHITECTURE="amd64"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
|
||||
# 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"
|
||||
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
|
||||
@@ -35,7 +38,7 @@ done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $APP_VERSION
|
||||
Version: $DEB_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
@@ -50,7 +53,7 @@ EOF
|
||||
|
||||
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Version=$APP_VERSION
|
||||
Version=$DEB_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/bin/$PKG_NAME
|
||||
|
||||
@@ -9,7 +9,10 @@ ARCHITECTURE="arm64"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
|
||||
# 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"
|
||||
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
|
||||
@@ -35,7 +38,7 @@ done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $APP_VERSION
|
||||
Version: $DEB_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
@@ -49,7 +52,7 @@ EOF
|
||||
|
||||
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Version=$APP_VERSION
|
||||
Version=$DEB_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/bin/$PKG_NAME
|
||||
|
||||
302
src/autostart/autostart.cpp
Normal file
302
src/autostart/autostart.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
#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
|
||||
21
src/autostart/autostart.h
Normal file
21
src/autostart/autostart.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* @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
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "config_center.h"
|
||||
|
||||
#include "autostart.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
ConfigCenter::ConfigCenter(const std::string& config_path,
|
||||
@@ -48,7 +51,8 @@ int ConfigCenter::Load() {
|
||||
ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
enable_self_hosted_ =
|
||||
ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||
|
||||
enable_autostart_ =
|
||||
ini_.GetBoolValue(section_, "enable_autostart", enable_autostart_);
|
||||
enable_minimize_to_tray_ = ini_.GetBoolValue(
|
||||
section_, "enable_minimize_to_tray", enable_minimize_to_tray_);
|
||||
|
||||
@@ -71,6 +75,7 @@ int ConfigCenter::Save() {
|
||||
static_cast<long>(signal_server_port_));
|
||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||
ini_.SetBoolValue(section_, "enable_autostart", enable_autostart_);
|
||||
ini_.SetBoolValue(section_, "enable_minimize_to_tray",
|
||||
enable_minimize_to_tray_);
|
||||
|
||||
@@ -221,6 +226,29 @@ int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetAutostart(bool enable_autostart) {
|
||||
enable_autostart_ = enable_autostart;
|
||||
bool success = false;
|
||||
if (enable_autostart) {
|
||||
success = EnableAutostart("CrossDesk");
|
||||
} else {
|
||||
success = DisableAutostart("CrossDesk");
|
||||
}
|
||||
|
||||
ini_.SetBoolValue(section_, "enable_autostart", enable_autostart_);
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
LOG_ERROR("SetAutostart failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
|
||||
@@ -274,4 +302,6 @@ std::string ConfigCenter::GetDefaultCertFilePath() const {
|
||||
bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; }
|
||||
|
||||
bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; }
|
||||
|
||||
bool ConfigCenter::IsEnableAutostart() const { return enable_autostart_; }
|
||||
} // namespace crossdesk
|
||||
@@ -40,6 +40,7 @@ class ConfigCenter {
|
||||
int SetCertFilePath(const std::string& cert_file_path);
|
||||
int SetSelfHosted(bool enable_self_hosted);
|
||||
int SetMinimizeToTray(bool enable_minimize_to_tray);
|
||||
int SetAutostart(bool enable_autostart);
|
||||
|
||||
// read config
|
||||
|
||||
@@ -60,6 +61,7 @@ class ConfigCenter {
|
||||
std::string GetDefaultCertFilePath() const;
|
||||
bool IsSelfHosted() const;
|
||||
bool IsMinimizeToTray() const;
|
||||
bool IsEnableAutostart() const;
|
||||
|
||||
int Load();
|
||||
int Save();
|
||||
@@ -86,6 +88,7 @@ class ConfigCenter {
|
||||
std::string cert_file_path_default_ = "";
|
||||
bool enable_self_hosted_ = false;
|
||||
bool enable_minimize_to_tray_ = false;
|
||||
bool enable_autostart_ = false;
|
||||
};
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
@@ -120,8 +120,14 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Unhook() {
|
||||
CFRelease(run_loop_source_);
|
||||
CFRelease(event_tap_);
|
||||
if (run_loop_source_) {
|
||||
CFRelease(run_loop_source_);
|
||||
}
|
||||
|
||||
if (event_tap_) {
|
||||
CFRelease(event_tap_);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,11 +21,11 @@
|
||||
#define SETTINGS_WINDOW_WIDTH_CN 202
|
||||
#define SETTINGS_WINDOW_WIDTH_EN 248
|
||||
#if _WIN32
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 375
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 375
|
||||
#else
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 345
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 345
|
||||
#else
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 315
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 315
|
||||
#endif
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275
|
||||
@@ -47,6 +47,8 @@
|
||||
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_AUTOSTART_PADDING_CN 171
|
||||
#define ENABLE_AUTOSTART_PADDING_EN 218
|
||||
#define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171
|
||||
#define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218
|
||||
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90
|
||||
|
||||
@@ -168,8 +168,10 @@ static std::vector<std::string> access_website = {
|
||||
static std::vector<std::string> confirm_delete_connection = {
|
||||
reinterpret_cast<const char*>(u8"确认删除此连接"),
|
||||
"Confirm to delete this connection"};
|
||||
#if _WIN32
|
||||
|
||||
static std::vector<std::string> enable_autostart = {
|
||||
reinterpret_cast<const char*>(u8"开机自启:"), "Auto Start:"};
|
||||
#if _WIN32
|
||||
static std::vector<std::string> minimize_to_tray = {
|
||||
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
|
||||
"Minimize to system tray when exit:"};
|
||||
|
||||
@@ -260,6 +260,8 @@ int Render::LoadSettingsFromCacheFile() {
|
||||
enable_turn_ = config_center_->IsEnableTurn();
|
||||
enable_srtp_ = config_center_->IsEnableSrtp();
|
||||
enable_self_hosted_ = config_center_->IsSelfHosted();
|
||||
enable_autostart_ = config_center_->IsEnableAutostart();
|
||||
enable_minimize_to_tray_ = config_center_->IsMinimizeToTray();
|
||||
|
||||
language_button_value_last_ = language_button_value_;
|
||||
video_quality_button_value_last_ = video_quality_button_value_;
|
||||
@@ -268,6 +270,8 @@ int Render::LoadSettingsFromCacheFile() {
|
||||
enable_turn_last_ = enable_turn_;
|
||||
enable_srtp_last_ = enable_srtp_;
|
||||
enable_self_hosted_last_ = enable_self_hosted_;
|
||||
enable_autostart_last_ = enable_autostart_;
|
||||
enable_minimize_to_tray_last_ = enable_minimize_to_tray_;
|
||||
|
||||
LOG_INFO("Load settings from cache file");
|
||||
|
||||
@@ -325,8 +329,9 @@ int Render::ScreenCapturerInit() {
|
||||
|
||||
int Render::StartScreenCapturer() {
|
||||
if (screen_capturer_) {
|
||||
LOG_INFO("Start screen capturer");
|
||||
screen_capturer_->Start();
|
||||
LOG_INFO("Start screen capturer, show cursor: {}", show_cursor_);
|
||||
|
||||
screen_capturer_->Start(show_cursor_);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -316,6 +316,7 @@ class Render {
|
||||
bool start_speaker_capturer_ = false;
|
||||
bool speaker_capturer_is_started_ = false;
|
||||
bool start_keyboard_capturer_ = true;
|
||||
bool show_cursor_ = false;
|
||||
bool keyboard_capturer_is_started_ = false;
|
||||
bool foucs_on_main_window_ = false;
|
||||
bool foucs_on_stream_window_ = false;
|
||||
@@ -462,6 +463,8 @@ class Render {
|
||||
bool enable_turn_last_ = false;
|
||||
bool enable_srtp_last_ = false;
|
||||
bool enable_self_hosted_last_ = false;
|
||||
bool enable_autostart_ = false;
|
||||
bool enable_autostart_last_ = false;
|
||||
bool enable_minimize_to_tray_ = false;
|
||||
bool enable_minimize_to_tray_last_ = false;
|
||||
char signal_server_ip_self_[256] = "";
|
||||
|
||||
@@ -464,6 +464,13 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
||||
#else
|
||||
render->start_mouse_controller_ = true;
|
||||
#endif
|
||||
if (std::all_of(render->connection_status_.begin(),
|
||||
render->connection_status_.end(), [](const auto& kv) {
|
||||
return kv.first.find("web") != std::string::npos;
|
||||
})) {
|
||||
render->show_cursor_ = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ConnectionStatus::Closed: {
|
||||
@@ -486,6 +493,14 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
||||
|
||||
render->connection_status_.erase(remote_id);
|
||||
}
|
||||
|
||||
if (std::all_of(render->connection_status_.begin(),
|
||||
render->connection_status_.end(), [](const auto& kv) {
|
||||
return kv.first.find("web") == std::string::npos;
|
||||
})) {
|
||||
render->show_cursor_ = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
void Hyperlink(const std::string& label, const std::string& url) {
|
||||
void Hyperlink(const std::string& label, const std::string& url,
|
||||
const float window_width) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 255, 255));
|
||||
ImGui::SetCursorPosX(window_width * 0.1f);
|
||||
ImGui::Text("%s", label.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
@@ -67,7 +69,8 @@ int Render::AboutWindow() {
|
||||
#endif
|
||||
|
||||
std::string text = localization::version[localization_language_index_] +
|
||||
": CrossDesk v" + version;
|
||||
": CrossDesk " + version;
|
||||
ImGui::SetCursorPosX(about_window_width_ * 0.1f);
|
||||
ImGui::Text("%s", text.c_str());
|
||||
|
||||
if (update_available_) {
|
||||
@@ -76,14 +79,16 @@ int Render::AboutWindow() {
|
||||
": " + latest_version_;
|
||||
std::string access_website =
|
||||
localization::access_website[localization_language_index_];
|
||||
Hyperlink(latest_version, "https://crossdesk.cn");
|
||||
Hyperlink(latest_version, "https://crossdesk.cn", about_window_width_);
|
||||
}
|
||||
|
||||
ImGui::Text("");
|
||||
|
||||
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
|
||||
std::string license_text = "Licensed under GNU LGPL v3.";
|
||||
ImGui::SetCursorPosX(about_window_width_ * 0.1f);
|
||||
ImGui::Text("%s", copyright_text.c_str());
|
||||
ImGui::SetCursorPosX(about_window_width_ * 0.1f);
|
||||
ImGui::Text("%s", license_text.c_str());
|
||||
|
||||
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
|
||||
|
||||
@@ -233,6 +233,25 @@ int Render::SettingWindow() {
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_self_hosted", &enable_self_hosted_);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
|
||||
ImGui::Text("%s",
|
||||
localization::enable_autostart[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_AUTOSTART_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_AUTOSTART_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_autostart_", &enable_autostart_);
|
||||
}
|
||||
#if _WIN32
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -347,6 +366,22 @@ int Render::SettingWindow() {
|
||||
}
|
||||
enable_self_hosted_last_ = enable_self_hosted_;
|
||||
|
||||
if (enable_autostart_) {
|
||||
config_center_->SetAutostart(true);
|
||||
} else {
|
||||
config_center_->SetAutostart(false);
|
||||
}
|
||||
enable_autostart_last_ = enable_autostart_;
|
||||
|
||||
#if _WIN32
|
||||
if (enable_minimize_to_tray_) {
|
||||
config_center_->SetMinimizeToTray(true);
|
||||
} else {
|
||||
config_center_->SetMinimizeToTray(false);
|
||||
}
|
||||
enable_minimize_to_tray_last_ = enable_minimize_to_tray_;
|
||||
#endif
|
||||
|
||||
settings_window_pos_reset_ = true;
|
||||
|
||||
// Recreate peer instance
|
||||
|
||||
@@ -36,9 +36,21 @@ int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) {
|
||||
XRRCrtcInfo* crtc_info =
|
||||
XRRGetCrtcInfo(display_, screen_res_, output_info->crtc);
|
||||
|
||||
display_info_list_.push_back(
|
||||
DisplayInfo((void*)display_, output_info->name, true, crtc_info->x,
|
||||
crtc_info->y, crtc_info->width, crtc_info->height));
|
||||
std::string name(output_info->name);
|
||||
|
||||
if (name.empty()) {
|
||||
name = "Display" + std::to_string(i + 1);
|
||||
}
|
||||
|
||||
// clean display name, remove non-alphanumeric characters
|
||||
name.erase(
|
||||
std::remove_if(name.begin(), name.end(),
|
||||
[](unsigned char c) { return !std::isalnum(c); }),
|
||||
name.end());
|
||||
|
||||
display_info_list_.push_back(DisplayInfo(
|
||||
(void*)display_, name, true, crtc_info->x, crtc_info->y,
|
||||
crtc_info->x + crtc_info->width, crtc_info->y + crtc_info->height));
|
||||
|
||||
XRRFreeCrtcInfo(crtc_info);
|
||||
}
|
||||
@@ -86,7 +98,7 @@ int ScreenCapturerX11::Destroy() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Start() {
|
||||
int ScreenCapturerX11::Start(bool show_cursor) {
|
||||
if (running_) return 0;
|
||||
running_ = true;
|
||||
paused_ = false;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
@@ -30,7 +31,7 @@ class ScreenCapturerX11 : public ScreenCapturer {
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
@@ -54,6 +55,7 @@ class ScreenCapturerX11 : public ScreenCapturer {
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> paused_{false};
|
||||
std::atomic<int> monitor_index_{0};
|
||||
std::atomic<bool> show_cursor_{true};
|
||||
int fps_ = 60;
|
||||
cb_desktop_data callback_;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
@@ -61,6 +63,9 @@ class ScreenCapturerX11 : public ScreenCapturer {
|
||||
// 缓冲区
|
||||
std::vector<uint8_t> y_plane_;
|
||||
std::vector<uint8_t> uv_plane_;
|
||||
|
||||
// 鼠标光标相关
|
||||
void DrawCursor(XImage* image, int x, int y);
|
||||
};
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
@@ -28,8 +28,8 @@ int ScreenCapturerSck::Destroy() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Start() {
|
||||
screen_capturer_sck_impl_->Start();
|
||||
int ScreenCapturerSck::Start(bool show_cursor) {
|
||||
screen_capturer_sck_impl_->Start(show_cursor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class ScreenCapturerSck : public ScreenCapturer {
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
|
||||
@@ -57,7 +57,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
|
||||
int Start() override;
|
||||
int Start(bool show_cursor) override;
|
||||
|
||||
int SwitchTo(int monitor_index) override;
|
||||
|
||||
@@ -80,6 +80,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int fps_ = 60;
|
||||
bool show_cursor_ = false;
|
||||
|
||||
public:
|
||||
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
|
||||
@@ -225,13 +226,17 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||
CGRect bounds = CGDisplayBounds(display_id);
|
||||
bool is_primary = CGDisplayIsMain(display_id);
|
||||
|
||||
std::string name;
|
||||
name = GetDisplayName(display_id);
|
||||
std::string name = GetDisplayName(display_id);
|
||||
|
||||
if (name.empty()) {
|
||||
name = "Display " + std::to_string(unnamed_count++);
|
||||
name = "Display" + std::to_string(unnamed_count++);
|
||||
}
|
||||
|
||||
// clean display name, remove non-alphanumeric characters
|
||||
name.erase(
|
||||
std::remove_if(name.begin(), name.end(), [](unsigned char c) { return !std::isalnum(c); }),
|
||||
name.end());
|
||||
|
||||
DisplayInfo info((void *)(uintptr_t)display_id, name, is_primary,
|
||||
static_cast<int>(bounds.origin.x), static_cast<int>(bounds.origin.y),
|
||||
static_cast<int>(bounds.origin.x + bounds.size.width),
|
||||
@@ -246,7 +251,8 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Start() {
|
||||
int ScreenCapturerSckImpl::Start(bool show_cursor) {
|
||||
show_cursor_ = show_cursor;
|
||||
StartOrReconfigureCapturer();
|
||||
return 0;
|
||||
}
|
||||
@@ -328,7 +334,7 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
|
||||
excludingWindows:@[]];
|
||||
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
|
||||
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
config.showsCursor = false;
|
||||
config.showsCursor = show_cursor_;
|
||||
config.width = filter.contentRect.size.width * filter.pointPixelScale;
|
||||
config.height = filter.contentRect.size.height * filter.pointPixelScale;
|
||||
config.captureResolution = SCCaptureResolutionAutomatic;
|
||||
|
||||
@@ -24,7 +24,7 @@ class ScreenCapturer {
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb) = 0;
|
||||
virtual int Destroy() = 0;
|
||||
virtual int Start() = 0;
|
||||
virtual int Start(bool show_cursor) = 0;
|
||||
virtual int Stop() = 0;
|
||||
virtual int Pause(int monitor_index) = 0;
|
||||
virtual int Resume(int monitor_index) = 0;
|
||||
|
||||
@@ -152,7 +152,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
|
||||
|
||||
int ScreenCapturerWgc::Destroy() { return 0; }
|
||||
|
||||
int ScreenCapturerWgc::Start() {
|
||||
int ScreenCapturerWgc::Start(bool show_cursor) {
|
||||
if (running_ == true) {
|
||||
LOG_ERROR("Screen capturer already running");
|
||||
return 0;
|
||||
@@ -172,7 +172,7 @@ int ScreenCapturerWgc::Start() {
|
||||
if (sessions_[i].running_) {
|
||||
LOG_ERROR("Session {} is already running", i);
|
||||
} else {
|
||||
sessions_[i].session_->Start();
|
||||
sessions_[i].session_->Start(show_cursor);
|
||||
|
||||
if (i != 0) {
|
||||
sessions_[i].session_->Pause();
|
||||
|
||||
@@ -24,7 +24,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
||||
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
|
||||
@@ -29,7 +29,7 @@ class WgcSession {
|
||||
|
||||
virtual void RegisterObserver(wgc_session_observer* observer) = 0;
|
||||
|
||||
virtual int Start() = 0;
|
||||
virtual int Start(bool show_cursor) = 0;
|
||||
virtual int Stop() = 0;
|
||||
|
||||
virtual int Pause() = 0;
|
||||
|
||||
@@ -55,7 +55,7 @@ void WgcSessionImpl::RegisterObserver(wgc_session_observer* observer) {
|
||||
observer_ = observer;
|
||||
}
|
||||
|
||||
int WgcSessionImpl::Start() {
|
||||
int WgcSessionImpl::Start(bool show_cursor) {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
if (is_running_) return 0;
|
||||
@@ -91,7 +91,7 @@ int WgcSessionImpl::Start() {
|
||||
|
||||
capture_session_.StartCapture();
|
||||
|
||||
capture_session_.IsCursorCaptureEnabled(false);
|
||||
capture_session_.IsCursorCaptureEnabled(show_cursor);
|
||||
|
||||
error = 0;
|
||||
} catch (winrt::hresult_error) {
|
||||
|
||||
@@ -48,7 +48,7 @@ class WgcSessionImpl : public WgcSession {
|
||||
|
||||
void RegisterObserver(wgc_session_observer* observer) override;
|
||||
|
||||
int Start() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause() override;
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
static std::string latest_release_date_ = "";
|
||||
|
||||
std::string ExtractNumericPart(const std::string& ver) {
|
||||
size_t start = 0;
|
||||
while (start < ver.size() && !std::isdigit(ver[start])) start++;
|
||||
@@ -39,6 +43,39 @@ std::vector<int> SplitVersion(const std::string& ver) {
|
||||
return nums;
|
||||
}
|
||||
|
||||
// extract date from version string (format: v1.2.3-20251113-abc
|
||||
// or 1.2.3-20251113-abc)
|
||||
std::string ExtractDateFromVersion(const std::string& version) {
|
||||
size_t dash1 = version.find('-');
|
||||
if (dash1 != std::string::npos) {
|
||||
size_t dash2 = version.find('-', dash1 + 1);
|
||||
if (dash2 != std::string::npos) {
|
||||
std::string date_part = version.substr(dash1 + 1, dash2 - dash1 - 1);
|
||||
|
||||
bool is_date = true;
|
||||
for (char c : date_part) {
|
||||
if (!std::isdigit(c)) {
|
||||
is_date = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_date) {
|
||||
// convert YYYYMMDD to YYYY-MM-DD
|
||||
return date_part.substr(0, 4) + "-" + date_part.substr(4, 2) + "-" +
|
||||
date_part.substr(6, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// compare two dates in YYYY-MM-DD format
|
||||
bool IsNewerDate(const std::string& date1, const std::string& date2) {
|
||||
if (date1.empty() || date2.empty()) return false;
|
||||
// simple string comparison works for ISO date format (YYYY-MM-DD)
|
||||
return date2 > date1;
|
||||
}
|
||||
|
||||
bool IsNewerVersion(const std::string& current, const std::string& latest) {
|
||||
auto v1 = SplitVersion(ExtractNumericPart(current));
|
||||
auto v2 = SplitVersion(ExtractNumericPart(latest));
|
||||
@@ -51,6 +88,44 @@ bool IsNewerVersion(const std::string& current, const std::string& latest) {
|
||||
if (v2[i] > v1[i]) return true;
|
||||
if (v2[i] < v1[i]) return false;
|
||||
}
|
||||
|
||||
// if versions are equal, compare by release date
|
||||
if (!latest_release_date_.empty()) {
|
||||
// try to extract date from current version string
|
||||
std::string current_date = ExtractDateFromVersion(current);
|
||||
if (!current_date.empty()) {
|
||||
return IsNewerDate(current_date, latest_release_date_);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsNewerVersionWithDate(const std::string& current_version,
|
||||
const std::string& current_date,
|
||||
const std::string& latest_version,
|
||||
const std::string& latest_date) {
|
||||
// compare versions
|
||||
auto v1 = SplitVersion(ExtractNumericPart(current_version));
|
||||
auto v2 = SplitVersion(ExtractNumericPart(latest_version));
|
||||
|
||||
size_t len = std::max(v1.size(), v2.size());
|
||||
v1.resize(len, 0);
|
||||
v2.resize(len, 0);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (v2[i] > v1[i]) return true;
|
||||
if (v2[i] < v1[i]) return false;
|
||||
}
|
||||
|
||||
// if versions are equal, compare by release date
|
||||
if (!current_date.empty() && !latest_date.empty()) {
|
||||
return IsNewerDate(current_date, latest_date);
|
||||
}
|
||||
|
||||
// if dates are not available, versions are equal
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -64,17 +139,27 @@ std::string CheckUpdate() {
|
||||
if (res->status == 200) {
|
||||
try {
|
||||
auto j = json::parse(res->body);
|
||||
std::string latest = j["latest_version"];
|
||||
std::string latest = j["version"];
|
||||
if (j.contains("releaseDate") && j["releaseDate"].is_string()) {
|
||||
latest_release_date_ = j["releaseDate"];
|
||||
} else {
|
||||
latest_release_date_ = "";
|
||||
}
|
||||
return latest;
|
||||
} catch (std::exception&) {
|
||||
latest_release_date_ = "";
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
latest_release_date_ = "";
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
latest_release_date_ = "";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetLatestReleaseDate() { return latest_release_date_; }
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
Submodule submodules/minirtc updated: 2f8b0c1ff4...29ae03237f
@@ -133,9 +133,15 @@ target("thumbnail")
|
||||
add_files("src/thumbnail/*.cpp")
|
||||
add_includedirs("src/thumbnail", {public = true})
|
||||
|
||||
target("config_center")
|
||||
target("autostart")
|
||||
set_kind("object")
|
||||
add_deps("rd_log")
|
||||
add_files("src/autostart/*.cpp")
|
||||
add_includedirs("src/autostart", {public = true})
|
||||
|
||||
target("config_center")
|
||||
set_kind("object")
|
||||
add_deps("rd_log", "autostart")
|
||||
add_files("src/config_center/*.cpp")
|
||||
add_includedirs("src/config_center", {public = true})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user