diff --git a/.dockerignore b/.dockerignore index 7eea563..73969b1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,13 +8,15 @@ # Dependency manifests and local xmake package recipes are the only project # files needed while pre-warming the build image. !xmake.lua -!xmake/ -!xmake/** -!thirdparty/ -!thirdparty/** -!submodules/ -!submodules/xmake.lua -!submodules/minirtc/ -!submodules/minirtc/xmake.lua -!submodules/minirtc/thirdparty/ -!submodules/minirtc/thirdparty/** +!apps/ +!apps/desktop/ +!apps/desktop/xmake/ +!apps/desktop/xmake/** +!deps/thirdparty/ +!deps/thirdparty/** +!deps/submodules/ +!deps/submodules/xmake.lua +!deps/submodules/minirtc/ +!deps/submodules/minirtc/xmake.lua +!deps/submodules/minirtc/thirdparty/ +!deps/submodules/minirtc/thirdparty/** diff --git a/.github/workflows/build-linux-image.yml b/.github/workflows/build-linux-image.yml index 26ead49..2c6518f 100644 --- a/.github/workflows/build-linux-image.yml +++ b/.github/workflows/build-linux-image.yml @@ -11,11 +11,11 @@ on: - docker/linux-build/** - xmake.lua - xmake/** - - thirdparty/** - - submodules/xmake.lua - - submodules/minirtc - - submodules/minirtc/xmake.lua - - submodules/minirtc/thirdparty/** + - deps/thirdparty/** + - deps/submodules/xmake.lua + - deps/submodules/minirtc + - deps/submodules/minirtc/xmake.lua + - deps/submodules/minirtc/thirdparty/** workflow_dispatch: permissions: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4898d7..7d79c41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,12 +36,12 @@ jobs: - arch: amd64 runner: ubuntu-24.04 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: ./apps/desktop/scripts/linux/pkg_amd64.sh - arch: arm64 runner: ubuntu-24.04-arm 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: ./apps/desktop/scripts/linux/pkg_arm64.sh container: image: ${{ matrix.image }} @@ -138,6 +138,15 @@ jobs: --cc=gcc-10 --cxx=g++-10 --root -y xmake b -vy --root crossdesk + - name: Verify repository and wire boundaries + env: + XMAKE_GLOBALDIR: /data + run: | + xmake b -vy --root repository_structure_test crossdesk_wire_test keyboard_state_wire_test + xmake run --root repository_structure_test + xmake run --root crossdesk_wire_test + xmake run --root keyboard_state_wire_test + - name: Package run: | chmod +x ${{ matrix.package_script }} @@ -178,13 +187,13 @@ jobs: runner: macos-26-intel cache-key: intel-macos26-xcode26.6-min14 out-dir: ./build/macosx/x86_64/release/crossdesk - package_script: ./scripts/macosx/pkg_x64.sh + package_script: ./apps/desktop/scripts/macos/pkg_x64.sh - arch: arm64 binary_arch: arm64 runner: macos-26 cache-key: arm-macos26-xcode26.6-min14 out-dir: ./build/macosx/arm64/release/crossdesk - package_script: ./scripts/macosx/pkg_arm64.sh + package_script: ./apps/desktop/scripts/macos/pkg_arm64.sh steps: - name: Extract version number @@ -282,7 +291,7 @@ jobs: exit 1 fi - ./scripts/macosx/verify_app.sh "${APP_PATH}" "${{ matrix.binary_arch }}" "${EXPECTED_MACOS_SDK}" + ./apps/desktop/scripts/macos/verify_app.sh "${APP_PATH}" "${{ matrix.binary_arch }}" "${EXPECTED_MACOS_SDK}" - name: Upload build artifacts uses: actions/upload-artifact@v6 @@ -364,7 +373,7 @@ jobs: - name: Cache xmake dependencies uses: actions/cache@v5 with: - path: ios/.xmake/packages + path: apps/ios/.xmake/packages key: "${{ runner.os }}-xmake${{ env.XMAKE_VERSION }}-deps-ios-arm64-xcode26.6-xrepo1211093-min16-${{ github.run_id }}" restore-keys: | ${{ runner.os }}-xmake${{ env.XMAKE_VERSION }}-deps-ios-arm64-xcode26.6-xrepo1211093-min16- @@ -404,7 +413,7 @@ jobs: DERIVED_DATA="${RUNNER_TEMP}/CrossDeskMobileDerivedData" echo "DERIVED_DATA=${DERIVED_DATA}" >> "${GITHUB_ENV}" xcodebuild \ - -project ios/CrossDeskMobile.xcodeproj \ + -project apps/ios/CrossDeskMobile.xcodeproj \ -scheme CrossDeskMobile \ -configuration Release \ -destination 'generic/platform=iOS' \ @@ -571,7 +580,7 @@ jobs: - name: Copy nsProcess plugin to NSIS folder run: | $nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode" - copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir + copy "${{ github.workspace }}\apps\desktop\scripts\windows\nsProcess.dll" $nsisPluginDir - name: Build CrossDesk run: | @@ -581,14 +590,14 @@ jobs: - name: Package shell: pwsh run: | - & "${{ github.workspace }}\scripts\windows\pkg_x64.ps1" $env:VERSION_NUM + & "${{ github.workspace }}\apps\desktop\scripts\windows\pkg_x64.ps1" $env:VERSION_NUM - name: Upload unsigned Windows installer for test signing if: github.event_name == 'workflow_dispatch' && inputs.sign_windows_test id: upload-unsigned-windows-installer uses: actions/upload-artifact@v7 with: - path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe + path: ${{ github.workspace }}/apps/desktop/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe archive: false if-no-files-found: error retention-days: 1 @@ -614,7 +623,7 @@ jobs: run: | $installerName = "crossdesk-win-x64-${{ env.VERSION_NUM }}.exe" $signedInstaller = Join-Path "${{ github.workspace }}\signpath\signed-installer" $installerName - $destination = Join-Path "${{ github.workspace }}\scripts\windows" $installerName + $destination = Join-Path "${{ github.workspace }}\apps\desktop\scripts\windows" $installerName if (!(Test-Path $signedInstaller)) { throw "SignPath did not return the signed installer: $signedInstaller" @@ -671,7 +680,7 @@ jobs: uses: actions/upload-artifact@v6 with: name: crossdesk-win-x64-${{ env.VERSION_NUM }} - path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe + path: ${{ github.workspace }}/apps/desktop/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe - name: Upload portable artifact uses: actions/upload-artifact@v6 diff --git a/.gitignore b/.gitignore index fd2b3af..aa3ce12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ # Xmake cache .xmake/ build/ -ios/Vendor/ -ios/DerivedData/ -ios/.xmake/ +apps/ios/Vendor/ +apps/ios/DerivedData*/ +apps/ios/.xmake/ xcuserdata/ *.xcuserstate diff --git a/.gitmodules b/.gitmodules index 9199d86..d337519 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "submodules/minirtc"] - path = submodules/minirtc +[submodule "deps/submodules/minirtc"] + path = deps/submodules/minirtc url = https://github.com/kunkundi/minirtc.git diff --git a/README.md b/README.md index b23e3a0..af582c0 100644 --- a/README.md +++ b/README.md @@ -171,10 +171,10 @@ xmake b -vy crossdesk ```bash # amd64 -./scripts/linux/pkg_amd64.sh 1.0.0 +./apps/desktop/scripts/linux/pkg_amd64.sh 1.0.0 # arm64 -./scripts/linux/pkg_arm64.sh 1.0.0 +./apps/desktop/scripts/linux/pkg_arm64.sh 1.0.0 ``` 打包脚本会将 Slint 共享运行库安装到软件包私有目录 `/usr/lib/crossdesk`,无需用户另外安装 `libslint_cpp.so`。 diff --git a/README_EN.md b/README_EN.md index a3ce71f..ab1415b 100644 --- a/README_EN.md +++ b/README_EN.md @@ -179,10 +179,10 @@ Build a Debian package after compiling a release binary on the matching architec ```bash # amd64 -./scripts/linux/pkg_amd64.sh 1.0.0 +./apps/desktop/scripts/linux/pkg_amd64.sh 1.0.0 # arm64 -./scripts/linux/pkg_arm64.sh 1.0.0 +./apps/desktop/scripts/linux/pkg_arm64.sh 1.0.0 ``` The package scripts install the shared Slint runtime in the private `/usr/lib/crossdesk` directory, so users do not need to install `libslint_cpp.so` separately. diff --git a/icons/linux/crossdesk_128x128.png b/apps/desktop/resources/linux/crossdesk_128x128.png similarity index 100% rename from icons/linux/crossdesk_128x128.png rename to apps/desktop/resources/linux/crossdesk_128x128.png diff --git a/icons/linux/crossdesk_16x16.png b/apps/desktop/resources/linux/crossdesk_16x16.png similarity index 100% rename from icons/linux/crossdesk_16x16.png rename to apps/desktop/resources/linux/crossdesk_16x16.png diff --git a/icons/linux/crossdesk_24x24.png b/apps/desktop/resources/linux/crossdesk_24x24.png similarity index 100% rename from icons/linux/crossdesk_24x24.png rename to apps/desktop/resources/linux/crossdesk_24x24.png diff --git a/icons/linux/crossdesk_256x256.png b/apps/desktop/resources/linux/crossdesk_256x256.png similarity index 100% rename from icons/linux/crossdesk_256x256.png rename to apps/desktop/resources/linux/crossdesk_256x256.png diff --git a/icons/linux/crossdesk_32x32.png b/apps/desktop/resources/linux/crossdesk_32x32.png similarity index 100% rename from icons/linux/crossdesk_32x32.png rename to apps/desktop/resources/linux/crossdesk_32x32.png diff --git a/icons/linux/crossdesk_48x48.png b/apps/desktop/resources/linux/crossdesk_48x48.png similarity index 100% rename from icons/linux/crossdesk_48x48.png rename to apps/desktop/resources/linux/crossdesk_48x48.png diff --git a/icons/linux/crossdesk_512x512.png b/apps/desktop/resources/linux/crossdesk_512x512.png similarity index 100% rename from icons/linux/crossdesk_512x512.png rename to apps/desktop/resources/linux/crossdesk_512x512.png diff --git a/icons/linux/crossdesk_64x64.png b/apps/desktop/resources/linux/crossdesk_64x64.png similarity index 100% rename from icons/linux/crossdesk_64x64.png rename to apps/desktop/resources/linux/crossdesk_64x64.png diff --git a/icons/linux/crossdesk_96x96.png b/apps/desktop/resources/linux/crossdesk_96x96.png similarity index 100% rename from icons/linux/crossdesk_96x96.png rename to apps/desktop/resources/linux/crossdesk_96x96.png diff --git a/icons/macos/crossdesk.icns b/apps/desktop/resources/macos/crossdesk.icns similarity index 100% rename from icons/macos/crossdesk.icns rename to apps/desktop/resources/macos/crossdesk.icns diff --git a/icons/windows/crossdesk.ico b/apps/desktop/resources/windows/crossdesk.ico similarity index 100% rename from icons/windows/crossdesk.ico rename to apps/desktop/resources/windows/crossdesk.ico diff --git a/scripts/windows/crossdesk.manifest b/apps/desktop/resources/windows/crossdesk.manifest similarity index 100% rename from scripts/windows/crossdesk.manifest rename to apps/desktop/resources/windows/crossdesk.manifest diff --git a/scripts/windows/crossdesk.rc b/apps/desktop/resources/windows/crossdesk.rc similarity index 89% rename from scripts/windows/crossdesk.rc rename to apps/desktop/resources/windows/crossdesk.rc index 7f07aee..ec57a17 100644 --- a/scripts/windows/crossdesk.rc +++ b/apps/desktop/resources/windows/crossdesk.rc @@ -1,5 +1,5 @@ // Application icon resource; load by the resource name IDI_ICON1. -IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico" +IDI_ICON1 ICON "crossdesk.ico" #define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 #define RT_MANIFEST 24 diff --git a/scripts/windows/crossdesk_debug.manifest b/apps/desktop/resources/windows/crossdesk_debug.manifest similarity index 100% rename from scripts/windows/crossdesk_debug.manifest rename to apps/desktop/resources/windows/crossdesk_debug.manifest diff --git a/scripts/windows/crossdesk_portable.manifest b/apps/desktop/resources/windows/crossdesk_portable.manifest similarity index 100% rename from scripts/windows/crossdesk_portable.manifest rename to apps/desktop/resources/windows/crossdesk_portable.manifest diff --git a/scripts/windows/crossdesk_portable.rc b/apps/desktop/resources/windows/crossdesk_portable.rc similarity index 88% rename from scripts/windows/crossdesk_portable.rc rename to apps/desktop/resources/windows/crossdesk_portable.rc index 1fe77f5..980ca3a 100644 --- a/scripts/windows/crossdesk_portable.rc +++ b/apps/desktop/resources/windows/crossdesk_portable.rc @@ -1,6 +1,6 @@ // Portable build resource. The app itself runs as the current user; service // installation raises a separate UAC prompt only when the user chooses it. -IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico" +IDI_ICON1 ICON "crossdesk.ico" #define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 #define RT_MANIFEST 24 diff --git a/scripts/windows/crossdesk_service.rc b/apps/desktop/resources/windows/crossdesk_service.rc similarity index 100% rename from scripts/windows/crossdesk_service.rc rename to apps/desktop/resources/windows/crossdesk_service.rc diff --git a/scripts/windows/crossdesk_session_helper.rc b/apps/desktop/resources/windows/crossdesk_session_helper.rc similarity index 91% rename from scripts/windows/crossdesk_session_helper.rc rename to apps/desktop/resources/windows/crossdesk_session_helper.rc index 524798b..f9a8412 100644 --- a/scripts/windows/crossdesk_session_helper.rc +++ b/apps/desktop/resources/windows/crossdesk_session_helper.rc @@ -1,5 +1,5 @@ // Session helper icon and manifest resources. -IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico" +IDI_ICON1 ICON "crossdesk.ico" #define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 #define RT_MANIFEST 24 diff --git a/scripts/windows/version_info.rcinc b/apps/desktop/resources/windows/version_info.rcinc similarity index 100% rename from scripts/windows/version_info.rcinc rename to apps/desktop/resources/windows/version_info.rcinc diff --git a/scripts/windows/wgc_plugin.rc b/apps/desktop/resources/windows/wgc_plugin.rc similarity index 100% rename from scripts/windows/wgc_plugin.rc rename to apps/desktop/resources/windows/wgc_plugin.rc diff --git a/scripts/linux/package_deb.sh b/apps/desktop/scripts/linux/package_deb.sh similarity index 97% rename from scripts/linux/package_deb.sh rename to apps/desktop/scripts/linux/package_deb.sh index ca5eefc..d5b9992 100755 --- a/scripts/linux/package_deb.sh +++ b/apps/desktop/scripts/linux/package_deb.sh @@ -20,7 +20,7 @@ PORTAL_RUNTIME_RECOMMENDS="xdg-desktop-portal, xdg-desktop-portal-gtk | xdg-desk TRAY_RUNTIME_RECOMMENDS="libayatana-appindicator3-1 | libappindicator3-1" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" normalize_app_version() { local input="$1" @@ -142,7 +142,7 @@ ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME" for size in 16 24 32 48 64 96 128 256; do install -d "$ICON_BASE_DIR/${size}x${size}/apps" - install -m 0644 "$PROJECT_ROOT/icons/linux/crossdesk_${size}x${size}.png" \ + install -m 0644 "$PROJECT_ROOT/apps/desktop/resources/linux/crossdesk_${size}x${size}.png" \ "$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png" done diff --git a/scripts/linux/pkg_amd64.sh b/apps/desktop/scripts/linux/pkg_amd64.sh similarity index 100% rename from scripts/linux/pkg_amd64.sh rename to apps/desktop/scripts/linux/pkg_amd64.sh diff --git a/scripts/linux/pkg_arm64.sh b/apps/desktop/scripts/linux/pkg_arm64.sh similarity index 100% rename from scripts/linux/pkg_arm64.sh rename to apps/desktop/scripts/linux/pkg_arm64.sh diff --git a/scripts/macosx/bundle_app.sh b/apps/desktop/scripts/macos/bundle_app.sh similarity index 100% rename from scripts/macosx/bundle_app.sh rename to apps/desktop/scripts/macos/bundle_app.sh diff --git a/scripts/macosx/pkg_arm64.sh b/apps/desktop/scripts/macos/pkg_arm64.sh similarity index 98% rename from scripts/macosx/pkg_arm64.sh rename to apps/desktop/scripts/macos/pkg_arm64.sh index a4d3c23..6d018f6 100755 --- a/scripts/macosx/pkg_arm64.sh +++ b/apps/desktop/scripts/macos/pkg_arm64.sh @@ -2,7 +2,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" APP_NAME="crossdesk" APP_NAME_UPPER="CrossDesk" EXECUTABLE_PATH="${PROJECT_ROOT}/build/macosx/arm64/release/crossdesk" @@ -10,7 +10,7 @@ PLATFORM="macos" ARCH="arm64" BINARY_ARCH="arm64" IDENTIFIER="cn.crossdesk.app" -ICON_PATH="${PROJECT_ROOT}/icons/macos/crossdesk.icns" +ICON_PATH="${PROJECT_ROOT}/apps/desktop/resources/macos/crossdesk.icns" MACOS_MIN_VERSION="14.0" cd "$PROJECT_ROOT" diff --git a/scripts/macosx/pkg_x64.sh b/apps/desktop/scripts/macos/pkg_x64.sh similarity index 98% rename from scripts/macosx/pkg_x64.sh rename to apps/desktop/scripts/macos/pkg_x64.sh index 3f1ce79..313f41a 100755 --- a/scripts/macosx/pkg_x64.sh +++ b/apps/desktop/scripts/macos/pkg_x64.sh @@ -2,7 +2,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" APP_NAME="crossdesk" APP_NAME_UPPER="CrossDesk" EXECUTABLE_PATH="${PROJECT_ROOT}/build/macosx/x86_64/release/crossdesk" @@ -10,7 +10,7 @@ PLATFORM="macos" ARCH="x64" BINARY_ARCH="x86_64" IDENTIFIER="cn.crossdesk.app" -ICON_PATH="${PROJECT_ROOT}/icons/macos/crossdesk.icns" +ICON_PATH="${PROJECT_ROOT}/apps/desktop/resources/macos/crossdesk.icns" MACOS_MIN_VERSION="14.0" cd "$PROJECT_ROOT" diff --git a/scripts/macosx/tcc_postinstall.sh b/apps/desktop/scripts/macos/tcc_postinstall.sh similarity index 100% rename from scripts/macosx/tcc_postinstall.sh rename to apps/desktop/scripts/macos/tcc_postinstall.sh diff --git a/scripts/macosx/verify_app.sh b/apps/desktop/scripts/macos/verify_app.sh similarity index 100% rename from scripts/macosx/verify_app.sh rename to apps/desktop/scripts/macos/verify_app.sh diff --git a/scripts/windows/nsProcess.dll b/apps/desktop/scripts/windows/nsProcess.dll similarity index 100% rename from scripts/windows/nsProcess.dll rename to apps/desktop/scripts/windows/nsProcess.dll diff --git a/scripts/windows/nsis_script.nsi b/apps/desktop/scripts/windows/nsis_script.nsi similarity index 95% rename from scripts/windows/nsis_script.nsi rename to apps/desktop/scripts/windows/nsis_script.nsi index ad8af8f..16b1bd9 100644 --- a/scripts/windows/nsis_script.nsi +++ b/apps/desktop/scripts/windows/nsis_script.nsi @@ -12,7 +12,7 @@ !define PRODUCT_SERVICE_NAME "CrossDeskService" ; Installer icon path -!define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico" +!define MUI_ICON "${__FILEDIR__}\..\..\resources\windows\crossdesk.ico" ; Compression settings SetCompressor /FINAL lzma @@ -84,12 +84,12 @@ installApp: SetOverwrite ifnewer ; Main application executable path - File /oname=CrossDesk.exe "..\..\build\windows\x64\release\crossdesk.exe" + File /oname=CrossDesk.exe "..\..\..\..\build\windows\x64\release\crossdesk.exe" ; Bundle service-side binaries required by the Windows service flow - File "..\..\build\windows\x64\release\crossdesk_service.exe" - File "..\..\build\windows\x64\release\crossdesk_session_helper.exe" + File "..\..\..\..\build\windows\x64\release\crossdesk_service.exe" + File "..\..\..\..\build\windows\x64\release\crossdesk_session_helper.exe" ; Bundle runtime DLLs from the release output directory - File "..\..\build\windows\x64\release\*.dll" + File "..\..\..\..\build\windows\x64\release\*.dll" Call RegisterInstalledService diff --git a/scripts/windows/pkg_x64.ps1 b/apps/desktop/scripts/windows/pkg_x64.ps1 similarity index 100% rename from scripts/windows/pkg_x64.ps1 rename to apps/desktop/scripts/windows/pkg_x64.ps1 diff --git a/scripts/windows/signpath-installer.xml b/apps/desktop/scripts/windows/signpath-installer.xml similarity index 100% rename from scripts/windows/signpath-installer.xml rename to apps/desktop/scripts/windows/signpath-installer.xml diff --git a/apps/desktop/src/app/daemon.cpp b/apps/desktop/src/app/daemon.cpp new file mode 100644 index 0000000..7e3474a --- /dev/null +++ b/apps/desktop/src/app/daemon.cpp @@ -0,0 +1,76 @@ +#include "daemon.h" + +#include +#include +#include +#include + +#include "platform/daemon_backend.h" + +namespace { + +constexpr int kRestartDelayMs = 1000; + +} // namespace + +Daemon::Daemon(const std::string& name) : name_(name), running_(false) {} + +void Daemon::stop() { + running_.store(false); + crossdesk::platform::RequestDaemonStop(); +} + +bool Daemon::isRunning() const { + return running_.load() && !crossdesk::platform::DaemonStopRequested(); +} + +bool Daemon::start(MainLoopFunc loop) { + crossdesk::platform::ResetDaemonStop(); + if (!crossdesk::platform::PrepareDaemon()) return false; + running_.store(true); + return runWithRestart(std::move(loop)); +} + +bool Daemon::runWithRestart(MainLoopFunc loop) { + int restart_count = 0; + const std::string executable_path = + crossdesk::platform::GetDaemonExecutablePath(); + if (executable_path.empty()) { + std::cerr + << "Failed to get executable path, falling back to direct execution" + << std::endl; + while (isRunning()) { + try { + loop(); + break; + } catch (...) { + ++restart_count; + std::cerr << "Exception caught, restarting... (attempt " + << restart_count << ")" << std::endl; + std::this_thread::sleep_for( + std::chrono::milliseconds(kRestartDelayMs)); + } + } + return true; + } + + while (isRunning()) { + const crossdesk::platform::DaemonChildResult result = + crossdesk::platform::RunDaemonChild( + executable_path, [this] { return isRunning(); }); + if (!isRunning() || result.normal_exit) break; + + ++restart_count; + if (!result.started) { + std::cerr << "Failed to create child process"; + } else if (result.terminated_by_signal) { + std::cerr << "Child process crashed with signal " << result.exit_code; + } else { + std::cerr << "Child process exited with code " << result.exit_code; + } + std::cerr << ", restarting... (attempt " << restart_count << ")" + << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); + } + return true; +} diff --git a/src/app/daemon.h b/apps/desktop/src/app/daemon.h similarity index 84% rename from src/app/daemon.h rename to apps/desktop/src/app/daemon.h index f1311c5..45715bc 100644 --- a/src/app/daemon.h +++ b/apps/desktop/src/app/daemon.h @@ -8,7 +8,6 @@ #define _DAEMON_H_ #include -#include #include #include @@ -28,9 +27,6 @@ class Daemon { std::string name_; bool runWithRestart(MainLoopFunc loop); -#ifndef _WIN32 - static volatile std::sig_atomic_t stop_requested_; -#endif std::atomic running_; }; diff --git a/src/app/main.cpp b/apps/desktop/src/app/main.cpp similarity index 100% rename from src/app/main.cpp rename to apps/desktop/src/app/main.cpp diff --git a/apps/desktop/src/autostart/autostart.cpp b/apps/desktop/src/autostart/autostart.cpp new file mode 100644 index 0000000..44963c4 --- /dev/null +++ b/apps/desktop/src/autostart/autostart.cpp @@ -0,0 +1,21 @@ +#include "autostart.h" + +#include "platform/autostart_backend.h" + +namespace crossdesk { + +bool EnableAutostart(const std::string& app_name) { + const std::string executable_path = platform::GetAutostartExecutablePath(); + return !executable_path.empty() && + platform::EnableAutostart(app_name, executable_path); +} + +bool DisableAutostart(const std::string& app_name) { + return platform::DisableAutostart(app_name); +} + +bool IsAutostartEnabled(const std::string& app_name) { + return platform::IsAutostartEnabled(app_name); +} + +} // namespace crossdesk diff --git a/src/autostart/autostart.h b/apps/desktop/src/autostart/autostart.h similarity index 100% rename from src/autostart/autostart.h rename to apps/desktop/src/autostart/autostart.h diff --git a/src/common/display_info.h b/apps/desktop/src/common/display_info.h similarity index 100% rename from src/common/display_info.h rename to apps/desktop/src/common/display_info.h diff --git a/src/common/filesystem_utf8.h b/apps/desktop/src/common/filesystem_utf8.h similarity index 100% rename from src/common/filesystem_utf8.h rename to apps/desktop/src/common/filesystem_utf8.h diff --git a/src/common/nv12_scaler.cpp b/apps/desktop/src/common/nv12_scaler.cpp similarity index 100% rename from src/common/nv12_scaler.cpp rename to apps/desktop/src/common/nv12_scaler.cpp diff --git a/src/common/nv12_scaler.h b/apps/desktop/src/common/nv12_scaler.h similarity index 100% rename from src/common/nv12_scaler.h rename to apps/desktop/src/common/nv12_scaler.h diff --git a/src/common/platform.h b/apps/desktop/src/common/platform.h similarity index 93% rename from src/common/platform.h rename to apps/desktop/src/common/platform.h index dee6cc0..5658dc0 100644 --- a/src/common/platform.h +++ b/apps/desktop/src/common/platform.h @@ -7,7 +7,7 @@ #ifndef _PLATFORM_H_ #define _PLATFORM_H_ -#include +#include namespace crossdesk { diff --git a/src/common/rounded_corner_button.cpp b/apps/desktop/src/common/rounded_corner_button.cpp similarity index 100% rename from src/common/rounded_corner_button.cpp rename to apps/desktop/src/common/rounded_corner_button.cpp diff --git a/src/common/rounded_corner_button.h b/apps/desktop/src/common/rounded_corner_button.h similarity index 100% rename from src/common/rounded_corner_button.h rename to apps/desktop/src/common/rounded_corner_button.h diff --git a/src/common/shared_cursor_state.cpp b/apps/desktop/src/common/shared_cursor_state.cpp similarity index 97% rename from src/common/shared_cursor_state.cpp rename to apps/desktop/src/common/shared_cursor_state.cpp index 87b1229..9c742d2 100644 --- a/src/common/shared_cursor_state.cpp +++ b/apps/desktop/src/common/shared_cursor_state.cpp @@ -1,5 +1,7 @@ #include "shared_cursor_state.h" +#include + #include namespace crossdesk { diff --git a/src/common/shared_cursor_state.h b/apps/desktop/src/common/shared_cursor_state.h similarity index 94% rename from src/common/shared_cursor_state.h rename to apps/desktop/src/common/shared_cursor_state.h index a88cffe..98877b5 100644 --- a/src/common/shared_cursor_state.h +++ b/apps/desktop/src/common/shared_cursor_state.h @@ -3,7 +3,7 @@ #include -#include "remote_cursor_shape.h" +#include namespace crossdesk { diff --git a/src/config_center/SimpleIni.h b/apps/desktop/src/config_center/SimpleIni.h similarity index 100% rename from src/config_center/SimpleIni.h rename to apps/desktop/src/config_center/SimpleIni.h diff --git a/src/config_center/config_center.cpp b/apps/desktop/src/config_center/config_center.cpp similarity index 100% rename from src/config_center/config_center.cpp rename to apps/desktop/src/config_center/config_center.cpp diff --git a/src/config_center/config_center.h b/apps/desktop/src/config_center/config_center.h similarity index 100% rename from src/config_center/config_center.h rename to apps/desktop/src/config_center/config_center.h diff --git a/apps/desktop/src/device_controller/device_controller.h b/apps/desktop/src/device_controller/device_controller.h new file mode 100644 index 0000000..d562f21 --- /dev/null +++ b/apps/desktop/src/device_controller/device_controller.h @@ -0,0 +1,52 @@ +/* + * @Author: DI JUNKUN + * @Date: 2023-12-14 + * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _DEVICE_CONTROLLER_H_ +#define _DEVICE_CONTROLLER_H_ + +#include +#include + +#include + +#include "display_info.h" + +namespace crossdesk { + +// int key_code, bool is_down, uint32_t scan_code, bool extended +using OnKeyAction = void (*)(int, bool, uint32_t, bool, void*); + +class DeviceController { + public: + virtual ~DeviceController() = default; +}; + +class MouseController : public DeviceController { + public: + ~MouseController() override = default; + + virtual int Init(std::vector display_info_list) = 0; + virtual int Destroy() = 0; + virtual int SendMouseCommand(RemoteAction remote_action, + int display_index) = 0; + virtual void UpdateDisplayInfoList( + const std::vector&) {} +}; + +class KeyboardCapturer : public DeviceController { + public: + ~KeyboardCapturer() override = default; + + virtual int Hook(OnKeyAction on_key_action, void* user_ptr) = 0; + virtual int Unhook() = 0; + virtual int SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code = 0, + bool extended = false) = 0; +}; + +} // namespace crossdesk + +#endif diff --git a/src/device_controller/device_controller_factory.h b/apps/desktop/src/device_controller/device_controller_factory.h similarity index 57% rename from src/device_controller/device_controller_factory.h rename to apps/desktop/src/device_controller/device_controller_factory.h index d4ef51a..8fbf15f 100644 --- a/src/device_controller/device_controller_factory.h +++ b/apps/desktop/src/device_controller/device_controller_factory.h @@ -8,8 +8,6 @@ #define _DEVICE_CONTROLLER_FACTORY_H_ #include "device_controller.h" -#include "keyboard_capturer.h" -#include "mouse_controller.h" namespace crossdesk { @@ -21,16 +19,7 @@ class DeviceControllerFactory { virtual ~DeviceControllerFactory() {} public: - DeviceController* Create(Device device) { - switch (device) { - case Mouse: - return new MouseController(); - case Keyboard: - return new KeyboardCapturer(); - default: - return nullptr; - } - } + DeviceController* Create(Device device); }; } // namespace crossdesk -#endif \ No newline at end of file +#endif diff --git a/src/device_controller/keyboard_converter.h b/apps/desktop/src/device_controller/keyboard_converter.h similarity index 100% rename from src/device_controller/keyboard_converter.h rename to apps/desktop/src/device_controller/keyboard_converter.h diff --git a/src/gui/application/application_state.h b/apps/desktop/src/gui/application/application_state.h similarity index 100% rename from src/gui/application/application_state.h rename to apps/desktop/src/gui/application/application_state.h diff --git a/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp similarity index 99% rename from src/gui/application/gui_application.cpp rename to apps/desktop/src/gui/application/gui_application.cpp index 5334556..fc3764f 100644 --- a/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -1,5 +1,7 @@ #include "application/gui_application.h" +#include + #include #include #include @@ -35,9 +37,9 @@ #if _WIN32 #include -#include "platform/tray/win_tray.h" +#include "platform/windows/gui/tray/win_tray.h" #elif defined(__APPLE__) -#include "platform/tray/mac_tray.h" +#include "platform/macos/gui/tray/mac_tray.h" #include "platform/window_drag.h" #elif defined(__linux__) #include @@ -45,7 +47,7 @@ #include #include -#include "platform/tray/linux_tray.h" +#include "platform/linux/gui/tray/linux_tray.h" #endif #include "rd_log.h" #include "server_window_state.h" diff --git a/src/gui/application/gui_application.h b/apps/desktop/src/gui/application/gui_application.h similarity index 99% rename from src/gui/application/gui_application.h rename to apps/desktop/src/gui/application/gui_application.h index 4e46427..392d1f0 100644 --- a/src/gui/application/gui_application.h +++ b/apps/desktop/src/gui/application/gui_application.h @@ -6,6 +6,8 @@ #include #include +#include + #include "runtime/cursor_state_provider.h" #include "runtime/gui_runtime.h" diff --git a/src/gui/application/sdl_event_dispatch.cpp b/apps/desktop/src/gui/application/sdl_event_dispatch.cpp similarity index 100% rename from src/gui/application/sdl_event_dispatch.cpp rename to apps/desktop/src/gui/application/sdl_event_dispatch.cpp diff --git a/src/gui/application/sdl_events.cpp b/apps/desktop/src/gui/application/sdl_events.cpp similarity index 99% rename from src/gui/application/sdl_events.cpp rename to apps/desktop/src/gui/application/sdl_events.cpp index 3fb6fd9..604bb6a 100644 --- a/src/gui/application/sdl_events.cpp +++ b/apps/desktop/src/gui/application/sdl_events.cpp @@ -1,5 +1,7 @@ #include "application/gui_application.h" +#include + #include #include #include diff --git a/src/gui/application/server_window_state.h b/apps/desktop/src/gui/application/server_window_state.h similarity index 100% rename from src/gui/application/server_window_state.h rename to apps/desktop/src/gui/application/server_window_state.h diff --git a/src/gui/application/window_geometry.h b/apps/desktop/src/gui/application/window_geometry.h similarity index 100% rename from src/gui/application/window_geometry.h rename to apps/desktop/src/gui/application/window_geometry.h diff --git a/src/gui/application/window_lifecycle.cpp b/apps/desktop/src/gui/application/window_lifecycle.cpp similarity index 100% rename from src/gui/application/window_lifecycle.cpp rename to apps/desktop/src/gui/application/window_lifecycle.cpp diff --git a/src/gui/application/window_rendering.cpp b/apps/desktop/src/gui/application/window_rendering.cpp similarity index 100% rename from src/gui/application/window_rendering.cpp rename to apps/desktop/src/gui/application/window_rendering.cpp diff --git a/src/gui/assets/fonts/OPPOSans_Regular.h b/apps/desktop/src/gui/assets/fonts/OPPOSans_Regular.h similarity index 100% rename from src/gui/assets/fonts/OPPOSans_Regular.h rename to apps/desktop/src/gui/assets/fonts/OPPOSans_Regular.h diff --git a/src/gui/assets/fonts/fa_regular_400.h b/apps/desktop/src/gui/assets/fonts/fa_regular_400.h similarity index 100% rename from src/gui/assets/fonts/fa_regular_400.h rename to apps/desktop/src/gui/assets/fonts/fa_regular_400.h diff --git a/src/gui/assets/fonts/fa_solid_900.h b/apps/desktop/src/gui/assets/fonts/fa_solid_900.h similarity index 100% rename from src/gui/assets/fonts/fa_solid_900.h rename to apps/desktop/src/gui/assets/fonts/fa_solid_900.h diff --git a/src/gui/assets/icons/IconsFontAwesome6.h b/apps/desktop/src/gui/assets/icons/IconsFontAwesome6.h similarity index 100% rename from src/gui/assets/icons/IconsFontAwesome6.h rename to apps/desktop/src/gui/assets/icons/IconsFontAwesome6.h diff --git a/src/gui/assets/layouts/layout.h b/apps/desktop/src/gui/assets/layouts/layout.h similarity index 100% rename from src/gui/assets/layouts/layout.h rename to apps/desktop/src/gui/assets/layouts/layout.h diff --git a/src/gui/assets/layouts/layout_relative.h b/apps/desktop/src/gui/assets/layouts/layout_relative.h similarity index 100% rename from src/gui/assets/layouts/layout_relative.h rename to apps/desktop/src/gui/assets/layouts/layout_relative.h diff --git a/src/gui/assets/localization/localization.h b/apps/desktop/src/gui/assets/localization/localization.h similarity index 100% rename from src/gui/assets/localization/localization.h rename to apps/desktop/src/gui/assets/localization/localization.h diff --git a/src/gui/assets/localization/localization_data.h b/apps/desktop/src/gui/assets/localization/localization_data.h similarity index 100% rename from src/gui/assets/localization/localization_data.h rename to apps/desktop/src/gui/assets/localization/localization_data.h diff --git a/src/gui/features/clipboard/clipboard_controller.cpp b/apps/desktop/src/gui/features/clipboard/clipboard_controller.cpp similarity index 100% rename from src/gui/features/clipboard/clipboard_controller.cpp rename to apps/desktop/src/gui/features/clipboard/clipboard_controller.cpp diff --git a/src/gui/features/clipboard/clipboard_controller.h b/apps/desktop/src/gui/features/clipboard/clipboard_controller.h similarity index 100% rename from src/gui/features/clipboard/clipboard_controller.h rename to apps/desktop/src/gui/features/clipboard/clipboard_controller.h diff --git a/src/gui/features/devices/session_device_manager.cpp b/apps/desktop/src/gui/features/devices/session_device_manager.cpp similarity index 99% rename from src/gui/features/devices/session_device_manager.cpp rename to apps/desktop/src/gui/features/devices/session_device_manager.cpp index b056fc2..9125719 100644 --- a/src/gui/features/devices/session_device_manager.cpp +++ b/apps/desktop/src/gui/features/devices/session_device_manager.cpp @@ -1,9 +1,11 @@ #include "features/devices/session_device_manager.h" +#include + #include #include -#include "display_stream_id.h" +#include #include "platform.h" #include "rd_log.h" #include "runtime/gui_runtime.h" diff --git a/src/gui/features/devices/session_device_manager.h b/apps/desktop/src/gui/features/devices/session_device_manager.h similarity index 99% rename from src/gui/features/devices/session_device_manager.h rename to apps/desktop/src/gui/features/devices/session_device_manager.h index 293a65e..9b139df 100644 --- a/src/gui/features/devices/session_device_manager.h +++ b/apps/desktop/src/gui/features/devices/session_device_manager.h @@ -11,6 +11,8 @@ #include #include +#include + #include "device_controller.h" #include "device_controller_factory.h" #include "display_info.h" diff --git a/src/gui/features/file_transfer/file_transfer_manager.cpp b/apps/desktop/src/gui/features/file_transfer/file_transfer_manager.cpp similarity index 99% rename from src/gui/features/file_transfer/file_transfer_manager.cpp rename to apps/desktop/src/gui/features/file_transfer/file_transfer_manager.cpp index 39a4a37..3d8d6ce 100644 --- a/src/gui/features/file_transfer/file_transfer_manager.cpp +++ b/apps/desktop/src/gui/features/file_transfer/file_transfer_manager.cpp @@ -236,7 +236,7 @@ void FileTransferManager::Unregister(uint32_t file_id, bool per_peer) { void FileTransferManager::HandleAck(const char *data, size_t size) { FileTransferAck ack{}; - if (!protocol::DecodeFileTransferAck(data, size, &ack)) { + if (!DecodeFileTransferAck(data, size, &ack)) { LOG_ERROR("FileTransferAck: invalid payload, size={}", size); return; } diff --git a/src/gui/features/file_transfer/file_transfer_manager.h b/apps/desktop/src/gui/features/file_transfer/file_transfer_manager.h similarity index 100% rename from src/gui/features/file_transfer/file_transfer_manager.h rename to apps/desktop/src/gui/features/file_transfer/file_transfer_manager.h diff --git a/src/gui/features/input/keyboard_controller.cpp b/apps/desktop/src/gui/features/input/keyboard_controller.cpp similarity index 99% rename from src/gui/features/input/keyboard_controller.cpp rename to apps/desktop/src/gui/features/input/keyboard_controller.cpp index adac4a1..0e5548c 100644 --- a/src/gui/features/input/keyboard_controller.cpp +++ b/apps/desktop/src/gui/features/input/keyboard_controller.cpp @@ -1,5 +1,7 @@ #include "features/input/keyboard_controller.h" +#include + #include #include diff --git a/src/gui/features/input/keyboard_controller.h b/apps/desktop/src/gui/features/input/keyboard_controller.h similarity index 98% rename from src/gui/features/input/keyboard_controller.h rename to apps/desktop/src/gui/features/input/keyboard_controller.h index 56a0c68..672e827 100644 --- a/src/gui/features/input/keyboard_controller.h +++ b/apps/desktop/src/gui/features/input/keyboard_controller.h @@ -6,6 +6,8 @@ #include #include +#include + #include "device_controller.h" namespace crossdesk { diff --git a/src/gui/features/settings/settings_manager.cpp b/apps/desktop/src/gui/features/settings/settings_manager.cpp similarity index 100% rename from src/gui/features/settings/settings_manager.cpp rename to apps/desktop/src/gui/features/settings/settings_manager.cpp diff --git a/src/gui/features/settings/settings_manager.h b/apps/desktop/src/gui/features/settings/settings_manager.h similarity index 100% rename from src/gui/features/settings/settings_manager.h rename to apps/desktop/src/gui/features/settings/settings_manager.h diff --git a/src/gui/render.cpp b/apps/desktop/src/gui/render.cpp similarity index 100% rename from src/gui/render.cpp rename to apps/desktop/src/gui/render.cpp diff --git a/src/gui/render.h b/apps/desktop/src/gui/render.h similarity index 100% rename from src/gui/render.h rename to apps/desktop/src/gui/render.h diff --git a/src/gui/rendering/slint_video_presenter.cpp b/apps/desktop/src/gui/rendering/slint_video_presenter.cpp similarity index 99% rename from src/gui/rendering/slint_video_presenter.cpp rename to apps/desktop/src/gui/rendering/slint_video_presenter.cpp index c2e83bb..684b14d 100644 --- a/src/gui/rendering/slint_video_presenter.cpp +++ b/apps/desktop/src/gui/rendering/slint_video_presenter.cpp @@ -14,14 +14,14 @@ #include "rd_log.h" #if defined(__APPLE__) -#include "platform/metal_video_renderer.h" +#include "platform/macos/gui/metal_video_renderer.h" #else #if defined(_WIN32) #include #endif #include -#include "platform/opengl_video_renderer.h" +#include "platform/common/gui/opengl_video_renderer.h" #endif namespace crossdesk { diff --git a/src/gui/rendering/slint_video_presenter.h b/apps/desktop/src/gui/rendering/slint_video_presenter.h similarity index 100% rename from src/gui/rendering/slint_video_presenter.h rename to apps/desktop/src/gui/rendering/slint_video_presenter.h diff --git a/src/gui/runtime/connection_runtime.cpp b/apps/desktop/src/gui/runtime/connection_runtime.cpp similarity index 99% rename from src/gui/runtime/connection_runtime.cpp rename to apps/desktop/src/gui/runtime/connection_runtime.cpp index 9bf0277..9f043d1 100644 --- a/src/gui/runtime/connection_runtime.cpp +++ b/apps/desktop/src/gui/runtime/connection_runtime.cpp @@ -8,7 +8,7 @@ #include #include -#include "display_stream_id.h" +#include #include "localization.h" #include "platform.h" #include "platform/video_renderer.h" diff --git a/src/gui/runtime/cursor_position.h b/apps/desktop/src/gui/runtime/cursor_position.h similarity index 98% rename from src/gui/runtime/cursor_position.h rename to apps/desktop/src/gui/runtime/cursor_position.h index 3b21a98..a1939e9 100644 --- a/src/gui/runtime/cursor_position.h +++ b/apps/desktop/src/gui/runtime/cursor_position.h @@ -4,7 +4,8 @@ #include #include -#include "device_controller.h" +#include + #include "display_info.h" namespace crossdesk { diff --git a/src/gui/runtime/cursor_state_provider.cpp b/apps/desktop/src/gui/runtime/cursor_state_provider.cpp similarity index 99% rename from src/gui/runtime/cursor_state_provider.cpp rename to apps/desktop/src/gui/runtime/cursor_state_provider.cpp index 3f2372e..e158f48 100644 --- a/src/gui/runtime/cursor_state_provider.cpp +++ b/apps/desktop/src/gui/runtime/cursor_state_provider.cpp @@ -1,5 +1,7 @@ #include "runtime/cursor_state_provider.h" +#include + #if defined(_WIN32) #include diff --git a/src/gui/runtime/cursor_state_provider.h b/apps/desktop/src/gui/runtime/cursor_state_provider.h similarity index 95% rename from src/gui/runtime/cursor_state_provider.h rename to apps/desktop/src/gui/runtime/cursor_state_provider.h index b463730..84f1d09 100644 --- a/src/gui/runtime/cursor_state_provider.h +++ b/apps/desktop/src/gui/runtime/cursor_state_provider.h @@ -4,7 +4,8 @@ #include #include -#include "device_controller.h" +#include + #include "display_info.h" namespace crossdesk { diff --git a/src/gui/runtime/device_presence_cache.h b/apps/desktop/src/gui/runtime/device_presence_cache.h similarity index 100% rename from src/gui/runtime/device_presence_cache.h rename to apps/desktop/src/gui/runtime/device_presence_cache.h diff --git a/src/gui/runtime/gui_runtime.cpp b/apps/desktop/src/gui/runtime/gui_runtime.cpp similarity index 99% rename from src/gui/runtime/gui_runtime.cpp rename to apps/desktop/src/gui/runtime/gui_runtime.cpp index bb14a4f..c67276e 100644 --- a/src/gui/runtime/gui_runtime.cpp +++ b/apps/desktop/src/gui/runtime/gui_runtime.cpp @@ -10,7 +10,7 @@ #include #include -#include "display_stream_id.h" +#include #include "localization.h" #include "platform/video_renderer.h" #include "rd_log.h" diff --git a/src/gui/runtime/gui_runtime.h b/apps/desktop/src/gui/runtime/gui_runtime.h similarity index 100% rename from src/gui/runtime/gui_runtime.h rename to apps/desktop/src/gui/runtime/gui_runtime.h diff --git a/src/gui/runtime/gui_state.h b/apps/desktop/src/gui/runtime/gui_state.h similarity index 100% rename from src/gui/runtime/gui_state.h rename to apps/desktop/src/gui/runtime/gui_state.h diff --git a/src/gui/runtime/peer_data_callbacks.cpp b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp similarity index 99% rename from src/gui/runtime/peer_data_callbacks.cpp rename to apps/desktop/src/gui/runtime/peer_data_callbacks.cpp index 9c72c14..d90ab2c 100644 --- a/src/gui/runtime/peer_data_callbacks.cpp +++ b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp @@ -1,5 +1,7 @@ #include "runtime/peer_event_handler.h" +#include + #include #include #include @@ -97,7 +99,7 @@ void PeerEventHandler::OnReceiveDataBuffer( } receiver.SetOnSendAck([runtime, remote_user_id](const FileTransferAck &ack) -> int { - const auto encoded_ack = protocol::EncodeFileTransferAck(ack); + const auto encoded_ack = EncodeFileTransferAck(ack); bool is_server_sending = remote_user_id.rfind("C-", 0) != 0; if (is_server_sending) { auto props = diff --git a/src/gui/runtime/peer_event_handler.cpp b/apps/desktop/src/gui/runtime/peer_event_handler.cpp similarity index 99% rename from src/gui/runtime/peer_event_handler.cpp rename to apps/desktop/src/gui/runtime/peer_event_handler.cpp index 88be8de..4b01c35 100644 --- a/src/gui/runtime/peer_event_handler.cpp +++ b/apps/desktop/src/gui/runtime/peer_event_handler.cpp @@ -1,5 +1,7 @@ #include "runtime/peer_event_handler.h" +#include + #include #include #include diff --git a/src/gui/runtime/peer_event_handler.h b/apps/desktop/src/gui/runtime/peer_event_handler.h similarity index 100% rename from src/gui/runtime/peer_event_handler.h rename to apps/desktop/src/gui/runtime/peer_event_handler.h diff --git a/src/gui/runtime/peer_media_callbacks.cpp b/apps/desktop/src/gui/runtime/peer_media_callbacks.cpp similarity index 100% rename from src/gui/runtime/peer_media_callbacks.cpp rename to apps/desktop/src/gui/runtime/peer_media_callbacks.cpp diff --git a/src/gui/runtime/remote_action_codec.cpp b/apps/desktop/src/gui/runtime/remote_action_codec.cpp similarity index 99% rename from src/gui/runtime/remote_action_codec.cpp rename to apps/desktop/src/gui/runtime/remote_action_codec.cpp index 1f70ef9..e9dea98 100644 --- a/src/gui/runtime/remote_action_codec.cpp +++ b/apps/desktop/src/gui/runtime/remote_action_codec.cpp @@ -1,5 +1,7 @@ #include "runtime/remote_action_codec.h" +#include + #include #include diff --git a/src/gui/runtime/remote_action_codec.h b/apps/desktop/src/gui/runtime/remote_action_codec.h similarity index 93% rename from src/gui/runtime/remote_action_codec.h rename to apps/desktop/src/gui/runtime/remote_action_codec.h index 62a6094..76598f0 100644 --- a/src/gui/runtime/remote_action_codec.h +++ b/apps/desktop/src/gui/runtime/remote_action_codec.h @@ -4,7 +4,7 @@ #include #include -#include "device_controller.h" +#include namespace crossdesk::remote_action_codec { diff --git a/src/gui/runtime/remote_session.h b/apps/desktop/src/gui/runtime/remote_session.h similarity index 92% rename from src/gui/runtime/remote_session.h rename to apps/desktop/src/gui/runtime/remote_session.h index f86c094..a714989 100644 --- a/src/gui/runtime/remote_session.h +++ b/apps/desktop/src/gui/runtime/remote_session.h @@ -13,8 +13,8 @@ #include #include -#include "stream_names.h" -#include "device_controller.h" +#include +#include #include "display_info.h" #include "minirtc.h" @@ -61,14 +61,14 @@ struct FileTransferState { struct RemoteSession { Params params_; PeerPtr* peer_ = nullptr; - std::string audio_label_ = protocol::kAudioStream; - std::string data_label_ = protocol::kDataStream; - std::string mouse_label_ = protocol::kMouseStream; - std::string keyboard_label_ = protocol::kKeyboardStream; - std::string file_label_ = protocol::kFileStream; - std::string control_data_label_ = protocol::kControlStream; - std::string file_feedback_label_ = protocol::kFileFeedbackStream; - std::string clipboard_label_ = protocol::kClipboardStream; + std::string audio_label_ = kAudioStream; + std::string data_label_ = kDataStream; + std::string mouse_label_ = kMouseStream; + std::string keyboard_label_ = kKeyboardStream; + std::string file_label_ = kFileStream; + std::string control_data_label_ = kControlStream; + std::string file_feedback_label_ = kFileFeedbackStream; + std::string clipboard_label_ = kClipboardStream; std::string local_id_; std::string remote_id_; bool exit_ = false; diff --git a/src/gui/runtime/runtime_state.h b/apps/desktop/src/gui/runtime/runtime_state.h similarity index 93% rename from src/gui/runtime/runtime_state.h rename to apps/desktop/src/gui/runtime/runtime_state.h index 1acc0d6..1340ddb 100644 --- a/src/gui/runtime/runtime_state.h +++ b/apps/desktop/src/gui/runtime/runtime_state.h @@ -13,6 +13,8 @@ #include #include +#include + #include "config_center.h" #include "path_manager.h" #include "runtime/device_presence_cache.h" @@ -57,19 +59,19 @@ struct PeerState { std::string video_primary_label_ = "primary_display"; std::string video_secondary_label_ = "secondary_display"; std::string audio_label_ = "audio"; - std::string data_label_ = protocol::kDataStream; - std::string mouse_label_ = protocol::kMouseStream; - std::string keyboard_label_ = protocol::kKeyboardStream; + std::string data_label_ = kDataStream; + std::string mouse_label_ = kMouseStream; + std::string keyboard_label_ = kKeyboardStream; std::string info_label_ = "info"; - std::string control_data_label_ = protocol::kControlStream; - std::string file_label_ = protocol::kFileStream; - std::string file_feedback_label_ = protocol::kFileFeedbackStream; - std::string clipboard_label_ = protocol::kClipboardStream; + std::string control_data_label_ = kControlStream; + std::string file_label_ = kFileStream; + std::string file_feedback_label_ = kFileFeedbackStream; + std::string clipboard_label_ = kClipboardStream; Params params_; }; // OS-specific service and permission state is kept separate from transport -// state so platform code does not appear to be part of the peer protocol. +// state so platform code does not appear to be part of the wire contract. struct PlatformIntegrationState { #if _WIN32 std::atomic pending_windows_service_sas_{false}; diff --git a/src/gui/ui/common.slint b/apps/desktop/src/gui/ui/common.slint similarity index 100% rename from src/gui/ui/common.slint rename to apps/desktop/src/gui/ui/common.slint diff --git a/src/gui/ui/crossdesk_ui.slint b/apps/desktop/src/gui/ui/crossdesk_ui.slint similarity index 100% rename from src/gui/ui/crossdesk_ui.slint rename to apps/desktop/src/gui/ui/crossdesk_ui.slint diff --git a/src/gui/ui/icons/arrow-right-white.svg b/apps/desktop/src/gui/ui/icons/arrow-right-white.svg similarity index 100% rename from src/gui/ui/icons/arrow-right-white.svg rename to apps/desktop/src/gui/ui/icons/arrow-right-white.svg diff --git a/src/gui/ui/icons/arrow-right.svg b/apps/desktop/src/gui/ui/icons/arrow-right.svg similarity index 100% rename from src/gui/ui/icons/arrow-right.svg rename to apps/desktop/src/gui/ui/icons/arrow-right.svg diff --git a/src/gui/ui/icons/check.svg b/apps/desktop/src/gui/ui/icons/check.svg similarity index 100% rename from src/gui/ui/icons/check.svg rename to apps/desktop/src/gui/ui/icons/check.svg diff --git a/src/gui/ui/icons/chevron-down.svg b/apps/desktop/src/gui/ui/icons/chevron-down.svg similarity index 100% rename from src/gui/ui/icons/chevron-down.svg rename to apps/desktop/src/gui/ui/icons/chevron-down.svg diff --git a/src/gui/ui/icons/chevron-left.svg b/apps/desktop/src/gui/ui/icons/chevron-left.svg similarity index 100% rename from src/gui/ui/icons/chevron-left.svg rename to apps/desktop/src/gui/ui/icons/chevron-left.svg diff --git a/src/gui/ui/icons/chevron-right.svg b/apps/desktop/src/gui/ui/icons/chevron-right.svg similarity index 100% rename from src/gui/ui/icons/chevron-right.svg rename to apps/desktop/src/gui/ui/icons/chevron-right.svg diff --git a/src/gui/ui/icons/chevron-up.svg b/apps/desktop/src/gui/ui/icons/chevron-up.svg similarity index 100% rename from src/gui/ui/icons/chevron-up.svg rename to apps/desktop/src/gui/ui/icons/chevron-up.svg diff --git a/src/gui/ui/icons/close-white.svg b/apps/desktop/src/gui/ui/icons/close-white.svg similarity index 100% rename from src/gui/ui/icons/close-white.svg rename to apps/desktop/src/gui/ui/icons/close-white.svg diff --git a/src/gui/ui/icons/close.svg b/apps/desktop/src/gui/ui/icons/close.svg similarity index 100% rename from src/gui/ui/icons/close.svg rename to apps/desktop/src/gui/ui/icons/close.svg diff --git a/src/gui/ui/icons/compress.svg b/apps/desktop/src/gui/ui/icons/compress.svg similarity index 100% rename from src/gui/ui/icons/compress.svg rename to apps/desktop/src/gui/ui/icons/compress.svg diff --git a/src/gui/ui/icons/copy.svg b/apps/desktop/src/gui/ui/icons/copy.svg similarity index 100% rename from src/gui/ui/icons/copy.svg rename to apps/desktop/src/gui/ui/icons/copy.svg diff --git a/src/gui/ui/icons/expand.svg b/apps/desktop/src/gui/ui/icons/expand.svg similarity index 100% rename from src/gui/ui/icons/expand.svg rename to apps/desktop/src/gui/ui/icons/expand.svg diff --git a/src/gui/ui/icons/eye-slash.svg b/apps/desktop/src/gui/ui/icons/eye-slash.svg similarity index 100% rename from src/gui/ui/icons/eye-slash.svg rename to apps/desktop/src/gui/ui/icons/eye-slash.svg diff --git a/src/gui/ui/icons/eye.svg b/apps/desktop/src/gui/ui/icons/eye.svg similarity index 100% rename from src/gui/ui/icons/eye.svg rename to apps/desktop/src/gui/ui/icons/eye.svg diff --git a/src/gui/ui/icons/file-up.svg b/apps/desktop/src/gui/ui/icons/file-up.svg similarity index 100% rename from src/gui/ui/icons/file-up.svg rename to apps/desktop/src/gui/ui/icons/file-up.svg diff --git a/src/gui/ui/icons/keyboard.svg b/apps/desktop/src/gui/ui/icons/keyboard.svg similarity index 100% rename from src/gui/ui/icons/keyboard.svg rename to apps/desktop/src/gui/ui/icons/keyboard.svg diff --git a/src/gui/ui/icons/maximize.svg b/apps/desktop/src/gui/ui/icons/maximize.svg similarity index 100% rename from src/gui/ui/icons/maximize.svg rename to apps/desktop/src/gui/ui/icons/maximize.svg diff --git a/src/gui/ui/icons/menu.svg b/apps/desktop/src/gui/ui/icons/menu.svg similarity index 100% rename from src/gui/ui/icons/menu.svg rename to apps/desktop/src/gui/ui/icons/menu.svg diff --git a/src/gui/ui/icons/minimize.svg b/apps/desktop/src/gui/ui/icons/minimize.svg similarity index 100% rename from src/gui/ui/icons/minimize.svg rename to apps/desktop/src/gui/ui/icons/minimize.svg diff --git a/src/gui/ui/icons/monitor.svg b/apps/desktop/src/gui/ui/icons/monitor.svg similarity index 100% rename from src/gui/ui/icons/monitor.svg rename to apps/desktop/src/gui/ui/icons/monitor.svg diff --git a/src/gui/ui/icons/mouse-off.svg b/apps/desktop/src/gui/ui/icons/mouse-off.svg similarity index 100% rename from src/gui/ui/icons/mouse-off.svg rename to apps/desktop/src/gui/ui/icons/mouse-off.svg diff --git a/src/gui/ui/icons/mouse.svg b/apps/desktop/src/gui/ui/icons/mouse.svg similarity index 100% rename from src/gui/ui/icons/mouse.svg rename to apps/desktop/src/gui/ui/icons/mouse.svg diff --git a/src/gui/ui/icons/pen-white.svg b/apps/desktop/src/gui/ui/icons/pen-white.svg similarity index 100% rename from src/gui/ui/icons/pen-white.svg rename to apps/desktop/src/gui/ui/icons/pen-white.svg diff --git a/src/gui/ui/icons/pen.svg b/apps/desktop/src/gui/ui/icons/pen.svg similarity index 100% rename from src/gui/ui/icons/pen.svg rename to apps/desktop/src/gui/ui/icons/pen.svg diff --git a/src/gui/ui/icons/stats.svg b/apps/desktop/src/gui/ui/icons/stats.svg similarity index 100% rename from src/gui/ui/icons/stats.svg rename to apps/desktop/src/gui/ui/icons/stats.svg diff --git a/src/gui/ui/icons/trash-white.svg b/apps/desktop/src/gui/ui/icons/trash-white.svg similarity index 100% rename from src/gui/ui/icons/trash-white.svg rename to apps/desktop/src/gui/ui/icons/trash-white.svg diff --git a/src/gui/ui/icons/trash.svg b/apps/desktop/src/gui/ui/icons/trash.svg similarity index 100% rename from src/gui/ui/icons/trash.svg rename to apps/desktop/src/gui/ui/icons/trash.svg diff --git a/src/gui/ui/icons/volume-off.svg b/apps/desktop/src/gui/ui/icons/volume-off.svg similarity index 100% rename from src/gui/ui/icons/volume-off.svg rename to apps/desktop/src/gui/ui/icons/volume-off.svg diff --git a/src/gui/ui/icons/volume.svg b/apps/desktop/src/gui/ui/icons/volume.svg similarity index 100% rename from src/gui/ui/icons/volume.svg rename to apps/desktop/src/gui/ui/icons/volume.svg diff --git a/src/gui/ui/main_window.slint b/apps/desktop/src/gui/ui/main_window.slint similarity index 99% rename from src/gui/ui/main_window.slint rename to apps/desktop/src/gui/ui/main_window.slint index 7809ffd..fd58bbb 100644 --- a/src/gui/ui/main_window.slint +++ b/apps/desktop/src/gui/ui/main_window.slint @@ -443,7 +443,7 @@ export component MainWindow inherits Window { y: 7px; width: 16px; height: 16px; - source: @image-url("../../../icons/linux/crossdesk_16x16.png"); + source: @image-url("../../../resources/linux/crossdesk_16x16.png"); image-fit: contain; } Text { diff --git a/src/gui/ui/server_window.slint b/apps/desktop/src/gui/ui/server_window.slint similarity index 100% rename from src/gui/ui/server_window.slint rename to apps/desktop/src/gui/ui/server_window.slint diff --git a/src/gui/ui/stream_window.slint b/apps/desktop/src/gui/ui/stream_window.slint similarity index 100% rename from src/gui/ui/stream_window.slint rename to apps/desktop/src/gui/ui/stream_window.slint diff --git a/src/gui/ui/ui_localization.h b/apps/desktop/src/gui/ui/ui_localization.h similarity index 100% rename from src/gui/ui/ui_localization.h rename to apps/desktop/src/gui/ui/ui_localization.h diff --git a/src/gui/views/panels/local_peer_panel.cpp b/apps/desktop/src/gui/views/panels/local_peer_panel.cpp similarity index 100% rename from src/gui/views/panels/local_peer_panel.cpp rename to apps/desktop/src/gui/views/panels/local_peer_panel.cpp diff --git a/src/gui/views/panels/recent_connections_panel.cpp b/apps/desktop/src/gui/views/panels/recent_connections_panel.cpp similarity index 100% rename from src/gui/views/panels/recent_connections_panel.cpp rename to apps/desktop/src/gui/views/panels/recent_connections_panel.cpp diff --git a/src/gui/views/panels/remote_peer_panel.cpp b/apps/desktop/src/gui/views/panels/remote_peer_panel.cpp similarity index 100% rename from src/gui/views/panels/remote_peer_panel.cpp rename to apps/desktop/src/gui/views/panels/remote_peer_panel.cpp diff --git a/src/gui/views/toolbars/control_bar.cpp b/apps/desktop/src/gui/views/toolbars/control_bar.cpp similarity index 99% rename from src/gui/views/toolbars/control_bar.cpp rename to apps/desktop/src/gui/views/toolbars/control_bar.cpp index fcb08c0..62d89bf 100644 --- a/src/gui/views/toolbars/control_bar.cpp +++ b/apps/desktop/src/gui/views/toolbars/control_bar.cpp @@ -1,4 +1,6 @@ #include + +#include #include #include #include diff --git a/src/gui/views/toolbars/status_bar.cpp b/apps/desktop/src/gui/views/toolbars/status_bar.cpp similarity index 100% rename from src/gui/views/toolbars/status_bar.cpp rename to apps/desktop/src/gui/views/toolbars/status_bar.cpp diff --git a/src/gui/views/toolbars/title_bar.cpp b/apps/desktop/src/gui/views/toolbars/title_bar.cpp similarity index 100% rename from src/gui/views/toolbars/title_bar.cpp rename to apps/desktop/src/gui/views/toolbars/title_bar.cpp diff --git a/src/gui/views/windows/about_window.cpp b/apps/desktop/src/gui/views/windows/about_window.cpp similarity index 100% rename from src/gui/views/windows/about_window.cpp rename to apps/desktop/src/gui/views/windows/about_window.cpp diff --git a/src/gui/views/windows/connection_status_window.cpp b/apps/desktop/src/gui/views/windows/connection_status_window.cpp similarity index 100% rename from src/gui/views/windows/connection_status_window.cpp rename to apps/desktop/src/gui/views/windows/connection_status_window.cpp diff --git a/src/gui/views/windows/control_window.cpp b/apps/desktop/src/gui/views/windows/control_window.cpp similarity index 100% rename from src/gui/views/windows/control_window.cpp rename to apps/desktop/src/gui/views/windows/control_window.cpp diff --git a/src/gui/views/windows/file_transfer_window.cpp b/apps/desktop/src/gui/views/windows/file_transfer_window.cpp similarity index 100% rename from src/gui/views/windows/file_transfer_window.cpp rename to apps/desktop/src/gui/views/windows/file_transfer_window.cpp diff --git a/src/gui/views/windows/main_settings_window.cpp b/apps/desktop/src/gui/views/windows/main_settings_window.cpp similarity index 100% rename from src/gui/views/windows/main_settings_window.cpp rename to apps/desktop/src/gui/views/windows/main_settings_window.cpp diff --git a/src/gui/views/windows/main_window.cpp b/apps/desktop/src/gui/views/windows/main_window.cpp similarity index 100% rename from src/gui/views/windows/main_window.cpp rename to apps/desktop/src/gui/views/windows/main_window.cpp diff --git a/src/gui/views/windows/portable_service_install_window.cpp b/apps/desktop/src/gui/views/windows/portable_service_install_window.cpp similarity index 100% rename from src/gui/views/windows/portable_service_install_window.cpp rename to apps/desktop/src/gui/views/windows/portable_service_install_window.cpp diff --git a/src/gui/views/windows/server_settings_window.cpp b/apps/desktop/src/gui/views/windows/server_settings_window.cpp similarity index 100% rename from src/gui/views/windows/server_settings_window.cpp rename to apps/desktop/src/gui/views/windows/server_settings_window.cpp diff --git a/src/gui/views/windows/server_window.cpp b/apps/desktop/src/gui/views/windows/server_window.cpp similarity index 100% rename from src/gui/views/windows/server_window.cpp rename to apps/desktop/src/gui/views/windows/server_window.cpp diff --git a/src/gui/views/windows/stream_window.cpp b/apps/desktop/src/gui/views/windows/stream_window.cpp similarity index 100% rename from src/gui/views/windows/stream_window.cpp rename to apps/desktop/src/gui/views/windows/stream_window.cpp diff --git a/src/gui/views/windows/update_notification_window.cpp b/apps/desktop/src/gui/views/windows/update_notification_window.cpp similarity index 100% rename from src/gui/views/windows/update_notification_window.cpp rename to apps/desktop/src/gui/views/windows/update_notification_window.cpp diff --git a/src/log/rd_log.cpp b/apps/desktop/src/log/rd_log.cpp similarity index 100% rename from src/log/rd_log.cpp rename to apps/desktop/src/log/rd_log.cpp diff --git a/src/log/rd_log.h b/apps/desktop/src/log/rd_log.h similarity index 100% rename from src/log/rd_log.h rename to apps/desktop/src/log/rd_log.h diff --git a/apps/desktop/src/path_manager/path_manager.cpp b/apps/desktop/src/path_manager/path_manager.cpp new file mode 100644 index 0000000..c57fa1c --- /dev/null +++ b/apps/desktop/src/path_manager/path_manager.cpp @@ -0,0 +1,60 @@ +#include "path_manager.h" + +#include "platform/path_backend.h" + +#ifndef CROSSDESK_PORTABLE +#define CROSSDESK_PORTABLE 0 +#endif + +namespace { + +#if CROSSDESK_PORTABLE +std::filesystem::path GetPortableRootPath() { + std::filesystem::path executable_dir = + crossdesk::platform::GetExecutableDirectory(); + if (!executable_dir.empty()) return executable_dir; + + std::error_code error; + const std::filesystem::path current = + std::filesystem::current_path(error); + return error ? std::filesystem::path(".") : current; +} +#endif + +} // namespace + +namespace crossdesk { + +PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {} + +std::filesystem::path PathManager::GetConfigPath() { +#if CROSSDESK_PORTABLE + return GetPortableRootPath() / "data"; +#else + return platform::GetConfigPath(app_name_); +#endif +} + +std::filesystem::path PathManager::GetCachePath() { +#if CROSSDESK_PORTABLE + return GetPortableRootPath() / "data"; +#else + return platform::GetCachePath(app_name_); +#endif +} + +std::filesystem::path PathManager::GetLogPath() { +#if CROSSDESK_PORTABLE + return GetPortableRootPath() / "logs"; +#else + return platform::GetLogPath(app_name_); +#endif +} + +bool PathManager::CreateDirectories(const std::filesystem::path& path) { + std::error_code error; + const bool created = std::filesystem::create_directories(path, error); + return !error && (created || std::filesystem::exists(path)); +} + +} // namespace crossdesk diff --git a/src/path_manager/path_manager.h b/apps/desktop/src/path_manager/path_manager.h similarity index 64% rename from src/path_manager/path_manager.h rename to apps/desktop/src/path_manager/path_manager.h index 1adb308..06b78df 100644 --- a/src/path_manager/path_manager.h +++ b/apps/desktop/src/path_manager/path_manager.h @@ -9,10 +9,6 @@ #include #include -#ifdef _WIN32 -#include -#include -#endif namespace crossdesk { @@ -28,17 +24,8 @@ class PathManager { bool CreateDirectories(const std::filesystem::path& p); - private: -#ifdef _WIN32 - std::filesystem::path GetKnownFolder(REFKNOWNFOLDERID id); -#endif - - std::string GetHome(); - std::filesystem::path GetEnvOrDefault(const char* env_var, - const std::string& def); - private: std::string app_name_; }; } // namespace crossdesk -#endif \ No newline at end of file +#endif diff --git a/apps/desktop/src/platform/autostart_backend.h b/apps/desktop/src/platform/autostart_backend.h new file mode 100644 index 0000000..d061f5c --- /dev/null +++ b/apps/desktop/src/platform/autostart_backend.h @@ -0,0 +1,16 @@ +#ifndef CROSSDESK_PLATFORM_AUTOSTART_BACKEND_H_ +#define CROSSDESK_PLATFORM_AUTOSTART_BACKEND_H_ + +#include + +namespace crossdesk::platform { + +std::string GetAutostartExecutablePath(); +bool EnableAutostart(const std::string& app_name, + const std::string& executable_path); +bool DisableAutostart(const std::string& app_name); +bool IsAutostartEnabled(const std::string& app_name); + +} // namespace crossdesk::platform + +#endif // CROSSDESK_PLATFORM_AUTOSTART_BACKEND_H_ diff --git a/apps/desktop/src/platform/clipboard_backend.h b/apps/desktop/src/platform/clipboard_backend.h new file mode 100644 index 0000000..898e5f7 --- /dev/null +++ b/apps/desktop/src/platform/clipboard_backend.h @@ -0,0 +1,13 @@ +#ifndef CROSSDESK_PLATFORM_CLIPBOARD_BACKEND_H_ +#define CROSSDESK_PLATFORM_CLIPBOARD_BACKEND_H_ + +namespace crossdesk::platform { + +bool ClipboardMonitoring(); +void HandleClipboardChange(); +void RunClipboardMonitor(int check_interval_ms); +void WakeClipboardMonitor(); + +} // namespace crossdesk::platform + +#endif // CROSSDESK_PLATFORM_CLIPBOARD_BACKEND_H_ diff --git a/src/gui/platform/opengl_video_renderer.cpp b/apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp similarity index 99% rename from src/gui/platform/opengl_video_renderer.cpp rename to apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp index c287fa6..e5775b3 100644 --- a/src/gui/platform/opengl_video_renderer.cpp +++ b/apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp @@ -1,4 +1,4 @@ -#include "platform/opengl_video_renderer.h" +#include "platform/common/gui/opengl_video_renderer.h" #if !defined(_WIN32) && !defined(__linux__) #error "OpenGlVideoRenderer is only available on Windows and Linux" diff --git a/src/gui/platform/opengl_video_renderer.h b/apps/desktop/src/platform/common/gui/opengl_video_renderer.h similarity index 100% rename from src/gui/platform/opengl_video_renderer.h rename to apps/desktop/src/platform/common/gui/opengl_video_renderer.h diff --git a/apps/desktop/src/platform/common/gui/service_status_runtime.cpp b/apps/desktop/src/platform/common/gui/service_status_runtime.cpp new file mode 100644 index 0000000..2425ef0 --- /dev/null +++ b/apps/desktop/src/platform/common/gui/service_status_runtime.cpp @@ -0,0 +1,42 @@ +#include "runtime/gui_runtime.h" + +namespace crossdesk { + +void GuiRuntime::ResetRemoteServiceStatus(RemoteSession& props) { + props.remote_service_status_received_ = false; + props.remote_service_available_ = false; + props.remote_interactive_stage_.clear(); +} + +void GuiRuntime::ApplyRemoteServiceStatus(RemoteSession& props, + const ServiceStatus& status) { + props.remote_service_status_received_ = true; + props.remote_service_available_ = status.available; + props.remote_interactive_stage_ = status.interactive_stage; +} + +GuiRuntime::RemoteUnlockState GuiRuntime::GetRemoteUnlockState( + const RemoteSession& props) const { + if (!props.remote_service_status_received_) { + return RemoteUnlockState::none; + } + if (!props.remote_service_available_) { + return RemoteUnlockState::service_unavailable; + } + if (props.remote_interactive_stage_ == "credential-ui") { + return RemoteUnlockState::credential_ui; + } + if (props.remote_interactive_stage_ == "lock-screen") { + return RemoteUnlockState::lock_screen; + } + if (props.remote_interactive_stage_ == "secure-desktop") { + return RemoteUnlockState::secure_desktop; + } + return RemoteUnlockState::none; +} + +#if !defined(_WIN32) +void GuiRuntime::HandleWindowsServiceIntegration() {} +#endif + +} // namespace crossdesk diff --git a/src/gui/platform/video_renderer_factory_opengl.cpp b/apps/desktop/src/platform/common/gui/video_renderer_factory_opengl.cpp similarity index 79% rename from src/gui/platform/video_renderer_factory_opengl.cpp rename to apps/desktop/src/platform/common/gui/video_renderer_factory_opengl.cpp index 9389429..3896131 100644 --- a/src/gui/platform/video_renderer_factory_opengl.cpp +++ b/apps/desktop/src/platform/common/gui/video_renderer_factory_opengl.cpp @@ -1,6 +1,6 @@ #include -#include "platform/opengl_video_renderer.h" +#include "platform/common/gui/opengl_video_renderer.h" #include "platform/video_renderer.h" namespace crossdesk { diff --git a/src/device_controller/windows_key_metadata.h b/apps/desktop/src/platform/common/input/windows_key_metadata.h similarity index 100% rename from src/device_controller/windows_key_metadata.h rename to apps/desktop/src/platform/common/input/windows_key_metadata.h diff --git a/apps/desktop/src/platform/common/posix_daemon_backend.cpp b/apps/desktop/src/platform/common/posix_daemon_backend.cpp new file mode 100644 index 0000000..da3d185 --- /dev/null +++ b/apps/desktop/src/platform/common/posix_daemon_backend.cpp @@ -0,0 +1,56 @@ +#include "platform/daemon_backend.h" + +#include +#include +#include +#include + +#include +#include + +namespace crossdesk::platform { + +volatile std::sig_atomic_t g_daemon_stop_requested = 0; + +void ResetDaemonStop() { g_daemon_stop_requested = 0; } + +void RequestDaemonStop() { g_daemon_stop_requested = 1; } + +bool DaemonStopRequested() { return g_daemon_stop_requested != 0; } + +DaemonChildResult RunDaemonChild(const std::string& executable_path, + const std::function& keep_running) { + DaemonChildResult result; + const pid_t child = fork(); + if (child == 0) { + execl(executable_path.c_str(), executable_path.c_str(), "--child", + nullptr); + _exit(1); + } + if (child < 0) return result; + + result.started = true; + int status = 0; + pid_t waited = -1; + while (keep_running()) { + waited = waitpid(child, &status, WNOHANG); + if (waited == child || (waited < 0 && errno != EINTR)) break; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + if (!keep_running() && waited != child) { + kill(child, SIGTERM); + waited = waitpid(child, &status, 0); + } + if (waited < 0) return result; + + if (WIFEXITED(status)) { + result.exit_code = WEXITSTATUS(status); + result.normal_exit = result.exit_code == 0; + } else if (WIFSIGNALED(status)) { + result.terminated_by_signal = true; + result.exit_code = WTERMSIG(status); + } + return result; +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/daemon_backend.h b/apps/desktop/src/platform/daemon_backend.h new file mode 100644 index 0000000..7eb4e3d --- /dev/null +++ b/apps/desktop/src/platform/daemon_backend.h @@ -0,0 +1,29 @@ +#ifndef CROSSDESK_PLATFORM_DAEMON_BACKEND_H_ +#define CROSSDESK_PLATFORM_DAEMON_BACKEND_H_ + +#include +#include +#include + +namespace crossdesk::platform { + +extern volatile std::sig_atomic_t g_daemon_stop_requested; + +struct DaemonChildResult { + bool started = false; + bool normal_exit = false; + bool terminated_by_signal = false; + int exit_code = 0; +}; + +void ResetDaemonStop(); +void RequestDaemonStop(); +bool DaemonStopRequested(); +bool PrepareDaemon(); +std::string GetDaemonExecutablePath(); +DaemonChildResult RunDaemonChild(const std::string& executable_path, + const std::function& keep_running); + +} // namespace crossdesk::platform + +#endif // CROSSDESK_PLATFORM_DAEMON_BACKEND_H_ diff --git a/apps/desktop/src/platform/linux/autostart.cpp b/apps/desktop/src/platform/linux/autostart.cpp new file mode 100644 index 0000000..d66f683 --- /dev/null +++ b/apps/desktop/src/platform/linux/autostart.cpp @@ -0,0 +1,65 @@ +#include "platform/autostart_backend.h" + +#include +#include + +#include +#include +#include + +namespace crossdesk::platform { +namespace { + +std::filesystem::path AutostartPath(const std::string& app_name) { + const char* home = std::getenv("HOME"); + if (!home || !home[0]) return {}; + return std::filesystem::path(home) / ".config" / "autostart" / + (app_name + ".desktop"); +} + +} // namespace + +std::string GetAutostartExecutablePath() { + char path[PATH_MAX] = {}; + const ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1); + return count > 0 ? std::string(path, static_cast(count)) + : std::string(); +} + +bool EnableAutostart(const std::string& app_name, + const std::string& executable_path) { + const std::filesystem::path path = AutostartPath(app_name); + if (path.empty()) return false; + + std::error_code error; + std::filesystem::create_directories(path.parent_path(), error); + if (error) return false; + + std::ofstream file(path); + if (!file.is_open()) return false; + file << "[Desktop Entry]\n" + << "Type=Application\n" + << "Exec=" << executable_path << "\n" + << "Hidden=false\n" + << "NoDisplay=false\n" + << "X-GNOME-Autostart-enabled=true\n" + << "Terminal=false\n" + << "StartupNotify=false\n" + << "Name=" << app_name << "\n"; + file.close(); + return file.good(); +} + +bool DisableAutostart(const std::string& app_name) { + const std::filesystem::path path = AutostartPath(app_name); + if (path.empty()) return false; + std::error_code error; + return std::filesystem::remove(path, error) && !error; +} + +bool IsAutostartEnabled(const std::string& app_name) { + const std::filesystem::path path = AutostartPath(app_name); + return !path.empty() && std::filesystem::is_regular_file(path); +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/linux/clipboard.cpp b/apps/desktop/src/platform/linux/clipboard.cpp new file mode 100644 index 0000000..5d8de8b --- /dev/null +++ b/apps/desktop/src/platform/linux/clipboard.cpp @@ -0,0 +1,202 @@ +#include "clipboard.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "platform/clipboard_backend.h" +#include "rd_log.h" + +namespace crossdesk { + +std::string Clipboard::GetText() { + Display* display = XOpenDisplay(nullptr); + if (!display) { + LOG_ERROR("Clipboard::GetText: failed to open X display"); + return {}; + } + + Window owner = XGetSelectionOwner(display, XA_PRIMARY); + if (owner == None) { + owner = + XGetSelectionOwner(display, XInternAtom(display, "CLIPBOARD", False)); + if (owner == None) { + XCloseDisplay(display); + return {}; + } + } + + Atom selection = XA_PRIMARY; + Atom target = XInternAtom(display, "UTF8_STRING", False); + if (target == None) target = XA_STRING; + const Window window = XCreateSimpleWindow( + display, DefaultRootWindow(display), 0, 0, 1, 1, 0, 0, 0); + XSelectInput(display, window, PropertyChangeMask); + XConvertSelection(display, selection, target, XA_PRIMARY, window, + CurrentTime); + + std::string result; + bool done = false; + while (!done) { + XEvent event = {}; + XNextEvent(display, &event); + if (event.type != SelectionNotify) continue; + if (event.xselection.property == None) { + if (selection == XA_PRIMARY) { + selection = XInternAtom(display, "CLIPBOARD", False); + XConvertSelection(display, selection, target, XA_PRIMARY, window, + CurrentTime); + continue; + } + break; + } + + Atom actual_type = None; + int actual_format = 0; + unsigned long item_count = 0; + unsigned long bytes_after = 0; + unsigned char* data = nullptr; + if (XGetWindowProperty(display, window, XA_PRIMARY, 0, LONG_MAX / 4, + False, AnyPropertyType, &actual_type, + &actual_format, &item_count, &bytes_after, + &data) == Success) { + if (data) { + result.assign(reinterpret_cast(data), item_count); + XFree(data); + } + done = true; + } + } + + XDestroyWindow(display, window); + XCloseDisplay(display); + return result; +} + +bool Clipboard::SetText(const std::string& text) { + Display* display = XOpenDisplay(nullptr); + if (!display) { + LOG_ERROR("Clipboard::SetText: failed to open X display"); + return false; + } + + const Window window = XCreateSimpleWindow( + display, DefaultRootWindow(display), 0, 0, 1, 1, 0, 0, 0); + const Atom clipboard = XInternAtom(display, "CLIPBOARD", False); + const Atom utf8 = XInternAtom(display, "UTF8_STRING", False); + const Atom targets = XInternAtom(display, "TARGETS", False); + XSetSelectionOwner(display, clipboard, window, CurrentTime); + if (XGetSelectionOwner(display, clipboard) != window) { + XDestroyWindow(display, window); + XCloseDisplay(display); + return false; + } + + XChangeProperty(display, window, XA_PRIMARY, utf8, 8, PropModeReplace, + reinterpret_cast(text.data()), + static_cast(text.size())); + while (true) { + XEvent event = {}; + XNextEvent(display, &event); + if (event.type == SelectionClear) break; + if (event.type != SelectionRequest) continue; + + const XSelectionRequestEvent& request = event.xselectionrequest; + XSelectionEvent response = {}; + response.type = SelectionNotify; + response.display = request.display; + response.requestor = request.requestor; + response.selection = request.selection; + response.time = request.time; + response.target = request.target; + response.property = request.property; + if (request.target == targets) { + Atom supported[] = {utf8, XA_STRING, targets}; + XChangeProperty(display, request.requestor, request.property, XA_ATOM, + 32, PropModeReplace, + reinterpret_cast(supported), 3); + } else if (request.target == utf8 || request.target == XA_STRING) { + XChangeProperty(display, request.requestor, request.property, + request.target, 8, PropModeReplace, + reinterpret_cast(text.data()), + static_cast(text.size())); + } else { + response.property = None; + } + XSendEvent(display, request.requestor, False, 0, + reinterpret_cast(&response)); + XSync(display, False); + } + + XDestroyWindow(display, window); + XCloseDisplay(display); + return true; +} + +bool Clipboard::HasText() { + Display* display = XOpenDisplay(nullptr); + if (!display) return false; + const Atom clipboard = XInternAtom(display, "CLIPBOARD", False); + Window owner = XGetSelectionOwner(display, clipboard); + if (owner == None) owner = XGetSelectionOwner(display, XA_PRIMARY); + XCloseDisplay(display); + return owner != None; +} + +namespace platform { + +void RunClipboardMonitor(int check_interval_ms) { + Display* display = XOpenDisplay(nullptr); + if (!display) { + LOG_ERROR("Failed to open X display for clipboard monitoring"); + return; + } + + int event_base = 0; + int error_base = 0; + if (!XFixesQueryExtension(display, &event_base, &error_base)) { + LOG_WARN("XFixes extension not available, falling back to polling"); + XCloseDisplay(display); + while (ClipboardMonitoring()) { + std::this_thread::sleep_for( + std::chrono::milliseconds(check_interval_ms > 0 ? check_interval_ms + : 100)); + if (ClipboardMonitoring()) HandleClipboardChange(); + } + return; + } + + const Atom clipboard = XInternAtom(display, "CLIPBOARD", False); + const Window event_window = XCreateSimpleWindow( + display, DefaultRootWindow(display), 0, 0, 1, 1, 0, 0, 0); + XFixesSelectSelectionInput(display, event_window, clipboard, + XFixesSetSelectionOwnerNotifyMask | + XFixesSelectionWindowDestroyNotifyMask | + XFixesSelectionClientCloseNotifyMask); + LOG_INFO("Clipboard event monitoring started (Linux XFixes)"); + + while (ClipboardMonitoring()) { + while (ClipboardMonitoring() && XPending(display) > 0) { + XEvent event = {}; + XNextEvent(display, &event); + if (event.type == event_base + XFixesSelectionNotify) { + HandleClipboardChange(); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + + XFixesSelectSelectionInput(display, event_window, clipboard, 0); + XDestroyWindow(display, event_window); + XCloseDisplay(display); +} + +void WakeClipboardMonitor() {} + +} // namespace platform +} // namespace crossdesk diff --git a/src/common/linux_cursor_shape.cpp b/apps/desktop/src/platform/linux/common/linux_cursor_shape.cpp similarity index 99% rename from src/common/linux_cursor_shape.cpp rename to apps/desktop/src/platform/linux/common/linux_cursor_shape.cpp index 2ef2e75..def0792 100644 --- a/src/common/linux_cursor_shape.cpp +++ b/apps/desktop/src/platform/linux/common/linux_cursor_shape.cpp @@ -1,5 +1,7 @@ #include "linux_cursor_shape.h" +#include + #include #include #include diff --git a/src/common/linux_cursor_shape.h b/apps/desktop/src/platform/linux/common/linux_cursor_shape.h similarity index 97% rename from src/common/linux_cursor_shape.h rename to apps/desktop/src/platform/linux/common/linux_cursor_shape.h index 5724abf..b396c57 100644 --- a/src/common/linux_cursor_shape.h +++ b/apps/desktop/src/platform/linux/common/linux_cursor_shape.h @@ -6,7 +6,7 @@ #include #include -#include "remote_cursor_shape.h" +#include namespace crossdesk { diff --git a/src/common/wayland_portal_shared.cpp b/apps/desktop/src/platform/linux/common/wayland_portal_shared.cpp similarity index 100% rename from src/common/wayland_portal_shared.cpp rename to apps/desktop/src/platform/linux/common/wayland_portal_shared.cpp diff --git a/src/common/wayland_portal_shared.h b/apps/desktop/src/platform/linux/common/wayland_portal_shared.h similarity index 100% rename from src/common/wayland_portal_shared.h rename to apps/desktop/src/platform/linux/common/wayland_portal_shared.h diff --git a/apps/desktop/src/platform/linux/daemon_backend.cpp b/apps/desktop/src/platform/linux/daemon_backend.cpp new file mode 100644 index 0000000..54b5043 --- /dev/null +++ b/apps/desktop/src/platform/linux/daemon_backend.cpp @@ -0,0 +1,58 @@ +#include "platform/daemon_backend.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace crossdesk::platform { + +bool PrepareDaemon() { + const bool from_terminal = + isatty(STDIN_FILENO) != 0 || isatty(STDOUT_FILENO) != 0; + pid_t process = fork(); + if (process < 0) return false; + if (process > 0) _exit(0); + if (setsid() < 0) return false; + + process = fork(); + if (process < 0) return false; + if (process > 0) _exit(0); + + umask(0); + if (chdir("/") != 0) { + std::cerr << "Failed to change daemon working directory to /: " + << std::strerror(errno) << std::endl; + return false; + } + + const int null_fd = open("/dev/null", O_RDWR); + if (null_fd >= 0) { + dup2(null_fd, STDIN_FILENO); + if (!from_terminal) { + dup2(null_fd, STDOUT_FILENO); + dup2(null_fd, STDERR_FILENO); + } + if (null_fd > STDERR_FILENO) close(null_fd); + } + + signal(SIGTERM, [](int) { g_daemon_stop_requested = 1; }); + signal(SIGINT, [](int) { g_daemon_stop_requested = 1; }); + signal(SIGPIPE, SIG_IGN); + return true; +} + +std::string GetDaemonExecutablePath() { + char path[PATH_MAX] = {}; + const ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1); + return count > 0 ? std::string(path, static_cast(count)) + : std::string(); +} + +} // namespace crossdesk::platform diff --git a/src/gui/platform/tray/linux_tray.cpp b/apps/desktop/src/platform/linux/gui/tray/linux_tray.cpp similarity index 98% rename from src/gui/platform/tray/linux_tray.cpp rename to apps/desktop/src/platform/linux/gui/tray/linux_tray.cpp index b4fc927..1c9dc87 100644 --- a/src/gui/platform/tray/linux_tray.cpp +++ b/apps/desktop/src/platform/linux/gui/tray/linux_tray.cpp @@ -253,14 +253,14 @@ bool IsPointInsideRoundedRectangle(int point_x, int point_y, int x, int y, std::vector BuildIconCandidatePaths() { std::vector paths = { - "icons/linux/crossdesk_32x32.png", - "icons/linux/crossdesk_24x24.png", - "icons/linux/crossdesk_48x48.png", - "icons/linux/crossdesk_64x64.png", - "icons/linux/crossdesk_96x96.png", - "icons/linux/crossdesk_128x128.png", - "icons/linux/crossdesk_256x256.png", - "icons/linux/crossdesk_512x512.png", + "apps/desktop/resources/linux/crossdesk_32x32.png", + "apps/desktop/resources/linux/crossdesk_24x24.png", + "apps/desktop/resources/linux/crossdesk_48x48.png", + "apps/desktop/resources/linux/crossdesk_64x64.png", + "apps/desktop/resources/linux/crossdesk_96x96.png", + "apps/desktop/resources/linux/crossdesk_128x128.png", + "apps/desktop/resources/linux/crossdesk_256x256.png", + "apps/desktop/resources/linux/crossdesk_512x512.png", "/usr/share/icons/hicolor/32x32/apps/crossdesk.png", "/usr/share/icons/hicolor/24x24/apps/crossdesk.png", "/usr/share/icons/hicolor/48x48/apps/crossdesk.png", @@ -284,8 +284,8 @@ std::vector BuildIconCandidatePaths() { for (const char* size : {"32", "24", "48", "64", "96", "128", "256", "512", "16"}) { paths.push_back(base_dir / - ("icons/linux/crossdesk_" + std::string(size) + "x" + - size + ".png")); + ("apps/desktop/resources/linux/crossdesk_" + + std::string(size) + "x" + size + ".png")); paths.push_back(base_dir / ("../share/icons/hicolor/" + std::string(size) + "x" + size + "/apps/crossdesk.png")); diff --git a/src/gui/platform/tray/linux_tray.h b/apps/desktop/src/platform/linux/gui/tray/linux_tray.h similarity index 100% rename from src/gui/platform/tray/linux_tray.h rename to apps/desktop/src/platform/linux/gui/tray/linux_tray.h diff --git a/apps/desktop/src/platform/linux/input/device_controller_factory.cpp b/apps/desktop/src/platform/linux/input/device_controller_factory.cpp new file mode 100644 index 0000000..24c4b1f --- /dev/null +++ b/apps/desktop/src/platform/linux/input/device_controller_factory.cpp @@ -0,0 +1,19 @@ +#include "device_controller_factory.h" + +#include "keyboard_capturer.h" +#include "mouse_controller.h" + +namespace crossdesk { + +DeviceController* DeviceControllerFactory::Create(Device device) { + switch (device) { + case Mouse: + return new PlatformMouseController(); + case Keyboard: + return new PlatformKeyboardCapturer(); + default: + return nullptr; + } +} + +} // namespace crossdesk diff --git a/src/device_controller/keyboard/linux/keyboard_capturer.cpp b/apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer.cpp similarity index 95% rename from src/device_controller/keyboard/linux/keyboard_capturer.cpp rename to apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer.cpp index ba32663..28a3738 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer.cpp +++ b/apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer.cpp @@ -86,7 +86,7 @@ static int KeyboardEventHandler(Display* display, XEvent* event) { return 0; } -KeyboardCapturer::KeyboardCapturer() +PlatformKeyboardCapturer::PlatformKeyboardCapturer() : display_(nullptr), root_(0), running_(false), @@ -109,7 +109,7 @@ KeyboardCapturer::KeyboardCapturer() &minor_version) != 0; } -KeyboardCapturer::~KeyboardCapturer() { +PlatformKeyboardCapturer::~PlatformKeyboardCapturer() { Unhook(); CleanupWaylandPortal(); @@ -121,7 +121,7 @@ KeyboardCapturer::~KeyboardCapturer() { } } -int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { +int PlatformKeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { if (!display_) { LOG_ERROR("Display not initialized."); return -1; @@ -182,7 +182,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { return 0; } -int KeyboardCapturer::Unhook() { +int PlatformKeyboardCapturer::Unhook() { running_ = false; if (event_thread_.joinable()) { @@ -200,7 +200,7 @@ int KeyboardCapturer::Unhook() { return 0; } -int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, +int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, bool extended) { if (IsWaylandSession()) { if (!use_wayland_portal_ && !wayland_init_attempted_) { diff --git a/src/device_controller/keyboard/linux/keyboard_capturer.h b/apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer.h similarity index 92% rename from src/device_controller/keyboard/linux/keyboard_capturer.h rename to apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer.h index f17ac31..79812f4 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer.h +++ b/apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer.h @@ -22,10 +22,10 @@ struct _XDisplay; namespace crossdesk { -class KeyboardCapturer : public DeviceController { +class PlatformKeyboardCapturer final : public KeyboardCapturer { public: - KeyboardCapturer(); - virtual ~KeyboardCapturer(); + PlatformKeyboardCapturer(); + virtual ~PlatformKeyboardCapturer(); public: virtual int Hook(OnKeyAction on_key_action, void* user_ptr); diff --git a/src/device_controller/keyboard/linux/keyboard_capturer_wayland.cpp b/apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer_wayland.cpp similarity index 98% rename from src/device_controller/keyboard/linux/keyboard_capturer_wayland.cpp rename to apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer_wayland.cpp index 30129c6..726f9ee 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer_wayland.cpp +++ b/apps/desktop/src/platform/linux/input/keyboard/keyboard_capturer_wayland.cpp @@ -352,7 +352,7 @@ bool SendPortalRequestAndHandleResponse( } // namespace #endif -bool KeyboardCapturer::InitWaylandPortal() { +bool PlatformKeyboardCapturer::InitWaylandPortal() { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER CleanupWaylandPortal(); @@ -565,7 +565,7 @@ bool KeyboardCapturer::InitWaylandPortal() { #endif } -void KeyboardCapturer::CleanupWaylandPortal() { +void PlatformKeyboardCapturer::CleanupWaylandPortal() { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER if (dbus_connection_) { CloseWaylandPortalSessionAndConnection(dbus_connection_, @@ -579,7 +579,7 @@ void KeyboardCapturer::CleanupWaylandPortal() { wayland_session_handle_.clear(); } -int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down, +int PlatformKeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, bool extended) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER @@ -641,7 +641,7 @@ int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down, #endif } -bool KeyboardCapturer::NotifyWaylandKeyboardKeysym(int keysym, uint32_t state) { +bool PlatformKeyboardCapturer::NotifyWaylandKeyboardKeysym(int keysym, uint32_t state) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER return SendWaylandPortalVoidCall( "NotifyKeyboardKeysym", [&](DBusMessageIter* iter) { @@ -661,7 +661,7 @@ bool KeyboardCapturer::NotifyWaylandKeyboardKeysym(int keysym, uint32_t state) { #endif } -bool KeyboardCapturer::NotifyWaylandKeyboardKeycode(int keycode, +bool PlatformKeyboardCapturer::NotifyWaylandKeyboardKeycode(int keycode, uint32_t state) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER return SendWaylandPortalVoidCall( @@ -682,7 +682,7 @@ bool KeyboardCapturer::NotifyWaylandKeyboardKeycode(int keycode, #endif } -bool KeyboardCapturer::SendWaylandPortalVoidCall( +bool PlatformKeyboardCapturer::SendWaylandPortalVoidCall( const char* method_name, const std::function& append_args) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER diff --git a/src/device_controller/linux_evdev_keycode.h b/apps/desktop/src/platform/linux/input/linux_evdev_keycode.h similarity index 100% rename from src/device_controller/linux_evdev_keycode.h rename to apps/desktop/src/platform/linux/input/linux_evdev_keycode.h diff --git a/src/device_controller/linux_keycode.h b/apps/desktop/src/platform/linux/input/linux_keycode.h similarity index 100% rename from src/device_controller/linux_keycode.h rename to apps/desktop/src/platform/linux/input/linux_keycode.h diff --git a/src/device_controller/mouse/linux/mouse_controller.cpp b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.cpp similarity index 87% rename from src/device_controller/mouse/linux/mouse_controller.cpp rename to apps/desktop/src/platform/linux/input/mouse/mouse_controller.cpp index 1105e17..c93a3b7 100644 --- a/src/device_controller/mouse/linux/mouse_controller.cpp +++ b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.cpp @@ -1,5 +1,7 @@ #include "mouse_controller.h" +#include + #include #include #include @@ -10,11 +12,11 @@ namespace crossdesk { -MouseController::MouseController() {} +PlatformMouseController::PlatformMouseController() {} -MouseController::~MouseController() { Destroy(); } +PlatformMouseController::~PlatformMouseController() { Destroy(); } -int MouseController::Init(std::vector display_info_list) { +int PlatformMouseController::Init(std::vector display_info_list) { display_info_list_ = display_info_list; if (IsWaylandSession()) { @@ -47,7 +49,7 @@ int MouseController::Init(std::vector display_info_list) { return 0; } -void MouseController::UpdateDisplayInfoList( +void PlatformMouseController::UpdateDisplayInfoList( const std::vector& display_info_list) { if (display_info_list.empty()) { return; @@ -66,7 +68,7 @@ void MouseController::UpdateDisplayInfoList( } } -int MouseController::Destroy() { +int PlatformMouseController::Destroy() { CleanupWaylandPortal(); if (display_) { @@ -77,7 +79,7 @@ int MouseController::Destroy() { return 0; } -int MouseController::SendMouseCommand(RemoteAction remote_action, +int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, int display_index) { if (remote_action.type != ControlType::mouse) { return 0; @@ -160,7 +162,7 @@ int MouseController::SendMouseCommand(RemoteAction remote_action, return 0; } -void MouseController::SetMousePosition(int x, int y) { +void PlatformMouseController::SetMousePosition(int x, int y) { if (!display_) { return; } @@ -168,7 +170,7 @@ void MouseController::SetMousePosition(int x, int y) { XFlush(display_); } -void MouseController::SimulateKeyDown(int kval) { +void PlatformMouseController::SimulateKeyDown(int kval) { if (!display_) { return; } @@ -176,7 +178,7 @@ void MouseController::SimulateKeyDown(int kval) { XFlush(display_); } -void MouseController::SimulateKeyUp(int kval) { +void PlatformMouseController::SimulateKeyUp(int kval) { if (!display_) { return; } @@ -184,7 +186,7 @@ void MouseController::SimulateKeyUp(int kval) { XFlush(display_); } -void MouseController::SimulateMouseWheel(int direction_button, int count) { +void PlatformMouseController::SimulateMouseWheel(int direction_button, int count) { if (!display_) { return; } diff --git a/src/device_controller/mouse/linux/mouse_controller.h b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.h similarity index 93% rename from src/device_controller/mouse/linux/mouse_controller.h rename to apps/desktop/src/platform/linux/input/mouse/mouse_controller.h index 0d19b92..e7a5f22 100644 --- a/src/device_controller/mouse/linux/mouse_controller.h +++ b/apps/desktop/src/platform/linux/input/mouse/mouse_controller.h @@ -12,6 +12,8 @@ #include #include +#include + #include "device_controller.h" #include "display_info.h" @@ -21,10 +23,10 @@ struct _XDisplay; namespace crossdesk { -class MouseController : public DeviceController { +class PlatformMouseController final : public MouseController { public: - MouseController(); - virtual ~MouseController(); + PlatformMouseController(); + virtual ~PlatformMouseController(); public: virtual int Init(std::vector display_info_list); diff --git a/src/device_controller/mouse/linux/mouse_controller_wayland.cpp b/apps/desktop/src/platform/linux/input/mouse/mouse_controller_wayland.cpp similarity index 98% rename from src/device_controller/mouse/linux/mouse_controller_wayland.cpp rename to apps/desktop/src/platform/linux/input/mouse/mouse_controller_wayland.cpp index 387b146..c68bffe 100644 --- a/src/device_controller/mouse/linux/mouse_controller_wayland.cpp +++ b/apps/desktop/src/platform/linux/input/mouse/mouse_controller_wayland.cpp @@ -1,4 +1,6 @@ #include + +#include #include #include #include @@ -17,7 +19,7 @@ namespace crossdesk { -void MouseController::OnWaylandDisplayInfoListUpdated() { +void PlatformMouseController::OnWaylandDisplayInfoListUpdated() { const uintptr_t stream0 = display_info_list_.empty() ? 0 @@ -535,7 +537,7 @@ bool SendPortalRequestAndHandleResponse( } // namespace #endif -bool MouseController::InitWaylandPortal() { +bool PlatformMouseController::InitWaylandPortal() { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER CleanupWaylandPortal(); @@ -871,7 +873,7 @@ bool MouseController::InitWaylandPortal() { #endif } -void MouseController::CleanupWaylandPortal() { +void PlatformMouseController::CleanupWaylandPortal() { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER if (using_shared_wayland_session_) { DBusConnection* close_connection = nullptr; @@ -908,7 +910,7 @@ void MouseController::CleanupWaylandPortal() { using_shared_wayland_session_ = false; } -int MouseController::SendWaylandMouseCommand(RemoteAction remote_action, +int PlatformMouseController::SendWaylandMouseCommand(RemoteAction remote_action, int display_index) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER if (!dbus_connection_ || wayland_session_handle_.empty()) { @@ -1095,7 +1097,7 @@ int MouseController::SendWaylandMouseCommand(RemoteAction remote_action, #endif } -bool MouseController::NotifyWaylandPointerMotion(double dx, double dy) { +bool PlatformMouseController::NotifyWaylandPointerMotion(double dx, double dy) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER return SendWaylandPortalVoidCall( "NotifyPointerMotion", [&](DBusMessageIter* iter) { @@ -1113,7 +1115,7 @@ bool MouseController::NotifyWaylandPointerMotion(double dx, double dy) { #endif } -bool MouseController::NotifyWaylandPointerMotionAbsolute(uint32_t stream, +bool PlatformMouseController::NotifyWaylandPointerMotionAbsolute(uint32_t stream, double x, double y) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER return SendWaylandPortalVoidCall( @@ -1135,7 +1137,7 @@ bool MouseController::NotifyWaylandPointerMotionAbsolute(uint32_t stream, #endif } -bool MouseController::NotifyWaylandPointerButton(int button, uint32_t state) { +bool PlatformMouseController::NotifyWaylandPointerButton(int button, uint32_t state) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER return SendWaylandPortalVoidCall( "NotifyPointerButton", [&](DBusMessageIter* iter) { @@ -1155,7 +1157,7 @@ bool MouseController::NotifyWaylandPointerButton(int button, uint32_t state) { #endif } -bool MouseController::NotifyWaylandPointerAxisDiscrete(uint32_t axis, +bool PlatformMouseController::NotifyWaylandPointerAxisDiscrete(uint32_t axis, int32_t steps) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER return SendWaylandPortalVoidCall( @@ -1176,7 +1178,7 @@ bool MouseController::NotifyWaylandPointerAxisDiscrete(uint32_t axis, #endif } -bool MouseController::SendWaylandPortalVoidCall( +bool PlatformMouseController::SendWaylandPortalVoidCall( const char* method_name, const std::function& append_args) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER diff --git a/apps/desktop/src/platform/linux/path_backend.cpp b/apps/desktop/src/platform/linux/path_backend.cpp new file mode 100644 index 0000000..899bbab --- /dev/null +++ b/apps/desktop/src/platform/linux/path_backend.cpp @@ -0,0 +1,51 @@ +#include "platform/path_backend.h" + +#include +#include + +#include +#include + +namespace crossdesk::platform { +namespace { + +std::filesystem::path Home() { + const char* value = std::getenv("HOME"); + return value ? std::filesystem::path(value) : std::filesystem::path(); +} + +std::filesystem::path EnvOrDefault(const char* name, + const std::filesystem::path& fallback) { + const char* value = std::getenv(name); + return value ? std::filesystem::path(value) : fallback; +} + +} // namespace + +std::filesystem::path GetExecutableDirectory() { + std::vector buffer(PATH_MAX); + while (true) { + const ssize_t length = + readlink("/proc/self/exe", buffer.data(), buffer.size() - 1); + if (length <= 0) return {}; + if (static_cast(length) < buffer.size() - 1) { + buffer[static_cast(length)] = '\0'; + return std::filesystem::path(buffer.data()).parent_path(); + } + buffer.resize(buffer.size() * 2); + } +} + +std::filesystem::path GetConfigPath(const std::string& app_name) { + return EnvOrDefault("XDG_CONFIG_HOME", Home() / ".config") / app_name; +} + +std::filesystem::path GetCachePath(const std::string& app_name) { + return EnvOrDefault("XDG_CACHE_HOME", Home() / ".cache") / app_name; +} + +std::filesystem::path GetLogPath(const std::string& app_name) { + return GetCachePath(app_name) / "logs"; +} + +} // namespace crossdesk::platform diff --git a/src/screen_capturer/linux/screen_capturer_drm.cpp b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_drm.cpp similarity index 99% rename from src/screen_capturer/linux/screen_capturer_drm.cpp rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_drm.cpp index d235367..fc4bc5f 100644 --- a/src/screen_capturer/linux/screen_capturer_drm.cpp +++ b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_drm.cpp @@ -26,7 +26,7 @@ #include #include -#include "display_stream_id.h" +#include #include "libyuv.h" #include "rd_log.h" diff --git a/src/screen_capturer/linux/screen_capturer_drm.h b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_drm.h similarity index 100% rename from src/screen_capturer/linux/screen_capturer_drm.h rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_drm.h diff --git a/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_factory.cpp b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_factory.cpp new file mode 100644 index 0000000..d7b7d44 --- /dev/null +++ b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_factory.cpp @@ -0,0 +1,11 @@ +#include "screen_capturer_factory.h" + +#include "screen_capturer_linux.h" + +namespace crossdesk { + +ScreenCapturer* ScreenCapturerFactory::Create() { + return new ScreenCapturerLinux(); +} + +} // namespace crossdesk diff --git a/src/screen_capturer/linux/screen_capturer_linux.cpp b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_linux.cpp similarity index 99% rename from src/screen_capturer/linux/screen_capturer_linux.cpp rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_linux.cpp index e708436..cba08f6 100644 --- a/src/screen_capturer/linux/screen_capturer_linux.cpp +++ b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_linux.cpp @@ -6,7 +6,7 @@ #include #include -#include "display_stream_id.h" +#include #include "platform.h" #include "rd_log.h" #if defined(CROSSDESK_HAS_DRM) && CROSSDESK_HAS_DRM diff --git a/src/screen_capturer/linux/screen_capturer_linux.h b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_linux.h similarity index 100% rename from src/screen_capturer/linux/screen_capturer_linux.h rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_linux.h diff --git a/src/screen_capturer/linux/screen_capturer_wayland.cpp b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland.cpp similarity index 100% rename from src/screen_capturer/linux/screen_capturer_wayland.cpp rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland.cpp diff --git a/src/screen_capturer/linux/screen_capturer_wayland.h b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland.h similarity index 100% rename from src/screen_capturer/linux/screen_capturer_wayland.h rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland.h diff --git a/src/screen_capturer/linux/screen_capturer_wayland_build.h b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_build.h similarity index 100% rename from src/screen_capturer/linux/screen_capturer_wayland_build.h rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_build.h diff --git a/src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_pipewire.cpp similarity index 99% rename from src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_pipewire.cpp index 7f12ec7..9f97fb3 100644 --- a/src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp +++ b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_pipewire.cpp @@ -1,4 +1,6 @@ #include "screen_capturer_wayland.h" + +#include #include "screen_capturer_wayland_build.h" #if CROSSDESK_WAYLAND_BUILD_ENABLED @@ -15,7 +17,7 @@ #include #include -#include "display_stream_id.h" +#include #include "libyuv.h" #include "linux_cursor_shape.h" #include "rd_log.h" diff --git a/src/screen_capturer/linux/screen_capturer_wayland_portal.cpp b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_portal.cpp similarity index 99% rename from src/screen_capturer/linux/screen_capturer_wayland_portal.cpp rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_portal.cpp index 32de28f..211e9dd 100644 --- a/src/screen_capturer/linux/screen_capturer_wayland_portal.cpp +++ b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_portal.cpp @@ -1,4 +1,6 @@ #include "screen_capturer_wayland.h" + +#include #include "screen_capturer_wayland_build.h" #include "wayland_portal_shared.h" diff --git a/src/screen_capturer/linux/screen_capturer_x11.cpp b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_x11.cpp similarity index 99% rename from src/screen_capturer/linux/screen_capturer_x11.cpp rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_x11.cpp index 243bd3c..48c2a93 100644 --- a/src/screen_capturer/linux/screen_capturer_x11.cpp +++ b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_x11.cpp @@ -10,7 +10,7 @@ #include #include -#include "display_stream_id.h" +#include #include "libyuv.h" #include "rd_log.h" diff --git a/src/screen_capturer/linux/screen_capturer_x11.h b/apps/desktop/src/platform/linux/screen_capturer/screen_capturer_x11.h similarity index 100% rename from src/screen_capturer/linux/screen_capturer_x11.h rename to apps/desktop/src/platform/linux/screen_capturer/screen_capturer_x11.h diff --git a/apps/desktop/src/platform/linux/speaker_capturer/speaker_capturer_factory.cpp b/apps/desktop/src/platform/linux/speaker_capturer/speaker_capturer_factory.cpp new file mode 100644 index 0000000..4c24b4c --- /dev/null +++ b/apps/desktop/src/platform/linux/speaker_capturer/speaker_capturer_factory.cpp @@ -0,0 +1,11 @@ +#include "speaker_capturer_factory.h" + +#include "speaker_capturer_linux.h" + +namespace crossdesk { + +SpeakerCapturer* SpeakerCapturerFactory::Create() { + return new SpeakerCapturerLinux(); +} + +} // namespace crossdesk diff --git a/src/speaker_capturer/linux/speaker_capturer_linux.cpp b/apps/desktop/src/platform/linux/speaker_capturer/speaker_capturer_linux.cpp similarity index 100% rename from src/speaker_capturer/linux/speaker_capturer_linux.cpp rename to apps/desktop/src/platform/linux/speaker_capturer/speaker_capturer_linux.cpp diff --git a/src/speaker_capturer/linux/speaker_capturer_linux.h b/apps/desktop/src/platform/linux/speaker_capturer/speaker_capturer_linux.h similarity index 100% rename from src/speaker_capturer/linux/speaker_capturer_linux.h rename to apps/desktop/src/platform/linux/speaker_capturer/speaker_capturer_linux.h diff --git a/apps/desktop/src/platform/linux/system_info.cpp b/apps/desktop/src/platform/linux/system_info.cpp new file mode 100644 index 0000000..0acd193 --- /dev/null +++ b/apps/desktop/src/platform/linux/system_info.cpp @@ -0,0 +1,81 @@ +#include "platform.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rd_log.h" + +namespace crossdesk { + +std::string GetMac() { + const int socket_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (socket_fd < 0) return {}; + + char buffer[1024] = {}; + struct ifconf config = {}; + config.ifc_len = sizeof(buffer); + config.ifc_buf = buffer; + if (ioctl(socket_fd, SIOCGIFCONF, &config) < 0) { + close(socket_fd); + return {}; + } + + std::string result; + struct ifreq request = {}; + struct ifreq* cursor = config.ifc_req; + const struct ifreq* end = cursor + config.ifc_len / sizeof(struct ifreq); + for (; cursor != end; ++cursor) { + std::strncpy(request.ifr_name, cursor->ifr_name, + sizeof(request.ifr_name) - 1); + if (ioctl(socket_fd, SIOCGIFFLAGS, &request) < 0 || + (request.ifr_flags & IFF_LOOPBACK) || + ioctl(socket_fd, SIOCGIFHWADDR, &request) < 0) { + continue; + } + char address[16] = {}; + int length = 0; + for (int index = 0; index < 6; ++index) { + length += std::snprintf( + address + length, sizeof(address) - length, "%.2X", + static_cast(request.ifr_hwaddr.sa_data[index])); + } + result = address; + break; + } + close(socket_fd); + return result; +} + +std::string GetHostName() { + char hostname[256] = {}; + if (gethostname(hostname, sizeof(hostname)) == -1) { + LOG_ERROR("gethostname failed"); + return {}; + } + return hostname; +} + +bool IsWaylandSession() { + const char* session_type = std::getenv("XDG_SESSION_TYPE"); + if (session_type) { + if (std::strcmp(session_type, "wayland") == 0 || + std::strcmp(session_type, "Wayland") == 0) { + return true; + } + if (std::strcmp(session_type, "x11") == 0 || + std::strcmp(session_type, "X11") == 0) { + return false; + } + } + const char* display = std::getenv("WAYLAND_DISPLAY"); + return display && display[0] != '\0'; +} + +} // namespace crossdesk diff --git a/apps/desktop/src/platform/macos/autostart.cpp b/apps/desktop/src/platform/macos/autostart.cpp new file mode 100644 index 0000000..52a4126 --- /dev/null +++ b/apps/desktop/src/platform/macos/autostart.cpp @@ -0,0 +1,74 @@ +#include "platform/autostart_backend.h" + +#include +#include + +#include +#include +#include + +namespace crossdesk::platform { +namespace { + +std::filesystem::path AutostartPath(const std::string& app_name) { + const char* home = std::getenv("HOME"); + if (!home || !home[0]) return {}; + return std::filesystem::path(home) / "Library" / "LaunchAgents" / + (app_name + ".plist"); +} + +} // namespace + +std::string GetAutostartExecutablePath() { + char path[PATH_MAX] = {}; + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) != 0) return {}; + char resolved[PATH_MAX] = {}; + return realpath(path, resolved) ? std::string(resolved) : std::string(path); +} + +bool EnableAutostart(const std::string& app_name, + const std::string& executable_path) { + const std::filesystem::path path = AutostartPath(app_name); + if (path.empty()) return false; + + std::error_code error; + std::filesystem::create_directories(path.parent_path(), error); + if (error) return false; + + std::ofstream file(path); + if (!file.is_open()) return false; + file << R"( + + + + Label + )" + << app_name << R"( + ProgramArguments + + )" + << executable_path << R"( + + RunAtLoad + + +)"; + file.close(); + return file.good(); +} + +bool DisableAutostart(const std::string& app_name) { + const std::filesystem::path path = AutostartPath(app_name); + if (path.empty()) return false; + std::error_code error; + return std::filesystem::remove(path, error) && !error; +} + +bool IsAutostartEnabled(const std::string& app_name) { + const std::filesystem::path path = AutostartPath(app_name); + return !path.empty() && std::filesystem::is_regular_file(path); +} + +} // namespace crossdesk::platform diff --git a/src/tools/clipboard_mac.mm b/apps/desktop/src/platform/macos/clipboard.mm similarity index 86% rename from src/tools/clipboard_mac.mm rename to apps/desktop/src/platform/macos/clipboard.mm index d488a0f..6d3fa68 100644 --- a/src/tools/clipboard_mac.mm +++ b/apps/desktop/src/platform/macos/clipboard.mm @@ -8,20 +8,13 @@ #include #include -#include -#include #include -#include +#include "platform/clipboard_backend.h" #include "rd_log.h" namespace crossdesk { -extern std::atomic g_monitoring; -extern std::mutex g_monitor_mutex; -extern std::string g_last_clipboard_text; -extern Clipboard::OnClipboardChanged g_on_clipboard_changed; - static CFRunLoopRef g_monitor_runloop = nullptr; std::string Clipboard::GetText() { @@ -57,9 +50,9 @@ bool Clipboard::HasText() { } } -extern void HandleClipboardChange(); +namespace platform { -void StartMacOSClipboardMonitoring() { +void RunClipboardMonitor(int) { @autoreleasepool { NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; @@ -82,7 +75,7 @@ void StartMacOSClipboardMonitoring() { [NSTimer scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer* timer) { - if (!g_monitoring.load()) { + if (!ClipboardMonitoring()) { [timer invalidate]; return; } @@ -93,7 +86,7 @@ void StartMacOSClipboardMonitoring() { } }]; - while (g_monitoring.load()) { + while (ClipboardMonitoring()) { @autoreleasepool { NSDate* date = [NSDate dateWithTimeIntervalSinceNow:0.1]; [runLoop runMode:NSDefaultRunLoopMode beforeDate:date]; @@ -109,12 +102,13 @@ void StartMacOSClipboardMonitoring() { } } -void StopMacOSClipboardMonitoring() { - // Wake up the RunLoop immediately so it can check g_monitoring and exit +void WakeClipboardMonitor() { + // Wake up the RunLoop immediately so it can observe monitoring shutdown. // This ensures the RunLoop exits promptly instead of waiting up to 0.1 seconds if (g_monitor_runloop) { CFRunLoopWakeUp(g_monitor_runloop); } } +} // namespace platform } // namespace crossdesk diff --git a/apps/desktop/src/platform/macos/daemon_backend.cpp b/apps/desktop/src/platform/macos/daemon_backend.cpp new file mode 100644 index 0000000..23482e7 --- /dev/null +++ b/apps/desktop/src/platform/macos/daemon_backend.cpp @@ -0,0 +1,20 @@ +#include "platform/daemon_backend.h" + +#include +#include + +#include + +namespace crossdesk::platform { + +bool PrepareDaemon() { return true; } + +std::string GetDaemonExecutablePath() { + char path[PATH_MAX] = {}; + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) != 0) return {}; + char resolved[PATH_MAX] = {}; + return realpath(path, resolved) ? std::string(resolved) : std::string(path); +} + +} // namespace crossdesk::platform diff --git a/src/gui/platform/metal_video_renderer.h b/apps/desktop/src/platform/macos/gui/metal_video_renderer.h similarity index 100% rename from src/gui/platform/metal_video_renderer.h rename to apps/desktop/src/platform/macos/gui/metal_video_renderer.h diff --git a/src/gui/platform/metal_video_renderer.mm b/apps/desktop/src/platform/macos/gui/metal_video_renderer.mm similarity index 99% rename from src/gui/platform/metal_video_renderer.mm rename to apps/desktop/src/platform/macos/gui/metal_video_renderer.mm index e3e96ba..b789a88 100644 --- a/src/gui/platform/metal_video_renderer.mm +++ b/apps/desktop/src/platform/macos/gui/metal_video_renderer.mm @@ -1,4 +1,4 @@ -#include "platform/metal_video_renderer.h" +#include "platform/macos/gui/metal_video_renderer.h" #import #import diff --git a/src/gui/runtime/cursor_state_provider_mac.mm b/apps/desktop/src/platform/macos/gui/runtime/cursor_state_provider_mac.mm similarity index 99% rename from src/gui/runtime/cursor_state_provider_mac.mm rename to apps/desktop/src/platform/macos/gui/runtime/cursor_state_provider_mac.mm index a86cf2f..cbf9c46 100644 --- a/src/gui/runtime/cursor_state_provider_mac.mm +++ b/apps/desktop/src/platform/macos/gui/runtime/cursor_state_provider_mac.mm @@ -1,5 +1,7 @@ #include "runtime/cursor_state_provider.h" +#include + #if defined(__APPLE__) #import diff --git a/src/gui/runtime/mac_permission_runtime.mm b/apps/desktop/src/platform/macos/gui/runtime/mac_permission_runtime.mm similarity index 100% rename from src/gui/runtime/mac_permission_runtime.mm rename to apps/desktop/src/platform/macos/gui/runtime/mac_permission_runtime.mm diff --git a/src/gui/platform/tray/mac_tray.h b/apps/desktop/src/platform/macos/gui/tray/mac_tray.h similarity index 100% rename from src/gui/platform/tray/mac_tray.h rename to apps/desktop/src/platform/macos/gui/tray/mac_tray.h diff --git a/src/gui/platform/tray/mac_tray.mm b/apps/desktop/src/platform/macos/gui/tray/mac_tray.mm similarity index 97% rename from src/gui/platform/tray/mac_tray.mm rename to apps/desktop/src/platform/macos/gui/tray/mac_tray.mm index 1956b0c..de33941 100644 --- a/src/gui/platform/tray/mac_tray.mm +++ b/apps/desktop/src/platform/macos/gui/tray/mac_tray.mm @@ -214,7 +214,7 @@ struct MacTrayImpl { [candidate_paths addObject:[current_directory stringByAppendingPathComponent: - @"icons/macos/crossdesk.icns"]]; + @"apps/desktop/resources/macos/crossdesk.icns"]]; const char *base_path = SDL_GetBasePath(); if (base_path && base_path[0] != '\0') { @@ -222,11 +222,12 @@ struct MacTrayImpl { [candidate_paths addObject:[base_directory stringByAppendingPathComponent: - @"icons/macos/crossdesk.icns"]]; + @"apps/desktop/resources/macos/crossdesk.icns"]]; [candidate_paths addObject:[base_directory stringByAppendingPathComponent: - @"../../../../icons/macos/crossdesk.icns"]]; + @"../../../../apps/desktop/resources/macos/" + "crossdesk.icns"]]; } for (NSString *candidate_path in candidate_paths) { diff --git a/src/gui/platform/video_renderer_factory_metal.mm b/apps/desktop/src/platform/macos/gui/video_renderer_factory_metal.mm similarity index 80% rename from src/gui/platform/video_renderer_factory_metal.mm rename to apps/desktop/src/platform/macos/gui/video_renderer_factory_metal.mm index 27dfbdd..23e2665 100644 --- a/src/gui/platform/video_renderer_factory_metal.mm +++ b/apps/desktop/src/platform/macos/gui/video_renderer_factory_metal.mm @@ -2,7 +2,7 @@ #include -#include "platform/metal_video_renderer.h" +#include "platform/macos/gui/metal_video_renderer.h" namespace crossdesk { diff --git a/src/gui/views/windows/request_permission_window.mm b/apps/desktop/src/platform/macos/gui/views/request_permission_window.mm similarity index 100% rename from src/gui/views/windows/request_permission_window.mm rename to apps/desktop/src/platform/macos/gui/views/request_permission_window.mm diff --git a/src/gui/platform/window_drag_mac.mm b/apps/desktop/src/platform/macos/gui/window_drag_mac.mm similarity index 100% rename from src/gui/platform/window_drag_mac.mm rename to apps/desktop/src/platform/macos/gui/window_drag_mac.mm diff --git a/src/common/window_util_mac.h b/apps/desktop/src/platform/macos/gui/window_util_mac.h similarity index 100% rename from src/common/window_util_mac.h rename to apps/desktop/src/platform/macos/gui/window_util_mac.h diff --git a/src/common/window_util_mac.mm b/apps/desktop/src/platform/macos/gui/window_util_mac.mm similarity index 100% rename from src/common/window_util_mac.mm rename to apps/desktop/src/platform/macos/gui/window_util_mac.mm diff --git a/apps/desktop/src/platform/macos/input/device_controller_factory.cpp b/apps/desktop/src/platform/macos/input/device_controller_factory.cpp new file mode 100644 index 0000000..24c4b1f --- /dev/null +++ b/apps/desktop/src/platform/macos/input/device_controller_factory.cpp @@ -0,0 +1,19 @@ +#include "device_controller_factory.h" + +#include "keyboard_capturer.h" +#include "mouse_controller.h" + +namespace crossdesk { + +DeviceController* DeviceControllerFactory::Create(Device device) { + switch (device) { + case Mouse: + return new PlatformMouseController(); + case Keyboard: + return new PlatformKeyboardCapturer(); + default: + return nullptr; + } +} + +} // namespace crossdesk diff --git a/src/device_controller/keyboard/mac/keyboard_capturer.cpp b/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.cpp similarity index 95% rename from src/device_controller/keyboard/mac/keyboard_capturer.cpp rename to apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.cpp index d87c6b4..ae9acf6 100644 --- a/src/device_controller/keyboard/mac/keyboard_capturer.cpp +++ b/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.cpp @@ -113,7 +113,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, return event; } - KeyboardCapturer* keyboard_capturer = (KeyboardCapturer*)userInfo; + PlatformKeyboardCapturer* keyboard_capturer = (PlatformKeyboardCapturer*)userInfo; if (!keyboard_capturer) { LOG_ERROR("keyboard_capturer is nullptr"); return event; @@ -181,12 +181,12 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, return nullptr; } -KeyboardCapturer::KeyboardCapturer() +PlatformKeyboardCapturer::PlatformKeyboardCapturer() : event_tap_(nullptr), run_loop_source_(nullptr) {} -KeyboardCapturer::~KeyboardCapturer() { Unhook(); } +PlatformKeyboardCapturer::~PlatformKeyboardCapturer() { Unhook(); } -int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { +int PlatformKeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { if (event_tap_) { return 0; } @@ -231,7 +231,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { return 0; } -int KeyboardCapturer::Unhook() { +int PlatformKeyboardCapturer::Unhook() { g_unmapped_keycode_to_vk.clear(); g_on_key_action = nullptr; g_user_ptr = nullptr; @@ -292,7 +292,7 @@ CGEventFlags ToCGEventFlags(uint32_t injected_flags) { return flags; } -int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, +int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, bool extended) { (void)scan_code; (void)extended; diff --git a/src/device_controller/keyboard/mac/keyboard_capturer.h b/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.h similarity index 87% rename from src/device_controller/keyboard/mac/keyboard_capturer.h rename to apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.h index 0312c18..896299d 100644 --- a/src/device_controller/keyboard/mac/keyboard_capturer.h +++ b/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.h @@ -14,10 +14,10 @@ namespace crossdesk { -class KeyboardCapturer : public DeviceController { +class PlatformKeyboardCapturer final : public KeyboardCapturer { public: - KeyboardCapturer(); - virtual ~KeyboardCapturer(); + PlatformKeyboardCapturer(); + virtual ~PlatformKeyboardCapturer(); public: virtual int Hook(OnKeyAction on_key_action, void* user_ptr); diff --git a/src/device_controller/macos_keyboard_modifier_state.h b/apps/desktop/src/platform/macos/input/macos_keyboard_modifier_state.h similarity index 100% rename from src/device_controller/macos_keyboard_modifier_state.h rename to apps/desktop/src/platform/macos/input/macos_keyboard_modifier_state.h diff --git a/src/device_controller/macos_keycode.h b/apps/desktop/src/platform/macos/input/macos_keycode.h similarity index 100% rename from src/device_controller/macos_keycode.h rename to apps/desktop/src/platform/macos/input/macos_keycode.h diff --git a/src/device_controller/mouse/mac/mouse_controller.cpp b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.cpp similarity index 93% rename from src/device_controller/mouse/mac/mouse_controller.cpp rename to apps/desktop/src/platform/macos/input/mouse/mouse_controller.cpp index 0006c20..97b03e4 100644 --- a/src/device_controller/mouse/mac/mouse_controller.cpp +++ b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.cpp @@ -1,5 +1,7 @@ #include "mouse_controller.h" +#include + #include #include @@ -33,26 +35,26 @@ void SetClickState(CGEventRef event, int click_state) { } // namespace -MouseController::MouseController() {} +PlatformMouseController::PlatformMouseController() {} -MouseController::~MouseController() {} +PlatformMouseController::~PlatformMouseController() {} -int MouseController::Init(std::vector display_info_list) { +int PlatformMouseController::Init(std::vector display_info_list) { display_info_list_ = display_info_list; return 0; } -int MouseController::Destroy() { return 0; } +int PlatformMouseController::Destroy() { return 0; } -void MouseController::UpdateDisplayInfoList( +void PlatformMouseController::UpdateDisplayInfoList( const std::vector& display_info_list) { if (!display_info_list.empty()) { display_info_list_ = display_info_list; } } -int MouseController::BeginClick(ClickTracker& tracker, int x, int y) { +int PlatformMouseController::BeginClick(ClickTracker& tracker, int x, int y) { const auto now = std::chrono::steady_clock::now(); const bool continues_previous_click = tracker.has_last_down && @@ -71,7 +73,7 @@ int MouseController::BeginClick(ClickTracker& tracker, int x, int y) { return tracker.active_click_state; } -int MouseController::EndClick(ClickTracker& tracker, int x, int y) { +int PlatformMouseController::EndClick(ClickTracker& tracker, int x, int y) { const int click_state = tracker.active_click_state; if (!IsWithinClickDistance(tracker.last_down_x, tracker.last_down_y, x, y)) { tracker.has_last_down = false; @@ -82,7 +84,7 @@ int MouseController::EndClick(ClickTracker& tracker, int x, int y) { return click_state; } -int MouseController::SendMouseCommand(RemoteAction remote_action, +int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, int display_index) { if (remote_action.type != ControlType::mouse) { return 0; diff --git a/src/device_controller/mouse/mac/mouse_controller.h b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h similarity index 88% rename from src/device_controller/mouse/mac/mouse_controller.h rename to apps/desktop/src/platform/macos/input/mouse/mouse_controller.h index ba1b7dc..c184246 100644 --- a/src/device_controller/mouse/mac/mouse_controller.h +++ b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h @@ -10,15 +10,17 @@ #include #include +#include + #include "device_controller.h" #include "display_info.h" namespace crossdesk { -class MouseController : public DeviceController { +class PlatformMouseController final : public MouseController { public: - MouseController(); - virtual ~MouseController(); + PlatformMouseController(); + virtual ~PlatformMouseController(); public: virtual int Init(std::vector display_info_list); diff --git a/apps/desktop/src/platform/macos/path_backend.cpp b/apps/desktop/src/platform/macos/path_backend.cpp new file mode 100644 index 0000000..7381754 --- /dev/null +++ b/apps/desktop/src/platform/macos/path_backend.cpp @@ -0,0 +1,48 @@ +#include "platform/path_backend.h" + +#include + +#include +#include + +namespace crossdesk::platform { +namespace { + +std::filesystem::path Home() { + const char* value = std::getenv("HOME"); + return value ? std::filesystem::path(value) : std::filesystem::path(); +} + +std::filesystem::path EnvOrDefault(const char* name, + const std::filesystem::path& fallback) { + const char* value = std::getenv(name); + return value ? std::filesystem::path(value) : fallback; +} + +} // namespace + +std::filesystem::path GetExecutableDirectory() { + uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + std::vector buffer(size + 1); + if (_NSGetExecutablePath(buffer.data(), &size) != 0) return {}; + std::error_code error; + std::filesystem::path executable = + std::filesystem::weakly_canonical(buffer.data(), error); + if (error) executable = buffer.data(); + return executable.parent_path(); +} + +std::filesystem::path GetConfigPath(const std::string& app_name) { + return EnvOrDefault("XDG_CONFIG_HOME", Home() / ".config") / app_name; +} + +std::filesystem::path GetCachePath(const std::string& app_name) { + return EnvOrDefault("XDG_CACHE_HOME", Home() / ".cache") / app_name; +} + +std::filesystem::path GetLogPath(const std::string& app_name) { + return Home() / "Library" / "Logs" / app_name; +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/macos/screen_capturer/screen_capturer_factory.cpp b/apps/desktop/src/platform/macos/screen_capturer/screen_capturer_factory.cpp new file mode 100644 index 0000000..f8f2701 --- /dev/null +++ b/apps/desktop/src/platform/macos/screen_capturer/screen_capturer_factory.cpp @@ -0,0 +1,11 @@ +#include "screen_capturer_factory.h" + +#include "screen_capturer_sck.h" + +namespace crossdesk { + +ScreenCapturer* ScreenCapturerFactory::Create() { + return new ScreenCapturerSck(); +} + +} // namespace crossdesk diff --git a/src/screen_capturer/macosx/screen_capturer_sck.cpp b/apps/desktop/src/platform/macos/screen_capturer/screen_capturer_sck.cpp similarity index 100% rename from src/screen_capturer/macosx/screen_capturer_sck.cpp rename to apps/desktop/src/platform/macos/screen_capturer/screen_capturer_sck.cpp diff --git a/src/screen_capturer/macosx/screen_capturer_sck.h b/apps/desktop/src/platform/macos/screen_capturer/screen_capturer_sck.h similarity index 100% rename from src/screen_capturer/macosx/screen_capturer_sck.h rename to apps/desktop/src/platform/macos/screen_capturer/screen_capturer_sck.h diff --git a/src/screen_capturer/macosx/screen_capturer_sck_impl.mm b/apps/desktop/src/platform/macos/screen_capturer/screen_capturer_sck_impl.mm similarity index 99% rename from src/screen_capturer/macosx/screen_capturer_sck_impl.mm rename to apps/desktop/src/platform/macos/screen_capturer/screen_capturer_sck_impl.mm index 9d217ad..304e34c 100644 --- a/src/screen_capturer/macosx/screen_capturer_sck_impl.mm +++ b/apps/desktop/src/platform/macos/screen_capturer/screen_capturer_sck_impl.mm @@ -24,7 +24,7 @@ #include #include #include "display_info.h" -#include "display_stream_id.h" +#include #include "rd_log.h" using namespace crossdesk; diff --git a/apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_factory.cpp b/apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_factory.cpp new file mode 100644 index 0000000..3d8f51e --- /dev/null +++ b/apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_factory.cpp @@ -0,0 +1,11 @@ +#include "speaker_capturer_factory.h" + +#include "speaker_capturer_macosx.h" + +namespace crossdesk { + +SpeakerCapturer* SpeakerCapturerFactory::Create() { + return new SpeakerCapturerMacosx(); +} + +} // namespace crossdesk diff --git a/src/speaker_capturer/macosx/speaker_capturer_macosx.cpp b/apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_macosx.cpp similarity index 100% rename from src/speaker_capturer/macosx/speaker_capturer_macosx.cpp rename to apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_macosx.cpp diff --git a/src/speaker_capturer/macosx/speaker_capturer_macosx.h b/apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_macosx.h similarity index 100% rename from src/speaker_capturer/macosx/speaker_capturer_macosx.h rename to apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_macosx.h diff --git a/src/speaker_capturer/macosx/speaker_capturer_macosx.mm b/apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_macosx.mm similarity index 100% rename from src/speaker_capturer/macosx/speaker_capturer_macosx.mm rename to apps/desktop/src/platform/macos/speaker_capturer/speaker_capturer_macosx.mm diff --git a/apps/desktop/src/platform/macos/system_info.cpp b/apps/desktop/src/platform/macos/system_info.cpp new file mode 100644 index 0000000..0373c35 --- /dev/null +++ b/apps/desktop/src/platform/macos/system_info.cpp @@ -0,0 +1,55 @@ +#include "platform.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "rd_log.h" + +namespace crossdesk { + +std::string GetMac() { + struct ifaddrs* addresses = nullptr; + if (getifaddrs(&addresses) != 0) return {}; + + std::string result; + for (struct ifaddrs* cursor = addresses; cursor; cursor = cursor->ifa_next) { + if (!cursor->ifa_addr || cursor->ifa_addr->sa_family != AF_LINK || + std::strcmp(cursor->ifa_name, "en0") != 0) { + continue; + } + const auto* link = + reinterpret_cast(cursor->ifa_addr); + if (link->sdl_type != IFT_ETHER) continue; + const auto* bytes = reinterpret_cast( + &link->sdl_data[link->sdl_nlen]); + char address[16] = {}; + int length = 0; + for (int index = 0; index < link->sdl_alen; ++index) { + length += std::snprintf(address + length, sizeof(address) - length, + "%.2X", bytes[index]); + } + result = address; + break; + } + freeifaddrs(addresses); + return result; +} + +std::string GetHostName() { + char hostname[256] = {}; + if (gethostname(hostname, sizeof(hostname)) == -1) { + LOG_ERROR("gethostname failed"); + return {}; + } + return hostname; +} + +bool IsWaylandSession() { return false; } + +} // namespace crossdesk diff --git a/apps/desktop/src/platform/path_backend.h b/apps/desktop/src/platform/path_backend.h new file mode 100644 index 0000000..c2d3ca9 --- /dev/null +++ b/apps/desktop/src/platform/path_backend.h @@ -0,0 +1,16 @@ +#ifndef CROSSDESK_PLATFORM_PATH_BACKEND_H_ +#define CROSSDESK_PLATFORM_PATH_BACKEND_H_ + +#include +#include + +namespace crossdesk::platform { + +std::filesystem::path GetExecutableDirectory(); +std::filesystem::path GetConfigPath(const std::string& app_name); +std::filesystem::path GetCachePath(const std::string& app_name); +std::filesystem::path GetLogPath(const std::string& app_name); + +} // namespace crossdesk::platform + +#endif // CROSSDESK_PLATFORM_PATH_BACKEND_H_ diff --git a/src/gui/platform/video_renderer.h b/apps/desktop/src/platform/video_renderer.h similarity index 100% rename from src/gui/platform/video_renderer.h rename to apps/desktop/src/platform/video_renderer.h diff --git a/src/gui/platform/window_drag.h b/apps/desktop/src/platform/window_drag.h similarity index 100% rename from src/gui/platform/window_drag.h rename to apps/desktop/src/platform/window_drag.h diff --git a/apps/desktop/src/platform/windows/autostart.cpp b/apps/desktop/src/platform/windows/autostart.cpp new file mode 100644 index 0000000..43eb666 --- /dev/null +++ b/apps/desktop/src/platform/windows/autostart.cpp @@ -0,0 +1,69 @@ +#include "platform/autostart_backend.h" + +#include + +#include +#include + +namespace crossdesk::platform { +namespace { + +constexpr const char* kRunKey = + "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; + +} // namespace + +std::string GetAutostartExecutablePath() { + char path[32768] = {}; + const DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path)); + return length > 0 && length < sizeof(path) ? std::string(path, length) + : std::string(); +} + +bool EnableAutostart(const std::string& app_name, + const std::string& executable_path) { + if (!std::filesystem::exists(executable_path)) return false; + + HKEY key = nullptr; + if (RegOpenKeyExA(HKEY_CURRENT_USER, kRunKey, 0, KEY_WRITE, &key) != + ERROR_SUCCESS) { + return false; + } + + std::string value = executable_path; + if (value.find(' ') != std::string::npos && + (value.front() != '"' || value.back() != '"')) { + value = "\"" + value + "\""; + } + const LONG result = RegSetValueExA( + key, app_name.c_str(), 0, REG_SZ, + reinterpret_cast(value.c_str()), + static_cast(value.size() + 1)); + RegCloseKey(key); + return result == ERROR_SUCCESS; +} + +bool DisableAutostart(const std::string& app_name) { + HKEY key = nullptr; + if (RegOpenKeyExA(HKEY_CURRENT_USER, kRunKey, 0, KEY_WRITE, &key) != + ERROR_SUCCESS) { + return false; + } + const LONG result = RegDeleteValueA(key, app_name.c_str()); + RegCloseKey(key); + return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND; +} + +bool IsAutostartEnabled(const std::string& app_name) { + HKEY key = nullptr; + if (RegOpenKeyExA(HKEY_CURRENT_USER, kRunKey, 0, KEY_READ, &key) != + ERROR_SUCCESS) { + return false; + } + const LONG result = + RegQueryValueExA(key, app_name.c_str(), nullptr, nullptr, nullptr, nullptr); + RegCloseKey(key); + return result == ERROR_SUCCESS; +} + +} // namespace crossdesk::platform diff --git a/apps/desktop/src/platform/windows/clipboard.cpp b/apps/desktop/src/platform/windows/clipboard.cpp new file mode 100644 index 0000000..1038190 --- /dev/null +++ b/apps/desktop/src/platform/windows/clipboard.cpp @@ -0,0 +1,139 @@ +#include "clipboard.h" + +#include + +#include + +#include "platform/clipboard_backend.h" +#include "rd_log.h" + +namespace crossdesk { +namespace { + +HWND g_clipboard_window = nullptr; +constexpr const char* kClipboardWindowClass = "CrossDeskClipboardMonitor"; + +LRESULT CALLBACK ClipboardWindowProc(HWND window, UINT message, WPARAM wparam, + LPARAM lparam) { + if (message == WM_CLIPBOARDUPDATE) { + platform::HandleClipboardChange(); + return 0; + } + return DefWindowProc(window, message, wparam, lparam); +} + +} // namespace + +std::string Clipboard::GetText() { + if (!OpenClipboard(nullptr)) { + LOG_ERROR("Clipboard::GetText: failed to open clipboard"); + return {}; + } + + std::string result; + HANDLE data = GetClipboardData(CF_UNICODETEXT); + if (data) { + const wchar_t* text = static_cast(GlobalLock(data)); + if (text) { + const int size = + WideCharToMultiByte(CP_UTF8, 0, text, -1, nullptr, 0, nullptr, nullptr); + if (size > 1) { + result.resize(static_cast(size)); + WideCharToMultiByte(CP_UTF8, 0, text, -1, result.data(), size, + nullptr, nullptr); + result.resize(static_cast(size - 1)); + } + GlobalUnlock(data); + } + } + CloseClipboard(); + return result; +} + +bool Clipboard::SetText(const std::string& text) { + if (!OpenClipboard(nullptr)) { + LOG_ERROR("Clipboard::SetText: failed to open clipboard"); + return false; + } + if (!EmptyClipboard()) { + LOG_ERROR("Clipboard::SetText: failed to empty clipboard"); + CloseClipboard(); + return false; + } + + const int size = + MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, nullptr, 0); + HGLOBAL memory = + size > 0 ? GlobalAlloc(GMEM_MOVEABLE, size * sizeof(wchar_t)) : nullptr; + if (!memory) { + LOG_ERROR("Clipboard::SetText: failed to allocate memory"); + CloseClipboard(); + return false; + } + + wchar_t* output = static_cast(GlobalLock(memory)); + if (!output) { + GlobalFree(memory); + CloseClipboard(); + return false; + } + MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, output, size); + GlobalUnlock(memory); + if (!SetClipboardData(CF_UNICODETEXT, memory)) { + GlobalFree(memory); + CloseClipboard(); + return false; + } + CloseClipboard(); + return true; +} + +bool Clipboard::HasText() { + if (!OpenClipboard(nullptr)) return false; + const bool result = IsClipboardFormatAvailable(CF_UNICODETEXT) || + IsClipboardFormatAvailable(CF_TEXT); + CloseClipboard(); + return result; +} + +namespace platform { + +void RunClipboardMonitor(int) { + WNDCLASSA window_class = {}; + window_class.lpfnWndProc = ClipboardWindowProc; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.lpszClassName = kClipboardWindowClass; + RegisterClassA(&window_class); + + g_clipboard_window = CreateWindowA( + kClipboardWindowClass, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, + nullptr, nullptr); + if (!g_clipboard_window || + !AddClipboardFormatListener(g_clipboard_window)) { + LOG_ERROR("Failed to initialize clipboard monitor window"); + if (g_clipboard_window) DestroyWindow(g_clipboard_window); + g_clipboard_window = nullptr; + return; + } + + LOG_INFO("Clipboard event monitoring started (Windows)"); + MSG message = {}; + while (ClipboardMonitoring()) { + const BOOL result = GetMessage(&message, nullptr, 0, 0); + if (result == 0 || result == -1) break; + TranslateMessage(&message); + DispatchMessage(&message); + } + + RemoveClipboardFormatListener(g_clipboard_window); + DestroyWindow(g_clipboard_window); + g_clipboard_window = nullptr; + UnregisterClassA(kClipboardWindowClass, GetModuleHandle(nullptr)); +} + +void WakeClipboardMonitor() { + if (g_clipboard_window) PostMessage(g_clipboard_window, WM_QUIT, 0, 0); +} + +} // namespace platform +} // namespace crossdesk diff --git a/apps/desktop/src/platform/windows/daemon_backend.cpp b/apps/desktop/src/platform/windows/daemon_backend.cpp new file mode 100644 index 0000000..8d05cb8 --- /dev/null +++ b/apps/desktop/src/platform/windows/daemon_backend.cpp @@ -0,0 +1,64 @@ +#include "platform/daemon_backend.h" + +#include + +#include +#include +#include +#include + +namespace crossdesk::platform { +namespace { + +std::atomic g_stop_requested{false}; + +} // namespace + +void ResetDaemonStop() { g_stop_requested.store(false); } + +void RequestDaemonStop() { g_stop_requested.store(true); } + +bool DaemonStopRequested() { return g_stop_requested.load(); } + +bool PrepareDaemon() { return true; } + +std::string GetDaemonExecutablePath() { + char path[32768] = {}; + const DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path)); + return length > 0 && length < sizeof(path) ? std::string(path, length) + : std::string(); +} + +DaemonChildResult RunDaemonChild(const std::string& executable_path, + const std::function& keep_running) { + DaemonChildResult result; + STARTUPINFOA startup = {sizeof(startup)}; + PROCESS_INFORMATION process = {}; + std::string command = "\"" + executable_path + "\" --child"; + std::vector command_buffer(command.begin(), command.end()); + command_buffer.push_back('\0'); + if (!CreateProcessA(nullptr, command_buffer.data(), nullptr, nullptr, FALSE, + 0, nullptr, nullptr, &startup, &process)) { + return result; + } + + result.started = true; + while (keep_running()) { + const DWORD wait_result = WaitForSingleObject(process.hProcess, 200); + if (wait_result == WAIT_OBJECT_0 || wait_result == WAIT_FAILED) break; + } + if (!keep_running()) { + TerminateProcess(process.hProcess, 1); + WaitForSingleObject(process.hProcess, 3000); + } + + DWORD exit_code = 0; + GetExitCodeProcess(process.hProcess, &exit_code); + result.exit_code = static_cast(exit_code); + result.normal_exit = exit_code == 0; + CloseHandle(process.hProcess); + CloseHandle(process.hThread); + return result; +} + +} // namespace crossdesk::platform diff --git a/src/gui/application/portable_service_integration.cpp b/apps/desktop/src/platform/windows/gui/application/portable_service_integration.cpp similarity index 100% rename from src/gui/application/portable_service_integration.cpp rename to apps/desktop/src/platform/windows/gui/application/portable_service_integration.cpp diff --git a/src/gui/runtime/windows_service_runtime.cpp b/apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp similarity index 87% rename from src/gui/runtime/windows_service_runtime.cpp rename to apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp index 84a7384..9d3f1a5 100644 --- a/src/gui/runtime/windows_service_runtime.cpp +++ b/apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp @@ -1,5 +1,7 @@ #include "runtime/gui_runtime.h" +#include + #include #include #include @@ -87,39 +89,6 @@ bool QueryWindowsServiceInteractiveStatus( #endif } // namespace -void GuiRuntime::ResetRemoteServiceStatus(RemoteSession &props) { - props.remote_service_status_received_ = false; - props.remote_service_available_ = false; - props.remote_interactive_stage_.clear(); -} - -void GuiRuntime::ApplyRemoteServiceStatus(RemoteSession &props, - const ServiceStatus &status) { - props.remote_service_status_received_ = true; - props.remote_service_available_ = status.available; - props.remote_interactive_stage_ = status.interactive_stage; -} - -GuiRuntime::RemoteUnlockState -GuiRuntime::GetRemoteUnlockState(const RemoteSession &props) const { - if (!props.remote_service_status_received_) { - return RemoteUnlockState::none; - } - if (!props.remote_service_available_) { - return RemoteUnlockState::service_unavailable; - } - if (props.remote_interactive_stage_ == "credential-ui") { - return RemoteUnlockState::credential_ui; - } - if (props.remote_interactive_stage_ == "lock-screen") { - return RemoteUnlockState::lock_screen; - } - if (props.remote_interactive_stage_ == "secure-desktop") { - return RemoteUnlockState::secure_desktop; - } - return RemoteUnlockState::none; -} - void GuiRuntime::HandleWindowsServiceIntegration() { #if _WIN32 static bool last_logged_service_available = true; diff --git a/src/gui/platform/tray/win_tray.cpp b/apps/desktop/src/platform/windows/gui/tray/win_tray.cpp similarity index 100% rename from src/gui/platform/tray/win_tray.cpp rename to apps/desktop/src/platform/windows/gui/tray/win_tray.cpp diff --git a/src/gui/platform/tray/win_tray.h b/apps/desktop/src/platform/windows/gui/tray/win_tray.h similarity index 100% rename from src/gui/platform/tray/win_tray.h rename to apps/desktop/src/platform/windows/gui/tray/win_tray.h diff --git a/apps/desktop/src/platform/windows/input/device_controller_factory.cpp b/apps/desktop/src/platform/windows/input/device_controller_factory.cpp new file mode 100644 index 0000000..24c4b1f --- /dev/null +++ b/apps/desktop/src/platform/windows/input/device_controller_factory.cpp @@ -0,0 +1,19 @@ +#include "device_controller_factory.h" + +#include "keyboard_capturer.h" +#include "mouse_controller.h" + +namespace crossdesk { + +DeviceController* DeviceControllerFactory::Create(Device device) { + switch (device) { + case Mouse: + return new PlatformMouseController(); + case Keyboard: + return new PlatformKeyboardCapturer(); + default: + return nullptr; + } +} + +} // namespace crossdesk diff --git a/src/device_controller/keyboard/windows/keyboard_capturer.cpp b/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.cpp similarity index 90% rename from src/device_controller/keyboard/windows/keyboard_capturer.cpp rename to apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.cpp index a59599a..70f5e22 100644 --- a/src/device_controller/keyboard/windows/keyboard_capturer.cpp +++ b/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.cpp @@ -29,11 +29,11 @@ bool PreferSideSpecificVkInjection(int key_code) { } // namespace -KeyboardCapturer::KeyboardCapturer() {} +PlatformKeyboardCapturer::PlatformKeyboardCapturer() {} -KeyboardCapturer::~KeyboardCapturer() { Unhook(); } +PlatformKeyboardCapturer::~PlatformKeyboardCapturer() { Unhook(); } -int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { +int PlatformKeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { if (capture_thread_.joinable()) { return 0; } @@ -47,7 +47,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { capture_start_succeeded_ = false; } - capture_thread_ = std::thread(&KeyboardCapturer::RawInputThreadMain, this); + capture_thread_ = std::thread(&PlatformKeyboardCapturer::RawInputThreadMain, this); std::unique_lock lock(capture_state_mutex_); capture_start_condition_.wait( @@ -66,7 +66,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { return 0; } -int KeyboardCapturer::Unhook() { +int PlatformKeyboardCapturer::Unhook() { DWORD capture_thread_id = 0; { std::lock_guard lock(capture_state_mutex_); @@ -86,7 +86,7 @@ int KeyboardCapturer::Unhook() { return 0; } -void KeyboardCapturer::RawInputThreadMain() { +void PlatformKeyboardCapturer::RawInputThreadMain() { const DWORD thread_id = GetCurrentThreadId(); MSG message{}; @@ -129,11 +129,11 @@ void KeyboardCapturer::RawInputThreadMain() { LOG_INFO("Keyboard raw input capture stopped, thread_id={}", thread_id); } -bool KeyboardCapturer::CreateRawInputWindow() { +bool PlatformKeyboardCapturer::CreateRawInputWindow() { const HINSTANCE instance = GetModuleHandleW(nullptr); WNDCLASSEXW window_class{}; window_class.cbSize = sizeof(window_class); - window_class.lpfnWndProc = &KeyboardCapturer::RawInputWindowProc; + window_class.lpfnWndProc = &PlatformKeyboardCapturer::RawInputWindowProc; window_class.hInstance = instance; window_class.lpszClassName = kRawInputWindowClassName; @@ -172,7 +172,7 @@ bool KeyboardCapturer::CreateRawInputWindow() { return true; } -void KeyboardCapturer::DestroyRawInputWindow() { +void PlatformKeyboardCapturer::DestroyRawInputWindow() { if (raw_input_registered_) { RAWINPUTDEVICE keyboard_device{}; keyboard_device.usUsagePage = HID_USAGE_PAGE_GENERIC; @@ -192,13 +192,13 @@ void KeyboardCapturer::DestroyRawInputWindow() { } } -LRESULT CALLBACK KeyboardCapturer::RawInputWindowProc( +LRESULT CALLBACK PlatformKeyboardCapturer::RawInputWindowProc( HWND window, UINT message, WPARAM w_param, LPARAM l_param) { - KeyboardCapturer* capturer = reinterpret_cast( + PlatformKeyboardCapturer* capturer = reinterpret_cast( GetWindowLongPtrW(window, GWLP_USERDATA)); if (message == WM_NCCREATE) { auto* create = reinterpret_cast(l_param); - capturer = static_cast(create->lpCreateParams); + capturer = static_cast(create->lpCreateParams); SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast(capturer)); } else if (message == WM_INPUT && capturer) { @@ -209,7 +209,7 @@ LRESULT CALLBACK KeyboardCapturer::RawInputWindowProc( return DefWindowProcW(window, message, w_param, l_param); } -void KeyboardCapturer::HandleRawInput(HRAWINPUT raw_input_handle) { +void PlatformKeyboardCapturer::HandleRawInput(HRAWINPUT raw_input_handle) { RAWINPUT input{}; UINT input_size = sizeof(input); const UINT bytes_read = @@ -262,7 +262,7 @@ void KeyboardCapturer::HandleRawInput(HRAWINPUT raw_input_handle) { } // Apply remote keyboard commands to the local machine. -int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, +int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, bool extended) { INPUT input = {0}; input.type = INPUT_KEYBOARD; diff --git a/src/device_controller/keyboard/windows/keyboard_capturer.h b/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.h similarity index 90% rename from src/device_controller/keyboard/windows/keyboard_capturer.h rename to apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.h index 9f64e0d..eca54cb 100644 --- a/src/device_controller/keyboard/windows/keyboard_capturer.h +++ b/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.h @@ -17,10 +17,10 @@ namespace crossdesk { -class KeyboardCapturer : public DeviceController { +class PlatformKeyboardCapturer final : public KeyboardCapturer { public: - KeyboardCapturer(); - virtual ~KeyboardCapturer(); + PlatformKeyboardCapturer(); + virtual ~PlatformKeyboardCapturer(); public: virtual int Hook(OnKeyAction on_key_action, void* user_ptr); diff --git a/src/device_controller/mouse/windows/mouse_controller.cpp b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp similarity index 88% rename from src/device_controller/mouse/windows/mouse_controller.cpp rename to apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp index b066171..a6f292a 100644 --- a/src/device_controller/mouse/windows/mouse_controller.cpp +++ b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp @@ -1,24 +1,26 @@ #include "mouse_controller.h" +#include + #include #include "rd_log.h" namespace crossdesk { -MouseController::MouseController() {} +PlatformMouseController::PlatformMouseController() {} -MouseController::~MouseController() {} +PlatformMouseController::~PlatformMouseController() {} -int MouseController::Init(std::vector display_info_list) { +int PlatformMouseController::Init(std::vector display_info_list) { display_info_list_ = display_info_list; return 0; } -int MouseController::Destroy() { return 0; } +int PlatformMouseController::Destroy() { return 0; } -int MouseController::SendMouseCommand(RemoteAction remote_action, +int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, int display_index) { if (display_index < 0 || display_index >= static_cast(display_info_list_.size())) { diff --git a/src/device_controller/mouse/windows/mouse_controller.h b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.h similarity index 77% rename from src/device_controller/mouse/windows/mouse_controller.h rename to apps/desktop/src/platform/windows/input/mouse/mouse_controller.h index 8cc57a5..af97f24 100644 --- a/src/device_controller/mouse/windows/mouse_controller.h +++ b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.h @@ -9,15 +9,17 @@ #include +#include + #include "device_controller.h" #include "display_info.h" namespace crossdesk { -class MouseController : public DeviceController { +class PlatformMouseController final : public MouseController { public: - MouseController(); - virtual ~MouseController(); + PlatformMouseController(); + virtual ~PlatformMouseController(); public: virtual int Init(std::vector display_info_list); diff --git a/src/common/windows_input_marker.h b/apps/desktop/src/platform/windows/input/windows_input_marker.h similarity index 100% rename from src/common/windows_input_marker.h rename to apps/desktop/src/platform/windows/input/windows_input_marker.h diff --git a/src/device_controller/windows_keycode.h b/apps/desktop/src/platform/windows/input/windows_keycode.h similarity index 100% rename from src/device_controller/windows_keycode.h rename to apps/desktop/src/platform/windows/input/windows_keycode.h diff --git a/apps/desktop/src/platform/windows/path_backend.cpp b/apps/desktop/src/platform/windows/path_backend.cpp new file mode 100644 index 0000000..34ff2b7 --- /dev/null +++ b/apps/desktop/src/platform/windows/path_backend.cpp @@ -0,0 +1,51 @@ +#include "platform/path_backend.h" + +#include +#include + +#include + +namespace crossdesk::platform { +namespace { + +std::filesystem::path GetKnownFolder(REFKNOWNFOLDERID id) { + PWSTR value = nullptr; + if (FAILED(SHGetKnownFolderPath(id, 0, nullptr, &value))) return {}; + const std::filesystem::path result(value); + CoTaskMemFree(value); + return result; +} + +} // namespace + +std::filesystem::path GetExecutableDirectory() { + std::vector buffer(MAX_PATH); + while (true) { + const DWORD length = GetModuleFileNameW( + nullptr, buffer.data(), static_cast(buffer.size())); + if (length == 0 || buffer.size() >= 32768) return {}; + if (length < buffer.size()) { + return std::filesystem::path(buffer.data(), buffer.data() + length) + .parent_path(); + } + buffer.resize(buffer.size() * 2); + } +} + +std::filesystem::path GetConfigPath(const std::string& app_name) { + return GetKnownFolder(FOLDERID_RoamingAppData) / app_name; +} + +std::filesystem::path GetCachePath(const std::string& app_name) { +#ifdef CROSSDESK_DEBUG + return "cache"; +#else + return GetKnownFolder(FOLDERID_LocalAppData) / app_name / "cache"; +#endif +} + +std::filesystem::path GetLogPath(const std::string& app_name) { + return GetKnownFolder(FOLDERID_LocalAppData) / app_name / "logs"; +} + +} // namespace crossdesk::platform diff --git a/src/screen_capturer/windows/screen_capturer_dxgi.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp similarity index 99% rename from src/screen_capturer/windows/screen_capturer_dxgi.cpp rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp index a0cbc22..2e4f25a 100644 --- a/src/screen_capturer/windows/screen_capturer_dxgi.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp @@ -5,7 +5,7 @@ #include #include -#include "display_stream_id.h" +#include #include "libyuv.h" #include "rd_log.h" diff --git a/src/screen_capturer/windows/screen_capturer_dxgi.h b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.h similarity index 100% rename from src/screen_capturer/windows/screen_capturer_dxgi.h rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.h diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_factory.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_factory.cpp new file mode 100644 index 0000000..8e9f072 --- /dev/null +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_factory.cpp @@ -0,0 +1,11 @@ +#include "screen_capturer_factory.h" + +#include "screen_capturer_win.h" + +namespace crossdesk { + +ScreenCapturer* ScreenCapturerFactory::Create() { + return new ScreenCapturerWin(); +} + +} // namespace crossdesk diff --git a/src/screen_capturer/windows/screen_capturer_gdi.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp similarity index 99% rename from src/screen_capturer/windows/screen_capturer_gdi.cpp rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp index ed4d396..5c60ad6 100644 --- a/src/screen_capturer/windows/screen_capturer_gdi.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp @@ -5,7 +5,7 @@ #include #include -#include "display_stream_id.h" +#include #include "libyuv.h" #include "rd_log.h" diff --git a/src/screen_capturer/windows/screen_capturer_gdi.h b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.h similarity index 100% rename from src/screen_capturer/windows/screen_capturer_gdi.h rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.h diff --git a/src/screen_capturer/windows/screen_capturer_wgc.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_wgc.cpp similarity index 99% rename from src/screen_capturer/windows/screen_capturer_wgc.cpp rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_wgc.cpp index 470fa8c..e65bfb0 100644 --- a/src/screen_capturer/windows/screen_capturer_wgc.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_wgc.cpp @@ -7,7 +7,7 @@ #include -#include "display_stream_id.h" +#include #include "libyuv.h" #include "rd_log.h" diff --git a/src/screen_capturer/windows/screen_capturer_wgc.h b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_wgc.h similarity index 100% rename from src/screen_capturer/windows/screen_capturer_wgc.h rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_wgc.h diff --git a/src/screen_capturer/windows/screen_capturer_win.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp similarity index 99% rename from src/screen_capturer/windows/screen_capturer_win.cpp rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp index c720a1e..a0c79ae 100644 --- a/src/screen_capturer/windows/screen_capturer_win.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp @@ -14,7 +14,7 @@ #include #include -#include "display_stream_id.h" +#include #include "interactive_state.h" #include "rd_log.h" #include "screen_capturer_dxgi.h" @@ -417,7 +417,7 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) { if (it != stream_id_alias_.end()) { mapped_stream_id = it->second; } else { - // Unknown backend labels are presentation data, not protocol IDs. + // Unknown backend labels are presentation data, not wire IDs. // Resolve them through the selected logical display instead. mapped_stream_id.clear(); } diff --git a/src/screen_capturer/windows/screen_capturer_win.h b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h similarity index 100% rename from src/screen_capturer/windows/screen_capturer_win.h rename to apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h diff --git a/src/screen_capturer/windows/wgc_plugin_api.h b/apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_api.h similarity index 100% rename from src/screen_capturer/windows/wgc_plugin_api.h rename to apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_api.h diff --git a/src/screen_capturer/windows/wgc_plugin_entry.cpp b/apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp similarity index 100% rename from src/screen_capturer/windows/wgc_plugin_entry.cpp rename to apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp diff --git a/src/screen_capturer/windows/wgc_session.h b/apps/desktop/src/platform/windows/screen_capturer/wgc_session.h similarity index 100% rename from src/screen_capturer/windows/wgc_session.h rename to apps/desktop/src/platform/windows/screen_capturer/wgc_session.h diff --git a/src/screen_capturer/windows/wgc_session_impl.cpp b/apps/desktop/src/platform/windows/screen_capturer/wgc_session_impl.cpp similarity index 100% rename from src/screen_capturer/windows/wgc_session_impl.cpp rename to apps/desktop/src/platform/windows/screen_capturer/wgc_session_impl.cpp diff --git a/src/screen_capturer/windows/wgc_session_impl.h b/apps/desktop/src/platform/windows/screen_capturer/wgc_session_impl.h similarity index 100% rename from src/screen_capturer/windows/wgc_session_impl.h rename to apps/desktop/src/platform/windows/screen_capturer/wgc_session_impl.h diff --git a/src/service/windows/interactive_state.h b/apps/desktop/src/platform/windows/service/interactive_state.h similarity index 100% rename from src/service/windows/interactive_state.h rename to apps/desktop/src/platform/windows/service/interactive_state.h diff --git a/src/service/windows/main.cpp b/apps/desktop/src/platform/windows/service/main.cpp similarity index 100% rename from src/service/windows/main.cpp rename to apps/desktop/src/platform/windows/service/main.cpp diff --git a/src/service/windows/service_host.cpp b/apps/desktop/src/platform/windows/service/service_host.cpp similarity index 100% rename from src/service/windows/service_host.cpp rename to apps/desktop/src/platform/windows/service/service_host.cpp diff --git a/src/service/windows/service_host.h b/apps/desktop/src/platform/windows/service/service_host.h similarity index 100% rename from src/service/windows/service_host.h rename to apps/desktop/src/platform/windows/service/service_host.h diff --git a/src/service/windows/session_helper_main.cpp b/apps/desktop/src/platform/windows/service/session_helper_main.cpp similarity index 100% rename from src/service/windows/session_helper_main.cpp rename to apps/desktop/src/platform/windows/service/session_helper_main.cpp diff --git a/src/service/windows/session_helper_shared.h b/apps/desktop/src/platform/windows/service/session_helper_shared.h similarity index 100% rename from src/service/windows/session_helper_shared.h rename to apps/desktop/src/platform/windows/service/session_helper_shared.h diff --git a/apps/desktop/src/platform/windows/speaker_capturer/speaker_capturer_factory.cpp b/apps/desktop/src/platform/windows/speaker_capturer/speaker_capturer_factory.cpp new file mode 100644 index 0000000..0be0f7b --- /dev/null +++ b/apps/desktop/src/platform/windows/speaker_capturer/speaker_capturer_factory.cpp @@ -0,0 +1,11 @@ +#include "speaker_capturer_factory.h" + +#include "speaker_capturer_wasapi.h" + +namespace crossdesk { + +SpeakerCapturer* SpeakerCapturerFactory::Create() { + return new SpeakerCapturerWasapi(); +} + +} // namespace crossdesk diff --git a/src/speaker_capturer/windows/speaker_capturer_wasapi.cpp b/apps/desktop/src/platform/windows/speaker_capturer/speaker_capturer_wasapi.cpp similarity index 100% rename from src/speaker_capturer/windows/speaker_capturer_wasapi.cpp rename to apps/desktop/src/platform/windows/speaker_capturer/speaker_capturer_wasapi.cpp diff --git a/src/speaker_capturer/windows/speaker_capturer_wasapi.h b/apps/desktop/src/platform/windows/speaker_capturer/speaker_capturer_wasapi.h similarity index 100% rename from src/speaker_capturer/windows/speaker_capturer_wasapi.h rename to apps/desktop/src/platform/windows/speaker_capturer/speaker_capturer_wasapi.h diff --git a/apps/desktop/src/platform/windows/system_info.cpp b/apps/desktop/src/platform/windows/system_info.cpp new file mode 100644 index 0000000..99c9bfc --- /dev/null +++ b/apps/desktop/src/platform/windows/system_info.cpp @@ -0,0 +1,45 @@ +#include "platform.h" + +#include +#include + +#include + +#include "rd_log.h" + +namespace crossdesk { + +std::string GetMac() { + IP_ADAPTER_INFO adapters[16] = {}; + DWORD size = sizeof(adapters); + if (GetAdaptersInfo(adapters, &size) != ERROR_SUCCESS) return {}; + + char address[16] = {}; + int length = 0; + const PIP_ADAPTER_INFO adapter = adapters; + for (UINT index = 0; index < adapter->AddressLength; ++index) { + length += sprintf_s(address + length, sizeof(address) - length, "%.2X", + adapter->Address[index]); + } + return address; +} + +std::string GetHostName() { + WSADATA data = {}; + if (WSAStartup(MAKEWORD(2, 2), &data) != 0) { + LOG_ERROR("WSAStartup failed"); + return {}; + } + char hostname[256] = {}; + if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) { + LOG_ERROR("gethostname failed: {}", WSAGetLastError()); + WSACleanup(); + return {}; + } + WSACleanup(); + return hostname; +} + +bool IsWaylandSession() { return false; } + +} // namespace crossdesk diff --git a/src/screen_capturer/screen_capturer.h b/apps/desktop/src/screen_capturer/screen_capturer.h similarity index 100% rename from src/screen_capturer/screen_capturer.h rename to apps/desktop/src/screen_capturer/screen_capturer.h diff --git a/apps/desktop/src/screen_capturer/screen_capturer_factory.h b/apps/desktop/src/screen_capturer/screen_capturer_factory.h new file mode 100644 index 0000000..a4400ab --- /dev/null +++ b/apps/desktop/src/screen_capturer/screen_capturer_factory.h @@ -0,0 +1,22 @@ +/* + * @Author: DI JUNKUN + * @Date: 2023-12-15 + * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _SCREEN_CAPTURER_FACTORY_H_ +#define _SCREEN_CAPTURER_FACTORY_H_ + +#include "screen_capturer.h" + +namespace crossdesk { + +class ScreenCapturerFactory { + public: + virtual ~ScreenCapturerFactory() {} + + public: + ScreenCapturer* Create(); +}; +} // namespace crossdesk +#endif diff --git a/src/speaker_capturer/speaker_capturer.h b/apps/desktop/src/speaker_capturer/speaker_capturer.h similarity index 100% rename from src/speaker_capturer/speaker_capturer.h rename to apps/desktop/src/speaker_capturer/speaker_capturer.h diff --git a/apps/desktop/src/speaker_capturer/speaker_capturer_factory.h b/apps/desktop/src/speaker_capturer/speaker_capturer_factory.h new file mode 100644 index 0000000..522d586 --- /dev/null +++ b/apps/desktop/src/speaker_capturer/speaker_capturer_factory.h @@ -0,0 +1,22 @@ +/* + * @Author: DI JUNKUN + * @Date: 2024-07-22 + * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _SPEAKER_CAPTURER_FACTORY_H_ +#define _SPEAKER_CAPTURER_FACTORY_H_ + +#include "speaker_capturer.h" + +namespace crossdesk { + +class SpeakerCapturerFactory { + public: + virtual ~SpeakerCapturerFactory() {} + + public: + SpeakerCapturer* Create(); +}; +} // namespace crossdesk +#endif diff --git a/src/thumbnail/stb_image.h b/apps/desktop/src/thumbnail/stb_image.h similarity index 100% rename from src/thumbnail/stb_image.h rename to apps/desktop/src/thumbnail/stb_image.h diff --git a/src/thumbnail/stb_image_write.h b/apps/desktop/src/thumbnail/stb_image_write.h similarity index 100% rename from src/thumbnail/stb_image_write.h rename to apps/desktop/src/thumbnail/stb_image_write.h diff --git a/src/thumbnail/thumbnail.cpp b/apps/desktop/src/thumbnail/thumbnail.cpp similarity index 100% rename from src/thumbnail/thumbnail.cpp rename to apps/desktop/src/thumbnail/thumbnail.cpp diff --git a/src/thumbnail/thumbnail.h b/apps/desktop/src/thumbnail/thumbnail.h similarity index 100% rename from src/thumbnail/thumbnail.h rename to apps/desktop/src/thumbnail/thumbnail.h diff --git a/apps/desktop/src/tools/clipboard.cpp b/apps/desktop/src/tools/clipboard.cpp new file mode 100644 index 0000000..34f41b4 --- /dev/null +++ b/apps/desktop/src/tools/clipboard.cpp @@ -0,0 +1,86 @@ +#include "clipboard.h" + +#include +#include +#include +#include +#include + +#include "platform/clipboard_backend.h" +#include "rd_log.h" + +namespace crossdesk { +namespace { + +std::atomic g_monitoring{false}; +std::thread g_monitor_thread; +std::mutex g_monitor_mutex; +std::string g_last_clipboard_text; +Clipboard::OnClipboardChanged g_on_clipboard_changed; + +} // namespace + +namespace platform { + +bool ClipboardMonitoring() { return g_monitoring.load(); } + +void HandleClipboardChange() { + if (!Clipboard::HasText()) { + std::lock_guard lock(g_monitor_mutex); + if (!g_last_clipboard_text.empty()) { + g_last_clipboard_text.clear(); + LOG_INFO("Clipboard content cleared"); + } + return; + } + + const std::string current_text = Clipboard::GetText(); + std::lock_guard lock(g_monitor_mutex); + if (current_text == g_last_clipboard_text) return; + g_last_clipboard_text = current_text; + if (!current_text.empty() && g_on_clipboard_changed) { + const int result = + g_on_clipboard_changed(current_text.c_str(), current_text.length()); + if (result != 0) { + LOG_WARN("Clipboard callback returned error: {}", result); + } + } +} + +} // namespace platform + +void Clipboard::StartMonitoring(int check_interval_ms, + OnClipboardChanged on_changed) { + if (g_monitoring.load()) { + LOG_WARN("Clipboard monitoring is already running"); + return; + } + + { + std::lock_guard lock(g_monitor_mutex); + g_on_clipboard_changed = std::move(on_changed); + g_last_clipboard_text = HasText() ? GetText() : std::string(); + } + g_monitoring.store(true); + g_monitor_thread = std::thread( + [check_interval_ms] { platform::RunClipboardMonitor(check_interval_ms); }); + LOG_INFO("Clipboard event monitoring started"); +} + +void Clipboard::StopMonitoring() { + if (!g_monitoring.exchange(false)) return; + + platform::WakeClipboardMonitor(); + if (g_monitor_thread.joinable()) g_monitor_thread.join(); + + { + std::lock_guard lock(g_monitor_mutex); + g_last_clipboard_text.clear(); + g_on_clipboard_changed = nullptr; + } + LOG_INFO("Clipboard monitoring stopped"); +} + +bool Clipboard::IsMonitoring() { return g_monitoring.load(); } + +} // namespace crossdesk diff --git a/src/tools/clipboard.h b/apps/desktop/src/tools/clipboard.h similarity index 100% rename from src/tools/clipboard.h rename to apps/desktop/src/tools/clipboard.h diff --git a/src/tools/file_transfer.cpp b/apps/desktop/src/tools/file_transfer.cpp similarity index 97% rename from src/tools/file_transfer.cpp rename to apps/desktop/src/tools/file_transfer.cpp index 194972e..dbf7548 100644 --- a/src/tools/file_transfer.cpp +++ b/apps/desktop/src/tools/file_transfer.cpp @@ -93,7 +93,7 @@ std::vector FileSender::BuildChunk(uint32_t file_id, uint64_t offset, uint32_t data_size, const std::string* file_name, bool is_first, bool is_last) { - return protocol::EncodeFileChunk(file_id, offset, total_size, data, + return EncodeFileChunk(file_id, offset, total_size, data, data_size, file_name, is_first, is_last); } @@ -137,8 +137,8 @@ std::filesystem::path FileReceiver::GetDefaultDesktopPath() { } bool FileReceiver::OnData(const char* data, size_t size) { - protocol::FileChunkView chunk; - if (!protocol::DecodeFileChunk(data, size, &chunk)) { + FileChunkView chunk; + if (!DecodeFileChunk(data, size, &chunk)) { LOG_ERROR("FileReceiver::OnData: invalid buffer"); return false; } diff --git a/src/tools/file_transfer.h b/apps/desktop/src/tools/file_transfer.h similarity index 92% rename from src/tools/file_transfer.h rename to apps/desktop/src/tools/file_transfer.h index 5cb4ae4..6d6e487 100644 --- a/src/tools/file_transfer.h +++ b/apps/desktop/src/tools/file_transfer.h @@ -14,8 +14,8 @@ #include #include -#include "file_transfer_protocol.h" -#include "stream_names.h" +#include +#include namespace crossdesk { @@ -37,10 +37,10 @@ class FileSender { // Return 0 on success, <0 on error. int SendFile(const std::filesystem::path& path, const std::string& label, const SendFunc& send, - std::size_t chunk_size = protocol::kFileChunkSize, + std::size_t chunk_size = kFileChunkSize, uint32_t file_id = 0); - // build a single encoded chunk buffer according to FileChunkHeader protocol. + // Build a single encoded chunk buffer using the FileChunkHeader wire format. static std::vector BuildChunk(uint32_t file_id, uint64_t offset, uint64_t total_size, const char* data, uint32_t data_size, diff --git a/src/version_checker/version_checker.cpp b/apps/desktop/src/version_checker/version_checker.cpp similarity index 100% rename from src/version_checker/version_checker.cpp rename to apps/desktop/src/version_checker/version_checker.cpp diff --git a/src/version_checker/version_checker.h b/apps/desktop/src/version_checker/version_checker.h similarity index 100% rename from src/version_checker/version_checker.h rename to apps/desktop/src/version_checker/version_checker.h diff --git a/tests/connection_status_protocol_test.cpp b/apps/desktop/tests/connection_status_protocol_test.cpp similarity index 86% rename from tests/connection_status_protocol_test.cpp rename to apps/desktop/tests/connection_status_protocol_test.cpp index 572d152..3677a5a 100644 --- a/tests/connection_status_protocol_test.cpp +++ b/apps/desktop/tests/connection_status_protocol_test.cpp @@ -11,7 +11,7 @@ std::filesystem::path FindRepoRoot() { while (!current.empty()) { if (std::filesystem::exists(current / "xmake.lua") && std::filesystem::exists(current / - "submodules/minirtc/src/api/minirtc.h")) { + "deps/submodules/minirtc/src/api/minirtc.h")) { return current; } current = current.parent_path(); @@ -60,23 +60,23 @@ int main() { } const std::string minirtc_api = - ReadFile(repo_root / "submodules/minirtc/src/api/minirtc.h"); + ReadFile(repo_root / "deps/submodules/minirtc/src/api/minirtc.h"); const std::string peer_connection = - ReadFile(repo_root / "submodules/minirtc/src/pc/peer_connection.cpp"); + ReadFile(repo_root / "deps/submodules/minirtc/src/pc/peer_connection.cpp"); const std::string main_window = - ReadFile(repo_root / "src/gui/ui/main_window.slint"); + ReadFile(repo_root / "apps/desktop/src/gui/ui/main_window.slint"); const std::string gui_application = - ReadFile(repo_root / "src/gui/application/gui_application.cpp"); + ReadFile(repo_root / "apps/desktop/src/gui/application/gui_application.cpp"); const std::string runtime_state_h = - ReadFile(repo_root / "src/gui/runtime/runtime_state.h"); + ReadFile(repo_root / "apps/desktop/src/gui/runtime/runtime_state.h"); const std::string remote_session_h = - ReadFile(repo_root / "src/gui/runtime/remote_session.h"); + ReadFile(repo_root / "apps/desktop/src/gui/runtime/remote_session.h"); const std::string connection_runtime_cpp = - ReadFile(repo_root / "src/gui/runtime/connection_runtime.cpp"); + ReadFile(repo_root / "apps/desktop/src/gui/runtime/connection_runtime.cpp"); const std::string peer_event_handler_cpp = - ReadFile(repo_root / "src/gui/runtime/peer_event_handler.cpp"); + ReadFile(repo_root / "apps/desktop/src/gui/runtime/peer_event_handler.cpp"); const std::string application_state_h = - ReadFile(repo_root / "src/gui/application/application_state.h"); + ReadFile(repo_root / "apps/desktop/src/gui/application/application_state.h"); bool ok = true; ok &= ExpectContains("minirtc.h", minirtc_api, "RemoteUnavailable"); diff --git a/apps/desktop/tests/cursor_position_test.cpp b/apps/desktop/tests/cursor_position_test.cpp new file mode 100644 index 0000000..ced7cad --- /dev/null +++ b/apps/desktop/tests/cursor_position_test.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include + +#include "cursor_position.h" +#include "display_info.h" + +namespace { + +bool Expect(bool condition, const char* message) { + if (condition) return true; + std::cerr << message << '\n'; + return false; +} + +} // namespace + +int main() { + const std::vector displays = { + crossdesk::DisplayInfo("Test Display", 0, 0, 1920, 1080)}; + crossdesk::CursorState normalized_cursor{}; + const bool ok = Expect( + crossdesk::NormalizeCursorPosition(960.0, 540.0, displays, 0, + &normalized_cursor) && + normalized_cursor.position_valid && + normalized_cursor.x == 0.5f && normalized_cursor.y == 0.5f, + "cursor position should use continuous display extents"); + return ok ? 0 : 1; +} diff --git a/tests/linux_keyboard_x11_integration_test.cpp b/apps/desktop/tests/linux_keyboard_x11_integration_test.cpp similarity index 97% rename from tests/linux_keyboard_x11_integration_test.cpp rename to apps/desktop/tests/linux_keyboard_x11_integration_test.cpp index 121a391..f43fb8e 100644 --- a/tests/linux_keyboard_x11_integration_test.cpp +++ b/apps/desktop/tests/linux_keyboard_x11_integration_test.cpp @@ -23,7 +23,7 @@ int main() { XSetInputFocus(receiver, window, RevertToParent, CurrentTime); XSync(receiver, False); - crossdesk::KeyboardCapturer keyboard; + crossdesk::PlatformKeyboardCapturer 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); diff --git a/tests/macos_keyboard_modifier_state_test.cpp b/apps/desktop/tests/macos_keyboard_modifier_state_test.cpp similarity index 100% rename from tests/macos_keyboard_modifier_state_test.cpp rename to apps/desktop/tests/macos_keyboard_modifier_state_test.cpp diff --git a/tests/path_manager_portable_test.cpp b/apps/desktop/tests/path_manager_portable_test.cpp similarity index 100% rename from tests/path_manager_portable_test.cpp rename to apps/desktop/tests/path_manager_portable_test.cpp diff --git a/apps/desktop/tests/repository_structure_test.cpp b/apps/desktop/tests/repository_structure_test.cpp new file mode 100644 index 0000000..ef7fba8 --- /dev/null +++ b/apps/desktop/tests/repository_structure_test.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::filesystem::path FindRepoRoot() { + std::filesystem::path current = std::filesystem::current_path(); + while (!current.empty()) { + if (std::filesystem::exists(current / "xmake.lua") && + std::filesystem::exists( + current / + "libs/wire/include/remote_action.h") && + std::filesystem::exists( + current / "apps/desktop/xmake/targets.lua")) { + return current; + } + current = current.parent_path(); + } + return {}; +} + +std::string ReadFile(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + if (!file) { + return {}; + } + std::ostringstream contents; + contents << file.rdbuf(); + return contents.str(); +} + +bool IsCheckedWireFile(const std::filesystem::path& path) { + static constexpr std::array kExtensions = { + ".h", ".hpp", ".c", ".cc", ".cpp", ".m", ".mm", ".lua"}; + const std::string extension = path.extension().string(); + for (std::string_view candidate : kExtensions) { + if (extension == candidate) { + return true; + } + } + return false; +} + +bool CheckWireBoundary(const std::filesystem::path& repo_root) { + static constexpr std::array kForbiddenReferences = { + "apps/desktop", "../desktop", "platform/common", "", + "", "(character))) { + compact_call.push_back(character); + } + } + if (call.find(kPlatformPath) != std::string_view::npos && + compact_call.find("public=true") != std::string::npos) { + std::cerr << "platform implementation include directory is public: " + << call << '\n'; + ok = false; + } + cursor = call_end + 1; + } + return ok; +} + +bool CheckWireLayout(const std::filesystem::path& repo_root) { + static constexpr std::array kForbiddenPaths = { + "libs/wire/include/crossdesk", "libs/wire/xmake.lua", "libs/wire/.xmake", + "xmake.protocol.lua", "xmake.wire.lua"}; + bool ok = true; + for (std::string_view relative_path : kForbiddenPaths) { + if (!std::filesystem::exists(repo_root / relative_path)) { + continue; + } + std::cerr << "forbidden legacy layer, standalone project, or wire cache: " + << relative_path << '\n'; + ok = false; + } + return ok; +} + +} // namespace + +int main() { + const std::filesystem::path repo_root = FindRepoRoot(); + if (repo_root.empty()) { + std::cerr << "failed to locate repository root\n"; + return 1; + } + + bool ok = true; + static constexpr std::array kLegacyRootDirectories = { + "src", "ios", "scripts", "icons", + "tests", "xmake", "shared", "platforms", + "wire", "submodules", "thirdparty"}; + for (std::string_view directory : kLegacyRootDirectories) { + if (!std::filesystem::exists(repo_root / directory)) { + continue; + } + std::cerr << "legacy root directory still exists: " << directory << '\n'; + ok = false; + } + + ok &= CheckWireBoundary(repo_root); + ok &= CheckPlatformIncludesArePrivate(repo_root); + ok &= CheckWireLayout(repo_root); + return ok ? 0 : 1; +} diff --git a/tests/slint_ui_smoke_test.cpp b/apps/desktop/tests/slint_ui_smoke_test.cpp similarity index 100% rename from tests/slint_ui_smoke_test.cpp rename to apps/desktop/tests/slint_ui_smoke_test.cpp diff --git a/tests/version_checker_test.cpp b/apps/desktop/tests/version_checker_test.cpp similarity index 100% rename from tests/version_checker_test.cpp rename to apps/desktop/tests/version_checker_test.cpp diff --git a/tests/video_callback_lifetime_test.cpp b/apps/desktop/tests/video_callback_lifetime_test.cpp similarity index 95% rename from tests/video_callback_lifetime_test.cpp rename to apps/desktop/tests/video_callback_lifetime_test.cpp index 457942b..67170e9 100644 --- a/tests/video_callback_lifetime_test.cpp +++ b/apps/desktop/tests/video_callback_lifetime_test.cpp @@ -11,7 +11,7 @@ std::filesystem::path FindRepoRoot() { while (!current.empty()) { if (std::filesystem::exists(current / "xmake.lua") && std::filesystem::exists( - current / "submodules/minirtc/src/transport/ice_transport_controller.cpp")) { + current / "deps/submodules/minirtc/src/transport/ice_transport_controller.cpp")) { return current; } current = current.parent_path(); @@ -74,7 +74,7 @@ int main() { const std::string controller = ReadFile( repo_root / - "submodules/minirtc/src/transport/ice_transport_controller.cpp"); + "deps/submodules/minirtc/src/transport/ice_transport_controller.cpp"); const std::string destroy = ExtractSection( controller, "void IceTransportController::Destroy()", "uint32_t IceTransportController::AddVideoSendChannel"); diff --git a/tests/windows_manifest_resource_test.cpp b/apps/desktop/tests/windows_manifest_resource_test.cpp similarity index 74% rename from tests/windows_manifest_resource_test.cpp rename to apps/desktop/tests/windows_manifest_resource_test.cpp index 8d3ce91..f7eda1c 100644 --- a/tests/windows_manifest_resource_test.cpp +++ b/apps/desktop/tests/windows_manifest_resource_test.cpp @@ -25,7 +25,7 @@ std::filesystem::path FindRepoRoot() { std::filesystem::path current = std::filesystem::current_path(); while (!current.empty()) { if (std::filesystem::exists(current / "xmake.lua") && - std::filesystem::exists(current / "scripts/windows/crossdesk.rc")) { + std::filesystem::exists(current / "apps/desktop/resources/windows/crossdesk.rc")) { return current; } current = current.parent_path(); @@ -81,24 +81,24 @@ int main() { return 1; } - const std::string rc = ReadFile(repo_root / "scripts/windows/crossdesk.rc"); + const std::string rc = ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk.rc"); const std::string portable_rc = - ReadFile(repo_root / "scripts/windows/crossdesk_portable.rc"); + ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_portable.rc"); const std::string service_rc = - ReadFile(repo_root / "scripts/windows/crossdesk_service.rc"); + ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_service.rc"); const std::string session_helper_rc = - ReadFile(repo_root / "scripts/windows/crossdesk_session_helper.rc"); + ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_session_helper.rc"); const std::string wgc_plugin_rc = - ReadFile(repo_root / "scripts/windows/wgc_plugin.rc"); + ReadFile(repo_root / "apps/desktop/resources/windows/wgc_plugin.rc"); const std::string version_info = - ReadFile(repo_root / "scripts/windows/version_info.rcinc"); + ReadFile(repo_root / "apps/desktop/resources/windows/version_info.rcinc"); const std::string manifest = - ReadFile(repo_root / "scripts/windows/crossdesk.manifest"); + ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk.manifest"); const std::string debug_manifest = - ReadFile(repo_root / "scripts/windows/crossdesk_debug.manifest"); + ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_debug.manifest"); const std::string portable_manifest = - ReadFile(repo_root / "scripts/windows/crossdesk_portable.manifest"); - const std::string targets = ReadFile(repo_root / "xmake/targets.lua"); + ReadFile(repo_root / "apps/desktop/resources/windows/crossdesk_portable.manifest"); + const std::string targets = ReadFile(repo_root / "apps/desktop/xmake/targets.lua"); bool ok = true; ok &= ExpectContains("crossdesk.rc", rc, "crossdesk.manifest"); @@ -125,15 +125,15 @@ int main() { "ProductVersion"); ok &= ExpectContains("version_info.rcinc", version_info, "OriginalFilename"); - ok &= ExpectContains("xmake/targets.lua", targets, - "scripts/windows/crossdesk_portable.rc"); - ok &= ExpectContains("xmake/targets.lua", targets, - "scripts/windows/crossdesk_service.rc"); - ok &= ExpectContains("xmake/targets.lua", targets, - "scripts/windows/crossdesk_session_helper.rc"); - ok &= ExpectContains("xmake/targets.lua", targets, - "scripts/windows/wgc_plugin.rc"); - ok &= ExpectContains("xmake/targets.lua", targets, "CROSSDESK_PORTABLE"); + ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets, + "apps/desktop/resources/windows/crossdesk_portable.rc"); + ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets, + "apps/desktop/resources/windows/crossdesk_service.rc"); + ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets, + "apps/desktop/resources/windows/crossdesk_session_helper.rc"); + ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets, + "apps/desktop/resources/windows/wgc_plugin.rc"); + ok &= ExpectContains("apps/desktop/xmake/targets.lua", targets, "CROSSDESK_PORTABLE"); ok &= ExpectContains("crossdesk.manifest", manifest, "level=\"requireAdministrator\""); ok &= ExpectContains("crossdesk.manifest", manifest, @@ -162,11 +162,11 @@ int main() { "processorArchitecture=\"*\""); #ifdef _WIN32 ok &= - ExpectActivationContext(repo_root / "scripts/windows/crossdesk.manifest"); + ExpectActivationContext(repo_root / "apps/desktop/resources/windows/crossdesk.manifest"); ok &= ExpectActivationContext(repo_root / - "scripts/windows/crossdesk_debug.manifest"); + "apps/desktop/resources/windows/crossdesk_debug.manifest"); ok &= ExpectActivationContext(repo_root / - "scripts/windows/crossdesk_portable.manifest"); + "apps/desktop/resources/windows/crossdesk_portable.manifest"); #endif return ok ? 0 : 1; } diff --git a/tests/windows_mouse_controller_safety_test.cpp b/apps/desktop/tests/windows_mouse_controller_safety_test.cpp similarity index 89% rename from tests/windows_mouse_controller_safety_test.cpp rename to apps/desktop/tests/windows_mouse_controller_safety_test.cpp index b98d6e5..0763325 100644 --- a/tests/windows_mouse_controller_safety_test.cpp +++ b/apps/desktop/tests/windows_mouse_controller_safety_test.cpp @@ -12,7 +12,7 @@ std::filesystem::path FindRepoRoot() { if (std::filesystem::exists(current / "xmake.lua") && std::filesystem::exists( current / - "src/device_controller/mouse/windows/mouse_controller.cpp")) { + "apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp")) { return current; } current = current.parent_path(); @@ -51,7 +51,7 @@ int main() { } const std::string mouse_controller = ReadFile( - repo_root / "src/device_controller/mouse/windows/mouse_controller.cpp"); + repo_root / "apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp"); bool ok = true; ok &= ExpectContains("mouse_controller.cpp", mouse_controller, diff --git a/tests/windows_sas_guard_test.cpp b/apps/desktop/tests/windows_sas_guard_test.cpp similarity index 89% rename from tests/windows_sas_guard_test.cpp rename to apps/desktop/tests/windows_sas_guard_test.cpp index 22006f5..b5d84d9 100644 --- a/tests/windows_sas_guard_test.cpp +++ b/apps/desktop/tests/windows_sas_guard_test.cpp @@ -13,7 +13,7 @@ std::filesystem::path FindRepoRoot() { while (!current.empty()) { if (std::filesystem::exists(current / "xmake.lua") && std::filesystem::exists(current / - "src/service/windows/service_host.cpp")) { + "apps/desktop/src/platform/windows/service/service_host.cpp")) { return current; } current = current.parent_path(); @@ -71,19 +71,19 @@ int main() { } const std::string stream_window = - ReadFile(repo_root / "src/gui/ui/stream_window.slint"); + ReadFile(repo_root / "apps/desktop/src/gui/ui/stream_window.slint"); const std::string gui_application = - ReadFile(repo_root / "src/gui/application/gui_application.cpp"); + ReadFile(repo_root / "apps/desktop/src/gui/application/gui_application.cpp"); const std::string windows_service_runtime = - ReadFile(repo_root / "src/gui/runtime/windows_service_runtime.cpp"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp"); const std::string runtime_state_h = - ReadFile(repo_root / "src/gui/runtime/runtime_state.h"); + ReadFile(repo_root / "apps/desktop/src/gui/runtime/runtime_state.h"); const std::string service_host = - ReadFile(repo_root / "src/service/windows/service_host.cpp"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.cpp"); const std::string service_host_h = - ReadFile(repo_root / "src/service/windows/service_host.h"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.h"); const std::string session_helper = - ReadFile(repo_root / "src/service/windows/session_helper_main.cpp"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/service/session_helper_main.cpp"); bool ok = true; ok &= ExpectTrue( diff --git a/tests/windows_service_mouse_ipc_test.cpp b/apps/desktop/tests/windows_service_mouse_ipc_test.cpp similarity index 92% rename from tests/windows_service_mouse_ipc_test.cpp rename to apps/desktop/tests/windows_service_mouse_ipc_test.cpp index 3269051..a61295c 100644 --- a/tests/windows_service_mouse_ipc_test.cpp +++ b/apps/desktop/tests/windows_service_mouse_ipc_test.cpp @@ -11,7 +11,7 @@ std::filesystem::path FindRepoRoot() { while (!current.empty()) { if (std::filesystem::exists(current / "xmake.lua") && std::filesystem::exists(current / - "src/service/windows/service_host.cpp")) { + "apps/desktop/src/platform/windows/service/service_host.cpp")) { return current; } current = current.parent_path(); @@ -60,23 +60,23 @@ int main() { } const std::string service_host = - ReadFile(repo_root / "src/service/windows/service_host.cpp"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.cpp"); const std::string service_host_h = - ReadFile(repo_root / "src/service/windows/service_host.h"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/service/service_host.h"); const std::string session_helper = - ReadFile(repo_root / "src/service/windows/session_helper_main.cpp"); - const std::string targets = ReadFile(repo_root / "xmake/targets.lua"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/service/session_helper_main.cpp"); + const std::string targets = ReadFile(repo_root / "apps/desktop/xmake/targets.lua"); const std::string interactive_state = - ReadFile(repo_root / "src/service/windows/interactive_state.h"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/service/interactive_state.h"); const std::string gui_input_sources = - ReadFile(repo_root / "src/gui/runtime/peer_data_callbacks.cpp") + "\n" + - ReadFile(repo_root / "src/gui/features/input/keyboard_controller.cpp"); + ReadFile(repo_root / "apps/desktop/src/gui/runtime/peer_data_callbacks.cpp") + "\n" + + ReadFile(repo_root / "apps/desktop/src/gui/features/input/keyboard_controller.cpp"); const std::string windows_service_runtime = - ReadFile(repo_root / "src/gui/runtime/windows_service_runtime.cpp"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp"); const std::string screen_capturer_h = - ReadFile(repo_root / "src/screen_capturer/windows/screen_capturer_win.h"); + ReadFile(repo_root / "apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h"); const std::string screen_capturer_cpp = ReadFile( - repo_root / "src/screen_capturer/windows/screen_capturer_win.cpp"); + repo_root / "apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp"); bool ok = true; ok &= ExpectContains("service_host.cpp", service_host, diff --git a/xmake/options.lua b/apps/desktop/xmake/options.lua similarity index 95% rename from xmake/options.lua rename to apps/desktop/xmake/options.lua index b82b0bf..9428b5d 100644 --- a/xmake/options.lua +++ b/apps/desktop/xmake/options.lua @@ -29,12 +29,8 @@ function setup_options_and_dependencies() set_description("Build CrossDesk as a portable package that stores data beside the executable") option_end() - add_rules("mode.release", "mode.debug") -- Preserve the existing codebase/toolchain contract. Slint targets opt in to -- C++20 locally because its generated C++ API requires it. - set_languages("c++17") - set_encodings("utf-8") - local crossdesk_version = get_config("CROSSDESK_VERSION") or "0.0.0" local version_base = crossdesk_version:gsub("^v", ""):match("^(%d[%d%.]*)") or "0.0.0" local version_parts = {} @@ -78,7 +74,6 @@ function setup_options_and_dependencies() add_requires("slint 1.17.1", {configs = {shared = true}}) add_requires("libsdl3 3.2.26", {configs = {shared = false}}) add_requires("openssl3 3.3.2", {system = false}) - add_requires("nlohmann_json 3.11.3") add_requires("cpp-httplib v0.26.0", {configs = {ssl = true}}) add_requires("tinyfiledialogs 3.15.1") end diff --git a/xmake/platform.lua b/apps/desktop/xmake/platform.lua similarity index 100% rename from xmake/platform.lua rename to apps/desktop/xmake/platform.lua diff --git a/xmake/rules/slint.lua b/apps/desktop/xmake/rules/slint.lua similarity index 100% rename from xmake/rules/slint.lua rename to apps/desktop/xmake/rules/slint.lua diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua new file mode 100644 index 0000000..766ba38 --- /dev/null +++ b/apps/desktop/xmake/targets.lua @@ -0,0 +1,413 @@ +function setup_targets() + add_packages("spdlog", "libsdl3", "nlohmann_json") + + includes("deps/submodules", "deps/thirdparty") + + local function copy_slint_runtime(target) + if not target:is_plat("windows") then + return + end + + local slint = target:pkg("slint") + if not slint and target:dep("gui") then + slint = target:dep("gui"):pkg("slint") + end + assert(slint, "the Slint package is required to copy its Windows runtime") + local runtime_dir = path.join(slint:installdir(), "lib") + local runtime_dll = path.join(runtime_dir, "slint_cpp.dll") + assert(os.isfile(runtime_dll), "Slint runtime not found: " .. runtime_dll) + os.cp(runtime_dll, target:targetdir()) + end + + local crossdesk_windows_resource = "apps/desktop/resources/windows/crossdesk.rc" + if is_config("CROSSDESK_PORTABLE", true) then + crossdesk_windows_resource = "apps/desktop/resources/windows/crossdesk_portable.rc" + end + + target("rd_log") + set_kind("object") + add_packages("spdlog") + add_files("apps/desktop/src/log/rd_log.cpp") + add_includedirs("apps/desktop/src/log", {public = true}) + + target("common") + set_kind("object") + add_deps("rd_log", "crossdesk_wire") + add_packages("libyuv") + add_files("apps/desktop/src/common/*.cpp") + remove_files("apps/desktop/src/common/rounded_corner_button.cpp") + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/system_info.cpp") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/system_info.cpp") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/linux/system_info.cpp", + "apps/desktop/src/platform/linux/common/*.cpp") + add_includedirs("apps/desktop/src/platform/linux/common") + end + add_includedirs("apps/desktop/src/common", {public = true}) + + target("path_manager") + set_kind("object") + add_deps("rd_log") + add_files("apps/desktop/src/path_manager/*.cpp") + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/path_backend.cpp") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/path_backend.cpp") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/linux/path_backend.cpp") + end + add_includedirs("apps/desktop/src", {public = true}) + add_includedirs("apps/desktop/src/path_manager", {public = true}) + + target("path_manager_portable_test") + set_kind("binary") + set_default(false) + set_policy("build.ccache", false) + add_defines("CROSSDESK_PORTABLE=1") + add_includedirs("apps/desktop/src", "apps/desktop/src/path_manager") + add_files("apps/desktop/tests/path_manager_portable_test.cpp", + "apps/desktop/src/path_manager/path_manager.cpp") + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/path_backend.cpp") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/path_backend.cpp") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/linux/path_backend.cpp") + end + + target("macos_keyboard_modifier_state_test") + set_kind("binary") + set_default(false) + add_includedirs("apps/desktop/src/platform/macos/input") + add_files("apps/desktop/tests/macos_keyboard_modifier_state_test.cpp") + + target("cursor_position_test") + set_kind("binary") + set_default(false) + add_deps("crossdesk_wire") + add_includedirs("apps/desktop/src/common", + "apps/desktop/src/gui/runtime") + add_files("apps/desktop/tests/cursor_position_test.cpp") + + target("repository_structure_test") + set_kind("binary") + set_default(false) + add_files("apps/desktop/tests/repository_structure_test.cpp") + + target("connection_status_protocol_test") + set_kind("binary") + set_default(false) + add_files("apps/desktop/tests/connection_status_protocol_test.cpp") + + target("video_callback_lifetime_test") + set_kind("binary") + set_default(false) + add_files("apps/desktop/tests/video_callback_lifetime_test.cpp") + + target("windows_manifest_resource_test") + set_kind("binary") + set_default(false) + add_files("apps/desktop/tests/windows_manifest_resource_test.cpp") + + target("windows_service_mouse_ipc_test") + set_kind("binary") + set_default(false) + add_files("apps/desktop/tests/windows_service_mouse_ipc_test.cpp") + + target("windows_mouse_controller_safety_test") + set_kind("binary") + set_default(false) + add_files("apps/desktop/tests/windows_mouse_controller_safety_test.cpp") + + target("windows_sas_guard_test") + set_kind("binary") + set_default(false) + add_includedirs("apps/desktop/src/platform/windows/service") + add_files("apps/desktop/tests/windows_sas_guard_test.cpp") + + target("slint_ui_smoke_test") + set_kind("binary") + set_languages("c++20") + set_default(false) + if is_os("windows") then + add_cxxflags("/bigobj") + end + add_packages("slint") + add_includedirs("apps/desktop/src/gui", "apps/desktop/src/gui/assets/fonts", + "apps/desktop/src/gui/assets/localization") + add_rules("slint") + add_files("apps/desktop/src/gui/ui/crossdesk_ui.slint") + add_files("apps/desktop/tests/slint_ui_smoke_test.cpp") + after_build(copy_slint_runtime) + + target("version_checker_test") + set_kind("binary") + set_default(false) + add_packages("cpp-httplib") + add_deps("rd_log") + add_includedirs("apps/desktop/src/version_checker") + add_files("apps/desktop/tests/version_checker_test.cpp", + "apps/desktop/src/version_checker/version_checker.cpp") + if is_os("macosx") then + add_defines("CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN") + add_frameworks("Security", "CoreFoundation") + end + + target("screen_capturer") + set_kind("object") + add_deps("rd_log", "common", "crossdesk_wire") + add_includedirs("apps/desktop/src/screen_capturer", {public = true}) + if is_os("windows") then + add_packages("libyuv") + add_files("apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp", + "apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp", + "apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp", + "apps/desktop/src/platform/windows/screen_capturer/screen_capturer_factory.cpp") + add_includedirs("apps/desktop/src/platform/windows/screen_capturer", + "apps/desktop/src/platform/windows/service") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/screen_capturer/*.cpp", + "apps/desktop/src/platform/macos/screen_capturer/*.mm") + add_includedirs("apps/desktop/src/platform/macos/screen_capturer") + elseif is_os("linux") then + add_packages("libyuv") + add_files("apps/desktop/src/platform/linux/screen_capturer/screen_capturer_linux.cpp") + add_files("apps/desktop/src/platform/linux/screen_capturer/screen_capturer_x11.cpp") + add_files("apps/desktop/src/platform/linux/screen_capturer/screen_capturer_drm.cpp") + add_files("apps/desktop/src/platform/linux/screen_capturer/screen_capturer_factory.cpp") + if is_config("USE_WAYLAND", true) then + add_files("apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland.cpp") + add_files("apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_portal.cpp") + add_files("apps/desktop/src/platform/linux/screen_capturer/screen_capturer_wayland_pipewire.cpp") + end + add_includedirs("apps/desktop/src/platform/linux/screen_capturer", + "apps/desktop/src/platform/linux/common") + end + + target("speaker_capturer") + set_kind("object") + add_deps("rd_log", "crossdesk_wire") + add_includedirs("apps/desktop/src/speaker_capturer", {public = true}) + if is_os("windows") then + add_packages("miniaudio") + add_files("apps/desktop/src/platform/windows/speaker_capturer/*.cpp") + add_includedirs("apps/desktop/src/platform/windows/speaker_capturer") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/speaker_capturer/*.cpp", + "apps/desktop/src/platform/macos/speaker_capturer/*.mm") + add_includedirs("apps/desktop/src/platform/macos/speaker_capturer") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/linux/speaker_capturer/*.cpp") + add_includedirs("apps/desktop/src/platform/linux/speaker_capturer") + end + + target("device_controller") + set_kind("object") + add_deps("rd_log", "common", "crossdesk_wire") + add_includedirs("apps/desktop/src/device_controller", {public = true}) + add_includedirs("apps/desktop/src/platform/common/input") + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/input/mouse/*.cpp", + "apps/desktop/src/platform/windows/input/keyboard/*.cpp", + "apps/desktop/src/platform/windows/input/device_controller_factory.cpp") + add_includedirs("apps/desktop/src/platform/windows/input/mouse", + "apps/desktop/src/platform/windows/input/keyboard", + "apps/desktop/src/platform/windows/input") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/input/mouse/*.cpp", + "apps/desktop/src/platform/macos/input/keyboard/*.cpp", + "apps/desktop/src/platform/macos/input/device_controller_factory.cpp") + add_includedirs("apps/desktop/src/platform/macos/input/mouse", + "apps/desktop/src/platform/macos/input/keyboard", + "apps/desktop/src/platform/macos/input") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/linux/input/mouse/*.cpp", + "apps/desktop/src/platform/linux/input/keyboard/*.cpp", + "apps/desktop/src/platform/linux/input/device_controller_factory.cpp") + add_includedirs("apps/desktop/src/platform/linux/input/mouse", + "apps/desktop/src/platform/linux/input/keyboard", + "apps/desktop/src/platform/linux/input", + "apps/desktop/src/platform/linux/common") + end + + if is_os("linux") then + target("linux_keyboard_x11_integration_test") + set_kind("binary") + set_default(false) + add_deps("device_controller") + add_files("apps/desktop/tests/linux_keyboard_x11_integration_test.cpp") + end + + target("thumbnail") + set_kind("object") + add_packages("libyuv", "openssl3") + add_deps("rd_log", "common") + add_files("apps/desktop/src/thumbnail/*.cpp") + add_includedirs("apps/desktop/src/thumbnail", {public = true}) + + target("autostart") + set_kind("object") + add_deps("rd_log") + add_files("apps/desktop/src/autostart/*.cpp") + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/autostart.cpp") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/autostart.cpp") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/linux/autostart.cpp") + end + add_includedirs("apps/desktop/src") + add_includedirs("apps/desktop/src/autostart", {public = true}) + + target("config_center") + set_kind("object") + add_deps("rd_log", "autostart") + add_files("apps/desktop/src/config_center/*.cpp") + add_includedirs("apps/desktop/src/config_center", {public = true}) + + target("assets") + set_kind("headeronly") + add_includedirs("apps/desktop/src/gui/assets/localization", + "apps/desktop/src/gui/assets/fonts", + "apps/desktop/src/gui/assets/icons", + "apps/desktop/src/gui/assets/layouts", {public = true}) + + target("version_checker") + set_kind("object") + add_packages("cpp-httplib") + add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"") + add_deps("rd_log") + add_files("apps/desktop/src/version_checker/*.cpp") + add_includedirs("apps/desktop/src/version_checker", {public = true}) + if is_os("macosx") then + add_defines("CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN") + add_frameworks("Security", "CoreFoundation") + end + + target("tools") + set_kind("object") + add_deps("rd_log", "common", "crossdesk_wire") + add_files("apps/desktop/src/tools/*.cpp") + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/clipboard.cpp") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/clipboard.mm") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/linux/clipboard.cpp") + end + add_includedirs("apps/desktop/src", "apps/desktop/src/tools", {public = true}) + + target("gui") + set_kind("object") + set_languages("c++20") + -- spdlog 1.14 bundles fmt 10, whose consteval parser is rejected by + -- current Apple Clang in C++20 mode. Keep the established dependency + -- version and use fmt's supported runtime-parser fallback here. + add_defines("FMT_CONSTEVAL=") + add_packages("slint", {public = true}) + add_packages("libyuv", "tinyfiledialogs") + add_rules("slint") + add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"") + add_deps("rd_log", "common", "assets", "config_center", "minirtc", + "path_manager", "screen_capturer", "speaker_capturer", + "device_controller", "thumbnail", "version_checker", "tools", + "crossdesk_wire") + add_files("apps/desktop/src/gui/render.cpp", "apps/desktop/src/gui/application/gui_application.cpp", + "apps/desktop/src/gui/rendering/*.cpp", + "apps/desktop/src/gui/runtime/*.cpp", + "apps/desktop/src/platform/common/gui/service_status_runtime.cpp", + "apps/desktop/src/gui/features/devices/*.cpp", "apps/desktop/src/gui/features/input/*.cpp", + "apps/desktop/src/gui/features/clipboard/*.cpp", "apps/desktop/src/gui/features/file_transfer/*.cpp", + "apps/desktop/src/gui/features/settings/*.cpp", "apps/desktop/src/gui/ui/crossdesk_ui.slint") + add_includedirs("apps/desktop/src", "apps/desktop/src/gui", {public = true}) + add_includedirs("apps/desktop/src/platform/common/input") + if is_os("windows") then + add_cxxflags("/bigobj") + add_links("opengl32") + add_files("apps/desktop/src/platform/windows/gui/tray/win_tray.cpp", + "apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp", + "apps/desktop/src/platform/common/gui/video_renderer_factory_opengl.cpp", + "apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp", + "apps/desktop/src/platform/windows/gui/application/portable_service_integration.cpp") + add_includedirs("apps/desktop/src/platform/common/gui", + "apps/desktop/src/platform/windows/gui/tray") + add_includedirs("apps/desktop/src/platform/windows/service") + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/macos/gui/runtime/*.mm", + "apps/desktop/src/platform/macos/gui/tray/*.mm", + "apps/desktop/src/platform/macos/gui/window_drag_mac.mm", + "apps/desktop/src/platform/macos/gui/metal_video_renderer.mm", + "apps/desktop/src/platform/macos/gui/video_renderer_factory_metal.mm") + add_includedirs("apps/desktop/src/platform/macos/gui", + "apps/desktop/src/platform/macos/gui/tray") + elseif is_os("linux") then + add_links("GL") + add_files("apps/desktop/src/platform/linux/gui/tray/linux_tray.cpp", + "apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp", + "apps/desktop/src/platform/common/gui/video_renderer_factory_opengl.cpp") + add_includedirs("apps/desktop/src/platform/common/gui", + "apps/desktop/src/platform/linux/gui/tray", + "apps/desktop/src/platform/linux/common") + end + + if is_os("windows") then + target("wgc_plugin") + set_kind("shared") + add_packages("libyuv") + add_deps("rd_log", "path_manager", "crossdesk_wire") + add_defines("CROSSDESK_WGC_PLUGIN_BUILD=1") + -- Keep the project on C++17 while C++/WinRT still falls back to + -- MSVC's deprecated experimental coroutine header. + add_defines("_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS") + add_links("windowsapp") + add_files("apps/desktop/src/platform/windows/screen_capturer/screen_capturer_wgc.cpp", + "apps/desktop/src/platform/windows/screen_capturer/wgc_session_impl.cpp", + "apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp") + add_files("apps/desktop/resources/windows/wgc_plugin.rc") + add_includedirs("apps/desktop/src/common", "apps/desktop/src/screen_capturer", + "apps/desktop/src/platform/windows/screen_capturer") + + target("crossdesk_service") + set_kind("binary") + add_deps("rd_log", "path_manager") + add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv") + add_files("apps/desktop/src/platform/windows/service/main.cpp", + "apps/desktop/src/platform/windows/service/service_host.cpp") + add_files("apps/desktop/resources/windows/crossdesk_service.rc") + add_includedirs("apps/desktop/src/platform/windows/service") + + target("crossdesk_session_helper") + set_kind("binary") + add_packages("libyuv") + add_deps("rd_log", "path_manager") + add_links("Advapi32", "User32", "Wtsapi32", "Gdi32") + add_files("apps/desktop/src/platform/windows/service/session_helper_main.cpp") + add_files("apps/desktop/resources/windows/crossdesk_session_helper.rc") + add_includedirs("apps/desktop/src/common", + "apps/desktop/src/platform/windows/service") + add_includedirs("apps/desktop/src/platform/windows/input") + end + + target("crossdesk") + set_kind("binary") + add_deps("rd_log", "common", "gui") + add_files("apps/desktop/src/app/*.cpp") + add_includedirs("apps/desktop/src", "apps/desktop/src/app", {public = true}) + if is_os("windows") then + add_files("apps/desktop/src/platform/windows/daemon_backend.cpp") + add_files("apps/desktop/src/platform/windows/service/service_host.cpp") + add_includedirs("apps/desktop/src/platform/windows/service") + add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv") + add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper") + add_files(crossdesk_windows_resource) + elseif is_os("macosx") then + add_files("apps/desktop/src/platform/common/posix_daemon_backend.cpp", + "apps/desktop/src/platform/macos/daemon_backend.cpp") + elseif is_os("linux") then + add_files("apps/desktop/src/platform/common/posix_daemon_backend.cpp", + "apps/desktop/src/platform/linux/daemon_backend.cpp") + end + after_build(copy_slint_runtime) +end diff --git a/ios/CrossDeskMobile.xcodeproj/project.pbxproj b/apps/ios/CrossDeskMobile.xcodeproj/project.pbxproj similarity index 97% rename from ios/CrossDeskMobile.xcodeproj/project.pbxproj rename to apps/ios/CrossDeskMobile.xcodeproj/project.pbxproj index beaa325..3c42045 100644 --- a/ios/CrossDeskMobile.xcodeproj/project.pbxproj +++ b/apps/ios/CrossDeskMobile.xcodeproj/project.pbxproj @@ -272,10 +272,8 @@ GENERATE_INFOPLIST_FILE = NO; HEADER_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/../submodules/minirtc/src/api", - "$(PROJECT_DIR)/../src/device_controller", - "$(PROJECT_DIR)/../src/common", - "$(PROJECT_DIR)/../src/tools", + "$(PROJECT_DIR)/../../deps/submodules/minirtc/src/api", + "$(PROJECT_DIR)/../../libs/wire/include", ); INFOPLIST_FILE = CrossDeskMobile/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -323,10 +321,8 @@ GENERATE_INFOPLIST_FILE = NO; HEADER_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/../submodules/minirtc/src/api", - "$(PROJECT_DIR)/../src/device_controller", - "$(PROJECT_DIR)/../src/common", - "$(PROJECT_DIR)/../src/tools", + "$(PROJECT_DIR)/../../deps/submodules/minirtc/src/api", + "$(PROJECT_DIR)/../../libs/wire/include", ); INFOPLIST_FILE = CrossDeskMobile/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/ios/CrossDeskMobile.xcodeproj/xcshareddata/xcschemes/CrossDeskMobile.xcscheme b/apps/ios/CrossDeskMobile.xcodeproj/xcshareddata/xcschemes/CrossDeskMobile.xcscheme similarity index 100% rename from ios/CrossDeskMobile.xcodeproj/xcshareddata/xcschemes/CrossDeskMobile.xcscheme rename to apps/ios/CrossDeskMobile.xcodeproj/xcshareddata/xcschemes/CrossDeskMobile.xcscheme diff --git a/ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/Contents.json rename to apps/ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/CrossDeskAppIcon-1024.png b/apps/ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/CrossDeskAppIcon-1024.png similarity index 100% rename from ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/CrossDeskAppIcon-1024.png rename to apps/ios/CrossDeskMobile/Assets.xcassets/AppIcon.appiconset/CrossDeskAppIcon-1024.png diff --git a/ios/CrossDeskMobile/Assets.xcassets/Contents.json b/apps/ios/CrossDeskMobile/Assets.xcassets/Contents.json similarity index 100% rename from ios/CrossDeskMobile/Assets.xcassets/Contents.json rename to apps/ios/CrossDeskMobile/Assets.xcassets/Contents.json diff --git a/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.h b/apps/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.h similarity index 100% rename from ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.h rename to apps/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.h diff --git a/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.mm b/apps/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.mm similarity index 98% rename from ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.mm rename to apps/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.mm index 1604ee9..2ea2ddc 100644 --- a/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.mm +++ b/apps/ios/CrossDeskMobile/Bridge/CrossDeskRTCBridge.mm @@ -15,9 +15,10 @@ #include #include -#include "device_controller.h" -#include "file_transfer_protocol.h" -#include "stream_names.h" +#include +#include +#include +#include #include "minirtc.h" namespace { @@ -29,16 +30,16 @@ using crossdesk::MouseFlag; using crossdesk::RemoteAction; using crossdesk::ServiceCommandFlag; using crossdesk::kFileAckMagic; -using crossdesk::protocol::kAudioStream; -using crossdesk::protocol::kClipboardStream; -using crossdesk::protocol::kControlStream; -using crossdesk::protocol::kDataStream; -using crossdesk::protocol::kFileChunkSize; -using crossdesk::protocol::kFileFeedbackStream; -using crossdesk::protocol::kFileStream; -using crossdesk::protocol::kKeyboardStream; -using crossdesk::protocol::kMaxClipboardBytes; -using crossdesk::protocol::kMouseStream; +using crossdesk::kAudioStream; +using crossdesk::kClipboardStream; +using crossdesk::kControlStream; +using crossdesk::kDataStream; +using crossdesk::kFileChunkSize; +using crossdesk::kFileFeedbackStream; +using crossdesk::kFileStream; +using crossdesk::kKeyboardStream; +using crossdesk::kMaxClipboardBytes; +using crossdesk::kMouseStream; struct IncomingFile { std::string name; @@ -564,8 +565,8 @@ Params MakeParams(const RTCState &state, const std::string &user_id, - (void)requestKeyFrame { dispatch_async(_rtcQueue, ^{ if (self->_state->controller_peer) { - const std::string stream = - "Display" + std::to_string(self->_selectedDisplay.load() + 1); + const std::string stream = crossdesk::MakeDisplayStreamId( + static_cast(self->_selectedDisplay.load())); RequestVideoKeyFrame(self->_state->controller_peer, stream.c_str()); } }); @@ -744,7 +745,7 @@ Params MakeParams(const RTCState &state, const std::string &user_id, const bool last = offset + bytes_read >= total_size; const std::string file_name(last_component.UTF8String ?: "file"); const std::string* file_name_pointer = first ? &file_name : nullptr; - std::vector chunk = crossdesk::protocol::EncodeFileChunk( + std::vector chunk = crossdesk::EncodeFileChunk( file_id, offset, total_size, payload.data(), static_cast(bytes_read), file_name_pointer, first, last); if (chunk.empty()) { @@ -1058,8 +1059,8 @@ Params MakeParams(const RTCState &state, const std::string &user_id, const std::string host_info = action.to_json(); SendReliableDataFrame(self->_state->controller_peer, host_info.data(), host_info.size(), kControlStream); - const std::string stream = - "Display" + std::to_string(self->_selectedDisplay.load() + 1); + const std::string stream = crossdesk::MakeDisplayStreamId( + static_cast(self->_selectedDisplay.load())); RequestVideoKeyFrame(self->_state->controller_peer, stream.c_str()); }); } @@ -1098,8 +1099,8 @@ Params MakeParams(const RTCState &state, const std::string &user_id, if (!frame || frame->width == 0 || frame->height == 0) return; if (sourceID && sourceIDSize > 0) { const std::string source(sourceID, sourceIDSize); - const std::string expected = - "Display" + std::to_string(_selectedDisplay.load() + 1); + const std::string expected = crossdesk::MakeDisplayStreamId( + static_cast(_selectedDisplay.load())); if (source != expected) return; } const bool has_native_pixel_buffer = @@ -1397,7 +1398,7 @@ Params MakeParams(const RTCState &state, const std::string &user_id, if (source == kFileFeedbackStream) { FileTransferAck ack{}; - if (!crossdesk::protocol::DecodeFileTransferAck(data, size, &ack)) return; + if (!crossdesk::DecodeFileTransferAck(data, size, &ack)) return; NSString *name = nil; double progress = 0; @@ -1430,8 +1431,8 @@ Params MakeParams(const RTCState &state, const std::string &user_id, } if (source != kFileStream) return; - crossdesk::protocol::FileChunkView chunk; - if (!crossdesk::protocol::DecodeFileChunk(data, size, &chunk)) return; + crossdesk::FileChunkView chunk; + if (!crossdesk::DecodeFileChunk(data, size, &chunk)) return; const crossdesk::FileChunkHeader& header = chunk.header; NSURL *completed_url = nil; @@ -1532,7 +1533,7 @@ Params MakeParams(const RTCState &state, const std::string &user_id, dispatch_async(_rtcQueue, ^{ if (self->_state->controller_peer) { const auto encoded_ack = - crossdesk::protocol::EncodeFileTransferAck(ack); + crossdesk::EncodeFileTransferAck(ack); SendReliableDataFrame(self->_state->controller_peer, encoded_ack.data(), encoded_ack.size(), kFileFeedbackStream); diff --git a/ios/CrossDeskMobile/ContentView.swift b/apps/ios/CrossDeskMobile/ContentView.swift similarity index 100% rename from ios/CrossDeskMobile/ContentView.swift rename to apps/ios/CrossDeskMobile/ContentView.swift diff --git a/ios/CrossDeskMobile/CrossDeskMobile-Bridging-Header.h b/apps/ios/CrossDeskMobile/CrossDeskMobile-Bridging-Header.h similarity index 100% rename from ios/CrossDeskMobile/CrossDeskMobile-Bridging-Header.h rename to apps/ios/CrossDeskMobile/CrossDeskMobile-Bridging-Header.h diff --git a/ios/CrossDeskMobile/CrossDeskMobileApp.swift b/apps/ios/CrossDeskMobile/CrossDeskMobileApp.swift similarity index 100% rename from ios/CrossDeskMobile/CrossDeskMobileApp.swift rename to apps/ios/CrossDeskMobile/CrossDeskMobileApp.swift diff --git a/ios/CrossDeskMobile/Info.plist b/apps/ios/CrossDeskMobile/Info.plist similarity index 100% rename from ios/CrossDeskMobile/Info.plist rename to apps/ios/CrossDeskMobile/Info.plist diff --git a/ios/CrossDeskMobile/Input/RemoteTouchInputView.swift b/apps/ios/CrossDeskMobile/Input/RemoteTouchInputView.swift similarity index 100% rename from ios/CrossDeskMobile/Input/RemoteTouchInputView.swift rename to apps/ios/CrossDeskMobile/Input/RemoteTouchInputView.swift diff --git a/ios/CrossDeskMobile/RemoteSessionModel.swift b/apps/ios/CrossDeskMobile/RemoteSessionModel.swift similarity index 100% rename from ios/CrossDeskMobile/RemoteSessionModel.swift rename to apps/ios/CrossDeskMobile/RemoteSessionModel.swift diff --git a/ios/CrossDeskMobile/RemoteSessionView.swift b/apps/ios/CrossDeskMobile/RemoteSessionView.swift similarity index 100% rename from ios/CrossDeskMobile/RemoteSessionView.swift rename to apps/ios/CrossDeskMobile/RemoteSessionView.swift diff --git a/ios/CrossDeskMobile/Rendering/NativeMetalVideoView.swift b/apps/ios/CrossDeskMobile/Rendering/NativeMetalVideoView.swift similarity index 100% rename from ios/CrossDeskMobile/Rendering/NativeMetalVideoView.swift rename to apps/ios/CrossDeskMobile/Rendering/NativeMetalVideoView.swift diff --git a/ios/README.md b/apps/ios/README.md similarity index 95% rename from ios/README.md rename to apps/ios/README.md index 9c7589b..64de240 100644 --- a/ios/README.md +++ b/apps/ios/README.md @@ -14,13 +14,13 @@ There is no `WKWebView` or browser runtime. Open `CrossDeskMobile.xcodeproj`, select the `CrossDeskMobile` scheme and a physical device, then build. The first build phase compiles MiniRTC and the -shared CrossDesk protocol, then merges their iPhoneOS static dependencies into +CrossDesk wire library, then merges their iPhoneOS static dependencies into a local archive under `Vendor/`. You can also verify the target without code signing: ```sh -xcodebuild -project ios/CrossDeskMobile.xcodeproj \ +xcodebuild -project apps/ios/CrossDeskMobile.xcodeproj \ -scheme CrossDeskMobile \ -configuration Debug \ -destination 'generic/platform=iOS' \ diff --git a/ios/scripts/build_minirtc_ios.sh b/apps/ios/scripts/build_minirtc_ios.sh similarity index 73% rename from ios/scripts/build_minirtc_ios.sh rename to apps/ios/scripts/build_minirtc_ios.sh index d6c157f..b48865d 100755 --- a/ios/scripts/build_minirtc_ios.sh +++ b/apps/ios/scripts/build_minirtc_ios.sh @@ -4,8 +4,8 @@ set -euo pipefail SCRIPT_DIR="${0:A:h}" IOS_DIR="${SCRIPT_DIR:h}" -REPO_DIR="${IOS_DIR:h}" -MINIRTC_DIR="${REPO_DIR}/submodules/minirtc" +REPO_DIR="${IOS_DIR:h:h}" +MINIRTC_DIR="${REPO_DIR}/deps/submodules/minirtc" CONFIG_NAME="${CONFIGURATION:-Debug}" MODE="${CONFIG_NAME:l}" @@ -63,6 +63,7 @@ run_xmake() { NO_COLOR="${NO_COLOR:-}" \ DEVELOPER_DIR="${XMAKE_DEVELOPER_DIR}" \ XMAKE_PKG_INSTALLDIR="${XMAKE_PKG_INSTALLDIR}" \ + CROSSDESK_SOURCE_DIR="${REPO_DIR}" \ "${XMAKE_BIN}" "$@" } @@ -70,10 +71,13 @@ OUTPUT_DIR="${IOS_DIR}/Vendor/iphoneos/${CONFIG_NAME}" OUTPUT_LIBRARY="${OUTPUT_DIR}/libCrossDeskMiniRTC.a" MINIRTC_BUILD_DIR="${IOS_DIR}/.xmake/minirtc-build" MINIRTC_LIBRARY="${MINIRTC_BUILD_DIR}/iphoneos/arm64/${MODE}/libminirtc.a" -CROSSDESK_SOURCE_DIR="${REPO_DIR}/src" -CROSSDESK_BUILD_DIR="${IOS_DIR}/.xmake/crossdesk-build/iphoneos/arm64/${MODE}" +WIRE_BUILD_DIR="${IOS_DIR}/.xmake/wire-build" +WIRE_WORK_DIR="${IOS_DIR}/.xmake/wire-work" +WIRE_MANIFEST_FILE="${WIRE_WORK_DIR}/xmake.lua" +WIRE_LIBRARY="${WIRE_BUILD_DIR}/iphoneos/arm64/${MODE}/libcrossdesk_wire.a" -mkdir -p "${OUTPUT_DIR}" +mkdir -p "${OUTPUT_DIR}" "${WIRE_WORK_DIR}" +cp "${REPO_DIR}/xmake.lua" "${WIRE_MANIFEST_FILE}" # Xmake stores the configured build directory relative to the process working # directory. Xcode does not guarantee that directory for build phases, so keep @@ -92,6 +96,22 @@ if [[ ! -f "${MINIRTC_LIBRARY}" ]]; then exit 66 fi +# Build the platform-independent wire archive from the root project's target. +# The copied manifest only isolates Xmake's generated configuration; there is +# no second project definition to maintain. +( + cd "${WIRE_WORK_DIR}" + run_xmake f -P "${WIRE_WORK_DIR}" -c -o "${WIRE_BUILD_DIR}" \ + -p iphoneos -a arm64 -m "${MODE}" \ + --target_minver=16.0 -y + run_xmake b -P "${WIRE_WORK_DIR}" crossdesk_wire +) + +if [[ ! -f "${WIRE_LIBRARY}" ]]; then + print -u2 "CrossDesk wire archive was not produced at ${WIRE_LIBRARY}." + exit 66 +fi + # Xmake owns the package hashes, so query its resolved target instead of # embedding machine-specific ~/.xmake paths in the Xcode project. TARGET_INFO="$(cd "${MINIRTC_DIR}" && TERM=dumb NO_COLOR=1 \ @@ -122,49 +142,11 @@ for link_name in "${REQUIRED_LINKS[@]}"; do DEPENDENCY_ARCHIVES+=("${archive_path}") done -# Build the CrossDesk message and file-transfer codecs for the same iOS -# architecture, then merge them into the aggregate archive consumed by Xcode. -NLOHMANN_HEADER="$(find "${XMAKE_PKG_INSTALLDIR}/n/nlohmann_json" \ - -path '*/include/nlohmann/json.hpp' -print -quit 2>/dev/null || true)" -if [[ -z "${NLOHMANN_HEADER}" ]]; then - print -u2 "Unable to locate the nlohmann_json headers installed by Xmake." - exit 66 -fi -NLOHMANN_INCLUDE="${NLOHMANN_HEADER:h:h}" -IOS_SDK_PATH="$(xcrun --sdk iphoneos --show-sdk-path)" -mkdir -p "${CROSSDESK_BUILD_DIR}" - -CROSSDESK_CXXFLAGS=( - -std=c++17 -arch arm64 -isysroot "${IOS_SDK_PATH}" - -miphoneos-version-min=16.0 - -I"${CROSSDESK_SOURCE_DIR}/device_controller" - -I"${CROSSDESK_SOURCE_DIR}/common" - -I"${CROSSDESK_SOURCE_DIR}/tools" - -I"${NLOHMANN_INCLUDE}" -) -if [[ "${MODE}" == "debug" ]]; then - CROSSDESK_CXXFLAGS+=(-O0 -g) -else - CROSSDESK_CXXFLAGS+=(-O2 -DNDEBUG) -fi - -CROSSDESK_SOURCES=( - "${CROSSDESK_SOURCE_DIR}/device_controller/remote_action.cpp" - "${CROSSDESK_SOURCE_DIR}/tools/file_transfer_protocol.cpp" -) -CROSSDESK_OBJECTS=() -for source_path in "${CROSSDESK_SOURCES[@]}"; do - object_path="${CROSSDESK_BUILD_DIR}/${source_path:t:r}.o" - xcrun --sdk iphoneos clang++ "${CROSSDESK_CXXFLAGS[@]}" \ - -c "${source_path}" -o "${object_path}" - CROSSDESK_OBJECTS+=("${object_path}") -done - TEMP_LIBRARY="${OUTPUT_LIBRARY}.tmp" rm -f "${TEMP_LIBRARY}" /usr/bin/libtool -static -o "${TEMP_LIBRARY}" \ "${MINIRTC_LIBRARY}" "${DEPENDENCY_ARCHIVES[@]}" \ - "${CROSSDESK_OBJECTS[@]}" + "${WIRE_LIBRARY}" mv -f "${TEMP_LIBRARY}" "${OUTPUT_LIBRARY}" print "Created ${OUTPUT_LIBRARY}" diff --git a/submodules/minirtc b/deps/submodules/minirtc similarity index 100% rename from submodules/minirtc rename to deps/submodules/minirtc diff --git a/submodules/xmake.lua b/deps/submodules/xmake.lua similarity index 100% rename from submodules/xmake.lua rename to deps/submodules/xmake.lua diff --git a/thirdparty/cpp-httplib/patches/v0.26.0/fix-mingw.diff b/deps/thirdparty/cpp-httplib/patches/v0.26.0/fix-mingw.diff similarity index 100% rename from thirdparty/cpp-httplib/patches/v0.26.0/fix-mingw.diff rename to deps/thirdparty/cpp-httplib/patches/v0.26.0/fix-mingw.diff diff --git a/thirdparty/cpp-httplib/xmake.lua b/deps/thirdparty/cpp-httplib/xmake.lua similarity index 100% rename from thirdparty/cpp-httplib/xmake.lua rename to deps/thirdparty/cpp-httplib/xmake.lua diff --git a/thirdparty/rust/xmake.lua b/deps/thirdparty/rust/xmake.lua similarity index 100% rename from thirdparty/rust/xmake.lua rename to deps/thirdparty/rust/xmake.lua diff --git a/thirdparty/slint/xmake.lua b/deps/thirdparty/slint/xmake.lua similarity index 100% rename from thirdparty/slint/xmake.lua rename to deps/thirdparty/slint/xmake.lua diff --git a/thirdparty/xmake.lua b/deps/thirdparty/xmake.lua similarity index 100% rename from thirdparty/xmake.lua rename to deps/thirdparty/xmake.lua diff --git a/docker/linux-build/Dockerfile b/docker/linux-build/Dockerfile index c11e134..93a9ce6 100644 --- a/docker/linux-build/Dockerfile +++ b/docker/linux-build/Dockerfile @@ -144,11 +144,11 @@ RUN case "${TARGETARCH}" in \ WORKDIR /tmp/crossdesk-dependencies COPY xmake.lua ./xmake.lua -COPY xmake ./xmake -COPY thirdparty ./thirdparty -COPY submodules/xmake.lua ./submodules/xmake.lua -COPY submodules/minirtc/xmake.lua ./submodules/minirtc/xmake.lua -COPY submodules/minirtc/thirdparty ./submodules/minirtc/thirdparty +COPY apps/desktop/xmake ./apps/desktop/xmake +COPY deps/thirdparty ./deps/thirdparty +COPY deps/submodules/xmake.lua ./deps/submodules/xmake.lua +COPY deps/submodules/minirtc/xmake.lua ./deps/submodules/minirtc/xmake.lua +COPY deps/submodules/minirtc/thirdparty ./deps/submodules/minirtc/thirdparty # Configure the real project manifests so the image cache stays aligned with # CrossDesk's exact dependency graph. Source files are intentionally excluded diff --git a/docs/gui-architecture.md b/docs/gui-architecture.md index ed647d3..e4980f2 100644 --- a/docs/gui-architecture.md +++ b/docs/gui-architecture.md @@ -1,11 +1,11 @@ # GUI 目录结构与职责说明 -本文档说明 `src/gui` 的目录结构、核心类型、依赖方向和代码归属规则,用于避免 `Render`、`GuiApplication` 或 `GuiRuntime` 再次演变成职责混杂的大类。 +本文档说明 `apps/desktop/src/gui` 的目录结构、核心类型、依赖方向和代码归属规则,用于避免 `Render`、`GuiApplication` 或 `GuiRuntime` 再次演变成职责混杂的大类。 ## 目录结构 ```text -src/gui/ +apps/desktop/src/gui/ ├── render.h # 对外稳定入口 Render ├── render.cpp # 创建 GuiApplication 并转发 Run ├── application/ # SDL/ImGui 应用外壳 @@ -19,8 +19,6 @@ src/gui/ ├── runtime/ # 非界面的 GUI 运行时 │ ├── gui_runtime.h/.cpp # 运行时协调接口与公共实现 │ ├── connection_runtime.cpp # 连接、在线探测、超时及会话清理 -│ ├── windows_service_runtime.cpp # Windows 服务和安全桌面集成 -│ ├── mac_permission_runtime.mm # macOS 权限检查和系统设置调用 │ ├── gui_state.h # ApplicationState 与 RuntimeState 组合点 │ ├── runtime_state.h # 配置、连接、平台和通信状态 │ ├── remote_session.h # 单个远端会话 RemoteSession @@ -39,11 +37,11 @@ src/gui/ │ ├── panels/ # 主窗口内嵌面板 │ ├── toolbars/ # 标题栏、状态栏和控制栏 │ └── windows/ # 独立窗口和模态对话框 -├── platform/ # 原生桌面平台适配 -│ └── tray/ # Windows、macOS 和 Linux 托盘 └── assets/ # 字体、图标、布局和本地化资源 ``` +平台私有 GUI 实现统一位于 `apps/desktop/src/platform//gui/`,包括托盘、macOS Metal 渲染与权限、Windows 服务运行时等;Windows 与 Linux 共用的 OpenGL 渲染器位于 `apps/desktop/src/platform/common/gui/`。 + ## 分层关系 ```text @@ -69,7 +67,7 @@ Render 底层功能模块不应反向依赖具体面板、工具栏或窗口。 -桌面端与 iOS Bridge 复用 `src/` 中的协议定义:`device_controller/` 保存控制消息,`common/` 保存数据流名称和鼠标指针类型,`tools/` 保存文件传输线格式。操作系统输入注入、剪贴板、文件读写以及界面状态仍由各自应用实现。 +桌面端与 iOS Bridge 统一依赖静态库 `crossdesk_wire`。线上契约头位于 `libs/wire/include/`,包含控制消息、鼠标指针类型、显示流 ID、数据流名称和文件传输线格式。操作系统输入注入、剪贴板、文件读写以及界面状态仍由各自应用实现,`libs/wire/` 不得依赖桌面端或操作系统专用头文件。 ## 核心类型 @@ -199,12 +197,12 @@ SDL quit / tray exit | 连接、会话、Peer 回调和平台运行时 | `runtime/` | | 视频、音频、鼠标、键盘设备生命周期 | `features/devices/` | | 键盘状态与桌面输入处理 | `features/input/` | -| 控制消息和线格式 | `device_controller/`、`common/`、`tools/` | +| 线上控制消息和线格式 | `libs/wire/` | | 剪贴板同步 | `features/clipboard/` | | 文件传输 | `features/file_transfer/` | | 配置持久化 | `features/settings/` | | 主窗口内嵌区域 | `views/panels/` | | 标题栏、状态栏、控制栏 | `views/toolbars/` | | 独立窗口和对话框 | `views/windows/` | -| 系统托盘 | `platform/tray/` | +| 系统托盘 | `apps/desktop/src/platform//gui/tray/` | | 字体、图标、布局和本地化数据 | `assets/` | diff --git a/src/common/display_stream_id.h b/libs/wire/include/display_stream_id.h similarity index 86% rename from src/common/display_stream_id.h rename to libs/wire/include/display_stream_id.h index 9ccf9cc..78233b2 100644 --- a/src/common/display_stream_id.h +++ b/libs/wire/include/display_stream_id.h @@ -1,12 +1,18 @@ -#ifndef CROSSDESK_DISPLAY_STREAM_ID_H_ -#define CROSSDESK_DISPLAY_STREAM_ID_H_ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-01 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _DISPLAY_STREAM_ID_H_ +#define _DISPLAY_STREAM_ID_H_ #include #include namespace crossdesk { -// MiniRTC stream identifiers are protocol-facing logical identifiers. Keep +// MiniRTC stream identifiers are wire-facing logical identifiers. Keep // them independent from platform display handles and user-visible names, both // of which may change or contain duplicate/Unicode text. inline std::string MakeDisplayStreamId(size_t display_index) { @@ -56,4 +62,4 @@ inline std::string ResolveDisplayStreamId( } // namespace crossdesk -#endif // CROSSDESK_DISPLAY_STREAM_ID_H_ +#endif \ No newline at end of file diff --git a/src/tools/file_transfer_protocol.h b/libs/wire/include/file_transfer_format.h similarity index 90% rename from src/tools/file_transfer_protocol.h rename to libs/wire/include/file_transfer_format.h index 1e35dde..cdfd66a 100644 --- a/src/tools/file_transfer_protocol.h +++ b/libs/wire/include/file_transfer_format.h @@ -1,5 +1,11 @@ -#ifndef _FILE_TRANSFER_PROTOCOL_H_ -#define _FILE_TRANSFER_PROTOCOL_H_ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-01 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _FILE_TRANSFER_FORMAT_H_ +#define _FILE_TRANSFER_FORMAT_H_ #include #include @@ -37,8 +43,6 @@ static_assert(sizeof(FileChunkHeader) == 31, static_assert(sizeof(FileTransferAck) == 28, "FileTransferAck wire layout must remain stable"); -namespace protocol { - struct FileChunkView { FileChunkHeader header{}; std::string file_name; @@ -61,7 +65,6 @@ std::array EncodeFileTransferAck( bool DecodeFileTransferAck(const char* data, std::size_t size, FileTransferAck* output); -} // namespace protocol } // namespace crossdesk #endif diff --git a/src/device_controller/device_controller.h b/libs/wire/include/remote_action.h similarity index 87% rename from src/device_controller/device_controller.h rename to libs/wire/include/remote_action.h index c21b00b..f7281bc 100644 --- a/src/device_controller/device_controller.h +++ b/libs/wire/include/remote_action.h @@ -1,17 +1,17 @@ /* * @Author: DI JUNKUN - * @Date: 2023-12-14 - * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. + * @Date: 2026-09-01 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. */ -#ifndef _DEVICE_CONTROLLER_H_ -#define _DEVICE_CONTROLLER_H_ +#ifndef _REMOTE_ACTION_H_ +#define _REMOTE_ACTION_H_ #include #include #include -#include "remote_cursor_shape.h" +#include namespace crossdesk { @@ -134,14 +134,6 @@ struct RemoteAction { // Other RemoteAction variants do not own memory and are left unchanged. void FreeRemoteAction(RemoteAction& action); -// int key_code, bool is_down, uint32_t scan_code, bool extended -using OnKeyAction = void (*)(int, bool, uint32_t, bool, void*); - -class DeviceController { - public: - virtual ~DeviceController() = default; -}; - } // namespace crossdesk -#endif +#endif \ No newline at end of file diff --git a/src/common/remote_cursor_shape.h b/libs/wire/include/remote_cursor_shape.h similarity index 78% rename from src/common/remote_cursor_shape.h rename to libs/wire/include/remote_cursor_shape.h index 906b710..f9a42c2 100644 --- a/src/common/remote_cursor_shape.h +++ b/libs/wire/include/remote_cursor_shape.h @@ -1,11 +1,17 @@ -#ifndef CROSSDESK_COMMON_REMOTE_CURSOR_SHAPE_H_ -#define CROSSDESK_COMMON_REMOTE_CURSOR_SHAPE_H_ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-01 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _REMOTE_CURSOR_SHAPE_H_ +#define _REMOTE_CURSOR_SHAPE_H_ #include namespace crossdesk { -// Keep these values aligned with Slint's MouseCursor enum. The wire protocol +// Keep these values aligned with Slint's MouseCursor enum. The wire // intentionally carries a semantic cursor instead of a platform handle so a // Windows, macOS or Linux host can control a different desktop platform. enum class RemoteCursorShape : uint8_t { @@ -42,4 +48,4 @@ enum class RemoteCursorShape : uint8_t { } // namespace crossdesk -#endif // CROSSDESK_COMMON_REMOTE_CURSOR_SHAPE_H_ +#endif diff --git a/src/common/stream_names.h b/libs/wire/include/stream_names.h similarity index 80% rename from src/common/stream_names.h rename to libs/wire/include/stream_names.h index d47dceb..b25cde7 100644 --- a/src/common/stream_names.h +++ b/libs/wire/include/stream_names.h @@ -1,9 +1,15 @@ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-01 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + #ifndef _STREAM_NAMES_H_ #define _STREAM_NAMES_H_ #include -namespace crossdesk::protocol { +namespace crossdesk { inline constexpr char kAudioStream[] = "control_audio"; inline constexpr char kDataStream[] = "data"; @@ -17,6 +23,6 @@ inline constexpr char kClipboardStream[] = "clipboard"; inline constexpr std::size_t kFileChunkSize = 64 * 1024; inline constexpr std::size_t kMaxClipboardBytes = 128 * 1024; -} // namespace crossdesk::protocol +} // namespace crossdesk #endif diff --git a/src/tools/file_transfer_protocol.cpp b/libs/wire/src/file_transfer.cpp similarity index 96% rename from src/tools/file_transfer_protocol.cpp rename to libs/wire/src/file_transfer.cpp index 8311689..eaff0df 100644 --- a/src/tools/file_transfer_protocol.cpp +++ b/libs/wire/src/file_transfer.cpp @@ -1,9 +1,9 @@ -#include "file_transfer_protocol.h" +#include #include #include -namespace crossdesk::protocol { +namespace crossdesk { std::vector EncodeFileChunk(uint32_t file_id, uint64_t offset, uint64_t total_size, const char* data, @@ -78,4 +78,4 @@ bool DecodeFileTransferAck(const char* data, std::size_t size, output->acked_offset <= output->total_size; } -} // namespace crossdesk::protocol +} // namespace crossdesk diff --git a/src/device_controller/remote_action.cpp b/libs/wire/src/remote_action.cpp similarity index 99% rename from src/device_controller/remote_action.cpp rename to libs/wire/src/remote_action.cpp index bb293c2..3876123 100644 --- a/src/device_controller/remote_action.cpp +++ b/libs/wire/src/remote_action.cpp @@ -1,4 +1,4 @@ -#include "device_controller.h" +#include #include #include diff --git a/tests/keyboard_state_protocol_test.cpp b/libs/wire/tests/keyboard_state_wire_test.cpp similarity index 94% rename from tests/keyboard_state_protocol_test.cpp rename to libs/wire/tests/keyboard_state_wire_test.cpp index 8c5e780..ddb1e2a 100644 --- a/tests/keyboard_state_protocol_test.cpp +++ b/libs/wire/tests/keyboard_state_wire_test.cpp @@ -1,7 +1,7 @@ #include #include -#include "device_controller.h" +#include namespace { @@ -42,6 +42,8 @@ int main() { crossdesk::ControlType::service_command, 6); ok &= ExpectEqual("keyboard_state type", crossdesk::ControlType::keyboard_state, 7); + ok &= ExpectEqual("cursor_state type", crossdesk::ControlType::cursor_state, + 8); crossdesk::RemoteAction action{}; action.type = crossdesk::ControlType::keyboard_state; diff --git a/tests/protocol_test.cpp b/libs/wire/tests/wire_test.cpp similarity index 61% rename from tests/protocol_test.cpp rename to libs/wire/tests/wire_test.cpp index 2fd4c2d..1c3ce9f 100644 --- a/tests/protocol_test.cpp +++ b/libs/wire/tests/wire_test.cpp @@ -1,11 +1,13 @@ #include +#include #include +#include #include -#include "device_controller.h" -#include "cursor_position.h" -#include "file_transfer_protocol.h" -#include "stream_names.h" +#include +#include +#include +#include namespace { @@ -20,6 +22,27 @@ bool Expect(bool condition, const char* message) { int main() { bool ok = true; + ok &= Expect(static_cast(crossdesk::ControlType::invalid) == -1 && + static_cast(crossdesk::ControlType::mouse) == 0 && + static_cast(crossdesk::ControlType::keyboard) == 1 && + static_cast(crossdesk::ControlType::audio_capture) == 2 && + static_cast(crossdesk::ControlType::host_infomation) == 3 && + static_cast(crossdesk::ControlType::display_id) == 4 && + static_cast(crossdesk::ControlType::service_status) == 5 && + static_cast(crossdesk::ControlType::service_command) == 6 && + static_cast(crossdesk::ControlType::keyboard_state) == 7 && + static_cast(crossdesk::ControlType::cursor_state) == 8, + "ControlType wire values changed"); + ok &= Expect(static_cast(crossdesk::RemoteCursorShape::default_cursor) == + 0 && + static_cast(crossdesk::RemoteCursorShape::pointer) == 3 && + static_cast(crossdesk::RemoteCursorShape::nwse_resize) == + 28, + "remote cursor wire values changed"); + ok &= Expect(sizeof(crossdesk::FileChunkHeader) == 31 && + sizeof(crossdesk::FileTransferAck) == 28, + "file transfer wire structure size changed"); + crossdesk::RemoteAction unset_action{}; ok &= Expect(unset_action.type == crossdesk::ControlType::invalid, "default RemoteAction should be invalid"); @@ -127,23 +150,13 @@ int main() { "host information JSON round trip failed"); crossdesk::FreeRemoteAction(parsed_host); - const std::vector displays = { - crossdesk::DisplayInfo("Test Display", 0, 0, 1920, 1080)}; - crossdesk::CursorState normalized_cursor{}; - ok &= Expect(crossdesk::NormalizeCursorPosition( - 960.0, 540.0, displays, 0, &normalized_cursor) && - normalized_cursor.position_valid && - normalized_cursor.x == 0.5f && - normalized_cursor.y == 0.5f, - "cursor position should use continuous display extents"); - const std::string name = "example.txt"; - const std::string payload = "CrossDesk protocol"; - const auto encoded = crossdesk::protocol::EncodeFileChunk( + const std::string payload = "CrossDesk wire"; + const auto encoded = crossdesk::EncodeFileChunk( 17, 0, payload.size(), payload.data(), static_cast(payload.size()), &name, true, true); - crossdesk::protocol::FileChunkView chunk; - ok &= Expect(crossdesk::protocol::DecodeFileChunk( + crossdesk::FileChunkView chunk; + ok &= Expect(crossdesk::DecodeFileChunk( encoded.data(), encoded.size(), &chunk), "file chunk should decode"); ok &= Expect(chunk.header.file_id == 17 && chunk.file_name == name && @@ -151,9 +164,30 @@ int main() { std::memcmp(chunk.payload, payload.data(), payload.size()) == 0, "file chunk round trip changed fields"); - ok &= Expect(!crossdesk::protocol::DecodeFileChunk( + ok &= Expect(!crossdesk::DecodeFileChunk( encoded.data(), encoded.size() - 1, &chunk), "truncated file chunk should be rejected"); + ok &= Expect(crossdesk::EncodeFileChunk( + 17, payload.size() + 1, payload.size(), nullptr, 0, nullptr, + false, false) + .empty(), + "file chunk offset beyond total size should be rejected"); + ok &= Expect(crossdesk::EncodeFileChunk( + 17, payload.size(), payload.size(), payload.data(), 1, + nullptr, false, false) + .empty(), + "file chunk payload beyond remaining size should be rejected"); + ok &= Expect(crossdesk::EncodeFileChunk( + 17, 0, 1, nullptr, 1, nullptr, false, false) + .empty(), + "non-empty file chunk with null payload should be rejected"); + const std::string oversized_name( + static_cast(std::numeric_limits::max()) + 1, + 'x'); + ok &= Expect(crossdesk::EncodeFileChunk( + 17, 0, 0, nullptr, 0, &oversized_name, true, true) + .empty(), + "oversized file name should be rejected"); crossdesk::FileTransferAck ack{}; ack.magic = crossdesk::kFileAckMagic; @@ -161,19 +195,39 @@ int main() { ack.acked_offset = payload.size(); ack.total_size = payload.size(); ack.flags = 1; - const auto encoded_ack = crossdesk::protocol::EncodeFileTransferAck(ack); + const auto encoded_ack = crossdesk::EncodeFileTransferAck(ack); crossdesk::FileTransferAck decoded_ack{}; - ok &= Expect(crossdesk::protocol::DecodeFileTransferAck( + ok &= Expect(crossdesk::DecodeFileTransferAck( encoded_ack.data(), encoded_ack.size(), &decoded_ack) && decoded_ack.file_id == ack.file_id && decoded_ack.acked_offset == ack.acked_offset && decoded_ack.flags == ack.flags, "file acknowledgement round trip failed"); + ok &= Expect(!crossdesk::DecodeFileTransferAck( + encoded_ack.data(), encoded_ack.size() - 1, &decoded_ack), + "short file acknowledgement should be rejected"); + auto invalid_ack = encoded_ack; + crossdesk::FileTransferAck invalid_ack_value = ack; + invalid_ack_value.acked_offset = invalid_ack_value.total_size + 1; + invalid_ack = + crossdesk::EncodeFileTransferAck(invalid_ack_value); + ok &= Expect(!crossdesk::DecodeFileTransferAck( + invalid_ack.data(), invalid_ack.size(), &decoded_ack), + "file acknowledgement beyond total size should be rejected"); - ok &= Expect(std::string(crossdesk::protocol::kControlStream) == + ok &= Expect(std::string(crossdesk::kControlStream) == "control_data" && - std::string(crossdesk::protocol::kFileFeedbackStream) == + std::string(crossdesk::kFileFeedbackStream) == "file_feedback", "stream names changed"); + ok &= Expect(crossdesk::MakeDisplayStreamId(0) == "Display1" && + crossdesk::MakeDisplayStreamId(2) == "Display3" && + crossdesk::IsRegisteredDisplayStreamId("Display2", 2) && + !crossdesk::IsRegisteredDisplayStreamId("Display3", 2), + "display stream identifiers changed"); + ok &= Expect(crossdesk::ResolveDisplayStreamId(nullptr, 1) == "Display1" && + crossdesk::ResolveDisplayStreamId("invalid", 3, 1) == + "Display2", + "display stream fallback changed"); return ok ? 0 : 1; } diff --git a/src/app/daemon.cpp b/src/app/daemon.cpp deleted file mode 100644 index 389b490..0000000 --- a/src/app/daemon.cpp +++ /dev/null @@ -1,342 +0,0 @@ -#include "daemon.h" - -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#include -#elif __APPLE__ -#include -#include -#include -#include -#include -#include -#include -#include -#else -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#endif - -#ifndef _WIN32 -volatile std::sig_atomic_t Daemon::stop_requested_ = 0; -#endif - -namespace { -constexpr int kRestartDelayMs = 1000; -#ifndef _WIN32 -constexpr int kWaitPollIntervalMs = 200; -#endif -} // namespace - -// get executable file path -static std::string GetExecutablePath() { -#ifdef _WIN32 - char path[32768]; - DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path)); - if (length > 0 && length < sizeof(path)) { - return std::string(path); - } -#elif __APPLE__ - char path[PATH_MAX]; - uint32_t size = sizeof(path); - if (_NSGetExecutablePath(path, &size) == 0) { - char resolved_path[PATH_MAX]; - if (realpath(path, resolved_path) != nullptr) { - return std::string(resolved_path); - } - return std::string(path); - } -#else - char path[PATH_MAX]; - ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1); - if (count != -1) { - path[count] = '\0'; - return std::string(path); - } -#endif - return ""; -} - -Daemon::Daemon(const std::string& name) : name_(name), running_(false) {} - -void Daemon::stop() { - running_.store(false); -#ifndef _WIN32 - stop_requested_ = 1; -#endif -} - -bool Daemon::isRunning() const { -#ifndef _WIN32 - return running_.load() && (stop_requested_ == 0); -#else - return running_.load(); -#endif -} - -bool Daemon::start(MainLoopFunc loop) { -#ifdef _WIN32 - running_.store(true); - return runWithRestart(loop); -#elif __APPLE__ - // macOS: Use child process monitoring (like Windows) to preserve GUI - stop_requested_ = 0; - running_.store(true); - return runWithRestart(loop); -#else - // linux: Daemonize first, then run with restart monitoring - stop_requested_ = 0; - - // check if running from terminal before fork - bool from_terminal = - (isatty(STDIN_FILENO) != 0) || (isatty(STDOUT_FILENO) != 0); - - // first fork: detach from terminal - pid_t pid = fork(); - if (pid < 0) { - std::cerr << "Failed to fork daemon process" << std::endl; - return false; - } - if (pid > 0) _exit(0); - - if (setsid() < 0) { - std::cerr << "Failed to create new session" << std::endl; - return false; - } - - pid = fork(); - if (pid < 0) { - std::cerr << "Failed to fork daemon process (second fork)" << std::endl; - return false; - } - if (pid > 0) _exit(0); - - umask(0); - if (chdir("/") != 0) { - std::cerr << "Failed to change daemon working directory to /: " - << std::strerror(errno) << std::endl; - return false; - } - - // redirect file descriptors: keep stdout/stderr if from terminal, else - // redirect to /dev/null - int fd = open("/dev/null", O_RDWR); - if (fd >= 0) { - dup2(fd, STDIN_FILENO); - if (!from_terminal) { - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - } - if (fd > 2) close(fd); - } - - // set up signal handlers - signal(SIGTERM, [](int) { stop_requested_ = 1; }); - signal(SIGINT, [](int) { stop_requested_ = 1; }); - - // ignore SIGPIPE - signal(SIGPIPE, SIG_IGN); - - running_.store(true); - return runWithRestart(loop); -#endif -} - -#ifdef _WIN32 -static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) { - try { - loop(); - return 0; // normal exit - } catch (const std::exception& e) { - std::cerr << "Exception caught: " << e.what() << std::endl; - return 1; // c++ exception - } catch (...) { - std::cerr << "Unknown exception caught" << std::endl; - return 1; // other exception - } -} - -static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) { - __try { - return RunLoopCatchCpp(loop); - } __except (EXCEPTION_EXECUTE_HANDLER) { - // catch system-level crashes (access violation, divide by zero, etc.) - DWORD code = GetExceptionCode(); - std::cerr << "System crash detected (SEH exception code: 0x" << std::hex - << code << std::dec << ")" << std::endl; - return 2; // System crash - } -} -#endif - -// run with restart logic: parent monitors child process and restarts on crash -bool Daemon::runWithRestart(MainLoopFunc loop) { - int restart_count = 0; - std::string exe_path = GetExecutablePath(); - if (exe_path.empty()) { - std::cerr - << "Failed to get executable path, falling back to direct execution" - << std::endl; - while (isRunning()) { - try { - loop(); - break; - } catch (...) { - restart_count++; - std::cerr << "Exception caught, restarting... (attempt " - << restart_count << ")" << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); - } - } - return true; - } - - while (isRunning()) { -#ifdef _WIN32 - // windows: use CreateProcess to create child process - STARTUPINFOA si = {sizeof(si)}; - PROCESS_INFORMATION pi = {0}; - - std::string cmd_line = "\"" + exe_path + "\" --child"; - std::vector cmd_line_buf(cmd_line.begin(), cmd_line.end()); - cmd_line_buf.push_back('\0'); - - BOOL success = CreateProcessA( - nullptr, // executable file path (specified in command line) - cmd_line_buf.data(), // command line arguments - nullptr, // process security attributes - nullptr, // thread security attributes - FALSE, // don't inherit handles - 0, // creation flags - nullptr, // environment variables (inherit from parent) - nullptr, // current directory - &si, // startup info - &pi // process information - ); - - if (!success) { - std::cerr << "Failed to create child process, error: " << GetLastError() - << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); - restart_count++; - continue; - } - - while (isRunning()) { - DWORD wait_result = WaitForSingleObject(pi.hProcess, 200); - if (wait_result == WAIT_OBJECT_0) { - break; - } - if (wait_result == WAIT_FAILED) { - std::cerr << "Failed waiting child process, error: " << GetLastError() - << std::endl; - break; - } - } - - if (!isRunning()) { - TerminateProcess(pi.hProcess, 1); - WaitForSingleObject(pi.hProcess, 3000); - } - - DWORD exit_code = 0; - GetExitCodeProcess(pi.hProcess, &exit_code); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - if (!isRunning() || exit_code == 0) { - break; // normal exit - } - restart_count++; - std::cerr << "Child process exited with code " << exit_code - << ", restarting... (attempt " << restart_count << ")" - << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); -#else - // linux: use fork + exec to create child process - pid_t pid = fork(); - if (pid == 0) { - execl(exe_path.c_str(), exe_path.c_str(), "--child", nullptr); - _exit(1); // exec failed - } else if (pid > 0) { - int status = 0; - pid_t waited_pid = -1; - while (isRunning()) { - waited_pid = waitpid(pid, &status, WNOHANG); - if (waited_pid == pid) { - break; - } - if (waited_pid < 0 && errno != EINTR) { - break; - } - std::this_thread::sleep_for( - std::chrono::milliseconds(kWaitPollIntervalMs)); - } - - if (!isRunning() && waited_pid != pid) { - kill(pid, SIGTERM); - waited_pid = waitpid(pid, &status, 0); - } - - if (waited_pid < 0) { - if (!isRunning()) { - break; - } - restart_count++; - std::cerr << "waitpid failed, errno: " << errno - << ", restarting... (attempt " << restart_count << ")" - << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); - continue; - } - - if (WIFEXITED(status)) { - int exit_code = WEXITSTATUS(status); - if (!isRunning() || exit_code == 0) { - break; // normal exit - } - restart_count++; - std::cerr << "Child process exited with code " << exit_code - << ", restarting... (attempt " << restart_count << ")" - << std::endl; - } else if (WIFSIGNALED(status)) { - if (!isRunning()) { - break; - } - restart_count++; - std::cerr << "Child process crashed with signal " << WTERMSIG(status) - << ", restarting... (attempt " << restart_count << ")" - << std::endl; - } else { - restart_count++; - std::cerr << "Child process exited with unknown status, restarting... " - "(attempt " - << restart_count << ")" << std::endl; - } - std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); - } else { - std::cerr << "Failed to fork child process" << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs)); - restart_count++; - } -#endif - } - - return true; -} diff --git a/src/autostart/autostart.cpp b/src/autostart/autostart.cpp deleted file mode 100644 index de369df..0000000 --- a/src/autostart/autostart.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "autostart.h" - -#include -#include -#include - -#ifdef _WIN32 -#include -#elif defined(__APPLE__) -#include -#include -#include -#elif defined(__linux__) -#include -#include -#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(regValue.c_str()), - static_cast(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"( - - - - Label - )" - << appName << R"( - ProgramArguments - - )" - << exePath << R"( - - RunAtLoad - - -)"; - 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 \ No newline at end of file diff --git a/src/common/platform.cpp b/src/common/platform.cpp deleted file mode 100644 index d0c2dea..0000000 --- a/src/common/platform.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "platform.h" - -#include -#include - -#include "rd_log.h" - -#ifdef _WIN32 -#include -#include -#elif __APPLE__ -#include -#include -#include -#include -#include -#elif __linux__ -#include -#include -#include -#include -#include -#endif - -namespace crossdesk { - -std::string GetMac() { - char mac_addr[16]; - int len = 0; -#ifdef _WIN32 - IP_ADAPTER_INFO adapterInfo[16]; - DWORD bufferSize = sizeof(adapterInfo); - DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize); - if (result == ERROR_SUCCESS) { - PIP_ADAPTER_INFO adapter = adapterInfo; - while (adapter) { - for (UINT i = 0; i < adapter->AddressLength; i++) { - len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X", - adapter->Address[i]); - } - break; - } - } -#elif __APPLE__ - std::string if_name = "en0"; - - struct ifaddrs* addrs; - struct ifaddrs* cursor; - const struct sockaddr_dl* dlAddr; - - if (!getifaddrs(&addrs)) { - cursor = addrs; - while (cursor != 0) { - const struct sockaddr_dl* socAddr = - (const struct sockaddr_dl*)cursor->ifa_addr; - if ((cursor->ifa_addr->sa_family == AF_LINK) && - (socAddr->sdl_type == IFT_ETHER) && - strcmp(if_name.c_str(), cursor->ifa_name) == 0) { - dlAddr = (const struct sockaddr_dl*)cursor->ifa_addr; - const unsigned char* base = - (const unsigned char*)&dlAddr->sdl_data[dlAddr->sdl_nlen]; - for (int i = 0; i < dlAddr->sdl_alen; i++) { - len += - snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]); - } - } - cursor = cursor->ifa_next; - } - freeifaddrs(addrs); - } -#elif __linux__ - int sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock < 0) { - return ""; - } - struct ifreq ifr; - struct ifconf ifc; - char buf[1024]; - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = buf; - if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { - close(sock); - return ""; - } - struct ifreq* it = ifc.ifc_req; - const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq)); - for (; it != end; ++it) { - std::strcpy(ifr.ifr_name, it->ifr_name); - if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { - continue; - } - if (ifr.ifr_flags & IFF_LOOPBACK) { - continue; - } - if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) { - continue; - } - std::string mac_address; - for (int i = 0; i < 6; ++i) { - len += sprintf(mac_addr + len, "%.2X", ifr.ifr_hwaddr.sa_data[i] & 0xff); - } - break; - } - close(sock); -#endif - return mac_addr; -} - -std::string GetHostName() { - char hostname[256]; -#ifdef _WIN32 - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { - LOG_ERROR("WSAStartup failed"); - return ""; - } - if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) { - LOG_ERROR("gethostname failed: {}", WSAGetLastError()); - WSACleanup(); - return ""; - } - WSACleanup(); -#else - if (gethostname(hostname, sizeof(hostname)) == -1) { - LOG_ERROR("gethostname failed"); - return ""; - } -#endif - return hostname; -} - -bool IsWaylandSession() { -#if defined(__linux__) && !defined(__APPLE__) - const char* session_type = std::getenv("XDG_SESSION_TYPE"); - if (session_type) { - if (std::strcmp(session_type, "wayland") == 0 || - std::strcmp(session_type, "Wayland") == 0) { - return true; - } - if (std::strcmp(session_type, "x11") == 0 || - std::strcmp(session_type, "X11") == 0) { - return false; - } - } - - const char* wayland_display = std::getenv("WAYLAND_DISPLAY"); - return wayland_display && wayland_display[0] != '\0'; -#else - return false; -#endif -} -} // namespace crossdesk diff --git a/src/path_manager/path_manager.cpp b/src/path_manager/path_manager.cpp deleted file mode 100644 index d6bd2ae..0000000 --- a/src/path_manager/path_manager.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "path_manager.h" - -#include -#include -#include - -#ifndef CROSSDESK_PORTABLE -#define CROSSDESK_PORTABLE 0 -#endif - -#if CROSSDESK_PORTABLE -#if defined(__APPLE__) -#include -#elif !defined(_WIN32) -#include -#include -#endif -#endif - -namespace { - -#if CROSSDESK_PORTABLE -std::filesystem::path GetExecutableDirectory() { -#ifdef _WIN32 - std::vector buffer(MAX_PATH); - while (true) { - DWORD length = - GetModuleFileNameW(nullptr, buffer.data(), - static_cast(buffer.size())); - if (length == 0) { - return {}; - } - if (length < buffer.size()) { - return std::filesystem::path(buffer.data(), buffer.data() + length) - .parent_path(); - } - if (buffer.size() >= 32768) { - return {}; - } - buffer.resize(buffer.size() * 2); - } -#elif defined(__APPLE__) - uint32_t size = 0; - _NSGetExecutablePath(nullptr, &size); - std::vector buffer(size + 1); - if (_NSGetExecutablePath(buffer.data(), &size) != 0) { - return {}; - } - - std::error_code ec; - std::filesystem::path executable = - std::filesystem::weakly_canonical(buffer.data(), ec); - if (ec) { - executable = buffer.data(); - } - return executable.parent_path(); -#else - std::vector buffer(PATH_MAX); - while (true) { - ssize_t length = readlink("/proc/self/exe", buffer.data(), - buffer.size() - 1); - if (length <= 0) { - return {}; - } - if (static_cast(length) < buffer.size() - 1) { - buffer[static_cast(length)] = '\0'; - return std::filesystem::path(buffer.data()).parent_path(); - } - buffer.resize(buffer.size() * 2); - } -#endif -} - -std::filesystem::path GetPortableRootPath() { - std::filesystem::path executable_dir = GetExecutableDirectory(); - if (!executable_dir.empty()) { - return executable_dir; - } - - std::error_code ec; - std::filesystem::path current = std::filesystem::current_path(ec); - return ec ? std::filesystem::path(".") : current; -} -#endif - -} // namespace - -namespace crossdesk { - -PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {} - -std::filesystem::path PathManager::GetConfigPath() { -#if CROSSDESK_PORTABLE - return GetPortableRootPath() / "data"; -#else -#ifdef _WIN32 - return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_; -#elif __APPLE__ - return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_; -#else - return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_; -#endif -#endif -} - -std::filesystem::path PathManager::GetCachePath() { -#if CROSSDESK_PORTABLE - return GetPortableRootPath() / "data"; -#else -#ifdef _WIN32 -#ifdef CROSSDESK_DEBUG - return "cache"; -#else - return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache"; -#endif -#elif __APPLE__ - return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; -#else - return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; -#endif -#endif -} - -std::filesystem::path PathManager::GetLogPath() { -#if CROSSDESK_PORTABLE - return GetPortableRootPath() / "logs"; -#else -#ifdef _WIN32 - return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "logs"; -#elif __APPLE__ - return GetHome() + "/Library/Logs/" + app_name_; -#else - return GetCachePath() / "logs"; -#endif -#endif -} - -bool PathManager::CreateDirectories(const std::filesystem::path& p) { - std::error_code ec; - bool created = std::filesystem::create_directories(p, ec); - if (ec) { - return false; - } - return created || std::filesystem::exists(p); -} - -#ifdef _WIN32 -std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) { - PWSTR path = NULL; - if (SUCCEEDED(SHGetKnownFolderPath(id, 0, NULL, &path))) { - std::wstring wpath(path); - CoTaskMemFree(path); - return std::filesystem::path(wpath); - } - return {}; -} -#endif - -std::string PathManager::GetHome() { -#ifdef _WIN32 - char path[MAX_PATH]; - if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path))) - return std::string(path); -#else - if (const char* home = getenv("HOME")) { - return std::string(home); - } -#endif - return {}; -} - -std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var, - const std::string& def) { -#ifdef _WIN32 - char* buffer = nullptr; - size_t size = 0; - - if (_dupenv_s(&buffer, &size, env_var) == 0 && buffer != nullptr) { - std::filesystem::path result(buffer); - free(buffer); - return result; - } -#else - if (const char* val = getenv(env_var)) { - return std::filesystem::path(val); - } -#endif - - return std::filesystem::path(def); -} -} // namespace crossdesk diff --git a/src/screen_capturer/screen_capturer_factory.h b/src/screen_capturer/screen_capturer_factory.h deleted file mode 100644 index 571adda..0000000 --- a/src/screen_capturer/screen_capturer_factory.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * @Author: DI JUNKUN - * @Date: 2023-12-15 - * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. - */ - -#ifndef _SCREEN_CAPTURER_FACTORY_H_ -#define _SCREEN_CAPTURER_FACTORY_H_ - -#ifdef _WIN32 -#include "screen_capturer_win.h" -#elif __linux__ -#include "screen_capturer_linux.h" -#elif __APPLE__ -// #include "screen_capturer_avf.h" -#include "screen_capturer_sck.h" -#endif - -namespace crossdesk { - -class ScreenCapturerFactory { - public: - virtual ~ScreenCapturerFactory() {} - - public: - ScreenCapturer* Create() { -#ifdef _WIN32 - return new ScreenCapturerWin(); -#elif __linux__ - return new ScreenCapturerLinux(); -#elif __APPLE__ - // return new ScreenCapturerAvf(); - return new ScreenCapturerSck(); -#else - return nullptr; -#endif - } -}; -} // namespace crossdesk -#endif diff --git a/src/speaker_capturer/speaker_capturer_factory.h b/src/speaker_capturer/speaker_capturer_factory.h deleted file mode 100644 index 0dbd4a1..0000000 --- a/src/speaker_capturer/speaker_capturer_factory.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * @Author: DI JUNKUN - * @Date: 2024-07-22 - * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. - */ - -#ifndef _SPEAKER_CAPTURER_FACTORY_H_ -#define _SPEAKER_CAPTURER_FACTORY_H_ - -#ifdef _WIN32 -#include "speaker_capturer_wasapi.h" -#elif __linux__ -#include "speaker_capturer_linux.h" -#elif __APPLE__ -#include "speaker_capturer_macosx.h" -#endif - -namespace crossdesk { - -class SpeakerCapturerFactory { - public: - virtual ~SpeakerCapturerFactory() {} - - public: - SpeakerCapturer* Create() { -#ifdef _WIN32 - return new SpeakerCapturerWasapi(); -#elif __linux__ - return new SpeakerCapturerLinux(); -#elif __APPLE__ - return new SpeakerCapturerMacosx(); -#else - return nullptr; -#endif - } -}; -} // namespace crossdesk -#endif \ No newline at end of file diff --git a/src/tools/clipboard.cpp b/src/tools/clipboard.cpp deleted file mode 100644 index fe0a8f6..0000000 --- a/src/tools/clipboard.cpp +++ /dev/null @@ -1,540 +0,0 @@ - -#include "clipboard.h" - -#include -#include -#include -#include -#include - -#include "rd_log.h" - -#ifdef _WIN32 -#include -#elif __linux__ -#include -#include -#include - -#include -#include -#endif - -namespace crossdesk { - -std::atomic g_monitoring{false}; -std::thread g_monitor_thread; -std::mutex g_monitor_mutex; -std::string g_last_clipboard_text; -int g_check_interval_ms = 100; -Clipboard::OnClipboardChanged g_on_clipboard_changed; - -#ifdef _WIN32 -HWND g_clipboard_wnd = nullptr; -const char* g_clipboard_class_name = "CrossDeskClipboardMonitor"; -#endif - -#ifdef __linux__ -Display* g_x11_display = nullptr; -Atom g_clipboard_atom = None; -Atom g_xfixes_selection_notify = None; -#endif -} // namespace crossdesk - -namespace crossdesk { - -#ifdef _WIN32 -std::string Clipboard::GetText() { - if (!OpenClipboard(nullptr)) { - LOG_ERROR("Clipboard::GetText: failed to open clipboard"); - return ""; - } - - std::string result; - HANDLE hData = GetClipboardData(CF_UNICODETEXT); - if (hData != nullptr) { - wchar_t* pszText = static_cast(GlobalLock(hData)); - if (pszText != nullptr) { - int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszText, -1, nullptr, 0, - nullptr, nullptr); - if (size_needed > 0) { - // -1 because WideCharToMultiByte contains '\0' - result.resize(size_needed - 1); - WideCharToMultiByte(CP_UTF8, 0, pszText, -1, &result[0], size_needed, - nullptr, nullptr); - } - GlobalUnlock(hData); - } - } - - CloseClipboard(); - return result; -} - -bool Clipboard::SetText(const std::string& text) { - if (!OpenClipboard(nullptr)) { - LOG_ERROR("Clipboard::SetText: failed to open clipboard"); - return false; - } - - if (!EmptyClipboard()) { - LOG_ERROR("Clipboard::SetText: failed to empty clipboard"); - CloseClipboard(); - return false; - } - - // Convert UTF-8 string to wide char - int size_needed = - MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, nullptr, 0); - if (size_needed <= 0) { - LOG_ERROR("Clipboard::SetText: failed to convert to wide char"); - CloseClipboard(); - return false; - } - - HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size_needed * sizeof(wchar_t)); - if (hMem == nullptr) { - LOG_ERROR("Clipboard::SetText: failed to allocate memory"); - CloseClipboard(); - return false; - } - - wchar_t* pszText = static_cast(GlobalLock(hMem)); - if (pszText == nullptr) { - LOG_ERROR("Clipboard::SetText: failed to lock memory"); - GlobalFree(hMem); - CloseClipboard(); - return false; - } - - MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, pszText, size_needed); - GlobalUnlock(hMem); - - if (SetClipboardData(CF_UNICODETEXT, hMem) == nullptr) { - LOG_ERROR("Clipboard::SetText: failed to set clipboard data"); - GlobalFree(hMem); - CloseClipboard(); - return false; - } - - CloseClipboard(); - return true; -} - -bool Clipboard::HasText() { - if (!OpenClipboard(nullptr)) { - return false; - } - - bool has_text = IsClipboardFormatAvailable(CF_UNICODETEXT) || - IsClipboardFormatAvailable(CF_TEXT); - CloseClipboard(); - return has_text; -} - -#elif __APPLE__ -// macOS implementation is in clipboard_mac.mm -#elif __linux__ - -std::string Clipboard::GetText() { - Display* display = XOpenDisplay(nullptr); - if (display == nullptr) { - LOG_ERROR("Clipboard::GetText: failed to open X display"); - return ""; - } - - std::string result; - Window owner = XGetSelectionOwner(display, XA_PRIMARY); - if (owner == None) { - // Try using CLIPBOARD - owner = - XGetSelectionOwner(display, XInternAtom(display, "CLIPBOARD", False)); - if (owner == None) { - XCloseDisplay(display); - return ""; - } - } - - Atom selection = XA_PRIMARY; - Atom target = XInternAtom(display, "UTF8_STRING", False); - if (target == None) { - target = XA_STRING; - } - - XEvent event; - Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, - 1, 1, 0, 0, 0); - XSelectInput(display, window, PropertyChangeMask); - - XConvertSelection(display, selection, target, XA_PRIMARY, window, - CurrentTime); - - // Wait for selection conversion to complete - bool done = false; - while (!done) { - XNextEvent(display, &event); - if (event.type == SelectionNotify) { - if (event.xselection.property == None) { - // Try using CLIPBOARD - if (selection == XA_PRIMARY) { - selection = XInternAtom(display, "CLIPBOARD", False); - XConvertSelection(display, selection, target, XA_PRIMARY, window, - CurrentTime); - continue; - } - break; - } - - Atom actual_type; - int actual_format; - unsigned long nitems; - unsigned long bytes_after; - unsigned char* data = nullptr; - - if (XGetWindowProperty(display, window, XA_PRIMARY, 0, LONG_MAX / 4, - False, AnyPropertyType, &actual_type, - &actual_format, &nitems, &bytes_after, - &data) == Success) { - if (data != nullptr) { - result = std::string(reinterpret_cast(data), nitems); - XFree(data); - } - done = true; - } - } - } - - XDestroyWindow(display, window); - XCloseDisplay(display); - return result; -} - -bool Clipboard::SetText(const std::string& text) { - Display* display = XOpenDisplay(nullptr); - if (display == nullptr) { - LOG_ERROR("Clipboard::SetText: failed to open X display"); - return false; - } - - Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, - 1, 1, 0, 0, 0); - Atom clipboard = XInternAtom(display, "CLIPBOARD", False); - Atom utf8_string = XInternAtom(display, "UTF8_STRING", False); - Atom targets = XInternAtom(display, "TARGETS", False); - Atom xa_string = XA_STRING; - - XSetSelectionOwner(display, clipboard, window, CurrentTime); - if (XGetSelectionOwner(display, clipboard) != window) { - LOG_ERROR("Clipboard::SetText: failed to set selection owner"); - XDestroyWindow(display, window); - XCloseDisplay(display); - return false; - } - - XChangeProperty(display, window, XA_PRIMARY, utf8_string, 8, PropModeReplace, - reinterpret_cast(text.c_str()), - static_cast(text.length())); - - XEvent event; - while (true) { - XNextEvent(display, &event); - if (event.type == SelectionRequest) { - XSelectionRequestEvent* req = &event.xselectionrequest; - XSelectionEvent se; - se.type = SelectionNotify; - se.display = req->display; - se.requestor = req->requestor; - se.selection = req->selection; - se.time = req->time; - se.target = req->target; - se.property = req->property; - - if (req->target == targets) { - // Return supported formats - Atom supported[] = {utf8_string, xa_string, targets}; - XChangeProperty(display, req->requestor, req->property, XA_ATOM, 32, - PropModeReplace, - reinterpret_cast(supported), 3); - se.property = req->property; - } else if (req->target == utf8_string || req->target == xa_string) { - // Return text data - XChangeProperty(display, req->requestor, req->property, req->target, 8, - PropModeReplace, - reinterpret_cast(text.c_str()), - static_cast(text.length())); - se.property = req->property; - } else { - se.property = None; - } - - XSendEvent(display, req->requestor, False, 0, - reinterpret_cast(&se)); - XSync(display, False); - } else if (event.type == SelectionClear) { - break; - } - } - - XDestroyWindow(display, window); - XCloseDisplay(display); - return true; -} - -bool Clipboard::HasText() { - Display* display = XOpenDisplay(nullptr); - if (display == nullptr) { - return false; - } - - Atom clipboard = XInternAtom(display, "CLIPBOARD", False); - Window owner = XGetSelectionOwner(display, clipboard); - if (owner == None) { - owner = XGetSelectionOwner(display, XA_PRIMARY); - } - - XCloseDisplay(display); - return owner != None; -} - -#else - -std::string Clipboard::GetText() { - LOG_ERROR("Clipboard::GetText: unsupported platform"); - return ""; -} - -bool Clipboard::SetText(const std::string& text) { - LOG_ERROR("Clipboard::SetText: unsupported platform"); - return false; -} - -bool Clipboard::HasText() { - LOG_ERROR("Clipboard::HasText: unsupported platform"); - return false; -} - -#endif - -void HandleClipboardChange() { - if (!Clipboard::HasText()) { - std::lock_guard lock(g_monitor_mutex); - if (!g_last_clipboard_text.empty()) { - g_last_clipboard_text.clear(); - LOG_INFO("Clipboard content cleared"); - } - return; - } - - std::string current_text = Clipboard::GetText(); - - // Check if the content has changed - { - std::lock_guard lock(g_monitor_mutex); - if (current_text != g_last_clipboard_text) { - g_last_clipboard_text = current_text; - if (!current_text.empty()) { - if (g_on_clipboard_changed) { - int ret = g_on_clipboard_changed(current_text.c_str(), - current_text.length()); - if (ret != 0) { - LOG_WARN("Clipboard callback returned error: {}", ret); - } - } - } - } - } -} - -#ifdef _WIN32 -LRESULT CALLBACK ClipboardWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, - LPARAM lParam) { - if (uMsg == WM_CLIPBOARDUPDATE) { - HandleClipboardChange(); - return 0; - } - return DefWindowProc(hwnd, uMsg, wParam, lParam); -} - -static void MonitorThreadFunc() { - // Create a hidden window to receive clipboard messages - WNDCLASSA wc = {0}; - wc.lpfnWndProc = ClipboardWndProc; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpszClassName = g_clipboard_class_name; - RegisterClassA(&wc); - - g_clipboard_wnd = CreateWindowA(g_clipboard_class_name, nullptr, 0, 0, 0, 0, - 0, HWND_MESSAGE, nullptr, nullptr, nullptr); - if (!g_clipboard_wnd) { - LOG_ERROR("Failed to create clipboard monitor window"); - g_monitoring.store(false); - return; - } - - // Register clipboard format listener - if (!AddClipboardFormatListener(g_clipboard_wnd)) { - LOG_ERROR("Failed to add clipboard format listener"); - DestroyWindow(g_clipboard_wnd); - g_clipboard_wnd = nullptr; - g_monitoring.store(false); - return; - } - - LOG_INFO("Clipboard event monitoring started (Windows)"); - - MSG msg; - while (g_monitoring.load()) { - BOOL ret = GetMessage(&msg, nullptr, 0, 0); - if (ret == 0 || ret == -1) { - break; - } - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - RemoveClipboardFormatListener(g_clipboard_wnd); - if (g_clipboard_wnd) { - DestroyWindow(g_clipboard_wnd); - g_clipboard_wnd = nullptr; - } - UnregisterClassA(g_clipboard_class_name, GetModuleHandle(nullptr)); -} - -#elif __APPLE__ -// macOS use notification mechanism, need external functions -extern void StartMacOSClipboardMonitoring(); -extern void StopMacOSClipboardMonitoring(); - -static void MonitorThreadFunc() { StartMacOSClipboardMonitoring(); } - -#elif __linux__ -static void MonitorThreadFunc() { - g_x11_display = XOpenDisplay(nullptr); - if (!g_x11_display) { - LOG_ERROR("Failed to open X display for clipboard monitoring"); - g_monitoring.store(false); - return; - } - - // Check if XFixes extension is available - int event_base, error_base; - if (!XFixesQueryExtension(g_x11_display, &event_base, &error_base)) { - LOG_WARN("XFixes extension not available, falling back to polling"); - XCloseDisplay(g_x11_display); - g_x11_display = nullptr; - // fallback to polling mode - while (g_monitoring.load()) { - std::this_thread::sleep_for( - std::chrono::milliseconds(g_check_interval_ms)); - if (!g_monitoring.load()) { - break; - } - HandleClipboardChange(); - } - return; - } - - g_clipboard_atom = XInternAtom(g_x11_display, "CLIPBOARD", False); - g_xfixes_selection_notify = - XInternAtom(g_x11_display, "XFIXES_SELECTION_NOTIFY", False); - - // Create event window - Window root = DefaultRootWindow(g_x11_display); - Window event_window = - XCreateSimpleWindow(g_x11_display, root, 0, 0, 1, 1, 0, 0, 0); - - // Select events to monitor - XFixesSelectSelectionInput(g_x11_display, event_window, g_clipboard_atom, - XFixesSetSelectionOwnerNotifyMask | - XFixesSelectionWindowDestroyNotifyMask | - XFixesSelectionClientCloseNotifyMask); - - LOG_INFO("Clipboard event monitoring started (Linux XFixes)"); - - XEvent event; - constexpr int kEventPollIntervalMs = 20; - while (g_monitoring.load()) { - // Avoid blocking on XNextEvent so StopMonitoring() can stop quickly. - while (g_monitoring.load() && XPending(g_x11_display) > 0) { - XNextEvent(g_x11_display, &event); - if (event.type == event_base + XFixesSelectionNotify) { - HandleClipboardChange(); - } - } - std::this_thread::sleep_for( - std::chrono::milliseconds(kEventPollIntervalMs)); - } - - XFixesSelectSelectionInput(g_x11_display, event_window, g_clipboard_atom, 0); - XDestroyWindow(g_x11_display, event_window); - XCloseDisplay(g_x11_display); - g_x11_display = nullptr; -} - -#else -// Fallback to polling mode for other platforms -static void MonitorThreadFunc() { - while (g_monitoring.load()) { - std::this_thread::sleep_for(std::chrono::milliseconds(g_check_interval_ms)); - if (!g_monitoring.load()) { - break; - } - HandleClipboardChange(); - } -} -#endif - -void Clipboard::StartMonitoring(int check_interval_ms, - OnClipboardChanged on_changed) { - if (g_monitoring.load()) { - LOG_WARN("Clipboard monitoring is already running"); - return; - } - - g_check_interval_ms = check_interval_ms > 0 ? check_interval_ms : 100; - { - std::lock_guard lock(g_monitor_mutex); - g_on_clipboard_changed = on_changed; - if (HasText()) { - g_last_clipboard_text = GetText(); - } else { - g_last_clipboard_text.clear(); - } - } - g_monitoring.store(true); - - g_monitor_thread = std::thread(MonitorThreadFunc); - LOG_INFO("Clipboard event monitoring started"); -} - -void Clipboard::StopMonitoring() { - if (!g_monitoring.load()) { - return; - } - - g_monitoring.store(false); - -#ifdef _WIN32 - if (g_clipboard_wnd) { - PostMessage(g_clipboard_wnd, WM_QUIT, 0, 0); - } -#elif __APPLE__ - StopMacOSClipboardMonitoring(); -#endif - - if (g_monitor_thread.joinable()) { - g_monitor_thread.join(); - } - - { - std::lock_guard lock(g_monitor_mutex); - g_last_clipboard_text.clear(); - g_on_clipboard_changed = nullptr; - } - - LOG_INFO("Clipboard monitoring stopped"); -} - -bool Clipboard::IsMonitoring() { return g_monitoring.load(); } - -} // namespace crossdesk diff --git a/xmake.lua b/xmake.lua index 2190487..95beb74 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,11 +1,50 @@ set_project("crossdesk") set_license("GPL-3.0-only") -includes("xmake/options.lua") -includes("xmake/platform.lua") -includes("xmake/rules/slint.lua") -includes("xmake/targets.lua") +local source_dir = os.getenv("CROSSDESK_SOURCE_DIR") or os.scriptdir() +local desktop_platform = is_plat("windows", "macosx", "linux") -setup_options_and_dependencies() -setup_platform_settings() -setup_targets() +add_rules("mode.release", "mode.debug") +set_languages("c++17") +set_encodings("utf-8") +add_requires("nlohmann_json 3.11.3") + +if desktop_platform then + includes(path.join(source_dir, "apps/desktop/xmake/options.lua")) + includes(path.join(source_dir, "apps/desktop/xmake/platform.lua")) + includes(path.join(source_dir, "apps/desktop/xmake/rules/slint.lua")) + includes(path.join(source_dir, "apps/desktop/xmake/targets.lua")) + + setup_options_and_dependencies() + setup_platform_settings() +end + +local wire_dir = path.join(source_dir, "libs/wire") + +target("crossdesk_wire") + set_kind("static") + add_packages("nlohmann_json") + add_files(path.join(wire_dir, "src/*.cpp")) + add_headerfiles(path.join(wire_dir, + "include/(*.h)")) + add_includedirs(path.join(wire_dir, "include"), {public = true}) +target_end() + +target("crossdesk_wire_test") + set_kind("binary") + set_default(false) + add_deps("crossdesk_wire") + add_files(path.join(wire_dir, "tests/wire_test.cpp")) +target_end() + +target("keyboard_state_wire_test") + set_kind("binary") + set_default(false) + add_deps("crossdesk_wire") + add_files(path.join(wire_dir, + "tests/keyboard_state_wire_test.cpp")) +target_end() + +if desktop_platform then + setup_targets() +end diff --git a/xmake/targets.lua b/xmake/targets.lua deleted file mode 100644 index 8cff705..0000000 --- a/xmake/targets.lua +++ /dev/null @@ -1,355 +0,0 @@ -function setup_targets() - add_packages("spdlog", "libsdl3", "nlohmann_json") - - includes("submodules", "thirdparty") - - local function copy_slint_runtime(target) - if not target:is_plat("windows") then - return - end - - local slint = target:pkg("slint") - if not slint and target:dep("gui") then - slint = target:dep("gui"):pkg("slint") - end - assert(slint, "the Slint package is required to copy its Windows runtime") - local runtime_dir = path.join(slint:installdir(), "lib") - local runtime_dll = path.join(runtime_dir, "slint_cpp.dll") - assert(os.isfile(runtime_dll), "Slint runtime not found: " .. runtime_dll) - os.cp(runtime_dll, target:targetdir()) - end - - local crossdesk_windows_resource = "scripts/windows/crossdesk.rc" - if is_config("CROSSDESK_PORTABLE", true) then - crossdesk_windows_resource = "scripts/windows/crossdesk_portable.rc" - end - - target("rd_log") - set_kind("object") - add_packages("spdlog") - add_files("src/log/rd_log.cpp") - add_includedirs("src/log", {public = true}) - - target("common") - set_kind("object") - add_deps("rd_log") - add_packages("libyuv") - add_files("src/common/*.cpp") - remove_files("src/common/rounded_corner_button.cpp") - if is_os("macosx") then - add_files("src/common/*.mm") - end - add_includedirs("src/common", {public = true}) - - target("path_manager") - set_kind("object") - add_deps("rd_log") - add_includedirs("src/path_manager", {public = true}) - add_files("src/path_manager/*.cpp") - add_includedirs("src/path_manager", {public = true}) - - target("path_manager_portable_test") - set_kind("binary") - set_default(false) - set_policy("build.ccache", false) - add_defines("CROSSDESK_PORTABLE=1") - add_includedirs("src/path_manager") - add_files("tests/path_manager_portable_test.cpp", - "src/path_manager/path_manager.cpp") - - target("macos_keyboard_modifier_state_test") - set_kind("binary") - set_default(false) - add_includedirs("src/device_controller") - add_files("tests/macos_keyboard_modifier_state_test.cpp") - - target("keyboard_state_protocol_test") - set_kind("binary") - set_default(false) - add_packages("nlohmann_json") - add_includedirs("src/device_controller", "src/common") - add_files("tests/keyboard_state_protocol_test.cpp", - "src/device_controller/remote_action.cpp") - - target("crossdesk_protocol_test") - set_kind("binary") - set_default(false) - add_packages("nlohmann_json") - add_includedirs("src/device_controller", "src/common", "src/tools", - "src/gui/runtime") - add_files("tests/protocol_test.cpp", - "src/device_controller/remote_action.cpp", - "src/tools/file_transfer_protocol.cpp") - - target("connection_status_protocol_test") - set_kind("binary") - set_default(false) - add_files("tests/connection_status_protocol_test.cpp") - - target("video_callback_lifetime_test") - set_kind("binary") - set_default(false) - add_files("tests/video_callback_lifetime_test.cpp") - - target("windows_manifest_resource_test") - set_kind("binary") - set_default(false) - add_files("tests/windows_manifest_resource_test.cpp") - - target("windows_service_mouse_ipc_test") - set_kind("binary") - set_default(false) - add_files("tests/windows_service_mouse_ipc_test.cpp") - - target("windows_mouse_controller_safety_test") - set_kind("binary") - set_default(false) - add_files("tests/windows_mouse_controller_safety_test.cpp") - - target("windows_sas_guard_test") - set_kind("binary") - set_default(false) - add_includedirs("src/service/windows") - add_files("tests/windows_sas_guard_test.cpp") - - target("slint_ui_smoke_test") - set_kind("binary") - set_languages("c++20") - set_default(false) - if is_os("windows") then - add_cxxflags("/bigobj") - end - add_packages("slint") - add_includedirs("src/gui", "src/gui/assets/fonts", - "src/gui/assets/localization") - add_rules("slint") - add_files("src/gui/ui/crossdesk_ui.slint") - add_files("tests/slint_ui_smoke_test.cpp") - after_build(copy_slint_runtime) - - target("version_checker_test") - set_kind("binary") - set_default(false) - add_packages("cpp-httplib") - add_deps("rd_log") - add_includedirs("src/version_checker") - add_files("tests/version_checker_test.cpp", - "src/version_checker/version_checker.cpp") - if is_os("macosx") then - add_defines("CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN") - add_frameworks("Security", "CoreFoundation") - end - - target("screen_capturer") - set_kind("object") - add_deps("rd_log", "common") - add_includedirs("src/screen_capturer", {public = true}) - if is_os("windows") then - add_packages("libyuv") - add_files("src/screen_capturer/windows/screen_capturer_dxgi.cpp", - "src/screen_capturer/windows/screen_capturer_gdi.cpp", - "src/screen_capturer/windows/screen_capturer_win.cpp") - add_includedirs("src/screen_capturer/windows", "src/service/windows", - {public = true}) - elseif is_os("macosx") then - add_files("src/screen_capturer/macosx/*.cpp", - "src/screen_capturer/macosx/*.mm") - add_includedirs("src/screen_capturer/macosx", {public = true}) - elseif is_os("linux") then - add_packages("libyuv") - add_files("src/screen_capturer/linux/screen_capturer_linux.cpp") - add_files("src/screen_capturer/linux/screen_capturer_x11.cpp") - add_files("src/screen_capturer/linux/screen_capturer_drm.cpp") - if is_config("USE_WAYLAND", true) then - add_files("src/screen_capturer/linux/screen_capturer_wayland.cpp") - add_files("src/screen_capturer/linux/screen_capturer_wayland_portal.cpp") - add_files("src/screen_capturer/linux/screen_capturer_wayland_pipewire.cpp") - end - add_includedirs("src/screen_capturer/linux", {public = true}) - end - - target("speaker_capturer") - set_kind("object") - add_deps("rd_log") - add_includedirs("src/speaker_capturer", {public = true}) - if is_os("windows") then - add_packages("miniaudio") - add_files("src/speaker_capturer/windows/*.cpp") - add_includedirs("src/speaker_capturer/windows", {public = true}) - elseif is_os("macosx") then - add_files("src/speaker_capturer/macosx/*.cpp", - "src/speaker_capturer/macosx/*.mm") - add_includedirs("src/speaker_capturer/macosx", {public = true}) - elseif is_os("linux") then - add_files("src/speaker_capturer/linux/*.cpp") - add_includedirs("src/speaker_capturer/linux", {public = true}) - end - - target("device_controller") - set_kind("object") - add_deps("rd_log", "common") - add_includedirs("src/device_controller", {public = true}) - add_files("src/device_controller/remote_action.cpp") - if is_os("windows") then - add_files("src/device_controller/mouse/windows/*.cpp", - "src/device_controller/keyboard/windows/*.cpp") - add_includedirs("src/device_controller/mouse/windows", - "src/device_controller/keyboard/windows", {public = true}) - elseif is_os("macosx") then - add_files("src/device_controller/mouse/mac/*.cpp", - "src/device_controller/keyboard/mac/*.cpp") - add_includedirs("src/device_controller/mouse/mac", - "src/device_controller/keyboard/mac", {public = true}) - elseif is_os("linux") then - add_files("src/device_controller/mouse/linux/*.cpp", - "src/device_controller/keyboard/linux/*.cpp") - add_includedirs("src/device_controller/mouse/linux", - "src/device_controller/keyboard/linux", {public = true}) - 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") - set_kind("object") - add_packages("libyuv", "openssl3") - add_deps("rd_log", "common") - add_files("src/thumbnail/*.cpp") - add_includedirs("src/thumbnail", {public = true}) - - 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}) - - target("assets") - set_kind("headeronly") - add_includedirs("src/gui/assets/localization", - "src/gui/assets/fonts", - "src/gui/assets/icons", - "src/gui/assets/layouts", {public = true}) - - target("version_checker") - set_kind("object") - add_packages("cpp-httplib") - add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"") - add_deps("rd_log") - add_files("src/version_checker/*.cpp") - add_includedirs("src/version_checker", {public = true}) - if is_os("macosx") then - add_defines("CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN") - add_frameworks("Security", "CoreFoundation") - end - - target("tools") - set_kind("object") - add_deps("rd_log", "common") - add_files("src/tools/*.cpp") - if is_os("macosx") then - add_files("src/tools/*.mm") - end - add_includedirs("src/tools", {public = true}) - - target("gui") - set_kind("object") - set_languages("c++20") - -- spdlog 1.14 bundles fmt 10, whose consteval parser is rejected by - -- current Apple Clang in C++20 mode. Keep the established dependency - -- version and use fmt's supported runtime-parser fallback here. - add_defines("FMT_CONSTEVAL=") - add_packages("slint", {public = true}) - add_packages("libyuv", "tinyfiledialogs") - add_rules("slint") - add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"") - add_deps("rd_log", "common", "assets", "config_center", "minirtc", - "path_manager", "screen_capturer", "speaker_capturer", - "device_controller", "thumbnail", "version_checker", "tools") - add_files("src/gui/render.cpp", "src/gui/application/gui_application.cpp", - "src/gui/application/portable_service_integration.cpp", - "src/gui/rendering/*.cpp", - "src/gui/runtime/*.cpp", - "src/gui/features/devices/*.cpp", "src/gui/features/input/*.cpp", - "src/gui/features/clipboard/*.cpp", "src/gui/features/file_transfer/*.cpp", - "src/gui/features/settings/*.cpp", "src/gui/ui/crossdesk_ui.slint") - add_includedirs("src/gui", {public = true}) - if is_os("windows") then - add_cxxflags("/bigobj") - add_links("opengl32") - add_files("src/gui/platform/tray/win_tray.cpp", - "src/gui/platform/opengl_video_renderer.cpp", - "src/gui/platform/video_renderer_factory_opengl.cpp") - add_includedirs("src/service/windows", {public = true}) - elseif is_os("macosx") then - add_files("src/gui/runtime/*.mm", "src/gui/platform/tray/*.mm", - "src/gui/platform/window_drag_mac.mm", - "src/gui/platform/metal_video_renderer.mm", - "src/gui/platform/video_renderer_factory_metal.mm") - elseif is_os("linux") then - add_links("GL") - add_files("src/gui/platform/tray/linux_tray.cpp", - "src/gui/platform/opengl_video_renderer.cpp", - "src/gui/platform/video_renderer_factory_opengl.cpp") - end - - if is_os("windows") then - target("wgc_plugin") - set_kind("shared") - add_packages("libyuv") - add_deps("rd_log", "path_manager") - add_defines("CROSSDESK_WGC_PLUGIN_BUILD=1") - -- Keep the project on C++17 while C++/WinRT still falls back to - -- MSVC's deprecated experimental coroutine header. - add_defines("_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS") - add_links("windowsapp") - add_files("src/screen_capturer/windows/screen_capturer_wgc.cpp", - "src/screen_capturer/windows/wgc_session_impl.cpp", - "src/screen_capturer/windows/wgc_plugin_entry.cpp") - add_files("scripts/windows/wgc_plugin.rc") - add_includedirs("src/common", "src/screen_capturer", - "src/screen_capturer/windows") - - target("crossdesk_service") - set_kind("binary") - add_deps("rd_log", "path_manager") - add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv") - add_files("src/service/windows/main.cpp", - "src/service/windows/service_host.cpp") - add_files("scripts/windows/crossdesk_service.rc") - add_includedirs("src/service/windows", {public = true}) - - target("crossdesk_session_helper") - set_kind("binary") - add_packages("libyuv") - add_deps("rd_log", "path_manager") - add_links("Advapi32", "User32", "Wtsapi32", "Gdi32") - add_files("src/service/windows/session_helper_main.cpp") - add_files("scripts/windows/crossdesk_session_helper.rc") - add_includedirs("src/common", "src/service/windows", {public = true}) - end - - target("crossdesk") - set_kind("binary") - add_deps("rd_log", "common", "gui") - add_files("src/app/*.cpp") - add_includedirs("src/app", {public = true}) - if is_os("windows") then - add_files("src/service/windows/service_host.cpp") - add_includedirs("src/service/windows", {public = true}) - add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv") - add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper") - add_files(crossdesk_windows_resource) - end - after_build(copy_slint_runtime) -end