diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 74e4efa99..cae5b82c7 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -18,7 +18,7 @@ on: env: LLVM_VERSION: "15.0.6" - FLUTTER_VERSION: "3.7.0" + FLUTTER_VERSION: "3.7.5" # vcpkg version: 2022.05.10 # for multiarch gcc compatibility VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44" @@ -260,7 +260,7 @@ jobs: job: - { target: x86_64-unknown-linux-gnu, - os: ubuntu-18.04, + os: ubuntu-20.04, extra-build-args: "", } steps: @@ -330,13 +330,13 @@ jobs: - { arch: x86_64, target: aarch64-linux-android, - os: ubuntu-18.04, + os: ubuntu-20.04, extra-build-features: "", } # - { # arch: x86_64, # target: armv7-linux-androideabi, - # os: ubuntu-18.04, + # os: ubuntu-20.04, # extra-build-features: "", # } steps: @@ -907,19 +907,19 @@ jobs: - { arch: x86_64, target: x86_64-unknown-linux-gnu, - os: ubuntu-18.04, + os: ubuntu-20.04, extra-build-features: "", } - { arch: x86_64, target: x86_64-unknown-linux-gnu, - os: ubuntu-18.04, + os: ubuntu-20.04, extra-build-features: "flatpak", } - { arch: x86_64, target: x86_64-unknown-linux-gnu, - os: ubuntu-18.04, + os: ubuntu-20.04, extra-build-features: "appimage", } # - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } diff --git a/.github/workflows/flutter-nightly.yml b/.github/workflows/flutter-nightly.yml index b08193971..ffcadd18b 100644 --- a/.github/workflows/flutter-nightly.yml +++ b/.github/workflows/flutter-nightly.yml @@ -732,7 +732,7 @@ jobs: x86_64) # no need mock on x86_64 export VCPKG_ROOT=/opt/artifacts/vcpkg - cargo build --lib --features hwcodec,flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release + cargo build --lib --features hwcodec,flutter,${{ matrix.job.extra-build-features }} --release ;; esac @@ -900,7 +900,7 @@ jobs: ln -s /usr/include /vcpkg/installed/arm64-linux/include export VCPKG_ROOT=/vcpkg # disable hwcodec for compilation - cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release + cargo build --lib --features flutter,${{ matrix.job.extra-build-features }} --release ;; armv7) cp -r /opt/artifacts/vcpkg/installed/lib/* /usr/lib/arm-linux-gnueabihf/ @@ -910,7 +910,7 @@ jobs: ln -s /usr/include /vcpkg/installed/arm-linux/include export VCPKG_ROOT=/vcpkg # disable hwcodec for compilation - cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release + cargo build --lib --features flutter,${{ matrix.job.extra-build-features }} --release ;; esac diff --git a/Cargo.lock b/Cargo.lock index a2cdf91a4..8f8895bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4656,7 +4656,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/fufesou/rdev#5b9fb5e42117f44e0ce0fe7cf2bddf270c75f1dc" +source = "git+https://github.com/fufesou/rdev#25a99ce71ab42843ad253dd51e6a35e83e87a8a4" dependencies = [ "cocoa", "core-foundation 0.9.3", diff --git a/build.py b/build.py index b30f76f95..45fe1b132 100755 --- a/build.py +++ b/build.py @@ -315,13 +315,10 @@ def build_flutter_dmg(version, features): # copy dylib system2( "cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib") - # ffi_bindgen_function_refactor() - # limitations from flutter rust bridge - system2('sed -i "" "s/char \*\*rustdesk_core_main(int \*args_len);//" flutter/macos/Runner/bridge_generated.h') os.chdir('flutter') system2('flutter build macos --release') system2( - "create-dmg rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app") + "create-dmg --volname \"RustDesk Installer\" --window-pos 200 120 --window-size 800 400 --icon-size 100 --app-drop-link 600 185 --icon RustDesk.app 200 190 --hide-extension RustDesk.app rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app") os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg") os.chdir("..") diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 155fcc743..e0306a3a7 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -217,6 +217,9 @@ class MyTheme { tabBarTheme: const TabBarTheme( labelColor: Colors.white70, ), + scrollbarTheme: ScrollbarThemeData( + thumbColor: MaterialStateProperty.all(Colors.grey[500]) + ), splashColor: Colors.transparent, highlightColor: Colors.transparent, splashFactory: isDesktop ? NoSplash.splashFactory : null, diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index e041b591d..52f64c0e3 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -19,7 +19,7 @@ import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart'; import '../../common/widgets/dialog.dart'; import '../../common/widgets/login.dart'; -const double _kTabWidth = 235; +const double _kTabWidth = 200; const double _kTabHeight = 42; const double _kCardFixedWidth = 540; const double _kCardLeftMargin = 15; diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index c27546d9f..f236a7828 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -920,6 +920,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { disableClipboard(), lockAfterSessionEnd(), privacyMode(), + swapKey(), ]); } @@ -953,12 +954,13 @@ class _DisplayMenuState extends State<_DisplayMenu> { final canvasModel = widget.ffi.canvasModel; final width = (canvasModel.getDisplayWidth() * canvasModel.scale + - canvasModel.windowBorderWidth * 2) * + CanvasModel.leftToEdge + + CanvasModel.rightToEdge) * scale + magicWidth; final height = (canvasModel.getDisplayHeight() * canvasModel.scale + - canvasModel.tabBarHeight + - canvasModel.windowBorderWidth * 2) * + CanvasModel.topToEdge + + CanvasModel.bottomToEdge) * scale + magicHeight; double left = wndRect.left + (wndRect.width - width) / 2; @@ -1027,10 +1029,10 @@ class _DisplayMenuState extends State<_DisplayMenu> { final canvasModel = widget.ffi.canvasModel; final displayWidth = canvasModel.getDisplayWidth(); final displayHeight = canvasModel.getDisplayHeight(); - final requiredWidth = displayWidth + - (canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2); - final requiredHeight = displayHeight + - (canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2); + final requiredWidth = + CanvasModel.leftToEdge + displayWidth + CanvasModel.rightToEdge; + final requiredHeight = + CanvasModel.topToEdge + displayHeight + CanvasModel.bottomToEdge; return selfWidth > (requiredWidth * scale) && selfHeight > (requiredHeight * scale); } @@ -1527,6 +1529,23 @@ class _DisplayMenuState extends State<_DisplayMenu> { ffi: widget.ffi, child: Text(translate('Privacy mode'))); } + + swapKey() { + final visible = perms['keyboard'] != false && + ((Platform.isMacOS && pi.platform != kPeerPlatformMacOS) || + (!Platform.isMacOS && pi.platform == kPeerPlatformMacOS)); + if (!visible) return Offstage(); + final option = 'allow_swap_key'; + final value = bind.sessionGetToggleOptionSync(id: widget.id, arg: option); + return _CheckboxMenuButton( + value: value, + onChanged: (value) { + if (value == null) return; + bind.sessionToggleOption(id: widget.id, value: option); + }, + ffi: widget.ffi, + child: Text(translate('Swap control-command key'))); + } } class _KeyboardMenu extends StatelessWidget { diff --git a/flutter/lib/desktop/widgets/scroll_wrapper.dart b/flutter/lib/desktop/widgets/scroll_wrapper.dart index 32ed149e5..c5bc3394b 100644 --- a/flutter/lib/desktop/widgets/scroll_wrapper.dart +++ b/flutter/lib/desktop/widgets/scroll_wrapper.dart @@ -14,6 +14,7 @@ class DesktopScrollWrapper extends StatelessWidget { return ImprovedScrolling( scrollController: scrollController, enableCustomMouseWheelScrolling: true, + // enableKeyboardScrolling: true, // strange behavior on mac customMouseWheelScrollConfig: CustomMouseWheelScrollConfig( scrollDuration: kDefaultScrollDuration, scrollCurve: Curves.linearToEaseOut, diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index b91453138..df9ad2585 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -458,10 +458,8 @@ class InputModel { return; } evt['type'] = type; - if (isDesktop) { - y = y - stateGlobal.tabBarHeight - stateGlobal.windowBorderWidth.value; - x -= stateGlobal.windowBorderWidth.value; - } + y -= CanvasModel.topToEdge; + x -= CanvasModel.leftToEdge; final canvasModel = parent.target!.canvasModel; final nearThr = 3; var nearRight = (canvasModel.size.width - x) < nearThr; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 21949705f..802a18a52 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -727,12 +727,25 @@ class CanvasModel with ChangeNotifier { double get scrollX => _scrollX; double get scrollY => _scrollY; + static double get leftToEdge => (isDesktop || isWebDesktop) + ? windowBorderWidth + kDragToResizeAreaPadding.left + : 0; + static double get rightToEdge => (isDesktop || isWebDesktop) + ? windowBorderWidth + kDragToResizeAreaPadding.right + : 0; + static double get topToEdge => (isDesktop || isWebDesktop) + ? tabBarHeight + windowBorderWidth + kDragToResizeAreaPadding.top + : 0; + static double get bottomToEdge => (isDesktop || isWebDesktop) + ? windowBorderWidth + kDragToResizeAreaPadding.bottom + : 0; + updateViewStyle() async { Size getSize() { final size = MediaQueryData.fromWindow(ui.window).size; // If minimized, w or h may be negative here. - double w = size.width - windowBorderWidth * 2; - double h = size.height - tabBarHeight - windowBorderWidth * 2; + double w = size.width - leftToEdge - rightToEdge; + double h = size.height - topToEdge - bottomToEdge; return Size(w < 0 ? 0 : w, h < 0 ? 0 : h); } @@ -806,10 +819,14 @@ class CanvasModel with ChangeNotifier { return parent.target?.ffiModel.display.height ?? defaultHeight; } - double get windowBorderWidth => stateGlobal.windowBorderWidth.value; - double get tabBarHeight => stateGlobal.tabBarHeight; + static double get windowBorderWidth => stateGlobal.windowBorderWidth.value; + static double get tabBarHeight => stateGlobal.tabBarHeight; moveDesktopMouse(double x, double y) { + if (size.width == 0 || size.height == 0) { + return; + } + // On mobile platforms, move the canvas with the cursor. final dw = getDisplayWidth() * _scale; final dh = getDisplayHeight() * _scale; diff --git a/flutter/macos/Runner.xcodeproj/project.pbxproj b/flutter/macos/Runner.xcodeproj/project.pbxproj index 0019335ef..c73e666c7 100644 --- a/flutter/macos/Runner.xcodeproj/project.pbxproj +++ b/flutter/macos/Runner.xcodeproj/project.pbxproj @@ -487,7 +487,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Apple Development"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 3bfc885c5..7bc82ed95 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -217,6 +217,8 @@ pub struct PeerConfig { pub lock_after_session_end: LockAfterSessionEnd, #[serde(flatten)] pub privacy_mode: PrivacyMode, + #[serde(flatten)] + pub allow_swap_key: AllowSwapKey, #[serde(default)] pub port_forwards: Vec<(i32, String, i32)>, #[serde(default)] @@ -1060,6 +1062,8 @@ serde_field_bool!( ); serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode); +serde_field_bool!(AllowSwapKey, "allow_swap_key", default_swap_key); + #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct LocalConfig { #[serde(default)] diff --git a/src/client.rs b/src/client.rs index ebfda7283..40a9f05b0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1230,6 +1230,8 @@ impl LoginConfigHandler { option.block_input = BoolOption::No.into(); } else if name == "show-quality-monitor" { config.show_quality_monitor.v = !config.show_quality_monitor.v; + } else if name == "allow_swap_key" { + config.allow_swap_key.v = !config.allow_swap_key.v; } else { let is_set = self .options @@ -1383,6 +1385,8 @@ impl LoginConfigHandler { self.config.disable_clipboard.v } else if name == "show-quality-monitor" { self.config.show_quality_monitor.v + } else if name == "allow_swap_key" { + self.config.allow_swap_key.v } else { !self.get_option(name).is_empty() } @@ -1807,6 +1811,7 @@ pub fn send_mouse( if check_scroll_on_mac(mask, x, y) { mouse_event.modifiers.push(ControlKey::Scroll.into()); } + interface.swap_modifier_mouse(&mut mouse_event); msg_out.set_mouse_event(mouse_event); interface.send(Data::Message(msg_out)); } @@ -2033,6 +2038,7 @@ pub trait Interface: Send + Clone + 'static + Sized { fn is_force_relay(&self) -> bool { self.get_login_config_handler().read().unwrap().force_relay } + fn swap_modifier_mouse(&self, _msg : &mut hbb_common::protos::message::MouseEvent) {} } /// Data used by the client interface. diff --git a/src/lang/en.rs b/src/lang/en.rs index 3e87cd661..250530013 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -39,8 +39,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("verification_tip", "A new device has been detected, and a verification code has been sent to the registered email address, enter the verification code to continue logging in."), ("software_render_tip", "If you have an Nvidia graphics card and the remote window closes immediately after connecting, installing the nouveau driver and choosing to use software rendering may help. A software restart is required."), ("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."), - ("request_elevation_tip","You can also request elevation if there is someone on the remote side."), - ("wait_accept_uac_tip","Please wait for the remote user to accept the UAC dialog."), + ("request_elevation_tip", "You can also request elevation if there is someone on the remote side."), + ("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."), ("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."), ("config_microphone", "In order to speak remotely, you need to grant RustDesk \"Record Audio\" permissions."), ("relay_hint_tip", "It may not be possible to connect directly, you can try to connect via relay. \nIn addition, if you want to use relay on your first try, you can add the \"/r\" suffix to the ID, or select the option \"Always connect via relay\" in the peer card."), diff --git a/src/lang/es.rs b/src/lang/es.rs index 8477ba99b..dc28cdae0 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -458,6 +458,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", "Reconectar"), ("Codec", "Códec"), ("Resolution", "Resolución"), - ("No transfers in progress", ""), + ("No transfers in progress", "No hay transferencias en curso"), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 2cefaa98f..824bd039c 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -456,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop voice call", "توقف تماس صوتی"), ("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"), ("Reconnect", "اتصال مجدد"), - ("Codec", ""), - ("Resolution", ""), - ("No transfers in progress", ""), + ("Codec", "کدک"), + ("Resolution", "وضوح"), + ("No transfers in progress", "هیچ انتقالی در حال انجام نیست"), ].iter().cloned().collect(); } diff --git a/src/ui/header.tis b/src/ui/header.tis index e25c0d544..257ba417e 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -198,6 +198,7 @@ class Header: Reactor.Component { {keyboard_enabled && clipboard_enabled ?