diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index bfa14172..70eda052 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -1,27 +1,27 @@ plugins { id 'com.android.application' } android { - namespace 'cn.crossdesk.mobile' - compileSdk 36 - ndkVersion '28.2.13676358' + namespace = 'cn.crossdesk.mobile' + compileSdk = 36 + ndkVersion = '28.2.13676358' defaultConfig { - applicationId 'cn.crossdesk.mobile' - minSdk 26 - targetSdk 36 - versionCode providers.gradleProperty('crossdeskVersionCode').get().toInteger() - versionName providers.gradleProperty('crossdeskVersionName').get() + applicationId = 'cn.crossdesk.mobile' + minSdk = 26 + targetSdk = 36 + versionCode = providers.gradleProperty('crossdeskVersionCode').get().toInteger() + versionName = providers.gradleProperty('crossdeskVersionName').get() ndk { abiFilters 'arm64-v8a' } - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + testInstrumentationRunner = 'androidx.test.runner.AndroidJUnitRunner' } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } sourceSets.main.jniLibs.srcDirs = [layout.buildDirectory.dir('native')] sourceSets.main.assets.srcDirs += [layout.buildDirectory.dir('generated/notices')] sourceSets.androidTest.java.srcDirs = ['../test/android/java'] sourceSets.androidTest.assets.srcDirs = ['../test/android/assets'] - buildTypes.release { minifyEnabled false } + buildTypes.release { minifyEnabled = false } packaging.jniLibs.useLegacyPackaging = false } diff --git a/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java b/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java index 77b23235..2a399905 100644 --- a/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java +++ b/apps/android/app/src/main/java/cn/crossdesk/mobile/MainActivity.java @@ -118,6 +118,9 @@ public final class MainActivity extends Activity implements NativeSession.Listen private String serverKey(){return host+":"+port;} private void toast(String value){Toast.makeText(this,value,Toast.LENGTH_SHORT).show();} private void hideKeyboard(){((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(root.getWindowToken(),0);root.clearFocus();} + // Legacy inset handling also includes the IME on non-session pages. The + // decor-fitting call below is restricted to Android 11–14. + @SuppressWarnings("deprecation") private void installRoot(String nextPage,boolean dark){ PageHost.Motion motion=PageHost.Motion.NONE; if(canvas!=null&&foreground&&!page.equals(nextPage)){ @@ -147,6 +150,8 @@ public final class MainActivity extends Activity implements NativeSession.Listen root.setFocusableInTouchMode(true);pageHost.show(canvas,motion); applySystemBars(dark); } + // Android 8–10 require system UI flags instead of WindowInsetsController. + @SuppressWarnings("deprecation") private void applySystemBars(boolean dark){ if(android.os.Build.VERSION.SDK_INT>=30){ android.view.WindowInsetsController controller=getWindow().getInsetsController(); @@ -302,7 +307,9 @@ public final class MainActivity extends Activity implements NativeSession.Listen rememberPassword=remember.isChecked();password.setText("");dialog.dismiss();begin(id,secret); });connect.setContentDescription(L10n.tr("确认连接"));sheet.addView(connect,ui.size(-1,48)); ScrollView scroll=new ScrollView(this);scroll.addView(sheet); - dialog.setContentView(scroll);Window window=dialog.getWindow();if(window!=null){window.setBackgroundDrawableResource(android.R.color.transparent);window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);window.setDimAmount(.22f);}dialog.show();sizePasswordDialog();password.requestFocus();if(!error.isEmpty())password.setError(error); + // This floating dialog uses window-managed IME resizing on all supported APIs. + @SuppressWarnings("deprecation") int resizeMode=WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + dialog.setContentView(scroll);Window window=dialog.getWindow();if(window!=null){window.setBackgroundDrawableResource(android.R.color.transparent);window.setSoftInputMode(resizeMode);window.setDimAmount(.22f);}dialog.show();sizePasswordDialog();password.requestFocus();if(!error.isEmpty())password.setError(error); dialog.setOnDismissListener(d->{password.setText("");passwordDialog=null;}); } private void sizePasswordDialog(){ @@ -715,6 +722,8 @@ public final class MainActivity extends Activity implements NativeSession.Listen private void navigateBack(){if(backAction==null)return;navigatingBack=true;try{backAction.run();}finally{navigatingBack=false;}} private void back(){if(controls!=null&&controls.closeKeyboard())return;pageHost.finish();if(session!=null){if(ready)confirmDisconnect();else end("");}else if(backAction!=null)navigateBack();else finish();} @Override public void onWindowFocusChanged(boolean focus){super.onWindowFocusChanged(focus);if(!focus)releaseKeys();else applySystemBars(ready);} + // API 26–32 use this callback; API 33+ registers OnBackInvokedCallback in onCreate. + @SuppressWarnings("deprecation") @android.annotation.SuppressLint("GestureBackNavigation") @Override public void onBackPressed(){back();} @Override public void onConfigurationChanged(Configuration config){ super.onConfigurationChanged(config); diff --git a/apps/android/app/src/main/java/cn/crossdesk/mobile/MobileUi.java b/apps/android/app/src/main/java/cn/crossdesk/mobile/MobileUi.java index 0e193097..de7858f9 100644 --- a/apps/android/app/src/main/java/cn/crossdesk/mobile/MobileUi.java +++ b/apps/android/app/src/main/java/cn/crossdesk/mobile/MobileUi.java @@ -90,6 +90,8 @@ final class MobileUi { } @Override public void setAlpha(int alpha) { bitmapPaint.setAlpha(alpha);invalidateSelf(); } @Override public void setColorFilter(ColorFilter filter) { bitmapPaint.setColorFilter(filter);invalidateSelf(); } + // Drawable still requires this abstract method, including on API 26. + @SuppressWarnings("deprecation") @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } } @android.annotation.SuppressLint("ViewConstructor") // Programmatic icon primitive, not inflated from XML. diff --git a/apps/desktop/src/gui/ui/file_browser.slint b/apps/desktop/src/gui/ui/file_browser.slint index 83b133d6..50bf425d 100644 --- a/apps/desktop/src/gui/ui/file_browser.slint +++ b/apps/desktop/src/gui/ui/file_browser.slint @@ -601,6 +601,9 @@ export component FileBrowserWindow inherits Window { SurfaceSeparator { x: 0px; y: 0px; width: parent.width; } focus := FocusScope { focus-on-click: true; + // Window sizing is defined above. Keep its layout constraints separate + // from children whose dimensions adapt to the current window size. + x: 0px; y: 0px; width: root.width; height: root.height; key-pressed(event) => { if event.text == Key.Escape { root.cancel-drag(); return accept; } reject diff --git a/apps/desktop/src/gui/ui/trusted_devices.slint b/apps/desktop/src/gui/ui/trusted_devices.slint index 84f8ed96..2251aa37 100644 --- a/apps/desktop/src/gui/ui/trusted_devices.slint +++ b/apps/desktop/src/gui/ui/trusted_devices.slint @@ -141,7 +141,6 @@ export component TrustedDevicesDialog inherits Rectangle { // Extend into the right margin to preserve the existing card width. width: parent.width - 56px + dialog.scrollbar-gutter; height: parent.height - 84px; - min-width: 0px; min-height: 0px; horizontal-scrollbar-policy: always-off; vertical-scrollbar-policy: as-needed; viewport-width: self.visible-width; diff --git a/apps/desktop/src/platform/common/updater_posix.cpp b/apps/desktop/src/platform/common/updater_posix.cpp index c1719202..62657b6f 100644 --- a/apps/desktop/src/platform/common/updater_posix.cpp +++ b/apps/desktop/src/platform/common/updater_posix.cpp @@ -102,7 +102,11 @@ void Launch(const fs::path& executable, const std::vector& argument } execv(executable.c_str(), argv.data()); const int error = errno; - (void)write(errors[1], &error, sizeof(error)); + // This small blocking pipe write is atomic. Retry signal interruptions so + // the parent cannot mistake a failed exec for a successful launch. + ssize_t written; + do { written = write(errors[1], &error, sizeof(error)); } + while (written < 0 && errno == EINTR); _exit(127); } close(errors[1]); diff --git a/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.h b/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.h index f506bf77..69ff4212 100644 --- a/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.h +++ b/apps/desktop/src/platform/macos/input/keyboard/keyboard_capturer.h @@ -17,16 +17,16 @@ namespace crossdesk { class PlatformKeyboardCapturer final : public KeyboardCapturer { public: PlatformKeyboardCapturer(); - virtual ~PlatformKeyboardCapturer(); + ~PlatformKeyboardCapturer() override; public: - virtual int Hook(OnKeyAction on_key_action, void* user_ptr); - virtual int Unhook(); + int Hook(OnKeyAction on_key_action, void* user_ptr) override; + int Unhook() override; bool SupportsTextInput() const override { return true; } int SendTextCommand(const std::string& text) override; - virtual int SendKeyboardCommand(int key_code, bool is_down, - uint32_t scan_code = 0, - bool extended = false); + int SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code = 0, + bool extended = false) override; private: CFMachPortRef event_tap_ = nullptr; diff --git a/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h index 7b68a0fc..44f8a98f 100644 --- a/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h +++ b/apps/desktop/src/platform/macos/input/mouse/mouse_controller.h @@ -1,7 +1,7 @@ /* * @Author: DI JUNKUN * @Date: 2023-12-14 - * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. */ #ifndef _MOUSE_CONTROLLER_H_ @@ -20,15 +20,15 @@ namespace crossdesk { class PlatformMouseController final : public MouseController { public: PlatformMouseController(); - virtual ~PlatformMouseController(); + ~PlatformMouseController() override; public: - virtual int Init(std::vector display_info_list); - virtual int Destroy(); - virtual int SendMouseCommand(RemoteAction remote_action, int display_index); + int Init(std::vector display_info_list) override; + int Destroy() override; + int SendMouseCommand(RemoteAction remote_action, int display_index) override; unsigned ReleasePressedButtons(unsigned mask = 7) override; void UpdateDisplayInfoList( - const std::vector& display_info_list); + const std::vector& display_info_list) override; private: struct ClickTracker { diff --git a/apps/desktop/xmake/platform.lua b/apps/desktop/xmake/platform.lua index 3c30bcc8..0b65aa64 100644 --- a/apps/desktop/xmake/platform.lua +++ b/apps/desktop/xmake/platform.lua @@ -99,9 +99,6 @@ function setup_platform_settings() add_cxflags("-Wno-unused-variable") elseif is_os("macosx") then - if is_arch("x86_64") then - add_ldflags("-Wl,-ld_classic") - end add_cxflags("-Wno-unused-variable") add_frameworks("Cocoa", "CoreGraphics", "Metal", "QuartzCore", "IOKit", "IOSurface", diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index 81c716fe..78f3c4f5 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -1,6 +1,4 @@ function setup_targets() - includes("deps/submodules", "deps/thirdparty") - local crossdesk_version = get_config("CROSSDESK_VERSION") or "0.0.0" local version_define = "CROSSDESK_VERSION=\"" .. crossdesk_version .. "\"" local windows_resource_defines = { diff --git a/apps/ios/scripts/build_minirtc_ios.sh b/apps/ios/scripts/build_minirtc_ios.sh index dba51d4a..8613b1db 100755 --- a/apps/ios/scripts/build_minirtc_ios.sh +++ b/apps/ios/scripts/build_minirtc_ios.sh @@ -196,7 +196,8 @@ fi # no second project definition to maintain. ( cd "${WIRE_WORK_DIR}" - configure_xmake "${WIRE_WORK_DIR}" "${WIRE_BUILD_DIR}" + configure_xmake "${WIRE_WORK_DIR}" "${WIRE_BUILD_DIR}" \ + --CROSSDESK_BUILD_DESKTOP=false --CROSSDESK_BUILD_CLI=false print "Building CrossDesk wire for ${SDK_NAME} (${MODE})" run_xmake b -P "${WIRE_WORK_DIR}" "${XMAKE_LOG_FLAGS[@]}" crossdesk_wire ) diff --git a/deps/submodules/minirtc b/deps/submodules/minirtc index d58bb8b9..ea29073d 160000 --- a/deps/submodules/minirtc +++ b/deps/submodules/minirtc @@ -1 +1 @@ -Subproject commit d58bb8b9dd1aebea2d483afd6c550e1f8a144f40 +Subproject commit ea29073daedb8e04b941a45c3deeda9750e3b8c4 diff --git a/libs/controller/src/file_browser.cpp b/libs/controller/src/file_browser.cpp index 52ff4e47..3fae30b4 100644 --- a/libs/controller/src/file_browser.cpp +++ b/libs/controller/src/file_browser.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace crossdesk { namespace { @@ -16,6 +17,9 @@ using Json = FileBrowser::Json; namespace fs = std::filesystem; constexpr size_t kPageEntries = 512; constexpr size_t kPageBytes = 45000; +fs::path PathFromUtf8(std::string_view value) { + return fs::path(std::u8string(value.begin(), value.end())); +} std::string Utf8(const fs::path& path) { const auto value = path.u8string(); return {value.begin(), value.end()}; @@ -26,7 +30,7 @@ fs::path Home() { #else const auto* home = std::getenv("HOME"); #endif - return home ? std::filesystem::u8path(home) : fs::current_path(); + return home ? PathFromUtf8(home) : fs::current_path(); } Json Failure(const std::string& reason) { return {{"ok", false}, {"error", reason}}; } template @@ -76,7 +80,7 @@ std::string Text(const Json& value, const char* key) { } fs::path ExistingDirectory(const std::string& path) { if (path.empty()) throw std::runtime_error("invalid_path"); - auto input = std::filesystem::u8path(path); + auto input = PathFromUtf8(path); if (!input.is_absolute()) throw std::runtime_error("invalid_path"); std::error_code ec; auto result = fs::canonical(input, ec); @@ -84,7 +88,7 @@ fs::path ExistingDirectory(const std::string& path) { return result; } fs::path Destination(const fs::path& root, const std::string& relative) { - auto path = std::filesystem::u8path(relative); + auto path = PathFromUtf8(relative); if (path.is_absolute() || path.has_root_name()) throw std::runtime_error("invalid_path"); auto result = root; for (const auto& component : path) { @@ -358,7 +362,7 @@ FileBrowser::Json FileBrowser::Directory(const std::string& value, const std::fu for (char drive = 'A'; drive <= 'Z'; ++drive) { const std::string root = std::string(1, drive) + ":\\"; std::error_code ec; - if (fs::is_directory(std::filesystem::u8path(root), ec)) + if (fs::is_directory(PathFromUtf8(root), ec)) entries.push_back({{"name", root}, {"path", root}, {"directory", true}, {"size", 0}, {"modified", ""}, {"link", false}}); } #else @@ -514,7 +518,7 @@ void FileBrowser::Upload(const std::string& peer, const std::vector }; // Validate the entire selection before creating remote folders or sending. for (const auto& source : paths) { - auto path = std::filesystem::u8path(source); + auto path = PathFromUtf8(source); if (!path.is_absolute() || path.filename().empty()) throw std::runtime_error("invalid_path"); add(path, path.filename()); if (fs::is_directory(fs::symlink_status(path))) { diff --git a/xmake.lua b/xmake.lua index 88df2a68..566d1165 100644 --- a/xmake.lua +++ b/xmake.lua @@ -86,6 +86,8 @@ if os.isdir(path.join(wire_dir, "tests")) then end if build_desktop then + includes(path.join(source_dir, "deps/submodules"), + path.join(source_dir, "deps/thirdparty")) setup_targets() end