Merge remote-tracking branch 'github/master' into sigma

This commit is contained in:
sjpark 2023-02-09 11:53:04 +09:00
commit e3963adf49
38 changed files with 498 additions and 339 deletions

View File

@ -30,13 +30,22 @@ body:
description: A clear and concise description of what you expected to happen
validations:
required: true
- type: input
id: os
attributes:
label: Operating system(s) on local side and remote side
description: What operating system(s) do you see this bug on? local side -> remote side.
placeholder: |
Windows 10 -> osx
validations:
required: true
- type: input
id: version
attributes:
label: Operating System(s) and RustDesk Version(s) on local side and remote side
description: What Operatiing System(s) and version(s) of RustDesk do you see this bug on? local side / remote side.
label: RustDesk Version(s) on local side and remote side
description: What RustDesk version(s) do you see this bug on? local side -> remote side.
placeholder: |
Windows 10, 1.1.9 / osx 13.1, 1.1.8
1.1.9 -> 1.1.8
validations:
required: true
- type: textarea

View File

@ -242,7 +242,6 @@ jobs:
security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain
# start sign the rustdesk.app and dmg
rm rustdesk-${{ env.VERSION }}.dmg || true
mv ./flutter/build/macos/Build/Products/Release/rustdesk.app ./flutter/build/macos/Build/Products/Release/RustDesk.app
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep ./flutter/build/macos/Build/Products/Release/RustDesk.app -v
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep rustdesk-${{ env.VERSION }}.dmg -v

2
Cargo.lock generated
View File

@ -4405,7 +4405,7 @@ dependencies = [
[[package]]
name = "rdev"
version = "0.5.0-2"
source = "git+https://github.com/fufesou/rdev#4d8231f05e14c5a04cd7d2c1288e87ad52d39e4c"
source = "git+https://github.com/fufesou/rdev#cedc4e62744566775026af4b434ef799804c1130"
dependencies = [
"cocoa",
"core-foundation 0.9.3",

View File

@ -322,8 +322,9 @@ def build_flutter_dmg(version, features):
os.system('sed -i "" "s/char \*\*rustdesk_core_main(int \*args_len);//" flutter/macos/Runner/bridge_generated.h')
os.chdir('flutter')
os.system('flutter build macos --release')
os.system('mv ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/RustDesk ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/rustdesk')
os.system(
"create-dmg rustdesk.dmg ./build/macos/Build/Products/Release/rustdesk.app")
"create-dmg rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg")
os.chdir("..")

View File

@ -367,20 +367,25 @@ class Dialog<T> {
}
}
class OverlayKeyState {
final _overlayKey = GlobalKey<OverlayState>();
/// use global overlay by default
OverlayState? get state =>
_overlayKey.currentState ?? globalKey.currentState?.overlay;
GlobalKey<OverlayState>? get key => _overlayKey;
}
class OverlayDialogManager {
OverlayState? _overlayState;
final Map<String, Dialog> _dialogs = {};
var _overlayKeyState = OverlayKeyState();
int _tagCount = 0;
OverlayEntry? _mobileActionsOverlayEntry;
/// By default OverlayDialogManager use global overlay
OverlayDialogManager() {
_overlayState = globalKey.currentState?.overlay;
}
void setOverlayState(OverlayState? overlayState) {
_overlayState = overlayState;
void setOverlayState(OverlayKeyState overlayKeyState) {
_overlayKeyState = overlayKeyState;
}
void dismissAll() {
@ -404,7 +409,7 @@ class OverlayDialogManager {
bool useAnimation = true,
bool forceGlobal = false}) {
final overlayState =
forceGlobal ? globalKey.currentState?.overlay : _overlayState;
forceGlobal ? globalKey.currentState?.overlay : _overlayKeyState.state;
if (overlayState == null) {
return Future.error(
@ -508,7 +513,8 @@ class OverlayDialogManager {
void showMobileActionsOverlay({FFI? ffi}) {
if (_mobileActionsOverlayEntry != null) return;
if (_overlayState == null) return;
final overlayState = _overlayKeyState.state;
if (overlayState == null) return;
// compute overlay position
final screenW = MediaQuery.of(globalKey.currentContext!).size.width;
@ -534,7 +540,7 @@ class OverlayDialogManager {
onHidePressed: () => hideMobileActionsOverlay(),
);
});
_overlayState!.insert(overlay);
overlayState.insert(overlay);
_mobileActionsOverlayEntry = overlay;
}

View File

@ -95,10 +95,31 @@ class ChatPage extends StatelessWidget implements PageShape {
color: Theme.of(context).colorScheme.primary)),
messageOptions: MessageOptions(
showOtherUsersAvatar: false,
showTime: true,
currentUserTextColor: Colors.white,
textColor: Colors.white,
maxWidth: constraints.maxWidth * 0.7,
messageTextBuilder: (message, _, __) {
final isOwnMessage =
message.user.id == currentUser.id;
return Column(
crossAxisAlignment: isOwnMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: <Widget>[
Text(message.text,
style: TextStyle(color: Colors.white)),
Padding(
padding: const EdgeInsets.only(top: 5),
child: Text(
"${message.createdAt.hour}:${message.createdAt.minute}",
style: TextStyle(
color: Colors.white,
fontSize: 10,
),
),
),
],
);
},
messageDecorationBuilder: (_, __, ___) =>
defaultMessageDecoration(
color: MyTheme.accent80,

View File

@ -1,6 +1,7 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import '../../consts.dart';
@ -96,12 +97,14 @@ class DraggableChatWindow extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Row(children: [
Icon(Icons.chat_bubble_outline,
size: 20, color: Theme.of(context).colorScheme.primary),
SizedBox(width: 6),
Text(translate("Chat"))
])),
child: Obx(() => Opacity(
opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4,
child: Row(children: [
Icon(Icons.chat_bubble_outline,
size: 20, color: Theme.of(context).colorScheme.primary),
SizedBox(width: 6),
Text(translate("Chat"))
])))),
Padding(
padding: EdgeInsets.all(2),
child: ActionIcon(
@ -304,15 +307,17 @@ class _DraggableState extends State<Draggable> {
if (widget.checkKeyboard) {
checkKeyboard();
}
if (widget.checkKeyboard) {
if (widget.checkScreenSize) {
checkScreenSize();
}
return Positioned(
top: _position.dy,
left: _position.dx,
width: widget.width,
height: widget.height,
child: widget.builder(context, onPanUpdate));
return Stack(children: [
Positioned(
top: _position.dy,
left: _position.dx,
width: widget.width,
height: widget.height,
child: widget.builder(context, onPanUpdate))
]);
}
}
@ -366,3 +371,55 @@ class QualityMonitor extends StatelessWidget {
)
: const SizedBox.shrink()));
}
class BlockableOverlayState extends OverlayKeyState {
final _middleBlocked = false.obs;
VoidCallback? onMiddleBlockedClick; // to-do use listener
RxBool get middleBlocked => _middleBlocked;
void addMiddleBlockedListener(void Function(bool) cb) {
_middleBlocked.listen(cb);
}
void setMiddleBlocked(bool blocked) {
if (blocked != _middleBlocked.value) {
_middleBlocked.value = blocked;
}
}
}
class BlockableOverlay extends StatelessWidget {
final Widget underlying;
final List<OverlayEntry>? upperLayer;
final BlockableOverlayState state;
BlockableOverlay(
{required this.underlying, required this.state, this.upperLayer});
@override
Widget build(BuildContext context) {
final initialEntries = [
OverlayEntry(builder: (_) => underlying),
/// middle layer
OverlayEntry(
builder: (context) => Obx(() => Listener(
onPointerDown: (_) {
state.onMiddleBlockedClick?.call();
},
child: Container(
color:
state.middleBlocked.value ? Colors.transparent : null)))),
];
if (upperLayer != null) {
initialEntries.addAll(upperLayer!);
}
/// set key
return Overlay(key: state.key, initialEntries: initialEntries);
}
}

View File

@ -64,23 +64,17 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
@override
Widget build(BuildContext context) {
final tabWidget = Container(
child: Overlay(initialEntries: [
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
onTap: DesktopTabPage.onAddSetting,
isClose: false,
),
));
})
]),
);
child: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
onTap: DesktopTabPage.onAddSetting,
isClose: false,
),
)));
return Platform.isMacOS
? tabWidget
: Obx(

View File

@ -80,6 +80,7 @@ class _FileManagerPageState extends State<FileManagerPage>
Entry? _lastClickEntry;
final _dropMaskVisible = false.obs; // TODO impl drop mask
final _overlayKeyState = OverlayKeyState();
ScrollController getBreadCrumbScrollController(bool isLocal) {
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
@ -115,6 +116,7 @@ class _FileManagerPageState extends State<FileManagerPage>
// register location listener
_locationNodeLocal.addListener(onLocalLocationFocusChanged);
_locationNodeRemote.addListener(onRemoteLocationFocusChanged);
_ffi.dialogManager.setOverlayState(_overlayKeyState);
}
@override
@ -137,9 +139,8 @@ class _FileManagerPageState extends State<FileManagerPage>
@override
Widget build(BuildContext context) {
super.build(context);
return Overlay(initialEntries: [
OverlayEntry(builder: (context) {
_ffi.dialogManager.setOverlayState(Overlay.of(context));
return Overlay(key: _overlayKeyState.key, initialEntries: [
OverlayEntry(builder: (_) {
return ChangeNotifierProvider.value(
value: _ffi.fileModel,
child: Consumer<FileModel>(builder: (context, model, child) {

View File

@ -61,6 +61,8 @@ class _RemotePageState extends State<RemotePage>
late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled;
final _blockableOverlayState = BlockableOverlayState();
final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode");
Function(bool)? _onEnterOrLeaveImage4Menubar;
@ -132,6 +134,13 @@ class _RemotePageState extends State<RemotePage>
// });
// _isCustomCursorInited = true;
// }
_ffi.dialogManager.setOverlayState(_blockableOverlayState);
_ffi.chatModel.setOverlayState(_blockableOverlayState);
// make remote page penetrable automatically, effective for chat over remote
_blockableOverlayState.onMiddleBlockedClick = () {
_blockableOverlayState.setMiddleBlocked(false);
};
}
@override
@ -191,39 +200,50 @@ class _RemotePageState extends State<RemotePage>
Widget buildBody(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Overlay(
initialEntries: [
OverlayEntry(builder: (context) {
_ffi.chatModel.setOverlayState(Overlay.of(context));
_ffi.dialogManager.setOverlayState(Overlay.of(context));
return Container(
color: Colors.black,
child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode,
onFocusChange: (bool imageFocused) {
debugPrint(
"onFocusChange(window active:${!_isWindowBlur}) $imageFocused");
// See [onWindowBlur].
if (Platform.isWindows) {
if (_isWindowBlur) {
imageFocused = false;
Future.delayed(Duration.zero, () {
_rawKeyFocusNode.unfocus();
});
}
if (imageFocused) {
_ffi.inputModel.enterOrLeave(true);
} else {
_ffi.inputModel.enterOrLeave(false);
}
}
},
inputModel: _ffi.inputModel,
child: getBodyForDesktop(context)));
})
],
));
backgroundColor: Theme.of(context).backgroundColor,
/// the Overlay key will be set with _blockableOverlayState in BlockableOverlay
/// see override build() in [BlockableOverlay]
body: BlockableOverlay(
state: _blockableOverlayState,
underlying: Container(
color: Colors.black,
child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode,
onFocusChange: (bool imageFocused) {
debugPrint(
"onFocusChange(window active:${!_isWindowBlur}) $imageFocused");
// See [onWindowBlur].
if (Platform.isWindows) {
if (_isWindowBlur) {
imageFocused = false;
Future.delayed(Duration.zero, () {
_rawKeyFocusNode.unfocus();
});
}
if (imageFocused) {
_ffi.inputModel.enterOrLeave(true);
} else {
_ffi.inputModel.enterOrLeave(false);
}
}
},
inputModel: _ffi.inputModel,
child: getBodyForDesktop(context))),
upperLayer: [
OverlayEntry(
builder: (context) => RemoteMenubar(
id: widget.id,
ffi: _ffi,
state: widget.menubarState,
onEnterOrLeaveImageSetter: (func) =>
_onEnterOrLeaveImage4Menubar = func,
onEnterOrLeaveImageCleaner: () =>
_onEnterOrLeaveImage4Menubar = null,
))
],
),
);
}
@override
@ -344,13 +364,6 @@ class _RemotePageState extends State<RemotePage>
QualityMonitor(_ffi.qualityMonitorModel), null, null),
),
);
paints.add(RemoteMenubar(
id: widget.id,
ffi: _ffi,
state: widget.menubarState,
onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func,
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null,
));
return Stack(
children: paints,
);

View File

@ -68,26 +68,19 @@ class _DesktopServerPageState extends State<DesktopServerPage>
],
child: Consumer<ServerModel>(
builder: (context, serverModel, child) => Container(
decoration: BoxDecoration(
border:
Border.all(color: MyTheme.color(context).border!)),
child: Overlay(initialEntries: [
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(child: ConnectionManager()),
],
),
),
);
})
]),
)));
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(child: ConnectionManager()),
],
),
),
))));
}
@override

View File

@ -688,9 +688,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
);
}
final _chatButtonKey = GlobalKey();
Widget _buildChat(BuildContext context) {
FfiModel ffiModel = Provider.of<FfiModel>(context);
return mod_menu.PopupMenuButton(
key: _chatButtonKey,
padding: EdgeInsets.zero,
icon: SvgPicture.asset(
"assets/chat.svg",
@ -779,8 +781,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style,
),
proc: () {
RenderBox? renderBox =
_chatButtonKey.currentContext?.findRenderObject() as RenderBox?;
Offset? initPos;
if (renderBox != null) {
final pos = renderBox.localToGlobal(Offset.zero);
initPos = Offset(pos.dx, pos.dy + _MenubarTheme.dividerHeight);
}
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
widget.ffi.chatModel.toggleChatOverlay();
widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
},
padding: padding,
dismissOnClicked: true,

View File

@ -327,14 +327,32 @@ class DesktopTab extends StatelessWidget {
));
}
List<Widget> _tabWidgets = [];
Widget _buildPageView() {
return _buildBlock(
child: Obx(() => PageView(
controller: state.value.pageController,
physics: NeverScrollableScrollPhysics(),
children: state.value.tabs
.map((tab) => tab.page)
.toList(growable: false))));
children: () {
/// to-do refactor, separate connection state and UI state for remote session.
/// [workaround] PageView children need an immutable list, after it has been passed into PageView
final tabLen = state.value.tabs.length;
if (tabLen == _tabWidgets.length) {
return _tabWidgets;
} else if (_tabWidgets.isNotEmpty &&
tabLen == _tabWidgets.length + 1) {
/// On add. Use the previous list(pointer) to prevent item's state init twice.
/// *[_tabWidgets.isNotEmpty] means TabsWindow(remote_tab_page or file_manager_tab_page) opened before, but was hidden. In this case, we have to reload, otherwise the child can't be built.
_tabWidgets.add(state.value.tabs.last.page);
return _tabWidgets;
} else {
/// On remove or change. Use new list(pointer) to reload list children so that items loading order is normal.
/// the Widgets in list must enable [AutomaticKeepAliveClientMixin]
final newList = state.value.tabs.map((v) => v.page).toList();
_tabWidgets = newList;
return newList;
}
}())));
}
/// Check whether to show ListView
@ -767,7 +785,8 @@ class _ListView extends StatelessWidget {
tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder,
maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor ?? MyTheme.tabbar(context).selectedTabBackgroundColor,
selectedTabBackgroundColor: selectedTabBackgroundColor ??
MyTheme.tabbar(context).selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
);
}).toList()));
@ -1121,7 +1140,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
dividerColor: dividerColor ?? this.dividerColor,
hoverColor: hoverColor ?? this.hoverColor,
closeHoverColor: closeHoverColor ?? this.closeHoverColor,
selectedTabBackgroundColor: selectedTabBackgroundColor ?? this.selectedTabBackgroundColor,
selectedTabBackgroundColor:
selectedTabBackgroundColor ?? this.selectedTabBackgroundColor,
);
}
@ -1147,7 +1167,8 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
dividerColor: Color.lerp(dividerColor, other.dividerColor, t),
hoverColor: Color.lerp(hoverColor, other.hoverColor, t),
closeHoverColor: Color.lerp(closeHoverColor, other.closeHoverColor, t),
selectedTabBackgroundColor: Color.lerp(selectedTabBackgroundColor, other.selectedTabBackgroundColor, t),
selectedTabBackgroundColor: Color.lerp(
selectedTabBackgroundColor, other.selectedTabBackgroundColor, t),
);
}

View File

@ -1,7 +1,10 @@
import 'dart:async';
import 'package:dash_chat_2/dash_chat_2.dart';
import 'package:draggable_float_widget/draggable_float_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/get.dart';
import 'package:window_manager/window_manager.dart';
@ -27,16 +30,13 @@ class MessageBody {
class ChatModel with ChangeNotifier {
static final clientModeID = -1;
/// _overlayState:
/// Desktop: store session overlay by using [setOverlayState].
/// Mobile: always null, use global overlay.
/// see [_getOverlayState] in [showChatIconOverlay] or [showChatWindowOverlay]
OverlayState? _overlayState;
OverlayEntry? chatIconOverlayEntry;
OverlayEntry? chatWindowOverlayEntry;
bool isConnManager = false;
RxBool isWindowFocus = true.obs;
BlockableOverlayState? _blockableOverlayState;
final Rx<VoiceCallStatus> _voiceCallStatus = Rx(VoiceCallStatus.notStarted);
Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus;
@ -58,6 +58,19 @@ class ChatModel with ChangeNotifier {
bool get isShowCMChatPage => _isShowCMChatPage;
void setOverlayState(BlockableOverlayState blockableOverlayState) {
_blockableOverlayState = blockableOverlayState;
_blockableOverlayState!.addMiddleBlockedListener((v) {
if (!v) {
isWindowFocus.value = false;
if (isWindowFocus.value) {
isWindowFocus.toggle();
}
}
});
}
final WeakReference<FFI> parent;
ChatModel(this.parent);
@ -74,20 +87,6 @@ class ChatModel with ChangeNotifier {
}
}
setOverlayState(OverlayState? os) {
_overlayState = os;
}
OverlayState? _getOverlayState() {
if (_overlayState == null) {
if (globalKey.currentState == null ||
globalKey.currentState!.overlay == null) return null;
return globalKey.currentState!.overlay;
} else {
return _overlayState;
}
}
showChatIconOverlay({Offset offset = const Offset(200, 50)}) {
if (chatIconOverlayEntry != null) {
chatIconOverlayEntry!.remove();
@ -100,7 +99,7 @@ class ChatModel with ChangeNotifier {
}
}
final overlayState = _getOverlayState();
final overlayState = _blockableOverlayState?.state;
if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) {
@ -132,23 +131,35 @@ class ChatModel with ChangeNotifier {
}
}
showChatWindowOverlay() {
showChatWindowOverlay({Offset? chatInitPos}) {
if (chatWindowOverlayEntry != null) return;
final overlayState = _getOverlayState();
isWindowFocus.value = true;
_blockableOverlayState?.setMiddleBlocked(true);
final overlayState = _blockableOverlayState?.state;
if (overlayState == null) return;
final overlay = OverlayEntry(builder: (context) {
return DraggableChatWindow(
position: const Offset(20, 80),
width: 250,
height: 350,
chatModel: this);
return Listener(
onPointerDown: (_) {
if (!isWindowFocus.value) {
isWindowFocus.value = true;
_blockableOverlayState?.setMiddleBlocked(true);
}
},
child: DraggableChatWindow(
position: chatInitPos ?? Offset(20, 80),
width: 250,
height: 350,
chatModel: this));
});
overlayState.insert(overlay);
chatWindowOverlayEntry = overlay;
requestChatInputFocus();
}
hideChatWindowOverlay() {
if (chatWindowOverlayEntry != null) {
_blockableOverlayState?.setMiddleBlocked(false);
chatWindowOverlayEntry!.remove();
chatWindowOverlayEntry = null;
return;
@ -158,13 +169,13 @@ class ChatModel with ChangeNotifier {
_isChatOverlayHide() => ((!isDesktop && chatIconOverlayEntry == null) ||
chatWindowOverlayEntry == null);
toggleChatOverlay() {
toggleChatOverlay({Offset? chatInitPos}) {
if (_isChatOverlayHide()) {
gFFI.invokeMethod("enable_soft_keyboard", true);
if (!isDesktop) {
showChatIconOverlay();
}
showChatWindowOverlay();
showChatWindowOverlay(chatInitPos: chatInitPos);
} else {
hideChatIconOverlay();
hideChatWindowOverlay();
@ -194,6 +205,7 @@ class ChatModel with ChangeNotifier {
await windowManager.setSizeAlignment(
kConnectionManagerWindowSize, Alignment.topRight);
} else {
requestChatInputFocus();
await windowManager.show();
await windowManager.setSizeAlignment(Size(600, 400), Alignment.topRight);
_isShowCMChatPage = !_isShowCMChatPage;
@ -291,7 +303,6 @@ class ChatModel with ChangeNotifier {
close() {
hideChatIconOverlay();
hideChatWindowOverlay();
_overlayState = null;
notifyListeners();
}
@ -299,6 +310,14 @@ class ChatModel with ChangeNotifier {
_messages[clientModeID]?.clear();
}
void requestChatInputFocus() {
Timer(Duration(milliseconds: 100), () {
if (inputNode.hasListeners && inputNode.canRequestFocus) {
inputNode.requestFocus();
}
});
}
void onVoiceCallWaiting() {
_voiceCallStatus.value = VoiceCallStatus.waitingForResponse;
}

View File

@ -18,7 +18,6 @@ import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:tuple/tuple.dart';
import 'package:image/image.dart' as img2;
import 'package:flutter_custom_cursor/cursor_manager.dart';
@ -26,7 +25,6 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import '../common.dart';
import '../common/shared_state.dart';
import '../utils/image.dart' as img;
import '../mobile/widgets/dialog.dart';
import 'input_model.dart';
@ -1393,13 +1391,13 @@ class FFI {
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
}
bind.sessionClose(id: id);
id = '';
imageModel.update(null);
cursorModel.clear();
ffiModel.clear();
canvasModel.clear();
inputModel.resetModifiers();
debugPrint('model $id closed');
id = '';
}
void setMethodCallHandler(FMethod callback) {

View File

@ -64,7 +64,7 @@
295AD07E63F13855C270A0E0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* rustdesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rustdesk.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10ED2044A3C60003C045 /* RustDesk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RustDesk.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -127,7 +127,7 @@
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* rustdesk.app */,
33CC10ED2044A3C60003C045 /* RustDesk.app */,
);
name = Products;
sourceTree = "<group>";
@ -212,7 +212,7 @@
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* rustdesk.app */;
productReference = 33CC10ED2044A3C60003C045 /* RustDesk.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@ -462,7 +462,6 @@
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PRODUCT_NAME = rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -608,7 +607,6 @@
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PRODUCT_NAME = rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
@ -646,7 +644,6 @@
/dev/null,
);
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PRODUCT_NAME = rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;

View File

@ -16,7 +16,7 @@ final testClients = [
Client(3, false, false, "UserDDDDDDDDDDDd", "441123123", true, false, false)
];
/// -t lib/cm_main.dart to test cm
/// flutter run -d {platform} -t lib/cm_test.dart to test cm
void main(List<String> args) async {
isTest = true;
WidgetsFlutterBinding.ensureInitialized();

View File

@ -2,11 +2,11 @@ fn main() {
let out_dir = format!("{}/protos", std::env::var("OUT_DIR").unwrap());
std::fs::create_dir_all(&out_dir).unwrap();
protobuf_codegen::Codegen::new()
.pure()
.out_dir(out_dir)
.inputs(&["protos/rendezvous.proto", "protos/message.proto"])
.inputs(["protos/rendezvous.proto", "protos/message.proto"])
.include("protos")
.customize(protobuf_codegen::Customize::default().tokio_bytes(true))
.run()

View File

@ -143,32 +143,32 @@ mod tests {
let mut buf = BytesMut::new();
let mut bytes: Vec<u8> = Vec::new();
bytes.resize(0x3F, 1);
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
assert!(codec.encode(bytes.into(), &mut buf).is_ok());
let buf_saved = buf.clone();
assert_eq!(buf.len(), 0x3F + 1);
if let Ok(Some(res)) = codec.decode(&mut buf) {
assert_eq!(res.len(), 0x3F);
assert_eq!(res[0], 1);
} else {
assert!(false);
panic!();
}
let mut codec2 = BytesCodec::new();
let mut buf2 = BytesMut::new();
if let Ok(None) = codec2.decode(&mut buf2) {
} else {
assert!(false);
panic!();
}
buf2.extend(&buf_saved[0..1]);
if let Ok(None) = codec2.decode(&mut buf2) {
} else {
assert!(false);
panic!();
}
buf2.extend(&buf_saved[1..]);
if let Ok(Some(res)) = codec2.decode(&mut buf2) {
assert_eq!(res.len(), 0x3F);
assert_eq!(res[0], 1);
} else {
assert!(false);
panic!();
}
}
@ -177,21 +177,21 @@ mod tests {
let mut codec = BytesCodec::new();
let mut buf = BytesMut::new();
let mut bytes: Vec<u8> = Vec::new();
assert!(!codec.encode("".into(), &mut buf).is_err());
assert!(codec.encode("".into(), &mut buf).is_ok());
assert_eq!(buf.len(), 1);
bytes.resize(0x3F + 1, 2);
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
assert!(codec.encode(bytes.into(), &mut buf).is_ok());
assert_eq!(buf.len(), 0x3F + 2 + 2);
if let Ok(Some(res)) = codec.decode(&mut buf) {
assert_eq!(res.len(), 0);
} else {
assert!(false);
panic!();
}
if let Ok(Some(res)) = codec.decode(&mut buf) {
assert_eq!(res.len(), 0x3F + 1);
assert_eq!(res[0], 2);
} else {
assert!(false);
panic!();
}
}
@ -201,13 +201,13 @@ mod tests {
let mut buf = BytesMut::new();
let mut bytes: Vec<u8> = Vec::new();
bytes.resize(0x3F - 1, 3);
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
assert!(codec.encode(bytes.into(), &mut buf).is_ok());
assert_eq!(buf.len(), 0x3F + 1 - 1);
if let Ok(Some(res)) = codec.decode(&mut buf) {
assert_eq!(res.len(), 0x3F - 1);
assert_eq!(res[0], 3);
} else {
assert!(false);
panic!();
}
}
#[test]
@ -216,13 +216,13 @@ mod tests {
let mut buf = BytesMut::new();
let mut bytes: Vec<u8> = Vec::new();
bytes.resize(0x3FFF, 4);
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
assert!(codec.encode(bytes.into(), &mut buf).is_ok());
assert_eq!(buf.len(), 0x3FFF + 2);
if let Ok(Some(res)) = codec.decode(&mut buf) {
assert_eq!(res.len(), 0x3FFF);
assert_eq!(res[0], 4);
} else {
assert!(false);
panic!();
}
}
@ -232,13 +232,13 @@ mod tests {
let mut buf = BytesMut::new();
let mut bytes: Vec<u8> = Vec::new();
bytes.resize(0x3FFFFF, 5);
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
assert!(codec.encode(bytes.into(), &mut buf).is_ok());
assert_eq!(buf.len(), 0x3FFFFF + 3);
if let Ok(Some(res)) = codec.decode(&mut buf) {
assert_eq!(res.len(), 0x3FFFFF);
assert_eq!(res[0], 5);
} else {
assert!(false);
panic!();
}
}
@ -248,33 +248,33 @@ mod tests {
let mut buf = BytesMut::new();
let mut bytes: Vec<u8> = Vec::new();
bytes.resize(0x3FFFFF + 1, 6);
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
assert!(codec.encode(bytes.into(), &mut buf).is_ok());
let buf_saved = buf.clone();
assert_eq!(buf.len(), 0x3FFFFF + 4 + 1);
if let Ok(Some(res)) = codec.decode(&mut buf) {
assert_eq!(res.len(), 0x3FFFFF + 1);
assert_eq!(res[0], 6);
} else {
assert!(false);
panic!();
}
let mut codec2 = BytesCodec::new();
let mut buf2 = BytesMut::new();
buf2.extend(&buf_saved[0..1]);
if let Ok(None) = codec2.decode(&mut buf2) {
} else {
assert!(false);
panic!();
}
buf2.extend(&buf_saved[1..6]);
if let Ok(None) = codec2.decode(&mut buf2) {
} else {
assert!(false);
panic!();
}
buf2.extend(&buf_saved[6..]);
if let Ok(Some(res)) = codec2.decode(&mut buf2) {
assert_eq!(res.len(), 0x3FFFFF + 1);
assert_eq!(res[0], 6);
} else {
assert!(false);
panic!();
}
}
}

View File

@ -288,7 +288,7 @@ fn patch(path: PathBuf) -> PathBuf {
.trim()
.to_owned();
if user != "root" {
return format!("/home/{}", user).into();
return format!("/home/{user}").into();
}
}
}
@ -525,7 +525,7 @@ impl Config {
let mut path: PathBuf = format!("/tmp/{}", *APP_NAME.read().unwrap()).into();
fs::create_dir(&path).ok();
fs::set_permissions(&path, fs::Permissions::from_mode(0o0777)).ok();
path.push(format!("ipc{}", postfix));
path.push(format!("ipc{postfix}"));
path.to_str().unwrap_or("").to_owned()
}
}
@ -562,7 +562,7 @@ impl Config {
.unwrap_or_default();
}
if !rendezvous_server.contains(':') {
rendezvous_server = format!("{}:{}", rendezvous_server, RENDEZVOUS_PORT);
rendezvous_server = format!("{rendezvous_server}:{RENDEZVOUS_PORT}");
}
rendezvous_server
}

View File

@ -211,11 +211,7 @@ pub fn gen_version() {
// generate build date
let build_date = format!("{}", chrono::Local::now().format("%Y-%m-%d %H:%M"));
file.write_all(
format!(
"#[allow(dead_code)]\npub const BUILD_DATE: &str = \"{}\";",
build_date
)
.as_bytes(),
format!("#[allow(dead_code)]\npub const BUILD_DATE: &str = \"{build_date}\";\n").as_bytes(),
)
.ok();
file.sync_all().ok();
@ -342,39 +338,39 @@ mod test {
#[test]
fn test_ipv6() {
assert_eq!(is_ipv6_str("1:2:3"), true);
assert_eq!(is_ipv6_str("[ab:2:3]:12"), true);
assert_eq!(is_ipv6_str("[ABEF:2a:3]:12"), true);
assert_eq!(is_ipv6_str("[ABEG:2a:3]:12"), false);
assert_eq!(is_ipv6_str("1[ab:2:3]:12"), false);
assert_eq!(is_ipv6_str("1.1.1.1"), false);
assert_eq!(is_ip_str("1.1.1.1"), true);
assert_eq!(is_ipv6_str("1:2:"), false);
assert_eq!(is_ipv6_str("1:2::0"), true);
assert_eq!(is_ipv6_str("[1:2::0]:1"), true);
assert_eq!(is_ipv6_str("[1:2::0]:"), false);
assert_eq!(is_ipv6_str("1:2::0]:1"), false);
assert!(is_ipv6_str("1:2:3"));
assert!(is_ipv6_str("[ab:2:3]:12"));
assert!(is_ipv6_str("[ABEF:2a:3]:12"));
assert!(!is_ipv6_str("[ABEG:2a:3]:12"));
assert!(!is_ipv6_str("1[ab:2:3]:12"));
assert!(!is_ipv6_str("1.1.1.1"));
assert!(is_ip_str("1.1.1.1"));
assert!(!is_ipv6_str("1:2:"));
assert!(is_ipv6_str("1:2::0"));
assert!(is_ipv6_str("[1:2::0]:1"));
assert!(!is_ipv6_str("[1:2::0]:"));
assert!(!is_ipv6_str("1:2::0]:1"));
}
#[test]
fn test_hostname_port() {
assert_eq!(is_domain_port_str("a:12"), false);
assert_eq!(is_domain_port_str("a.b.c:12"), false);
assert_eq!(is_domain_port_str("test.com:12"), true);
assert_eq!(is_domain_port_str("test-UPPER.com:12"), true);
assert_eq!(is_domain_port_str("some-other.domain.com:12"), true);
assert_eq!(is_domain_port_str("under_score:12"), false);
assert_eq!(is_domain_port_str("a@bc:12"), false);
assert_eq!(is_domain_port_str("1.1.1.1:12"), false);
assert_eq!(is_domain_port_str("1.2.3:12"), false);
assert_eq!(is_domain_port_str("1.2.3.45:12"), false);
assert_eq!(is_domain_port_str("a.b.c:123456"), false);
assert_eq!(is_domain_port_str("---:12"), false);
assert_eq!(is_domain_port_str(".:12"), false);
assert!(!is_domain_port_str("a:12"));
assert!(!is_domain_port_str("a.b.c:12"));
assert!(is_domain_port_str("test.com:12"));
assert!(is_domain_port_str("test-UPPER.com:12"));
assert!(is_domain_port_str("some-other.domain.com:12"));
assert!(!is_domain_port_str("under_score:12"));
assert!(!is_domain_port_str("a@bc:12"));
assert!(!is_domain_port_str("1.1.1.1:12"));
assert!(!is_domain_port_str("1.2.3:12"));
assert!(!is_domain_port_str("1.2.3.45:12"));
assert!(!is_domain_port_str("a.b.c:123456"));
assert!(!is_domain_port_str("---:12"));
assert!(!is_domain_port_str(".:12"));
// todo: should we also check for these edge cases?
// out-of-range port
assert_eq!(is_domain_port_str("test.com:0"), true);
assert_eq!(is_domain_port_str("test.com:98989"), true);
assert!(is_domain_port_str("test.com:0"));
assert!(is_domain_port_str("test.com:98989"));
}
#[test]

View File

@ -192,51 +192,51 @@ mod test {
let data = "Hello World";
let encrypted = encrypt_str_or_original(data, version);
let (decrypted, succ, store) = decrypt_str_or_original(&encrypted, version);
println!("data: {}", data);
println!("encrypted: {}", encrypted);
println!("decrypted: {}", decrypted);
println!("data: {data}");
println!("encrypted: {encrypted}");
println!("decrypted: {decrypted}");
assert_eq!(data, decrypted);
assert_eq!(version, &encrypted[..2]);
assert_eq!(succ, true);
assert_eq!(store, false);
assert!(succ);
assert!(!store);
let (_, _, store) = decrypt_str_or_original(&encrypted, "99");
assert_eq!(store, true);
assert_eq!(decrypt_str_or_original(&decrypted, version).1, false);
assert!(store);
assert!(!decrypt_str_or_original(&decrypted, version).1);
assert_eq!(encrypt_str_or_original(&encrypted, version), encrypted);
println!("test vec");
let data: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
let encrypted = encrypt_vec_or_original(&data, version);
let (decrypted, succ, store) = decrypt_vec_or_original(&encrypted, version);
println!("data: {:?}", data);
println!("encrypted: {:?}", encrypted);
println!("decrypted: {:?}", decrypted);
println!("data: {data:?}");
println!("encrypted: {encrypted:?}");
println!("decrypted: {decrypted:?}");
assert_eq!(data, decrypted);
assert_eq!(version.as_bytes(), &encrypted[..2]);
assert_eq!(store, false);
assert_eq!(succ, true);
assert!(!store);
assert!(succ);
let (_, _, store) = decrypt_vec_or_original(&encrypted, "99");
assert_eq!(store, true);
assert_eq!(decrypt_vec_or_original(&decrypted, version).1, false);
assert!(store);
assert!(!decrypt_vec_or_original(&decrypted, version).1);
assert_eq!(encrypt_vec_or_original(&encrypted, version), encrypted);
println!("test original");
let data = version.to_string() + "Hello World";
let (decrypted, succ, store) = decrypt_str_or_original(&data, version);
assert_eq!(data, decrypted);
assert_eq!(store, true);
assert_eq!(succ, false);
assert!(store);
assert!(!succ);
let verbytes = version.as_bytes();
let data: Vec<u8> = vec![verbytes[0] as u8, verbytes[1] as u8, 1, 2, 3, 4, 5, 6];
let data: Vec<u8> = vec![verbytes[0], verbytes[1], 1, 2, 3, 4, 5, 6];
let (decrypted, succ, store) = decrypt_vec_or_original(&data, version);
assert_eq!(data, decrypted);
assert_eq!(store, true);
assert_eq!(succ, false);
assert!(store);
assert!(!succ);
let (_, succ, store) = decrypt_str_or_original("", version);
assert_eq!(store, false);
assert_eq!(succ, false);
let (_, succ, store) = decrypt_vec_or_original(&vec![], version);
assert_eq!(store, false);
assert_eq!(succ, false);
assert!(!store);
assert!(!succ);
let (_, succ, store) = decrypt_vec_or_original(&[], version);
assert!(!store);
assert!(!succ);
}
}

View File

@ -60,7 +60,7 @@ fn get_display_server_of_session(session: &str) -> String {
.replace("TTY=", "")
.trim_end()
.into();
if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty))
if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{tty}.\\\\+Xorg\""))
// And check if Xorg is running on that tty
{
if xorg_results.trim_end() != "" {

View File

@ -1 +1 @@
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));

View File

@ -13,22 +13,22 @@ use tokio_socks::{IntoTargetAddr, TargetAddr};
pub fn check_port<T: std::string::ToString>(host: T, port: i32) -> String {
let host = host.to_string();
if crate::is_ipv6_str(&host) {
if host.starts_with("[") {
if host.starts_with('[') {
return host;
}
return format!("[{}]:{}", host, port);
return format!("[{host}]:{port}");
}
if !host.contains(":") {
return format!("{}:{}", host, port);
if !host.contains(':') {
return format!("{host}:{port}");
}
return host;
host
}
#[inline]
pub fn increase_port<T: std::string::ToString>(host: T, offset: i32) -> String {
let host = host.to_string();
if crate::is_ipv6_str(&host) {
if host.starts_with("[") {
if host.starts_with('[') {
let tmp: Vec<&str> = host.split("]:").collect();
if tmp.len() == 2 {
let port: i32 = tmp[1].parse().unwrap_or(0);
@ -37,8 +37,8 @@ pub fn increase_port<T: std::string::ToString>(host: T, offset: i32) -> String {
}
}
}
} else if host.contains(":") {
let tmp: Vec<&str> = host.split(":").collect();
} else if host.contains(':') {
let tmp: Vec<&str> = host.split(':').collect();
if tmp.len() == 2 {
let port: i32 = tmp[1].parse().unwrap_or(0);
if port > 0 {
@ -46,7 +46,7 @@ pub fn increase_port<T: std::string::ToString>(host: T, offset: i32) -> String {
}
}
}
return host;
host
}
pub fn test_if_valid_server(host: &str) -> String {
@ -148,7 +148,7 @@ pub async fn query_nip_io(addr: &SocketAddr) -> ResultType<SocketAddr> {
pub fn ipv4_to_ipv6(addr: String, ipv4: bool) -> String {
if !ipv4 && crate::is_ipv4_str(&addr) {
if let Some(ip) = addr.split(':').next() {
return addr.replace(ip, &format!("{}.nip.io", ip));
return addr.replace(ip, &format!("{ip}.nip.io"));
}
}
addr
@ -163,7 +163,7 @@ async fn test_target(target: &str) -> ResultType<SocketAddr> {
tokio::net::lookup_host(target)
.await?
.next()
.context(format!("Failed to look up host for {}", target))
.context(format!("Failed to look up host for {target}"))
}
#[inline]

View File

@ -100,7 +100,7 @@ impl FramedStream {
}
}
}
bail!(format!("Failed to connect to {}", remote_addr));
bail!(format!("Failed to connect to {remote_addr}"));
}
pub async fn connect<'a, 't, P, T>(

View File

@ -25,7 +25,7 @@ use hbb_common::{allow_err, get_time, message_proto::*, sleep};
use hbb_common::{fs, log, Stream};
use crate::client::{
new_voice_call_request, Client, CodecFormat, LoginConfigHandler, MediaData, MediaSender,
new_voice_call_request, Client, CodecFormat, MediaData, MediaSender,
QualityStatus, MILLI1, SEC30, SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED,
SERVER_KEYBOARD_ENABLED,
};

View File

@ -30,7 +30,7 @@ use hbb_common::{
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
use crate::ui_interface::{set_option, get_option};
use crate::ui_interface::{get_option, set_option};
pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future<Output = ()>;
@ -762,8 +762,3 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> Strin
fd_json.insert("entries".into(), json!(entries_out));
serde_json::to_string(&fd_json).unwrap_or("".into())
}
#[cfg(test)]
mod test_common {
}

View File

@ -1,6 +1,4 @@
use std::future::Future;
use hbb_common::{log, ResultType};
use hbb_common::log;
/// shared by flutter and sciter main function
///

View File

@ -1,6 +1,9 @@
use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char, thread};
use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char};
use std::str::FromStr;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
use std::thread;
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
use serde_json::json;
@ -928,7 +931,7 @@ pub fn main_start_dbus_server() {
{
use crate::dbus::start_dbus_server;
// spawn new thread to start dbus server
std::thread::spawn(|| {
thread::spawn(|| {
let _ = start_dbus_server();
});
}
@ -1275,7 +1278,7 @@ pub fn main_is_login_wayland() -> SyncReturn<bool> {
pub fn main_start_pa() {
#[cfg(target_os = "linux")]
std::thread::spawn(crate::ipc::start_pa);
thread::spawn(crate::ipc::start_pa);
}
pub fn main_hide_docker() -> SyncReturn<bool> {
@ -1302,9 +1305,9 @@ pub fn main_start_ipc_url_server() {
///
/// * macOS only
#[allow(unused_variables)]
pub fn send_url_scheme(url: String) {
pub fn send_url_scheme(_url: String) {
#[cfg(target_os = "macos")]
thread::spawn(move || crate::ui::macos::handle_url_scheme(url));
thread::spawn(move || crate::ui::macos::handle_url_scheme(_url));
}
#[cfg(target_os = "android")]
@ -1324,7 +1327,7 @@ pub mod server_side {
_class: JClass,
) {
log::debug!("startServer from java");
std::thread::spawn(move || start_server(true));
thread::spawn(move || start_server(true));
}
#[no_mangle]

View File

@ -16,10 +16,10 @@ use hbb_common::{
config::{self, Config, Config2},
futures::StreamExt as _,
futures_util::sink::SinkExt,
log, password_security as password, ResultType, timeout,
tokio,
log, password_security as password, timeout, tokio,
tokio::io::{AsyncRead, AsyncWrite},
tokio_util::codec::Framed,
ResultType,
};
use crate::rendezvous_mediator::RendezvousMediator;
@ -190,7 +190,7 @@ pub enum Data {
Socks(Option<config::Socks5Server>),
FS(FS),
Test,
SyncConfig(Option<(Config, Config2)>),
SyncConfig(Option<Box<(Config, Config2)>>),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
ClipboardFile(ClipboardFile),
ClipboardFileEnabled(bool),
@ -419,7 +419,8 @@ async fn handle(data: Data, stream: &mut Connection) {
let t = Config::get_nat_type();
allow_err!(stream.send(&Data::NatType(Some(t))).await);
}
Data::SyncConfig(Some((config, config2))) => {
Data::SyncConfig(Some(configs)) => {
let (config, config2) = *configs;
let _chk = CheckIfRestart::new();
Config::set(config);
Config2::set(config2);
@ -428,7 +429,9 @@ async fn handle(data: Data, stream: &mut Connection) {
Data::SyncConfig(None) => {
allow_err!(
stream
.send(&Data::SyncConfig(Some((Config::get(), Config2::get()))))
.send(&Data::SyncConfig(Some(
(Config::get(), Config2::get()).into()
)))
.await
);
}
@ -840,6 +843,19 @@ pub async fn test_rendezvous_server() -> ResultType<()> {
#[tokio::main(flavor = "current_thread")]
pub async fn send_url_scheme(url: String) -> ResultType<()> {
connect(1_000, "_url").await?.send(&Data::UrlLink(url)).await?;
connect(1_000, "_url")
.await?
.send(&Data::UrlLink(url))
.await?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn verify_ffi_enum_data_size() {
println!("{}", std::mem::size_of::<Data>());
assert!(std::mem::size_of::<Data>() < 96);
}
}

View File

@ -193,8 +193,8 @@ pub mod client {
#[cfg(windows)]
pub fn update_grab_get_key_name() {
match get_keyboard_mode_enum() {
KeyboardMode::Map => rdev::set_get_key_name(false),
KeyboardMode::Translate => rdev::set_get_key_name(true),
KeyboardMode::Map => rdev::set_get_key_unicode(false),
KeyboardMode::Translate => rdev::set_get_key_unicode(true),
_ => {}
};
}
@ -259,7 +259,7 @@ pub fn start_grab_loop() {
let mut _keyboard_mode = KeyboardMode::Map;
let scan_code = event.scan_code;
let _scan_code = event.scan_code;
let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) {
_keyboard_mode = client::process_event(&event, None);
if is_press {
@ -272,7 +272,7 @@ pub fn start_grab_loop() {
};
#[cfg(target_os = "windows")]
match scan_code {
match _scan_code {
0x1D | 0x021D => rdev::set_modifier(Key::ControlLeft, is_press),
0xE01D => rdev::set_modifier(Key::ControlRight, is_press),
0x2A => rdev::set_modifier(Key::ShiftLeft, is_press),
@ -288,7 +288,7 @@ pub fn start_grab_loop() {
#[cfg(target_os = "windows")]
unsafe {
// AltGr
if scan_code == 0x021D {
if _scan_code == 0x021D {
IS_0X021D_DOWN = is_press;
}
}
@ -303,6 +303,8 @@ pub fn start_grab_loop() {
if let Err(error) = rdev::grab(func) {
log::error!("rdev Error: {:?}", error)
}
#[cfg(target_os = "windows")]
rdev::set_event_popup(false);
});
#[cfg(target_os = "linux")]
@ -804,12 +806,10 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEv
fn try_fill_unicode(event: &Event, key_event: &KeyEvent, events: &mut Vec<KeyEvent>) {
match &event.unicode {
Some(unicode_info) => {
if !unicode_info.is_dead {
for code in &unicode_info.unicode {
let mut evt = key_event.clone();
evt.set_unicode(*code as _);
events.push(evt);
}
for code in &unicode_info.unicode {
let mut evt = key_event.clone();
evt.set_unicode(*code as _);
events.push(evt);
}
}
None => {}
@ -863,6 +863,12 @@ pub fn translate_virtual_keycode(event: &Event, mut key_event: KeyEvent) -> Opti
pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
let mut events: Vec<KeyEvent> = Vec::new();
if let Some(unicode_info) = &event.unicode {
if unicode_info.is_dead {
return events;
}
}
#[cfg(target_os = "windows")]
unsafe {
if event.scan_code == 0x021D {
@ -881,6 +887,13 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec<KeyEve
try_fill_unicode(event, &key_event, &mut events);
}
#[cfg(target_os = "windows")]
unsafe {
if IS_0X021D_DOWN {
return events;
}
}
#[cfg(not(target_os = "windows"))]
try_fill_unicode(event, &key_event, &mut events);
@ -888,7 +901,6 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec<KeyEve
if let Some(evt) = translate_virtual_keycode(event, key_event) {
events.push(evt);
}
return events;
}
events
}

View File

@ -392,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("or", "oder"),
("Continue with", "Fortfahren mit"),
("Elevate", "Erheben"),
("Zoom cursor", "Cursor zoomen"),
("Zoom cursor", "Cursor vergrößern"),
("Accept sessions via password", "Sitzung mit Passwort bestätigen"),
("Accept sessions via click", "Sitzung mit einem Klick bestätigen"),
("Accept sessions via both", "Sitzung mit Klick und Passwort bestätigen"),
@ -414,8 +414,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Select local keyboard type", "Lokalen Tastaturtyp auswählen"),
("software_render_tip", "Wenn Sie eine Nvidia-Grafikkarte haben und sich das entfernte Fenster sofort nach dem Herstellen der Verbindung schließt, kann es helfen, den Nouveau-Treiber zu installieren und Software-Rendering zu verwenden. Ein Neustart der Software ist erforderlich."),
("Always use software rendering", "Software-Rendering immer verwenden"),
("config_input", "Um den entfernten Desktop mit der Tastatur steuern zu können, müssen Sie RustDesk \"Input Monitoring\"-Rechte erteilen."),
("config_microphone", ""),
("config_input", "Um den entfernten Desktop mit der Tastatur steuern zu können, müssen Sie RustDesk die Berechtigung \"Input Monitoring\" erteilen."),
("config_microphone", "Um aus der Ferne sprechen zu können, müssen Sie RustDesk die Berechtigung \"Audio aufzeichnen\" erteilen."),
("request_elevation_tip", "Sie können auch erhöhte Rechte anfordern, wenn sich jemand auf der Gegenseite befindet."),
("Wait", "Warten"),
("Elevation Error", "Berechtigungsfehler"),
@ -445,9 +445,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Bitrate", "Bitrate"),
("FPS", "fps"),
("Auto", "Automatisch"),
("Other Default Options", "Weitere Standardoptionen"),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
("Other Default Options", "Weitere Standardeinstellungen"),
("Voice call", "Sprachanruf"),
("Text chat", "Text-Chat"),
("Stop voice call", "Sprachanruf beenden"),
].iter().cloned().collect();
}

View File

@ -446,8 +446,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("FPS", "FPS"),
("Auto", "Auto"),
("Other Default Options", "Altre Opzioni Predefinite"),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
("Voice call", "Chiamata vocale"),
("Text chat", "Chat testuale"),
("Stop voice call", "Interrompi la chiamata vocale"),
].iter().cloned().collect();
}

View File

@ -8,6 +8,9 @@ use std::{
use bytes::Bytes;
pub use connection::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::config::Config2;
use hbb_common::tcp::new_listener;
use hbb_common::{
allow_err,
anyhow::{anyhow, Context},
@ -17,18 +20,15 @@ use hbb_common::{
message_proto::*,
protobuf::{Enum, Message as _},
rendezvous_proto::*,
ResultType,
socket_client,
sodiumoxide::crypto::{box_, secretbox, sign}, Stream, timeout, tokio,
sodiumoxide::crypto::{box_, secretbox, sign},
timeout, tokio, ResultType, Stream,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::config::Config2;
use hbb_common::tcp::new_listener;
use service::{GenericService, Service, Subscriber};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
use service::{GenericService, Service, Subscriber};
use crate::ipc::{connect, Data};
use crate::ipc::Data;
pub mod audio_service;
cfg_if::cfg_if! {
@ -65,7 +65,7 @@ type ConnMap = HashMap<i32, ConnInner>;
lazy_static::lazy_static! {
pub static ref CHILD_PROCESS: Childs = Default::default();
pub static ref CONN_COUNT: Arc<Mutex<usize>> = Default::default();
// A client server used to provide local services(audio, video, clipboard, etc.)
// A client server used to provide local services(audio, video, clipboard, etc.)
// for all initiative connections.
//
// [Note]
@ -420,7 +420,8 @@ pub async fn start_server(is_server: bool) {
if conn.send(&Data::SyncConfig(None)).await.is_ok() {
if let Ok(Some(data)) = conn.next_timeout(1000).await {
match data {
Data::SyncConfig(Some((config, config2))) => {
Data::SyncConfig(Some(configs)) => {
let (config, config2) = *configs;
if Config::set(config) {
log::info!("config synced");
}
@ -450,28 +451,26 @@ pub async fn start_ipc_url_server() {
while let Some(Ok(conn)) = incoming.next().await {
let mut conn = crate::ipc::Connection::new(conn);
match conn.next_timeout(1000).await {
Ok(Some(data)) => {
match data {
Data::UrlLink(url) => {
#[cfg(feature = "flutter")]
{
if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(
crate::flutter::APP_TYPE_MAIN
) {
let mut m = HashMap::new();
m.insert("name", "on_url_scheme_received");
m.insert("url", url.as_str());
stream.add(serde_json::to_string(&m).unwrap());
} else {
log::warn!("No main window app found!");
}
}
}
_ => {
log::warn!("An unexpected data was sent to the ipc url server.")
Ok(Some(data)) => match data {
#[cfg(feature = "flutter")]
Data::UrlLink(url) => {
if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM
.read()
.unwrap()
.get(crate::flutter::APP_TYPE_MAIN)
{
let mut m = HashMap::new();
m.insert("name", "on_url_scheme_received");
m.insert("url", url.as_str());
stream.add(serde_json::to_string(&m).unwrap());
} else {
log::warn!("No main window app found!");
}
}
}
_ => {
log::warn!("An unexpected data was sent to the ipc url server.")
}
},
Err(err) => {
log::error!("{}", err);
}
@ -509,7 +508,8 @@ async fn sync_and_watch_config_dir() {
if conn.send(&Data::SyncConfig(None)).await.is_ok() {
if let Ok(Some(data)) = conn.next_timeout(1000).await {
match data {
Data::SyncConfig(Some((config, config2))) => {
Data::SyncConfig(Some(configs)) => {
let (config, config2) = *configs;
let _chk = crate::ipc::CheckIfRestart::new();
if cfg0.0 != config {
cfg0.0 = config.clone();
@ -534,7 +534,7 @@ async fn sync_and_watch_config_dir() {
let cfg = (Config::get(), Config2::get());
if cfg != cfg0 {
log::info!("config updated, sync to root");
match conn.send(&Data::SyncConfig(Some(cfg.clone()))).await {
match conn.send(&Data::SyncConfig(Some(cfg.clone().into()))).await {
Err(e) => {
log::error!("sync config to root failed: {}", e);
break;

View File

@ -14,12 +14,9 @@ use objc::{
sel, sel_impl,
};
use objc::runtime::Class;
use objc_id::WeakId;
use sciter::{Host, make_args};
use hbb_common::{log, tokio};
use crate::ui_cm_interface::start_ipc;
use hbb_common::log;
static APP_HANDLER_IVAR: &str = "GoDeskAppHandler";
@ -141,7 +138,7 @@ extern "C" fn application_should_handle_open_untitled_file(
if !LAUNCHED {
return YES;
}
hbb_common::log::debug!("icon clicked on finder");
log::debug!("icon clicked on finder");
if std::env::args().nth(1) == Some("--server".to_owned()) {
crate::platform::macos::check_main_window();
}
@ -267,4 +264,4 @@ pub fn make_tray() {
set_delegate(None);
}
crate::tray::make_tray();
}
}

View File

@ -845,6 +845,7 @@ pub fn elevate_portable(_id: i32) {
}
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn handle_incoming_voice_call(id: i32, accept: bool) {
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
@ -852,9 +853,10 @@ pub fn handle_incoming_voice_call(id: i32, accept: bool) {
};
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn close_voice_call(id: i32) {
if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) {
allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned())));
};
}
}

View File

@ -369,8 +369,8 @@ impl<T: InvokeUiSession> Session<T> {
#[cfg(target_os = "windows")]
{
match &self.lc.read().unwrap().keyboard_mode as _ {
"legacy" => rdev::set_get_key_name(true),
"translate" => rdev::set_get_key_name(true),
"legacy" => rdev::set_get_key_unicode(true),
"translate" => rdev::set_get_key_unicode(true),
_ => {}
}
}
@ -382,7 +382,7 @@ impl<T: InvokeUiSession> Session<T> {
pub fn leave(&self) {
#[cfg(target_os = "windows")]
{
rdev::set_get_key_name(false);
rdev::set_get_key_unicode(false);
}
IS_IN.store(false, Ordering::SeqCst);
keyboard::client::change_grab_status(GrabState::Wait);