Compare commits

..

11 Commits

Author SHA1 Message Date
dijunkun
07c7c7f179 [chore] update README 2025-11-21 02:40:51 +08:00
dijunkun
c5ceeb0d80 [ci] use China timezone for build date in version number 2025-11-21 02:33:39 +08:00
dijunkun
5ce0a891df [fix] resolve failures in connection destruction 2025-11-21 01:50:08 +08:00
dijunkun
f94ef49210 [fix] release keyboard hook after remote control disconnects, refs #23 2025-11-21 00:56:01 +08:00
dijunkun
5d0a4d1385 [fix] fix mouse wheel and touchpad swipe commands, refs #9, #23 2025-11-21 00:55:19 +08:00
dijunkun
dd482cee60 [fix] use static linking of libffi in glib to avoid version conflicts, fixes #16 2025-11-20 17:20:15 +08:00
dijunkun
e3c2edfb1c [fix] fix daemon not working on Linux 2025-11-20 15:35:37 +08:00
dijunkun
f3901d09ea [feat] add tooltip for the daemon option in the settings windows 2025-11-20 14:47:54 +08:00
Junkun Di
2b12749477 [chore] update README_EN.md 2025-11-20 13:05:14 +08:00
Junkun Di
759488f675 [chore] update README.md 2025-11-20 13:04:32 +08:00
dijunkun
4bb4240a9e [chore] update README 2025-11-20 11:15:34 +08:00
15 changed files with 826 additions and 782 deletions

View File

@@ -35,7 +35,7 @@ jobs:
id: set_deb_version id: set_deb_version
run: | run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(date +%Y%m%d) BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}" LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
else else
@@ -101,7 +101,7 @@ jobs:
id: set_deb_version id: set_deb_version
run: | run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(date +%Y%m%d) BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}" LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
else else
@@ -164,7 +164,7 @@ jobs:
run: | run: |
VERSION="${GITHUB_REF##*/}" VERSION="${GITHUB_REF##*/}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(date +%Y%m%d) BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
VERSION_NUM="v${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}" VERSION_NUM="v${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}" echo "VERSION_NUM=${VERSION_NUM}"
@@ -229,7 +229,7 @@ jobs:
$version = $version -replace '^v', '' $version = $version -replace '^v', ''
$version = $version -replace '/', '-' $version = $version -replace '/', '-'
$SHORT_SHA = $env:GITHUB_SHA.Substring(0,7) $SHORT_SHA = $env:GITHUB_SHA.Substring(0,7)
$BUILD_DATE = Get-Date -Format "yyyyMMdd" $BUILD_DATE = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "China Standard Time")).ToString("yyyyMMdd")
echo "VERSION_NUM=v$version-$BUILD_DATE-$SHORT_SHA" >> $env:GITHUB_ENV echo "VERSION_NUM=v$version-$BUILD_DATE-$SHORT_SHA" >> $env:GITHUB_ENV
echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV
@@ -340,8 +340,8 @@ jobs:
run: | run: |
VERSION="${GITHUB_REF##*/}" VERSION="${GITHUB_REF##*/}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(date +%Y%m%d) BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
BUILD_DATE_ISO=$(date +%Y-%m-%d) BUILD_DATE_ISO=$(TZ=Asia/Shanghai date +%Y-%m-%d)
VERSION_NUM="${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}" VERSION_NUM="${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}"
VERSION_WITH_V="v${VERSION_NUM}" VERSION_WITH_V="v${VERSION_NUM}"
VERSION_ONLY="${VERSION#v}" VERSION_ONLY="${VERSION#v}"

View File

@@ -11,8 +11,14 @@
[ [English](README_EN.md) / 中文 ] [ [English](README_EN.md) / 中文 ]
PC 客户端
![sup_example](https://github.com/user-attachments/assets/eeb64fbe-1f07-4626-be1c-b77396beb905) ![sup_example](https://github.com/user-attachments/assets/eeb64fbe-1f07-4626-be1c-b77396beb905)
Web 客户端
<p align="center">
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
</p>
## 简介 ## 简介
CrossDesk 是一个轻量级的跨平台远程桌面软件,支持 Web 端控制远程设备。 CrossDesk 是一个轻量级的跨平台远程桌面软件,支持 Web 端控制远程设备。
@@ -70,7 +76,14 @@ git submodule update
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
编译选项
```
--USE_CUDA=true/false: 启用 CUDA 硬件编解码,默认不启用
--CROSSDESK_VERSION=xxx: 指定 CrossDesk 的版本
# 示例
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
```
运行 运行
``` ```
xmake r crossdesk xmake r crossdesk
@@ -78,13 +91,14 @@ xmake r crossdesk
### 无 CUDA 环境下的开发支持 ### 无 CUDA 环境下的开发支持
对于**未安装 CUDA 环境的 Linux 开发者**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。 对于**未安装 CUDA 环境的 Linux 开发者,如果希望编译后的成果物拥有硬件编解码能力**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
进入容器,下载工程后执行: 进入容器,下载工程后执行:
``` ```
export CUDA_PATH=/usr/local/cuda export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data export XMAKE_GLOBALDIR=/data
xmake f --USE_CUDA=true
xmake b --root -vy crossdesk xmake b --root -vy crossdesk
``` ```
@@ -106,6 +120,7 @@ set CUDA_PATH=path_to_cuda_installdir
``` ```
重新执行: 重新执行:
``` ```
xmake f --USE_CUDA=true
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
@@ -166,7 +181,7 @@ sudo docker run -d \
-v /path/to/your/certs:/crossdesk-server/certs \ -v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \ -v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \ -v /path/to/your/logs:/crossdesk-server/logs \
crossdesk/crossdesk-server:latest crossdesk/crossdesk-server:v1.1.1
``` ```
上述命令中,用户需注意的参数如下: 上述命令中,用户需注意的参数如下:

View File

@@ -11,8 +11,14 @@
[ [中文](README.md) / English ] [ [中文](README.md) / English ]
PC Client
![sup_example](https://github.com/user-attachments/assets/3f17d8f3-7c4a-4b63-bae4-903363628687) ![sup_example](https://github.com/user-attachments/assets/3f17d8f3-7c4a-4b63-bae4-903363628687)
Web Client
<p align="center">
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
</p>
# Intro # Intro
CrossDesk is a lightweight cross-platform remote desktop software. CrossDesk is a lightweight cross-platform remote desktop software.
@@ -73,7 +79,14 @@ git submodule update
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
Build options:
```
--USE_CUDA=true/false: enable CUDA acceleration codec, default: false
--CROSSDESK_VERSION=xxx: set the version number
# example:
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
```
Run: Run:
``` ```
xmake r crossdesk xmake r crossdesk
@@ -81,7 +94,7 @@ xmake r crossdesk
#### Development Without CUDA Environment #### Development Without CUDA Environment
For **Linux developers who do not have a CUDA environment** installed, 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 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup. This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
After entering the container, download the project and run: After entering the container, download the project and run:
@@ -89,6 +102,7 @@ After entering the container, download the project and run:
export CUDA_PATH=/usr/local/cuda export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data export XMAKE_GLOBALDIR=/data
xmake f --USE_CUDA=true
xmake b --root -vy crossdesk xmake b --root -vy crossdesk
``` ```
@@ -111,6 +125,7 @@ set CUDA_PATH=path_to_cuda_installdir:
``` ```
Then re-run: Then re-run:
``` ```
xmake f --USE_CUDA=true
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
@@ -174,7 +189,7 @@ sudo docker run -d \
-v /path/to/your/certs:/crossdesk-server/certs \ -v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \ -v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \ -v /path/to/your/logs:/crossdesk-server/logs \
crossdesk/crossdesk-server:v1.1.0 crossdesk/crossdesk-server:v1.1.1
``` ```
The parameters you need to pay attention to are as follows: The parameters you need to pay attention to are as follows:

BIN
image.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -21,6 +21,7 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#else #else
#include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <signal.h> #include <signal.h>
@@ -77,21 +78,9 @@ Daemon::Daemon(const std::string& name)
{ {
} }
void Daemon::stop() { void Daemon::stop() { running_ = false; }
#ifdef _WIN32
running_ = false;
#else
running_ = false;
#endif
}
bool Daemon::isRunning() const { bool Daemon::isRunning() const { return running_; }
#ifdef _WIN32
return running_;
#else
return running_;
#endif
}
bool Daemon::start(MainLoopFunc loop) { bool Daemon::start(MainLoopFunc loop) {
#ifdef _WIN32 #ifdef _WIN32
@@ -102,15 +91,77 @@ bool Daemon::start(MainLoopFunc loop) {
running_ = true; running_ = true;
return runWithRestart(loop); return runWithRestart(loop);
#else #else
// Linux: Use traditional daemonization // linux: Daemonize first, then run with restart monitoring
instance_ = this; instance_ = this;
runUnix(loop);
return true; // check if running from terminal before fork
bool from_terminal =
(isatty(STDIN_FILENO) != 0) || (isatty(STDOUT_FILENO) != 0);
// first fork: detach from terminal
pid_t pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork daemon process" << std::endl;
return false;
}
if (pid > 0) _exit(0);
if (setsid() < 0) {
std::cerr << "Failed to create new session" << std::endl;
return false;
}
pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork daemon process (second fork)" << std::endl;
return false;
}
if (pid > 0) _exit(0);
umask(0);
chdir("/");
// redirect file descriptors: keep stdout/stderr if from terminal, else
// redirect to /dev/null
int fd = open("/dev/null", O_RDWR);
if (fd >= 0) {
dup2(fd, STDIN_FILENO);
if (!from_terminal) {
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
if (fd > 2) close(fd);
}
// set up signal handlers
signal(SIGTERM, [](int) {
if (instance_) instance_->stop();
});
signal(SIGINT, [](int) {
if (instance_) instance_->stop();
});
// ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);
// set up SIGCHLD handler to reap zombie processes
struct sigaction sa_chld;
sa_chld.sa_handler = [](int) {
// reap zombie processes
while (waitpid(-1, nullptr, WNOHANG) > 0) {
// continue until no more zombie children
}
};
sigemptyset(&sa_chld.sa_mask);
sa_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa_chld, nullptr);
running_ = true;
return runWithRestart(loop);
#endif #endif
} }
#ifdef _WIN32 #ifdef _WIN32
// windows: execute loop and catch C++ exceptions
static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) { static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) {
try { try {
loop(); loop();
@@ -124,12 +175,11 @@ static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) {
} }
} }
// windows: Use SEH wrapper function to catch system-level crashes
static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) { static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) {
__try { __try {
return RunLoopCatchCpp(loop); return RunLoopCatchCpp(loop);
} __except (EXCEPTION_EXECUTE_HANDLER) { } __except (EXCEPTION_EXECUTE_HANDLER) {
// Catch system-level crashes (access violation, divide by zero, etc.) // catch system-level crashes (access violation, divide by zero, etc.)
DWORD code = GetExceptionCode(); DWORD code = GetExceptionCode();
std::cerr << "System crash detected (SEH exception code: 0x" << std::hex std::cerr << "System crash detected (SEH exception code: 0x" << std::hex
<< code << std::dec << ")" << std::endl; << code << std::dec << ")" << std::endl;
@@ -138,9 +188,7 @@ static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) {
} }
#endif #endif
// run function with restart logic (infinite restart) // run with restart logic: parent monitors child process and restarts on crash
// use child process monitoring: parent process creates child process to run
// main program, monitors child process status, restarts on crash
bool Daemon::runWithRestart(MainLoopFunc loop) { bool Daemon::runWithRestart(MainLoopFunc loop) {
int restart_count = 0; int restart_count = 0;
std::string exe_path = GetExecutablePath(); std::string exe_path = GetExecutablePath();
@@ -148,7 +196,6 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
std::cerr std::cerr
<< "Failed to get executable path, falling back to direct execution" << "Failed to get executable path, falling back to direct execution"
<< std::endl; << std::endl;
// fallback to direct execution
while (isRunning()) { while (isRunning()) {
try { try {
loop(); loop();
@@ -170,7 +217,6 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
STARTUPINFOA si = {sizeof(si)}; STARTUPINFOA si = {sizeof(si)};
PROCESS_INFORMATION pi = {0}; PROCESS_INFORMATION pi = {0};
// build command line arguments (add --child flag)
std::string cmd_line = "\"" + exe_path + "\" --child"; std::string cmd_line = "\"" + exe_path + "\" --child";
std::vector<char> cmd_line_buf(cmd_line.begin(), cmd_line.end()); std::vector<char> cmd_line_buf(cmd_line.begin(), cmd_line.end());
cmd_line_buf.push_back('\0'); cmd_line_buf.push_back('\0');
@@ -197,7 +243,6 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
continue; continue;
} }
// wait for child process to exit
DWORD exit_code = 0; DWORD exit_code = 0;
WaitForSingleObject(pi.hProcess, INFINITE); WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exit_code); GetExitCodeProcess(pi.hProcess, &exit_code);
@@ -205,56 +250,57 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
if (exit_code == 0) { if (exit_code == 0) {
// normal exit break; // normal exit
break; }
} else {
// abnormal exit, restart
restart_count++; restart_count++;
std::cerr << "Child process exited with code " << exit_code std::cerr << "Child process exited with code " << exit_code
<< ", restarting... (attempt " << restart_count << ")" << ", restarting... (attempt " << restart_count << ")"
<< std::endl; << std::endl;
std::this_thread::sleep_for( std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
}
#else #else
// linux: use fork + exec to create child process // linux: use fork + exec to create child process
pid_t pid = fork(); pid_t pid = fork();
if (pid == 0) { if (pid == 0) {
// child process: execute main program, pass --child argument
execl(exe_path.c_str(), exe_path.c_str(), "--child", nullptr); execl(exe_path.c_str(), exe_path.c_str(), "--child", nullptr);
// if exec fails, exit _exit(1); // exec failed
_exit(1);
} else if (pid > 0) { } else if (pid > 0) {
// parent process: wait for child process to exit
int status = 0; int status = 0;
waitpid(pid, &status, 0); pid_t waited_pid = waitpid(pid, &status, 0);
if (waited_pid < 0) {
restart_count++;
std::cerr << "waitpid failed, errno: " << errno
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
continue;
}
if (WIFEXITED(status)) { if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status); int exit_code = WEXITSTATUS(status);
if (exit_code == 0) { if (exit_code == 0) {
// normal exit break; // normal exit
break; }
} else {
// abnormal exit, restart
restart_count++; restart_count++;
std::cerr << "Child process exited with code " << exit_code std::cerr << "Child process exited with code " << exit_code
<< ", restarting... (attempt " << restart_count << ")" << ", restarting... (attempt " << restart_count << ")"
<< std::endl; << std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
}
} else if (WIFSIGNALED(status)) { } else if (WIFSIGNALED(status)) {
// child process terminated by signal (crash)
int sig = WTERMSIG(status);
restart_count++; restart_count++;
std::cerr << "Child process crashed with signal " << sig std::cerr << "Child process crashed with signal " << WTERMSIG(status)
<< ", restarting... (attempt " << restart_count << ")" << ", restarting... (attempt " << restart_count << ")"
<< std::endl; << std::endl;
} else {
restart_count++;
std::cerr << "Child process exited with unknown status, restarting... "
"(attempt "
<< restart_count << ")" << std::endl;
}
std::this_thread::sleep_for( std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
}
} else { } else {
// fork failed
std::cerr << "Failed to fork child process" << std::endl; std::cerr << "Failed to fork child process" << std::endl;
std::this_thread::sleep_for( std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
@@ -265,132 +311,3 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
return true; return true;
} }
#ifndef _WIN32
void Daemon::runUnix(MainLoopFunc loop) {
struct sigaction sa;
sa.sa_handler = [](int) {};
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGPIPE, &sa, nullptr);
sigaction(SIGCHLD, &sa, nullptr);
// daemon mode: fork twice, redirect output
pid_t pid = fork();
if (pid > 0) _exit(0);
setsid();
pid = fork();
if (pid > 0) _exit(0);
umask(0);
chdir("/");
int fd = open("/dev/null", O_RDWR);
if (fd >= 0) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > 2) close(fd);
}
// catch signals for exit
signal(SIGTERM, [](int) { instance_->stop(); });
signal(SIGINT, [](int) { instance_->stop(); });
// catch crash signals, use atomic flags to mark crash
static std::atomic<bool> crash_detected{false};
static std::atomic<bool> should_restart{false};
// use safer signal handling: only set flags, don't call longjmp
struct sigaction sa_crash;
sa_crash.sa_handler = [](int sig) {
const char* sig_name = "Unknown";
switch (sig) {
case SIGSEGV:
sig_name = "SIGSEGV";
break;
case SIGABRT:
sig_name = "SIGABRT";
break;
case SIGFPE:
sig_name = "SIGFPE";
break;
case SIGILL:
sig_name = "SIGILL";
break;
}
std::cerr << "Crash signal detected: " << sig_name
<< ", will restart after process exits" << std::endl;
crash_detected = true;
should_restart = true;
// don't call longjmp, let program exit normally, restart by monitoring
// thread
};
sigemptyset(&sa_crash.sa_mask);
sa_crash.sa_flags = SA_RESETHAND; // handle only once, avoid recursion
sigaction(SIGSEGV, &sa_crash, nullptr);
sigaction(SIGABRT, &sa_crash, nullptr);
sigaction(SIGFPE, &sa_crash, nullptr);
sigaction(SIGILL, &sa_crash, nullptr);
running_ = true;
// run with restart logic (infinite restart)
// run main loop in separate thread, main thread monitors thread status
int restart_count = 0;
while (running_) {
crash_detected = false;
should_restart = false;
std::atomic<bool> loop_completed{false};
std::exception_ptr loop_exception = nullptr;
// run main loop in separate thread
std::thread loop_thread([&loop, &loop_completed, &loop_exception]() {
try {
loop();
loop_completed = true;
} catch (const std::exception& e) {
loop_exception = std::current_exception();
loop_completed = true;
} catch (...) {
loop_exception = std::current_exception();
loop_completed = true;
}
});
// wait for thread to complete
loop_thread.join();
// check exit reason
if (loop_exception) {
restart_count++;
try {
std::rethrow_exception(loop_exception);
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception caught" << std::endl;
}
std::cerr << "Restarting... (attempt " << restart_count << ")"
<< std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
continue;
}
// check if crash signal detected
if (crash_detected || should_restart) {
restart_count++;
std::cerr << "Crash detected, restarting... (attempt " << restart_count
<< ")" << std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
continue;
}
// normal exit
if (loop_completed) {
break;
}
}
}
#endif

View File

@@ -10,7 +10,6 @@
#include <functional> #include <functional>
#include <string> #include <string>
// default restart delay (milliseconds)
#define DAEMON_DEFAULT_RESTART_DELAY_MS 1000 #define DAEMON_DEFAULT_RESTART_DELAY_MS 1000
class Daemon { class Daemon {
@@ -19,13 +18,10 @@ class Daemon {
Daemon(const std::string& name); Daemon(const std::string& name);
// start daemon (restart after 1 second by default)
bool start(MainLoopFunc loop); bool start(MainLoopFunc loop);
// request exit
void stop(); void stop();
// check if running
bool isRunning() const; bool isRunning() const;
private: private:
@@ -36,7 +32,6 @@ class Daemon {
bool running_; bool running_;
#else #else
static Daemon* instance_; static Daemon* instance_;
void runUnix(MainLoopFunc loop);
volatile bool running_; volatile bool running_;
#endif #endif
}; };

View File

@@ -51,7 +51,16 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
} }
int KeyboardCapturer::Unhook() { int KeyboardCapturer::Unhook() {
g_on_key_action = nullptr;
g_user_ptr = nullptr;
running_ = false; running_ = false;
if (display_) {
XSelectInput(display_, DefaultRootWindow(display_), 0);
XFlush(display_);
}
return 0; return 0;
} }

View File

@@ -10,6 +10,10 @@ static void* g_user_ptr = nullptr;
CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
CGEventRef event, void* userInfo) { CGEventRef event, void* userInfo) {
if (!g_on_key_action) {
return event;
}
KeyboardCapturer* keyboard_capturer = (KeyboardCapturer*)userInfo; KeyboardCapturer* keyboard_capturer = (KeyboardCapturer*)userInfo;
if (!keyboard_capturer) { if (!keyboard_capturer) {
LOG_ERROR("keyboard_capturer is nullptr"); LOG_ERROR("keyboard_capturer is nullptr");
@@ -120,12 +124,23 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
} }
int KeyboardCapturer::Unhook() { int KeyboardCapturer::Unhook() {
g_on_key_action = nullptr;
g_user_ptr = nullptr;
if (event_tap_) {
CGEventTapEnable(event_tap_, false);
}
if (run_loop_source_) { if (run_loop_source_) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_,
kCFRunLoopCommonModes);
CFRelease(run_loop_source_); CFRelease(run_loop_source_);
run_loop_source_ = nullptr;
} }
if (event_tap_) { if (event_tap_) {
CFRelease(event_tap_); CFRelease(event_tap_);
event_tap_ = nullptr;
} }
return 0; return 0;

View File

@@ -39,7 +39,12 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
} }
int KeyboardCapturer::Unhook() { int KeyboardCapturer::Unhook() {
if (keyboard_hook_) {
g_on_key_action = nullptr;
g_user_ptr = nullptr;
UnhookWindowsHookEx(keyboard_hook_); UnhookWindowsHookEx(keyboard_hook_);
keyboard_hook_ = nullptr;
}
return 0; return 0;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -173,6 +173,9 @@ static std::vector<std::string> enable_autostart = {
reinterpret_cast<const char*>(u8"开机自启:"), "Auto Start:"}; reinterpret_cast<const char*>(u8"开机自启:"), "Auto Start:"};
static std::vector<std::string> enable_daemon = { static std::vector<std::string> enable_daemon = {
reinterpret_cast<const char*>(u8"启用守护进程:"), "Enable Daemon:"}; reinterpret_cast<const char*>(u8"启用守护进程:"), "Enable Daemon:"};
static std::vector<std::string> takes_effect_after_restart = {
reinterpret_cast<const char*>(u8"重启后生效"),
"Takes effect after restart"};
#if _WIN32 #if _WIN32
static std::vector<std::string> minimize_to_tray = { static std::vector<std::string> minimize_to_tray = {
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"), reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),

View File

@@ -1340,6 +1340,7 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
is_client_mode_ = false; is_client_mode_ = false;
reload_recent_connections_ = true; reload_recent_connections_ = true;
fullscreen_button_pressed_ = false; fullscreen_button_pressed_ = false;
start_keyboard_capturer_ = false;
just_created_ = false; just_created_ = false;
recent_connection_image_save_time_ = SDL_GetTicks(); recent_connection_image_save_time_ = SDL_GetTicks();
} else { } else {

View File

@@ -1,3 +1,5 @@
#include <cmath>
#include "device_controller.h" #include "device_controller.h"
#include "localization.h" #include "localization.h"
#include "platform.h" #include "platform.h"
@@ -101,28 +103,39 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
last_mouse_event.button.y >= props->stream_render_rect_.y && last_mouse_event.button.y >= props->stream_render_rect_.y &&
last_mouse_event.button.y <= props->stream_render_rect_.y + last_mouse_event.button.y <= props->stream_render_rect_.y +
props->stream_render_rect_.h) { props->stream_render_rect_.h) {
int scroll_x = event.wheel.x; float scroll_x = event.wheel.x;
int scroll_y = event.wheel.y; float scroll_y = event.wheel.y;
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
scroll_x = -scroll_x; scroll_x = -scroll_x;
scroll_y = -scroll_y; scroll_y = -scroll_y;
} }
remote_action.type = ControlType::mouse; remote_action.type = ControlType::mouse;
if (scroll_x == 0) {
auto roundUp = [](float value) -> int {
if (value > 0) {
return static_cast<int>(std::ceil(value));
} else if (value < 0) {
return static_cast<int>(std::floor(value));
}
return 0;
};
if (std::abs(scroll_y) >= std::abs(scroll_x)) {
remote_action.m.flag = MouseFlag::wheel_vertical; remote_action.m.flag = MouseFlag::wheel_vertical;
remote_action.m.s = scroll_y; remote_action.m.s = roundUp(scroll_y);
} else if (scroll_y == 0) { } else {
remote_action.m.flag = MouseFlag::wheel_horizontal; remote_action.m.flag = MouseFlag::wheel_horizontal;
remote_action.m.s = scroll_x; remote_action.m.s = roundUp(scroll_x);
} }
render_width = props->stream_render_rect_.w; render_width = props->stream_render_rect_.w;
render_height = props->stream_render_rect_.h; render_height = props->stream_render_rect_.h;
remote_action.m.x = remote_action.m.x =
(float)(event.button.x - props->stream_render_rect_.x) / render_width; (float)(last_mouse_event.button.x - props->stream_render_rect_.x) /
render_width;
remote_action.m.y = remote_action.m.y =
(float)(event.button.y - props->stream_render_rect_.y) / (float)(last_mouse_event.button.y - props->stream_render_rect_.y) /
render_height; render_height;
std::string msg = remote_action.to_json(); std::string msg = remote_action.to_json();
@@ -225,8 +238,6 @@ void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
props->video_height_ = video_frame->height; props->video_height_ = video_frame->height;
props->video_size_ = video_frame->size; props->video_size_ = video_frame->size;
LOG_ERROR("receive: {}x{}", props->video_width_, props->video_height_);
if (need_to_update_render_rect) { if (need_to_update_render_rect) {
render->UpdateRenderRect(); render->UpdateRenderRect();
} }

View File

@@ -272,6 +272,15 @@ int Render::SettingWindow() {
} }
ImGui::SetCursorPosY(settings_items_offset); ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_daemon_", &enable_daemon_); ImGui::Checkbox("##enable_daemon_", &enable_daemon_);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::Text("%s", localization::takes_effect_after_restart
[localization_language_index_]
.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
} }
#if _WIN32 #if _WIN32
ImGui::Separator(); ImGui::Separator();