Compare commits

..

1 Commits

83 changed files with 167 additions and 1212 deletions

View File

@@ -512,7 +512,7 @@ def main():
system2('pip3 install -r requirements.txt')
system2(
f'python3 ./generate.py -f ../../{res_dir} -o . -e ../../{res_dir}/rustdesk-{version}-win7-install.exe')
system2(f'mv ../../{res_dir}/rustdesk-{version}-win7-install.exe ../..')
system2('mv ../../{res_dir}/rustdesk-{version}-win7-install.exe ../..')
elif os.path.isfile('/usr/bin/pacman'):
# pacman -S -needed base-devel
system2("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version)

View File

@@ -62,13 +62,7 @@ class AudioRecordHandle(private var context: Context, private var isVideoStart:
return false
}
}
val recorder = try {
builder.build()
} catch (e: Exception) {
Log.e(logTag, "createAudioRecorder failed", e)
return false
}
audioRecorder = recorder
audioRecorder = builder.build()
Log.d(logTag, "createAudioRecorder done,minBufferSize:$minBufferSize")
return true
}

View File

@@ -532,9 +532,7 @@ class _RawTouchGestureDetectorRegionState
// Official
TapGestureRecognizer:
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(
supportedDevices: kTouchBasedDeviceKinds,
), (instance) {
() => TapGestureRecognizer(), (instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
@@ -542,18 +540,14 @@ class _RawTouchGestureDetectorRegionState
}),
DoubleTapGestureRecognizer:
GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
() => DoubleTapGestureRecognizer(
supportedDevices: kTouchBasedDeviceKinds,
), (instance) {
() => DoubleTapGestureRecognizer(), (instance) {
instance
..onDoubleTapDown = onDoubleTapDown
..onDoubleTap = onDoubleTap;
}),
LongPressGestureRecognizer:
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(
supportedDevices: kTouchBasedDeviceKinds,
), (instance) {
() => LongPressGestureRecognizer(), (instance) {
instance
..onLongPressDown = onLongPressDown
..onLongPressUp = onLongPressUp
@@ -563,9 +557,7 @@ class _RawTouchGestureDetectorRegionState
// Customized
HoldTapMoveGestureRecognizer:
GestureRecognizerFactoryWithHandlers<HoldTapMoveGestureRecognizer>(
() => HoldTapMoveGestureRecognizer(
supportedDevices: kTouchBasedDeviceKinds,
),
() => HoldTapMoveGestureRecognizer(),
(instance) => instance
..onHoldDragStart = onHoldDragStart
..onHoldDragUpdate = onHoldDragUpdate
@@ -573,18 +565,14 @@ class _RawTouchGestureDetectorRegionState
..onHoldDragEnd = onHoldDragEnd),
DoubleFinerTapGestureRecognizer:
GestureRecognizerFactoryWithHandlers<DoubleFinerTapGestureRecognizer>(
() => DoubleFinerTapGestureRecognizer(
supportedDevices: kTouchBasedDeviceKinds,
), (instance) {
() => DoubleFinerTapGestureRecognizer(), (instance) {
instance
..onDoubleFinerTap = onDoubleFinerTap
..onDoubleFinerTapDown = onDoubleFinerTapDown;
}),
CustomTouchGestureRecognizer:
GestureRecognizerFactoryWithHandlers<CustomTouchGestureRecognizer>(
() => CustomTouchGestureRecognizer(
supportedDevices: kTouchBasedDeviceKinds,
), (instance) {
() => CustomTouchGestureRecognizer(), (instance) {
instance.onOneFingerPanStart =
(DragStartDetails d) => onOneFingerPanStart(context, d);
instance

View File

@@ -759,18 +759,9 @@ List<TToggleMenu> toolbarPrivacyMode(
final ffiModel = ffi.ffiModel;
final pi = ffiModel.pi;
final sessionId = ffi.sessionId;
final hasPrivacyModePermission = ffiModel.permissions['privacy_mode'] != false;
// Backend revocation already attempts to turn privacy mode off.
// Still keep this menu when privacy mode is active, so users can turn it off
// if there is a sync delay, version mismatch, or off attempt failure.
if (!hasPrivacyModePermission && privacyModeState.isEmpty) {
return []; // No permission and not active, hide options.
}
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc) {
final enabled =
!ffiModel.viewOnly && (hasPrivacyModePermission || privacyModeState.isNotEmpty);
final enabled = !ffi.ffiModel.viewOnly;
return TToggleMenu(
value: privacyModeState.isNotEmpty,
onChanged: enabled
@@ -819,29 +810,18 @@ List<TToggleMenu> toolbarPrivacyMode(
})
];
} else {
final visibleImpls = hasPrivacyModePermission
? privacyModeImpls
: privacyModeImpls.where((e) {
final implKey = (e as List<dynamic>)[0] as String;
return privacyModeState.value == implKey;
}).toList();
return visibleImpls.map((e) {
return privacyModeImpls.map((e) {
final implKey = (e as List<dynamic>)[0] as String;
final implName = (e)[1] as String;
final enabled = !ffiModel.viewOnly &&
(hasPrivacyModePermission || privacyModeState.value == implKey);
return TToggleMenu(
child: Text(translate(implName)),
value: privacyModeState.value == implKey,
onChanged: enabled
? (value) {
if (value == null) return;
if (value && !hasPrivacyModePermission) return;
togglePrivacyModeTime = DateTime.now();
bind.sessionTogglePrivacyMode(
sessionId: sessionId, implKey: implKey, on: value);
}
: null);
onChanged: (value) {
if (value == null) return;
togglePrivacyModeTime = DateTime.now();
bind.sessionTogglePrivacyMode(
sessionId: sessionId, implKey: implKey, on: value);
});
}).toList();
}
}

View File

@@ -114,9 +114,6 @@ const String kOptionTerminalPersistent = "terminal-persistent";
const String kOptionEnableTunnel = "enable-tunnel";
const String kOptionEnableRemoteRestart = "enable-remote-restart";
const String kOptionEnableBlockInput = "enable-block-input";
const String kOptionEnablePrivacyMode = "enable-privacy-mode";
const String kOptionEnablePermChangeInAcceptWindow =
"enable-perm-change-in-accept-window";
const String kOptionAllowRemoteConfigModification =
"allow-remote-config-modification";
const String kOptionVerificationMethod = "verification-method";

View File

@@ -1062,10 +1062,6 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
_OptionCheckBox(context, 'Enable blocking user input',
kOptionEnableBlockInput,
enabled: enabled, fakeValue: fakeValue),
if (bind.mainSupportedPrivacyModeImpls() != '[]')
_OptionCheckBox(
context, 'Enable privacy mode', kOptionEnablePrivacyMode,
enabled: enabled, fakeValue: fakeValue),
_OptionCheckBox(context, 'Enable remote configuration modification',
kOptionAllowRemoteConfigModification,
enabled: enabled, fakeValue: fakeValue),

View File

@@ -610,24 +610,19 @@ class _PrivilegeBoard extends StatefulWidget {
class _PrivilegeBoardState extends State<_PrivilegeBoard> {
late final client = widget.client;
Widget buildPermissionIcon(bool enabled, IconData iconData,
Function(bool)? onTap, String tooltipText,
{required bool canModify}) {
Function(bool)? onTap, String tooltipText) {
return Tooltip(
message: "$tooltipText: ${enabled ? "ON" : "OFF"}",
waitDuration: Duration.zero,
child: Container(
decoration: BoxDecoration(
color: enabled
? (canModify ? MyTheme.accent : MyTheme.accent.withOpacity(0.6))
: Colors.grey[700],
color: enabled ? MyTheme.accent : Colors.grey[700],
borderRadius: BorderRadius.circular(10.0),
),
padding: EdgeInsets.all(8.0),
child: InkWell(
onTap: canModify
? () =>
checkClickTime(widget.client.id, () => onTap?.call(!enabled))
: null,
onTap: () =>
checkClickTime(widget.client.id, () => onTap?.call(!enabled)),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@@ -648,9 +643,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
Widget build(BuildContext context) {
final crossAxisCount = 4;
final spacing = 10.0;
final canModifyPermission =
bind.mainGetBuildinOption(key: kOptionEnablePermChangeInAcceptWindow) !=
'N';
return Container(
width: double.infinity,
height: 160.0,
@@ -697,7 +689,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable audio'),
canModify: canModifyPermission,
),
buildPermissionIcon(
client.recording,
@@ -712,7 +703,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable recording session'),
canModify: canModifyPermission,
),
]
: [
@@ -729,7 +719,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable keyboard/mouse'),
canModify: canModifyPermission,
),
buildPermissionIcon(
client.clipboard,
@@ -744,7 +733,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable clipboard'),
canModify: canModifyPermission,
),
buildPermissionIcon(
client.audio,
@@ -759,7 +747,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable audio'),
canModify: canModifyPermission,
),
buildPermissionIcon(
client.file,
@@ -774,7 +761,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable file copy and paste'),
canModify: canModifyPermission,
),
buildPermissionIcon(
client.restart,
@@ -789,7 +775,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable remote restart'),
canModify: canModifyPermission,
),
buildPermissionIcon(
client.recording,
@@ -804,7 +789,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable recording session'),
canModify: canModifyPermission,
),
// only windows support block input
if (isWindows)
@@ -821,23 +805,6 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
});
},
translate('Enable blocking user input'),
canModify: canModifyPermission,
),
if (bind.mainSupportedPrivacyModeImpls() != '[]')
buildPermissionIcon(
client.privacyMode,
Icons.visibility_off,
(enabled) {
bind.cmSwitchPermission(
connId: client.id,
name: "privacy_mode",
enabled: enabled);
setState(() {
client.privacyMode = enabled;
});
},
translate('Enable privacy mode'),
canModify: canModifyPermission,
)
],
),

View File

@@ -996,10 +996,10 @@ class _DisplayMenuState extends State<_DisplayMenu> {
toggles(),
];
// privacy mode
final privacyModeState = PrivacyModeState.find(id);
if (ffi.connType == ConnType.defaultConn &&
(pi.features.privacyMode || privacyModeState.isNotEmpty) &&
(ffiModel.keyboard || privacyModeState.isNotEmpty)) {
ffiModel.keyboard &&
pi.features.privacyMode) {
final privacyModeState = PrivacyModeState.find(id);
final privacyModeList =
toolbarPrivacyMode(privacyModeState, context, id, ffi);
if (privacyModeList.length == 1) {

View File

@@ -426,10 +426,12 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
}
return Container(
color: MyTheme.canvasColor,
child: RawTouchGestureDetectorRegion(
child: getBodyForMobile(),
ffi: gFFI,
),
child: inputModel.isPhysicalMouse.value
? getBodyForMobile()
: RawTouchGestureDetectorRegion(
child: getBodyForMobile(),
ffi: gFFI,
),
);
}),
),
@@ -1183,8 +1185,7 @@ void showOptions(
List<TToggleMenu> privacyModeList = [];
// privacy mode
final privacyModeState = PrivacyModeState.find(id);
if ((gFFI.ffiModel.pi.features.privacyMode && gFFI.ffiModel.keyboard) ||
privacyModeState.isNotEmpty) {
if (gFFI.ffiModel.keyboard && gFFI.ffiModel.pi.features.privacyMode) {
privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI);
if (privacyModeList.length == 1) {
displayToggles.add(privacyModeList[0]);

View File

@@ -583,16 +583,9 @@ class _PermissionCheckerState extends State<PermissionChecker> {
Widget build(BuildContext context) {
final serverModel = Provider.of<ServerModel>(context);
final hasAudioPermission = androidVersion >= 30;
final hideStopService = isAndroid &&
bind.mainGetBuildinOption(key: kOptionHideStopService) == 'Y';
final allowPermChangeInAcceptWindow = option2bool(
kOptionEnablePermChangeInAcceptWindow,
bind.mainGetBuildinOption(
key: kOptionEnablePermChangeInAcceptWindow,
));
final permissionChangeLocked = isAndroid &&
serverModel.clients.any((c) => !c.disconnected) &&
!allowPermChangeInAcceptWindow;
final hideStopService =
isAndroid &&
bind.mainGetBuildinOption(key: kOptionHideStopService) == 'Y';
return PaddingCard(
title: translate("Permissions"),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
@@ -615,21 +608,13 @@ class _PermissionCheckerState extends State<PermissionChecker> {
bind.mainGetLocalOption(key: "show-scam-warning") != "N"
? () => showScamWarning(context, serverModel)
: serverModel.toggleService),
PermissionRow(
translate("Input Control"),
serverModel.inputOk,
serverModel.toggleInput,
),
PermissionRow(
translate("Transfer file"),
serverModel.fileOk,
serverModel.toggleFile,
enabled: !permissionChangeLocked,
),
PermissionRow(translate("Input Control"), serverModel.inputOk,
serverModel.toggleInput),
PermissionRow(translate("Transfer file"), serverModel.fileOk,
serverModel.toggleFile),
hasAudioPermission
? PermissionRow(translate("Audio Capture"), serverModel.audioOk,
serverModel.toggleAudio,
enabled: !permissionChangeLocked)
serverModel.toggleAudio)
: Row(children: [
Icon(Icons.info_outline).marginOnly(right: 15),
Expanded(
@@ -638,25 +623,19 @@ class _PermissionCheckerState extends State<PermissionChecker> {
style: const TextStyle(color: MyTheme.darkGray),
))
]),
PermissionRow(
translate("Enable clipboard"),
serverModel.clipboardOk,
serverModel.toggleClipboard,
enabled: !permissionChangeLocked,
),
PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk,
serverModel.toggleClipboard),
]));
}
}
class PermissionRow extends StatelessWidget {
const PermissionRow(this.name, this.isOk, this.onPressed,
{Key? key, this.enabled = true})
const PermissionRow(this.name, this.isOk, this.onPressed, {Key? key})
: super(key: key);
final String name;
final bool isOk;
final VoidCallback onPressed;
final bool enabled;
@override
Widget build(BuildContext context) {
@@ -665,11 +644,9 @@ class PermissionRow extends StatelessWidget {
contentPadding: EdgeInsets.all(0),
title: Text(name),
value: isOk,
onChanged: enabled
? (bool value) {
onPressed();
}
: null);
onChanged: (bool value) {
onPressed();
});
}
}

View File

@@ -259,11 +259,13 @@ class _ViewCameraPageState extends State<ViewCameraPage>
}
return Container(
color: MyTheme.canvasColor,
child: RawTouchGestureDetectorRegion(
child: getBodyForMobile(),
ffi: gFFI,
isCamera: true,
),
child: inputModel.isPhysicalMouse.value
? getBodyForMobile()
: RawTouchGestureDetectorRegion(
child: getBodyForMobile(),
ffi: gFFI,
isCamera: true,
),
);
}),
),

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'dart:ui' as ui;
import 'package:desktop_multi_window/desktop_multi_window.dart';
@@ -16,7 +15,6 @@ import 'package:get/get.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../../models/state_model.dart';
import 'input_modifier_utils.dart';
import 'relative_mouse_model.dart';
import '../common.dart';
import '../consts.dart';
@@ -699,38 +697,6 @@ class InputModel {
}
}
// Safe: this only re-dispatches synthesized Shift key-up events.
// The key-up path clears the tracked Shift state so this does not loop.
void _releaseTrackedShiftKeyEventIfNeeded() {
final leftShift = toReleaseKeys.lastLShiftKeyEvent;
final rightShift = toReleaseKeys.lastRShiftKeyEvent;
if (leftShift != null) {
handleKeyEvent(leftShift);
}
if (rightShift != null) {
handleKeyEvent(rightShift);
}
}
// Safe: this only re-dispatches synthesized Shift key-up events.
// The raw key-up path clears the tracked Shift state so this does not loop.
void _releaseTrackedRawShiftKeyEventIfNeeded() {
final leftShift = toReleaseRawKeys.lastLShiftKeyEvent;
final rightShift = toReleaseRawKeys.lastRShiftKeyEvent;
if (leftShift != null) {
handleRawKeyEvent(RawKeyUpEvent(
data: leftShift.data,
character: leftShift.character,
));
}
if (rightShift != null) {
handleRawKeyEvent(RawKeyUpEvent(
data: rightShift.data,
character: rightShift.character,
));
}
}
KeyEventResult handleRawKeyEvent(RawKeyEvent e) {
if (isViewOnly) return KeyEventResult.handled;
if (isViewCamera) return KeyEventResult.handled;
@@ -785,27 +751,6 @@ class InputModel {
toReleaseRawKeys.updateKeyUp(key, e);
}
// On some mobile soft-keyboard paths, Flutter may leave cached Shift state
// set even though the current raw key event is not shifted anymore.
if (e is RawKeyDownEvent &&
shouldReleaseStaleMobileShift(
isMobile: isMobile,
cachedShiftPressed: shift,
actualShiftPressed: e.isShiftPressed,
logicalKey: e.logicalKey,
hasTrackedShiftKeyDown: toReleaseRawKeys.lastLShiftKeyEvent != null ||
toReleaseRawKeys.lastRShiftKeyEvent != null,
)) {
if (kDebugMode) {
debugPrint(
'input: releasing stale mobile Shift before replaying tracked raw '
'key-up (logicalKey=${e.logicalKey.keyLabel}, '
'actualShiftPressed=${e.isShiftPressed}, cachedShiftPressed=$shift)',
);
}
_releaseTrackedRawShiftKeyEventIfNeeded();
}
// * Currently mobile does not enable map mode
if ((isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) {
mapKeyboardModeRaw(e, iosCapsLock);
@@ -849,8 +794,6 @@ class InputModel {
iosCapsLock = _getIosCapsFromCharacter(e);
}
// Update cached modifier state before sending the event. The stale mobile
// Shift release check below relies on this cached state.
if (e is KeyUpEvent) {
handleKeyUpEventModifiers(e);
} else if (e is KeyDownEvent) {
@@ -888,21 +831,6 @@ class InputModel {
}
}
}
// On some mobile soft-keyboard paths, Flutter may leave cached Shift state
// set even though the current key event is not shifted anymore.
if (e is KeyDownEvent &&
shouldReleaseStaleMobileShift(
isMobile: isMobile,
cachedShiftPressed: shift,
actualShiftPressed: HardwareKeyboard.instance.isShiftPressed,
logicalKey: e.logicalKey,
hasTrackedShiftKeyDown: toReleaseKeys.lastLShiftKeyEvent != null ||
toReleaseKeys.lastRShiftKeyEvent != null,
)) {
_releaseTrackedShiftKeyEventIfNeeded();
}
final isDesktopAndMapMode =
isDesktop || (isWebDesktop && keyboardMode == kKeyMapMode);
if (isMobileAndMapMode || isDesktopAndMapMode) {
@@ -1495,16 +1423,6 @@ class InputModel {
return false;
}
/// iOS may emit a synthesized touch event after a real mouse click.
/// This helper ignores touch-down events that arrive shortly after a mouse down,
/// even when the position is far (e.g., near the top edge).
bool _shouldIgnoreTouchAfterMouse(int nowMs) {
if (!isIOS) return false;
const int kTouchAfterMouseWindowMs = 700;
final dt = nowMs - _lastMouseDownTimeMs;
return dt >= 0 && dt < kTouchAfterMouseWindowMs;
}
void onPointDownImage(PointerDownEvent e) {
debugPrint("onPointDownImage ${e.kind}");
_stopFling = true;
@@ -1517,9 +1435,6 @@ class InputModel {
// Track mouse down events for duplicate detection on iOS.
final nowMs = DateTime.now().millisecondsSinceEpoch;
if (e.kind == ui.PointerDeviceKind.mouse) {
if (!isPhysicalMouse.value) {
isPhysicalMouse.value = true;
}
_lastMouseDownTimeMs = nowMs;
_lastMouseDownPos = e.position;
}
@@ -1529,10 +1444,6 @@ class InputModel {
}
if (e.kind != ui.PointerDeviceKind.mouse) {
// Ignore duplicate touch events that follow a recent mouse click (iOS Magic Mouse issue).
if (isPhysicalMouse.value && _shouldIgnoreTouchAfterMouse(nowMs)) {
return;
}
if (isPhysicalMouse.value) {
isPhysicalMouse.value = false;
}

View File

@@ -1,38 +0,0 @@
import 'package:flutter/services.dart';
/// Returns true when a stale mobile one-shot Shift state should be released
/// by replaying a tracked Shift key-down as a synthesized key-up.
///
/// This is only valid on mobile when Flutter's cached Shift state is still on
/// (`cachedShiftPressed == true`) but the current hardware/raw event reports
/// Shift as off (`actualShiftPressed == false`).
///
/// A tracked Shift key-down is required so the caller can safely synthesize the
/// matching key-up. Both `shiftLeft` and `shiftRight` are excluded because the
/// Shift key event itself must be processed first; otherwise we could release
/// the tracked key while still handling the original Shift press/release.
/// Callers should evaluate this only after their cached modifier state has been
/// updated for the current event.
///
/// When this returns true, the caller logs a line like:
/// `input: releasing stale mobile Shift before replaying tracked raw key-up`
/// immediately before calling `_releaseTrackedRawShiftKeyEventIfNeeded()`.
bool shouldReleaseStaleMobileShift({
required bool isMobile,
required bool cachedShiftPressed,
required bool actualShiftPressed,
required LogicalKeyboardKey logicalKey,
required bool hasTrackedShiftKeyDown,
}) {
if (!isMobile || !cachedShiftPressed || actualShiftPressed) {
return false;
}
if (!hasTrackedShiftKeyDown) {
return false;
}
if (logicalKey == LogicalKeyboardKey.shiftLeft ||
logicalKey == LogicalKeyboardKey.shiftRight) {
return false;
}
return true;
}

View File

@@ -298,7 +298,7 @@ class ServerModel with ChangeNotifier {
}
toggleAudio() async {
if (clients.any((c) => !c.disconnected)) {
if (clients.isNotEmpty) {
await showClientsMayNotBeChangedAlert(parent.target);
}
if (!_audioOk && !await AndroidPermissionManager.check(kRecordAudio)) {
@@ -316,7 +316,7 @@ class ServerModel with ChangeNotifier {
}
toggleFile() async {
if (clients.any((c) => !c.disconnected)) {
if (clients.isNotEmpty) {
await showClientsMayNotBeChangedAlert(parent.target);
}
if (!_fileOk &&
@@ -345,7 +345,7 @@ class ServerModel with ChangeNotifier {
}
toggleInput() async {
if (clients.any((c) => !c.disconnected)) {
if (clients.isNotEmpty) {
await showClientsMayNotBeChangedAlert(parent.target);
}
if (_inputOk) {
@@ -549,19 +549,10 @@ class ServerModel with ChangeNotifier {
if (index < 0) {
_clients.add(client);
} else {
if (_clients[index].authorized) {
_clients[index].privacyMode = client.privacyMode;
notifyListeners();
return;
}
_clients[index].authorized = true;
_clients[index].privacyMode = client.privacyMode;
}
} else {
final index = _clients.indexWhere((c) => c.id == client.id);
if (index >= 0) {
_clients[index].privacyMode = client.privacyMode;
notifyListeners();
if (_clients.any((c) => c.id == client.id)) {
return;
}
_clients.add(client);
@@ -827,7 +818,6 @@ class Client {
bool restart = false;
bool recording = false;
bool blockInput = false;
bool privacyMode = false;
bool disconnected = false;
bool fromSwitch = false;
bool inVoiceCall = false;
@@ -856,7 +846,6 @@ class Client {
restart = json['restart'];
recording = json['recording'];
blockInput = json['block_input'];
privacyMode = json['privacy_mode'] ?? privacyMode;
disconnected = json['disconnected'];
fromSwitch = json['from_switch'];
inVoiceCall = json['in_voice_call'];
@@ -881,7 +870,6 @@ class Client {
data['restart'] = restart;
data['recording'] = recording;
data['block_input'] = blockInput;
data['privacy_mode'] = privacyMode;
data['disconnected'] = disconnected;
data['from_switch'] = fromSwitch;
data['in_voice_call'] = inVoiceCall;

View File

@@ -1729,7 +1729,7 @@ class RustdeskImpl {
}
String mainSupportedPrivacyModeImpls({dynamic hint}) {
return '[]';
throw UnimplementedError("mainSupportedPrivacyModeImpls");
}
String mainSupportedInputSource({dynamic hint}) {

View File

@@ -113,8 +113,8 @@ dependencies:
dev_dependencies:
icons_launcher: ^2.0.4
flutter_test:
sdk: flutter
#flutter_test:
#sdk: flutter
build_runner: ^2.4.6
freezed: ^2.4.2
flutter_lints: ^2.0.2

View File

@@ -1,125 +0,0 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_hbb/models/input_modifier_utils.dart';
void main() {
group('shouldReleaseStaleMobileShift', () {
test('does not release when cached shift is already false', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: false,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.keyD,
hasTrackedShiftKeyDown: true,
),
isFalse,
);
});
test('releases one-shot mobile shift after a text key', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: true,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.keyD,
hasTrackedShiftKeyDown: true,
),
isTrue,
);
});
test('does not release manually toggled shift without tracked key down',
() {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: true,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.keyD,
hasTrackedShiftKeyDown: false,
),
isFalse,
);
});
test('does not release when shift is still physically pressed', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: true,
actualShiftPressed: true,
logicalKey: LogicalKeyboardKey.keyD,
hasTrackedShiftKeyDown: true,
),
isFalse,
);
});
test('does not release on non-mobile platforms', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: false,
cachedShiftPressed: true,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.keyD,
hasTrackedShiftKeyDown: true,
),
isFalse,
);
});
test('releases on enter key', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: true,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.enter,
hasTrackedShiftKeyDown: true,
),
isTrue,
);
});
test('releases on arrow key', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: true,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.arrowLeft,
hasTrackedShiftKeyDown: true,
),
isTrue,
);
});
test('does not release on modifier events', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: true,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.shiftLeft,
hasTrackedShiftKeyDown: true,
),
isFalse,
);
});
test('does not release on shiftRight modifier events', () {
expect(
shouldReleaseStaleMobileShift(
isMobile: true,
cachedShiftPressed: true,
actualShiftPressed: false,
logicalKey: LogicalKeyboardKey.shiftRight,
hasTrackedShiftKeyDown: true,
),
isFalse,
);
});
});
}

View File

@@ -624,7 +624,6 @@ void CliprdrStream_Delete(CliprdrStream *instance)
if (instance)
{
free(instance->iStream.lpVtbl);
instance->iStream.lpVtbl = NULL;
free(instance);
}
}
@@ -2161,7 +2160,7 @@ static BOOL wf_cliprdr_add_to_file_arrays(wfClipboard *clipboard, WCHAR *full_fi
return FALSE;
/* add to name array */
clipboard->file_names[clipboard->nFiles] = (LPWSTR)malloc((size_t)MAX_PATH * sizeof(WCHAR));
clipboard->file_names[clipboard->nFiles] = (LPWSTR)malloc(MAX_PATH * 2);
if (!clipboard->file_names[clipboard->nFiles])
return FALSE;

View File

@@ -1745,9 +1745,6 @@ pub struct LoginConfigHandler {
pub direct: Option<bool>,
pub received: bool,
switch_uuid: Option<String>,
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
switch_back_allowed: bool,
pub save_ab_password_to_recent: bool, // true: connected with ab password
pub other_server: Option<(String, String, String)>,
pub custom_fps: Arc<Mutex<Option<usize>>>,
@@ -1864,11 +1861,6 @@ impl LoginConfigHandler {
self.direct = None;
self.received = false;
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
self.switch_back_allowed = false;
}
self.switch_uuid = switch_uuid;
self.adapter_luid = adapter_luid;
self.selected_windows_session_id = None;
@@ -1882,23 +1874,6 @@ impl LoginConfigHandler {
self.is_terminal_admin = is_terminal_admin;
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn allow_switch_back_once(&mut self) {
self.switch_back_allowed = true;
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn consume_switch_back_permission(&mut self) -> bool {
if self.switch_back_allowed {
self.switch_back_allowed = false;
true
} else {
false
}
}
/// Check if the client should auto login.
/// Return password if the client should auto login, otherwise return empty string.
pub fn should_auto_login(&self) -> String {
@@ -3402,36 +3377,6 @@ pub fn handle_login_error(
}
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn consume_local_switch_sides_uuid(id: &str, uuid: &Uuid) -> bool {
let Ok(mut conn) = crate::ipc::connect(1000, "").await else {
return false;
};
let uuid = uuid.to_string();
if conn
.send(&crate::ipc::Data::SwitchSidesUuid(
uuid.clone(),
id.to_owned(),
None,
))
.await
.is_err()
{
return false;
}
match conn.next_timeout(1000).await {
Ok(Some(crate::ipc::Data::SwitchSidesUuid(
returned_uuid,
returned_id,
Some(true),
))) => {
returned_uuid == uuid && returned_id == id
}
_ => false,
}
}
/// Handle hash message sent by peer.
/// Hash will be used for login.
///
@@ -3452,22 +3397,12 @@ pub async fn handle_hash(
// Take care of password application order
// switch_uuid
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
let uuid = lc.write().unwrap().switch_uuid.take();
if let Some(uuid) = uuid {
if let Ok(uuid) = uuid::Uuid::from_str(&uuid) {
let id = lc.read().unwrap().id.clone();
if !consume_local_switch_sides_uuid(&id, &uuid).await {
log::warn!("Ignored untrusted switch_uuid");
} else {
lc.write().unwrap().allow_switch_back_once();
send_switch_login_request(lc.clone(), peer, uuid).await;
lc.write().unwrap().password_source = Default::default();
return;
}
}
let uuid = lc.write().unwrap().switch_uuid.take();
if let Some(uuid) = uuid {
if let Ok(uuid) = uuid::Uuid::from_str(&uuid) {
send_switch_login_request(lc.clone(), peer, uuid).await;
lc.write().unwrap().password_source = Default::default();
return;
}
}
// last password

View File

@@ -1448,23 +1448,6 @@ impl<T: InvokeUiSession> Remote<T> {
if !self.handler.lc.read().unwrap().disable_clipboard.v {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
update_clipboard(_mcb.clipboards, ClipboardSide::Client);
#[cfg(target_os = "ios")]
{
if let Some(cb) = _mcb
.clipboards
.iter()
.find(|c| c.format.enum_value() == Ok(ClipboardFormat::Text))
{
let content = if cb.compress {
hbb_common::compress::decompress(&cb.content)
} else {
cb.content.to_vec()
};
if let Ok(content) = String::from_utf8(content) {
self.handler.clipboard(content);
}
}
}
#[cfg(target_os = "android")]
crate::clipboard::handle_msg_multi_clipboards(_mcb);
}
@@ -1797,9 +1780,6 @@ impl<T: InvokeUiSession> Remote<T> {
Ok(Permission::BlockInput) => {
self.handler.set_permission("block_input", p.enabled);
}
Ok(Permission::PrivacyMode) => {
self.handler.set_permission("privacy_mode", p.enabled);
}
_ => {}
}
}
@@ -1923,23 +1903,9 @@ impl<T: InvokeUiSession> Remote<T> {
);
}
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::SwitchBack(_)) => {
let allow_switch_back = self
.handler
.lc
.write()
.unwrap()
.consume_switch_back_permission();
if allow_switch_back {
self.handler.switch_back(&self.handler.get_id());
} else {
log::warn!(
"Ignored unsolicited SwitchBack from {}",
self.handler.get_id()
);
}
#[cfg(feature = "flutter")]
self.handler.switch_back(&self.handler.get_id());
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]

View File

@@ -605,30 +605,21 @@ pub fn session_handle_flutter_raw_key_event(
}
}
// SyncReturn<()> is used to make sure enter() and leave() are executed in the sequence this function is called.
//
// If the cursor jumps between remote page of two connections, leave view and enter view will be called.
// session_enter_or_leave() will be called then.
// As Rust is multi-threaded, enter() can be called before leave().
// The Rust-side grab ownership state filters stale transitions.
// As rust is multi-thread, it is possible that enter() is called before leave().
// This will cause the keyboard input to take no effect.
pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
let keyboard_mode = session.get_keyboard_mode();
// Use the full per-window UUID (not lc.session_id which is per-connection)
// so that two windows viewing the same peer get distinct grab owners.
let window_id = _session_id.as_u128();
if _enter {
set_cur_session_id_(_session_id, &keyboard_mode);
crate::keyboard::client::change_grab_status(
crate::common::GrabState::Run,
&keyboard_mode,
window_id,
);
session.enter(keyboard_mode);
} else {
crate::keyboard::client::change_grab_status(
crate::common::GrabState::Wait,
&keyboard_mode,
window_id,
);
session.leave(keyboard_mode);
}
}
SyncReturn(())
@@ -972,27 +963,6 @@ pub fn main_show_option(_key: String) -> SyncReturn<bool> {
}
pub fn main_set_option(key: String, value: String) {
#[cfg(target_os = "android")]
{
let is_permission_option = key.eq(config::keys::OPTION_ENABLE_CLIPBOARD)
|| key.eq(config::keys::OPTION_ENABLE_FILE_TRANSFER)
|| key.eq(config::keys::OPTION_ENABLE_AUDIO);
let allow_perm_change_in_accept_window = config::option2bool(
config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
);
if is_permission_option
&& !allow_perm_change_in_accept_window
&& crate::ui_cm_interface::has_active_clients()
{
log::info!(
"blocked main_set_option by policy, key={}, value={}",
key,
value
);
return;
}
}
#[cfg(target_os = "android")]
if key.eq(config::keys::OPTION_ENABLE_KEYBOARD) {
crate::ui_cm_interface::switch_permission_all(
@@ -1040,29 +1010,7 @@ pub fn main_get_options_sync() -> SyncReturn<String> {
}
pub fn main_set_options(json: String) {
let mut map: HashMap<String, String> = serde_json::from_str(&json).unwrap_or(HashMap::new());
#[cfg(target_os = "android")]
{
let allow_perm_change_in_accept_window = config::option2bool(
config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(config::keys::OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
);
if !allow_perm_change_in_accept_window && crate::ui_cm_interface::has_active_clients() {
for key in [
config::keys::OPTION_ENABLE_CLIPBOARD,
config::keys::OPTION_ENABLE_FILE_TRANSFER,
config::keys::OPTION_ENABLE_AUDIO,
] {
if let Some(value) = map.remove(key) {
log::info!(
"blocked main_set_options item by policy, key={}, value={}",
key,
value
);
}
}
}
}
let map: HashMap<String, String> = serde_json::from_str(&json).unwrap_or(HashMap::new());
if !map.is_empty() {
set_options(map)
}
@@ -2213,7 +2161,7 @@ pub fn cm_elevate_portable(conn_id: i32) {
}
pub fn cm_switch_back(conn_id: i32) {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(not(any(target_os = "ios")))]
crate::ui_cm_interface::switch_back(conn_id);
}

View File

@@ -237,7 +237,6 @@ pub enum Data {
restart: bool,
recording: bool,
block_input: bool,
privacy_mode: bool,
from_switch: bool,
},
ChatMessage {
@@ -285,14 +284,7 @@ pub enum Data {
Empty,
Disconnected,
DataPortableService(DataPortableService),
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
SwitchSidesRequest(String),
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
SwitchSidesUuid(String, String, Option<bool>),
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
SwitchSidesBack,
UrlLink(String),
VoiceCallIncoming,
@@ -778,8 +770,6 @@ async fn handle(data: Data, stream: &mut Connection) {
Data::TestRendezvousServer => {
crate::test_rendezvous_server();
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::SwitchSidesRequest(id) => {
let uuid = uuid::Uuid::new_v4();
crate::server::insert_switch_sides_uuid(id, uuid.clone());
@@ -789,19 +779,6 @@ async fn handle(data: Data, stream: &mut Connection) {
.await
);
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::SwitchSidesUuid(uuid, id, None) => {
let allowed = uuid
.parse::<uuid::Uuid>()
.map(|uuid| crate::server::remove_pending_switch_sides_uuid(&id, &uuid))
.unwrap_or(false);
allow_err!(
stream
.send(&Data::SwitchSidesUuid(uuid, id, Some(allowed)))
.await
);
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::Plugin(plugin) => crate::plugin::ipc::handle_plugin(plugin, stream).await,

View File

@@ -82,67 +82,8 @@ lazy_static::lazy_static! {
pub mod client {
use super::*;
/// Tracks grab ownership and serializes transitions across threads.
///
/// Multiple Flutter isolates (one per session window) call
/// `change_grab_status(Run/Wait)` concurrently. Without serialization a
/// stale `Wait` from session A can clobber session B's freshly acquired
/// grab on any desktop OS.
///
/// Windows and macOS are less susceptible in practice because the Flutter
/// side triggers `enterView` only after a mouse click inside the window,
/// but we cannot rely on that. On Linux/X11, `XGrabKeyboard` can also
/// cause a focus-change feedback loop (~10 Hz), so `last_grab` debounces
/// spurious `Wait` events that arrive shortly after a `Run`.
#[derive(Default)]
struct GrabOwnerState {
owner: Option<u128>,
last_grab: Option<std::time::Instant>,
/// True while a deferred-release thread is in flight. Prevents
/// spawning redundant threads during the X11 feedback loop.
deferred_pending: bool,
}
/// How long after a grab acquisition we suppress Wait from the same session.
/// Must exceed one full X11 feedback cycle (~100 ms: 50 ms enable + 50 ms disable).
#[cfg(target_os = "linux")]
const GRAB_DEBOUNCE_MS: u128 = 300;
lazy_static::lazy_static! {
static ref IS_GRAB_STARTED: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
static ref GRAB_STATE: Arc<Mutex<GrabOwnerState>> = Arc::new(Mutex::new(GrabOwnerState::default()));
}
#[cfg(target_os = "linux")]
lazy_static::lazy_static! {
static ref GRAB_OP_LOCK: Mutex<()> = Mutex::new(());
}
#[cfg(target_os = "linux")]
fn apply_run_grab_if_owner(session_id: u128, disable_first: bool) {
let _lock = GRAB_OP_LOCK.lock().unwrap();
let gs = GRAB_STATE.lock().unwrap();
if gs.owner != Some(session_id) {
return;
}
drop(gs);
if disable_first {
log::debug!("[grab] handoff: disable_grab before re-grab");
rdev::disable_grab();
}
rdev::enable_grab();
}
#[cfg(target_os = "linux")]
fn disable_grab_if_released() {
let _lock = GRAB_OP_LOCK.lock().unwrap();
let should_disable = {
let gs = GRAB_STATE.lock().unwrap();
gs.owner.is_none() && gs.last_grab.is_none()
};
if should_disable {
rdev::disable_grab();
}
}
pub fn start_grab_loop() {
@@ -155,167 +96,36 @@ pub mod client {
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn change_grab_status(state: GrabState, keyboard_mode: &str, session_id: u128) {
pub fn change_grab_status(state: GrabState, keyboard_mode: &str) {
#[cfg(feature = "flutter")]
if !IS_RDEV_ENABLED.load(Ordering::SeqCst) {
return;
}
// Serialize transitions so a stale `Wait` from a previous owner cannot
// clobber a fresh `Run` from a different session window.
let mut release_after_unlock = None;
#[cfg(target_os = "linux")]
let mut run_grab_after_unlock = None;
#[cfg(target_os = "linux")]
let mut disable_after_unlock = false;
let mut gs = GRAB_STATE.lock().unwrap();
match state {
GrabState::Ready => {}
GrabState::Run => {
#[cfg(windows)]
update_grab_get_key_name(keyboard_mode);
// Idempotent: if this session already owns the grab, just
// refresh the debounce timer (proves the session is still
// actively focused) and skip the actual grab call.
if gs.owner == Some(session_id) {
gs.last_grab = Some(std::time::Instant::now());
// Reset so the next Wait can spawn a fresh deferred-release
// timer with an up-to-date snapshot of last_grab.
gs.deferred_pending = false;
log::debug!(
"[grab] Run(0x{:x}): already owner, refresh debounce",
session_id
);
return;
}
log::debug!(
"[grab] Run(0x{:x}): prev_owner={}, mode={}",
session_id,
gs.owner
.map_or("none".to_string(), |id| format!("0x{:x}", id)),
keyboard_mode,
);
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
KEYBOARD_HOOKED.store(true, Ordering::SeqCst);
KEYBOARD_HOOKED.swap(true, Ordering::SeqCst);
#[cfg(target_os = "linux")]
let had_owner = gs.owner.is_some();
gs.owner = Some(session_id);
gs.last_grab = Some(std::time::Instant::now());
// Invalidate any in-flight deferred release from the previous
// owner so it cannot suppress a fresh timer for the new owner.
gs.deferred_pending = false;
#[cfg(target_os = "linux")]
{
run_grab_after_unlock = Some(had_owner);
}
rdev::enable_grab();
}
GrabState::Wait => {
// Drop stale `Wait` events that do not correspond to the
// current grab owner. This prevents a late PointerExit from
// session A from releasing session B's freshly acquired grab.
if gs.owner != Some(session_id) {
log::debug!(
"[grab] Wait(0x{:x}): ignored, owner={}",
session_id,
gs.owner
.map_or("none".to_string(), |id| format!("0x{:x}", id)),
);
return;
}
// Debounce: on Linux/X11, XGrabKeyboard causes a focus-change
// feedback loop (grab -> PointerExit -> ungrab -> PointerEnter ->
// grab -> ...). Suppress Wait if the grab was acquired recently
// by this same session -- it is X11 feedback, not a real leave.
// A deferred release is scheduled so that a genuine leave within
// the debounce window is not permanently lost.
#[cfg(target_os = "linux")]
if let Some(t) = gs.last_grab {
let elapsed = t.elapsed().as_millis();
if elapsed < GRAB_DEBOUNCE_MS {
if !gs.deferred_pending {
log::debug!(
"[grab] Wait(0x{:x}): debounced ({}ms < {}ms), scheduling deferred release",
session_id, elapsed, GRAB_DEBOUNCE_MS,
);
gs.deferred_pending = true;
let remaining = (GRAB_DEBOUNCE_MS - elapsed) as u64 + 50;
let snapshot = gs.last_grab;
let mode = keyboard_mode.to_string();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(remaining));
let release_keys = {
let mut gs = GRAB_STATE.lock().unwrap();
// Release only if no new Run has refreshed the grab since.
if gs.owner == Some(session_id) && gs.last_grab == snapshot {
let to_release = take_remote_keys();
gs.deferred_pending = false;
log::debug!(
"[grab] Wait(0x{:x}): deferred release",
session_id
);
KEYBOARD_HOOKED.store(false, Ordering::SeqCst);
gs.owner = None;
gs.last_grab = None;
Some(to_release)
} else {
log::debug!(
"[grab] Wait(0x{:x}): deferred release cancelled (grab refreshed)",
session_id,
);
None
}
};
if let Some(to_release) = release_keys {
disable_grab_if_released();
release_remote_keys_for_events(&mode, to_release);
}
});
} else {
log::debug!(
"[grab] Wait(0x{:x}): debounced, deferred release already pending",
session_id,
);
}
return;
}
}
log::debug!("[grab] Wait(0x{:x}): releasing grab", session_id);
#[cfg(windows)]
rdev::set_get_key_unicode(false);
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
KEYBOARD_HOOKED.store(false, Ordering::SeqCst);
release_remote_keys(keyboard_mode);
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
KEYBOARD_HOOKED.swap(false, Ordering::SeqCst);
gs.owner = None;
gs.last_grab = None;
gs.deferred_pending = false;
release_after_unlock = Some(take_remote_keys());
#[cfg(target_os = "linux")]
{
disable_after_unlock = true;
}
rdev::disable_grab();
}
GrabState::Exit => {}
}
drop(gs);
#[cfg(target_os = "linux")]
{
if disable_after_unlock {
disable_grab_if_released();
}
if let Some(disable_first) = run_grab_after_unlock {
apply_run_grab_if_owner(session_id, disable_first);
}
}
if let Some(to_release) = release_after_unlock {
release_remote_keys_for_events(keyboard_mode, to_release);
}
}
pub fn process_event(keyboard_mode: &str, event: &Event, lock_modes: Option<i32>) {
@@ -531,6 +341,7 @@ fn notify_exit_relative_mouse_mode() {
flutter::push_session_event(&session_id, "exit_relative_mouse_mode", vec![]);
}
/// Handle relative mouse mode shortcuts in the rdev grab loop.
/// Returns true if the event should be blocked from being sent to the peer.
#[cfg(feature = "flutter")]
@@ -729,12 +540,10 @@ pub fn is_long_press(event: &Event) -> bool {
return false;
}
fn take_remote_keys() -> HashMap<Key, Event> {
let mut to_release = TO_RELEASE.lock().unwrap();
std::mem::take(&mut *to_release)
}
fn release_remote_keys_for_events(keyboard_mode: &str, to_release: HashMap<Key, Event>) {
pub fn release_remote_keys(keyboard_mode: &str) {
// todo!: client quit suddenly, how to release keys?
let to_release = TO_RELEASE.lock().unwrap().clone();
TO_RELEASE.lock().unwrap().clear();
for (key, mut event) in to_release.into_iter() {
event.event_type = EventType::KeyRelease(key);
client::process_event(keyboard_mode, &event, None);
@@ -749,12 +558,6 @@ fn release_remote_keys_for_events(keyboard_mode: &str, to_release: HashMap<Key,
}
}
#[allow(dead_code)]
pub fn release_remote_keys(keyboard_mode: &str) {
// todo!: client quit suddenly, how to release keys?
release_remote_keys_for_events(keyboard_mode, take_remote_keys());
}
pub fn get_keyboard_mode_enum(keyboard_mode: &str) -> KeyboardMode {
match keyboard_mode {
"map" => KeyboardMode::Map,
@@ -945,6 +748,7 @@ pub fn event_to_key_events(
) -> Vec<KeyEvent> {
peer.retain(|c| !c.is_whitespace());
let mut key_event = KeyEvent::new();
update_modifiers_state(event);
match event.event_type {
@@ -957,7 +761,6 @@ pub fn event_to_key_events(
_ => {}
}
let mut key_event = KeyEvent::new();
key_event.mode = keyboard_mode.into();
let mut key_events = match keyboard_mode {

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "اسم العرض"),
("password-hidden-tip", "كلمة المرور مخفية"),
("preset-password-in-use-tip", "كلمة المرور المحددة مسبقًا قيد الاستخدام"),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Імя для адлюстравання"),
("password-hidden-tip", "Зададзены пастаянны пароль (скрыты)."),
("preset-password-in-use-tip", "Пададзены пароль цяпер выкарыстоўваецца"),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "显示名称"),
("password-hidden-tip", "永久密码已设置(已隐藏)"),
("preset-password-in-use-tip", "当前使用预设密码"),
("Enable privacy mode", "允许隐私模式"),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Anzeigename"),
("password-hidden-tip", "Ein permanentes Passwort wurde festgelegt (ausgeblendet)."),
("preset-password-in-use-tip", "Das voreingestellte Passwort wird derzeit verwendet."),
("Enable privacy mode", "Datenschutzmodus aktivieren"),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Εμφανιζόμενο όνομα"),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Nom daffichage"),
("password-hidden-tip", "Le mot de passe permanent est défini (masqué)."),
("preset-password-in-use-tip", "Le mot de passe prédéfini est actuellement utilisé."),
("Enable privacy mode", "Activer le mode de confidentialité"),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -742,6 +742,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "ડિસ્પ્લે નામ"),
("password-hidden-tip", "સુરક્ષા માટે પાસવર્ડ છુપાવેલ છે."),
("preset-password-in-use-tip", "પ્રીસેટ પાસવર્ડ વપરાશમાં છે."),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Kijelző név"),
("password-hidden-tip", "Állandó jelszó lett beállítva (rejtett)."),
("preset-password-in-use-tip", "Jelenleg az alapértelmezett jelszót használja."),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Visualizza nome"),
("password-hidden-tip", "È impostata una password permanente (nascosta)."),
("preset-password-in-use-tip", "È attualmente in uso la password preimpostata."),
("Enable privacy mode", "Abilita modalità privacy"),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "表示名"),
("password-hidden-tip", "永続的なパスワードが設定されています (非表示)"),
("preset-password-in-use-tip", "プリセットパスワードが現在使用されています"),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "표시 이름"),
("password-hidden-tip", "영구 비밀번호가 설정되었습니다 (숨김)."),
("preset-password-in-use-tip", "현재 사전 설정된 비밀번호가 사용 중입니다."),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Naam Weergeven"),
("password-hidden-tip", "Er is een permanent wachtwoord ingesteld (verborgen)."),
("preset-password-in-use-tip", "Het basis wachtwoord is momenteel in gebruik."),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Nazwa wyświetlana"),
("password-hidden-tip", "Ustawiono (ukryto) stare hasło."),
("preset-password-in-use-tip", "Obecnie używane jest hasło domyślne."),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Nume afișat"),
("password-hidden-tip", "Parola este ascunsă din motive de securitate. Fă clic pe pictograma ochiului pentru a o afișa."),
("preset-password-in-use-tip", "Se folosește o parolă prestabilită. Se recomandă setarea unei parole personalizate pentru securitate sporită."),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Отображаемое имя"),
("password-hidden-tip", "Установлен постоянный пароль (скрытый)."),
("preset-password-in-use-tip", "Установленный пароль сейчас используется."),
("Enable privacy mode", "Использовать режим конфиденциальности"),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -741,8 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Gelen oturumlar süresince ekranıık tutun"),
("Continue with {}", "{} ile devam et"),
("Display Name", "Görünen Ad"),
("password-hidden-tip", "Parola gizli"),
("preset-password-in-use-tip", "Önceden ayarlanmış parola kullanılıyor"),
("Enable privacy mode", "Gizlilik modunu etkinleştir"),
("password-hidden-tip", "Şifre gizli"),
("preset-password-in-use-tip", "Önceden ayarlanmış şifre kullanılıyor"),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "顯示名稱"),
("password-hidden-tip", "固定密碼已設定(已隱藏)"),
("preset-password-in-use-tip", "目前正在使用預設密碼"),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -743,6 +743,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
("Enable privacy mode", ""),
].iter().cloned().collect();
}

View File

@@ -6,7 +6,7 @@ use hbb_common::{
anyhow::anyhow,
bail,
config::{keys::OPTION_ALLOW_LINUX_HEADLESS, Config},
libc::{c_char, c_int, c_long, c_uint, c_ulong, c_void},
libc::{c_char, c_int, c_long, c_uint, c_void},
log,
message_proto::{DisplayInfo, Resolution},
regex::{Captures, Regex},
@@ -97,55 +97,10 @@ thread_local! {
static DISPLAY: RefCell<*mut c_void> = RefCell::new(unsafe { XOpenDisplay(std::ptr::null())});
}
// X11 error event structure for the custom error handler.
// See: https://www.x.org/releases/current/doc/libX11/libX11/libX11.html#Using-the-Default-Error-Handlers
#[repr(C)]
struct XErrorEvent {
type_: c_int,
display: *mut c_void, // Display*
resourceid: c_ulong, // XID
serial: c_ulong,
error_code: u8,
request_code: u8,
minor_code: u8,
}
type XErrorHandler = unsafe extern "C" fn(*mut c_void, *mut XErrorEvent) -> c_int;
const X11_BAD_WINDOW: u8 = 3;
const XDO_SUCCESS: c_int = 0;
const XDO_ERROR: c_int = 1;
/// Atomic flag set by the custom X error handler when a BadWindow error occurs.
static X_BAD_WINDOW_DETECTED: AtomicBool = AtomicBool::new(false);
static X_UNEXPECTED_ERROR_DETECTED: AtomicBool = AtomicBool::new(false);
/// Custom X error handler that catches BadWindow errors (error_code == 3) instead of
/// letting the default handler terminate the process.
/// See issue: https://github.com/rustdesk/rustdesk/issues/9003
unsafe extern "C" fn handle_x_error(_display: *mut c_void, event: *mut XErrorEvent) -> c_int {
if !event.is_null() && (*event).error_code == X11_BAD_WINDOW {
X_BAD_WINDOW_DETECTED.store(true, Ordering::SeqCst);
log::debug!("Caught X11 BadWindow error (suppressed), window was likely destroyed");
return 0;
}
X_UNEXPECTED_ERROR_DETECTED.store(true, Ordering::SeqCst);
if !event.is_null() {
log::warn!(
"X11 error: error_code={}, request_code={}, minor_code={}",
(*event).error_code,
(*event).request_code,
(*event).minor_code,
);
}
0
}
#[link(name = "X11")]
extern "C" {
fn XOpenDisplay(display_name: *const c_char) -> *mut c_void;
// fn XCloseDisplay(d: *mut c_void) -> c_int;
fn XSetErrorHandler(handler: Option<XErrorHandler>) -> Option<XErrorHandler>;
}
#[link(name = "Xfixes")]
@@ -276,47 +231,25 @@ pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
if libxdo_sys::xdo_get_active_window(*xdo as *const _, &mut window) != 0 {
return;
}
// XSetErrorHandler is process-global, not scoped to this Display/thread.
// This path is currently called by the single window_focus service thread.
// While installed, this handler can still observe unrelated X11 errors from
// other threads; unexpected errors make this geometry query fail.
X_BAD_WINDOW_DETECTED.store(false, Ordering::SeqCst);
X_UNEXPECTED_ERROR_DETECTED.store(false, Ordering::SeqCst);
let prev_handler = XSetErrorHandler(Some(handle_x_error));
let loc_ret = libxdo_sys::xdo_get_window_location(
if libxdo_sys::xdo_get_window_location(
*xdo as *const _,
window,
&mut x as _,
&mut y as _,
std::ptr::null_mut(),
);
let size_ret = if loc_ret == XDO_SUCCESS {
libxdo_sys::xdo_get_window_size(
*xdo as *const _,
window,
&mut width,
&mut height,
)
} else {
XDO_ERROR
};
// Do not call XSync(DISPLAY) here: DISPLAY is a separate
// XOpenDisplay() connection, while libxdo owns the Display*
// used by these geometry queries. These libxdo calls are
// synchronous XGetWindowAttributes-based queries, so the target
// BadWindow is expected to be delivered before the calls return.
XSetErrorHandler(prev_handler);
if X_BAD_WINDOW_DETECTED.load(Ordering::SeqCst)
|| X_UNEXPECTED_ERROR_DETECTED.load(Ordering::SeqCst)
|| loc_ret != XDO_SUCCESS
|| size_ret != XDO_SUCCESS
) != 0
{
return;
}
if libxdo_sys::xdo_get_window_size(
*xdo as *const _,
window,
&mut width,
&mut height,
) != 0
{
return;
}
let center_x = x + (width / 2) as c_int;
let center_y = y + (height / 2) as c_int;
res = displays.iter().position(|d| {
@@ -2217,10 +2150,7 @@ pub fn clear_gnome_shortcuts_inhibitor_permission() -> ResultType<()> {
|| err_name == "org.freedesktop.DBus.Error.UnknownObject"
|| err_name == "org.freedesktop.DBus.Error.ServiceUnknown"
{
log::info!(
"GNOME shortcuts inhibitor permission was not set ({})",
err_name
);
log::info!("GNOME shortcuts inhibitor permission was not set ({})", err_name);
Ok(())
} else {
bail!("Failed to clear permission: {}", e)

View File

@@ -73,17 +73,11 @@ lazy_static::lazy_static! {
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
pub static ref AUTHED_CONNS: Arc::<Mutex<Vec<AuthedConn>>> = Default::default();
pub static ref CONTROL_PERMISSIONS_ARRAY: Arc::<Mutex<Vec<(i32, ControlPermissions)>>> = Default::default();
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
static ref WAKELOCK_SENDER: Arc::<Mutex<std::sync::mpsc::Sender<(usize, usize)>>> = Arc::new(Mutex::new(start_wakelock_thread()));
static ref WAKELOCK_KEEP_AWAKE_OPTION: Arc::<Mutex<Option<bool>>> = Default::default();
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! {
static ref SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
static ref PENDING_SWITCH_SIDES_UUID: Arc::<Mutex<HashMap<String, (Instant, uuid::Uuid)>>> = Default::default();
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
@@ -247,7 +241,6 @@ pub struct Connection {
restart: bool,
recording: bool,
block_input: bool,
privacy_mode: bool,
control_permissions: Option<ControlPermissions>,
last_test_delay: Option<Instant>,
network_delay: u32,
@@ -322,6 +315,15 @@ pub struct Connection {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
terminal_user_token: Option<TerminalUserToken>,
terminal_generic_service: Option<Box<GenericService>>,
// When the host has multiple sessions and the client must pick one,
// defer notifying the CM (which would pop the permission window in the
// current `--server` session) until the client confirms the chosen
// session. Otherwise the dialog can briefly appear in the wrong session
// before the server is relaunched in the chosen one.
#[cfg(windows)]
wait_for_windows_session_id_confirm: bool,
#[cfg(windows)]
pending_cm_login: Option<(String, String, bool)>,
}
impl ConnInner {
@@ -438,7 +440,6 @@ impl Connection {
restart: Self::permission(keys::OPTION_ENABLE_REMOTE_RESTART, &control_permissions),
recording: Self::permission(keys::OPTION_ENABLE_RECORD_SESSION, &control_permissions),
block_input: Self::permission(keys::OPTION_ENABLE_BLOCK_INPUT, &control_permissions),
privacy_mode: Self::permission(keys::OPTION_ENABLE_PRIVACY_MODE, &control_permissions),
control_permissions,
last_test_delay: None,
network_delay: 0,
@@ -500,6 +501,10 @@ impl Connection {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
terminal_user_token: None,
terminal_generic_service: None,
#[cfg(windows)]
wait_for_windows_session_id_confirm: false,
#[cfg(windows)]
pending_cm_login: None,
};
let addr = hbb_common::try_into_v4(addr);
if !conn.on_open(addr).await {
@@ -535,9 +540,6 @@ impl Connection {
if !conn.block_input {
conn.send_permission(Permission::BlockInput, false).await;
}
if !conn.privacy_mode {
conn.send_permission(Permission::PrivacyMode, false).await;
}
let mut test_delay_timer =
crate::rustdesk_interval(time::interval_at(Instant::now(), TEST_DELAY_TIMEOUT));
let mut last_recv_time = Instant::now();
@@ -685,46 +687,6 @@ impl Connection {
} else if &name == "block_input" {
conn.block_input = enabled;
conn.send_permission(Permission::BlockInput, enabled).await;
} else if &name == "privacy_mode" {
// Keep permission state and runtime state consistent:
// when revoking the permission, try to leave privacy mode first.
// Otherwise we could end up in an inconsistent state where
// permission looks disabled while privacy mode is still active.
if !enabled && privacy_mode::is_in_privacy_mode() {
if let Some(conn_id) = privacy_mode::get_privacy_mode_conn_id() {
if conn_id == conn.inner.id() {
let impl_key =
privacy_mode::get_cur_impl_key().unwrap_or_default();
let turn_off_res =
privacy_mode::turn_off_privacy(conn_id, None);
match turn_off_res {
Some(Ok(_)) => {
let msg_out = crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::PrvOffByPeer,
impl_key.clone(),
);
conn.send(msg_out).await;
}
_ => {
let msg_out = Self::turn_off_privacy_result_to_msg(
turn_off_res,
impl_key,
);
conn.send(msg_out).await;
// Turn-off failed, so revert CM's optimistic toggle
// and keep the previous permission value.
conn.send_to_cm(ipc::Data::SwitchPermission {
name: "privacy_mode".to_owned(),
enabled: conn.privacy_mode,
});
continue;
}
}
}
}
}
conn.privacy_mode = enabled;
conn.send_permission(Permission::PrivacyMode, enabled).await;
}
}
ipc::Data::RawMessage(bytes) => {
@@ -781,8 +743,6 @@ impl Connection {
log::error!("Failed to start portable service from cm: {:?}", e);
}
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
ipc::Data::SwitchSidesBack => {
let mut misc = Misc::new();
misc.set_switch_back(SwitchBack::default());
@@ -1031,7 +991,7 @@ impl Connection {
if let Some(video_privacy_conn_id) = privacy_mode::get_privacy_mode_conn_id() {
if video_privacy_conn_id == id {
let _ = Self::turn_off_privacy_to_msg(id, String::new());
let _ = Self::turn_off_privacy_to_msg(id);
}
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
@@ -1877,6 +1837,10 @@ impl Connection {
})
.into();
*wait_session_id_confirm = true;
// Defer the CM permission window until the client confirms
// the session it wants — otherwise the prompt pops up in the
// current `--server` session before any choice is made.
self.wait_for_windows_session_id_confirm = true;
}
}
}
@@ -1935,6 +1899,14 @@ impl Connection {
}
fn try_start_cm(&mut self, peer_id: String, name: String, authorized: bool) {
// While waiting for the client to pick a Windows session, hold the
// Login back instead of telling the CM to show its window. The
// request will be replayed once the chosen session is confirmed.
#[cfg(windows)]
if self.wait_for_windows_session_id_confirm {
self.pending_cm_login = Some((peer_id, name, authorized));
return;
}
self.send_to_cm(ipc::Data::Login {
id: self.inner.id(),
is_file_transfer: self.file_transfer.is_some(),
@@ -1953,7 +1925,6 @@ impl Connection {
restart: self.restart,
recording: self.recording,
block_input: self.block_input,
privacy_mode: self.privacy_mode,
from_switch: self.from_switch,
});
}
@@ -2229,7 +2200,6 @@ impl Connection {
keys::OPTION_ENABLE_REMOTE_RESTART => Some(Permission::restart),
keys::OPTION_ENABLE_RECORD_SESSION => Some(Permission::recording),
keys::OPTION_ENABLE_BLOCK_INPUT => Some(Permission::block_input),
keys::OPTION_ENABLE_PRIVACY_MODE => Some(Permission::privacy_mode),
_ => None,
};
if let Some(permission) = permission {
@@ -2587,7 +2557,6 @@ impl Connection {
}
} else if let Some(message::Union::SwitchSidesResponse(_s)) = msg.union {
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Some(lr) = _s.lr.clone().take() {
self.handle_login_request_without_validation(&lr).await;
SWITCH_SIDES_UUID
@@ -3303,13 +3272,8 @@ impl Connection {
}
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::SwitchSidesRequest(s)) => {
if let Ok(uuid) = uuid::Uuid::from_slice(&s.uuid.to_vec()[..]) {
crate::server::insert_pending_switch_sides_uuid(
self.lr.my_id.clone(),
uuid.clone(),
);
crate::run_me(vec![
"--connect",
&self.lr.my_id,
@@ -3360,6 +3324,17 @@ impl Connection {
});
return false;
}
// Client confirmed it wants this session: replay
// the deferred CM login so the permission window
// appears here and only here.
if self.wait_for_windows_session_id_confirm {
self.wait_for_windows_session_id_confirm = false;
if let Some((peer_id, name, authorized)) =
self.pending_cm_login.take()
{
self.try_start_cm(peer_id, name, authorized);
}
}
if self.file_transfer.is_some() {
if let Some((dir, show_hidden)) = self.delayed_read_dir.take() {
self.read_dir(&dir, show_hidden);
@@ -4206,15 +4181,6 @@ impl Connection {
}
async fn turn_on_privacy(&mut self, impl_key: String) {
if !self.is_authed_remote_conn() || !self.privacy_mode {
let msg_out = crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::PrvOnFailedDenied,
impl_key,
);
self.send(msg_out).await;
return;
}
let msg_out = if !privacy_mode::is_privacy_mode_supported() {
crate::common::make_privacy_mode_msg_with_details(
back_notification::PrivacyModeState::PrvNotSupported,
@@ -4256,7 +4222,7 @@ impl Connection {
"Check privacy mode failed: {}, turn off privacy mode.",
&err_msg
);
let _ = Self::turn_off_privacy_to_msg(self.inner.id, String::new());
let _ = Self::turn_off_privacy_to_msg(self.inner.id);
crate::common::make_privacy_mode_msg_with_details(
back_notification::PrivacyModeState::PrvOnFailed,
err_msg,
@@ -4275,7 +4241,6 @@ impl Connection {
if privacy_mode::is_in_privacy_mode() {
let _ = Self::turn_off_privacy_to_msg(
privacy_mode::INVALID_PRIVACY_MODE_CONN_ID,
String::new(),
);
}
crate::common::make_privacy_mode_msg_with_details(
@@ -4303,23 +4268,14 @@ impl Connection {
impl_key,
)
} else {
Self::turn_off_privacy_to_msg(self.inner.id, impl_key)
Self::turn_off_privacy_to_msg(self.inner.id)
};
self.send(msg_out).await;
}
pub fn turn_off_privacy_to_msg(_conn_id: i32, impl_key: String) -> Message {
Self::turn_off_privacy_result_to_msg(
privacy_mode::turn_off_privacy(_conn_id, None),
impl_key,
)
}
fn turn_off_privacy_result_to_msg(
turn_off_res: Option<hbb_common::ResultType<()>>,
impl_key: String,
) -> Message {
match turn_off_res {
pub fn turn_off_privacy_to_msg(_conn_id: i32) -> Message {
let impl_key = "".to_owned();
match privacy_mode::turn_off_privacy(_conn_id, None) {
Some(Ok(_)) => crate::common::make_privacy_mode_msg(
back_notification::PrivacyModeState::PrvOffSucceeded,
impl_key,
@@ -4952,8 +4908,6 @@ impl Connection {
}
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
SWITCH_SIDES_UUID
.lock()
@@ -4961,27 +4915,6 @@ pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
.insert(id, (tokio::time::Instant::now(), uuid));
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn insert_pending_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
let mut uuids = PENDING_SWITCH_SIDES_UUID.lock().unwrap();
uuids.retain(|_, (instant, _)| instant.elapsed() < Duration::from_secs(10));
uuids.insert(id, (tokio::time::Instant::now(), uuid));
}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn remove_pending_switch_sides_uuid(id: &str, uuid: &uuid::Uuid) -> bool {
let mut uuids = PENDING_SWITCH_SIDES_UUID.lock().unwrap();
uuids.retain(|_, (instant, _)| instant.elapsed() < Duration::from_secs(10));
if uuids.get(id).map(|(_, stored_uuid)| stored_uuid == uuid) == Some(true) {
uuids.remove(id);
true
} else {
false
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn start_ipc(
mut rx_to_cm: mpsc::UnboundedReceiver<ipc::Data>,

View File

@@ -372,11 +372,6 @@ impl UI {
is_installed()
}
fn get_supported_privacy_mode_impls(&self) -> String {
serde_json::to_string(&crate::privacy_mode::get_supported_privacy_mode_impl())
.unwrap_or_default()
}
fn is_root(&self) -> bool {
is_root()
}
@@ -757,7 +752,6 @@ impl sciter::EventHandler for UI {
fn get_icon();
fn install_me(String, String);
fn is_installed();
fn get_supported_privacy_mode_impls();
fn is_root();
fn is_release();
fn set_socks(String, String, String);

View File

@@ -93,13 +93,6 @@ div.permissions > div:active {
opacity: 0.5;
}
div.permissions.locked,
div.permissions.locked *,
div.permissions.locked > div:active {
cursor: default !important;
opacity: 1;
}
icon.keyboard {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAgVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9d3yJTAAAAKnRSTlMA0Gd/0y8ILZgbJffDPUwV2nvzt+TMqZxyU7CMb1pYQyzsvKunkXE4AwJnNC24AAAA+0lEQVQ4y83O2U7DMBCF4ZMxk9rZk26kpQs7nPd/QJy4EiLbLf01N5Y/2YP/qxDFQvGB5NPC/ZpVnfJx4b5xyGfF95rkHvNCWH1u+N6J6T0sC7gqRy8uGPfBLEbozPXUjlkQKwGaFPNizwQbwkx0TDvhCii34ExZCSQVBdzIOEOyeclSHgBGXkpeygXSQgStACtWx4Z8rr8COHOvfEP/IbbsQAToFUAAV1M408IIjIGYAPoCSNRP7DQutfQTqxuAiH7UUg1FaJR2AGrrx52sK2ye28LZ0wBAEyR6y8X+NADhm1B4fgiiHXbRrTrxpwEY9RdM9wsepnvFHfUDwYEeiwAJr/gAAAAASUVORK5CYII=');
}
@@ -128,10 +121,6 @@ icon.block_input {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAjdJREFUWEe1V8tNAzEQfXOHAx2QG0UgQSqBFIIgHdABoQqOhBq4cCMlcMh90FvZq/HEXtvJxlKUZNceP783no+gY6jqNYBHAHcA+JufXTDBb37eRWTbalZqE82mz7W55v0ABMBGRCLA7PJJAKr6AiC3sT11NHyf2SEyQjvtAMKp3wBYo9VTGbYegjxxU65d5tg4YEBVbwF8ALgw2lLX4in80QqyZUEkAMLCb7P5n4hcdWifTA32Pg0bByA8AE4+oL3n9A1s7ERkEeeNAJzD/QC4OVaCAgjrU7wdK86zAHREJSKqyvvORRxVb67JFOT4NfYGpxwAqCo34oYcKxHZhOdzg7D2BhYigHj6RJ+5QbjrPezlqR61sZTOKYfztSUBWPoXpdA5FwjnC2sCGK+eiNRC8yw+oap0RiayLQHEPwf65zx7DibMoXcEEB0wq/85QJQAbEVkWbvP8f0pTFi/65ZgjtuRyJ7QYWL0OZnwTmiLDobH5nLqGDlUlcmON49jQwnsg/Wxma/VJ1zcGQIR7+OYJGyqbJWhhwlDPxh3JpNRL4Ba7nAsJckoYaFUv7UCyslBvQ3TNDWEfVsPJGH2FCkKTPAxD8ox+poFwJfZqqX15H6eYyK+TgJeriidLCJ7wAQHZ4Udy7u9iFxaG7mynEx4EF1leZDANzV7AE8i8joJICz2cvBxbExIYTZYTTQmxTxTzP+VnvC8rZlLOLEj7m5OW6JqtTs2US6247Hvy7XnX0OV05FP/gHde5fLZaGS8AAAAABJRU5ErkJggg==');
}
icon.privacy_mode {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAB7UlEQVR4AdyTrVYDMRCFuyjqiiuuOJA46sCVR6jDgQTXN+CgQIJCgkOCA0cduOLAgaOOuuW7czYhyWY5FcXQc28n85O5m9nsUuuPf/9IoCzLLnxd9MTCET3SvNckQnwL7lfcpnYueIGiKNbY8QYjERo+wZK4HuAcK94rVvGSWCO8gCqKjAixTXLPsAl7ldBxriASqAo6lfUnqUTaWAP5FajTYjxGCNXeYSRAwSflToBlKxSZKSCiMoUa6Uh+QNW/B37LC9D8lkTYHNegTf7JqNP8b5RB5AT7AkPoNqqXxUyATT28AUzhRuFFaLpDUYc9V1ihr7+EA/JdxUyAxQTWQDM3CuVSEWugGiUztJ5OIJPPhlKRbFEVXJZ1Anph8iNyTCsieA0dvIgCQY3ckBtyTIBjfuDcwRR2TPJDElkRcrpd6XcyJm7X2ATY3CKwi1UxxkNPeyiP/BAa8LVZObtdBMOPcYbvX7wXYJNE2lidBuNxyhgm0I1LCdcgFXmguXqoxhgJKELBKvYMhljH+ULEwDr8mEIRXWHSP6gJKIXIESxYh3PHzWJK1IuwjpAVcBWIhHPX0x2QE/vkHGofIzUevwr4KhZ003wvsOKYkAcxXfPoxbvk3AJuQ5MNRNwFsNKFCaibRGB0CxcqIJGU3wAAAP//8GtoDAAAAAZJREFUAwCJJuAxFVNbWwAAAABJRU5ErkJggg==');
}
div.outer_buttons {
flow:vertical;
border-spacing:8;

View File

@@ -36,8 +36,7 @@ impl InvokeUiCM for SciterHandler {
client.file,
client.restart,
client.recording,
client.block_input,
client.privacy_mode
client.block_input
),
);
}
@@ -158,18 +157,9 @@ impl SciterConnectionManager {
crate::ui_interface::get_option(key)
}
fn get_builtin_option(&self, key: String) -> String {
crate::ui_interface::get_builtin_option(&key)
}
fn hide_cm(&self) -> bool {
*crate::ui::cm::HIDE_CM.lock().unwrap()
}
fn get_supported_privacy_mode_impls(&self) -> String {
serde_json::to_string(&crate::privacy_mode::get_supported_privacy_mode_impl())
.unwrap_or_default()
}
}
impl sciter::EventHandler for SciterConnectionManager {
@@ -191,8 +181,6 @@ impl sciter::EventHandler for SciterConnectionManager {
fn can_elevate();
fn elevate_portable(i32);
fn get_option(String);
fn get_builtin_option(String);
fn hide_cm();
fn get_supported_privacy_mode_impls();
}
}

View File

@@ -4,9 +4,6 @@ var body;
var connections = [];
var show_chat = false;
var show_elevation = true;
var is_privacy_mode_supported = handler.get_supported_privacy_mode_impls() != '[]';
var allow_perm_change_in_accept_window =
handler.get_builtin_option('enable-perm-change-in-accept-window') != 'N';
var svg_elevate = <svg t="1667992597853" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1850" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M892.761 160.724v426.504c0 25.588-6.419 51.036-19.177 76.339-12.798 25.336-29.547 49.86-50.254 73.627-20.707 23.79-44.372 46.296-70.97 67.516-26.589 21.244-53.543 40.177-80.921 56.768-27.363 16.623-53.968 30.461-79.801 41.438-25.809 11.008-48.433 18.547-67.871 22.64l-9.203 1.53-8.43-1.53c-19.958-4.093-43.094-11.632-69.432-22.64-26.337-10.969-53.708-24.816-82.080-41.438-28.388-16.591-56.256-35.524-83.618-56.768-27.378-21.219-51.776-43.725-73.265-67.516-21.488-23.759-38.868-48.291-52.155-73.627-13.319-25.305-19.974-50.759-19.974-76.339v-426.504l31.455-4.629 352.892-65.97 359.784 65.97 23.017 4.629zM510.028 151.884l-4.211-0.844-302.89 51.476v269.101h307.102v-319.734zM815.434 471.634h-305.406v383.031c19.682-4.51 41.052-11.411 64.141-20.692 23.033-9.249 45.815-20.234 68.304-32.867 22.513-12.672 44.159-26.739 64.969-42.203 20.818-15.472 39.23-32.047 55.277-49.797 16.024-17.703 28.822-36.131 38.386-55.222 9.549-19.131 14.328-38.553 14.328-58.235v-124.015z" p-id="1851" fill="#ffffff"></path></svg>;
var hide_cm = undefined;
@@ -38,7 +35,6 @@ class Body: Reactor.Component
me.sendMsg(msg);
};
var right_style = show_chat ? "" : "display: none";
var permissions_locked = !allow_perm_change_in_accept_window;
var disconnected = c.disconnected;
var show_elevation_btn = handler.can_elevate() && show_elevation && !c.is_file_transfer && !c.is_view_camera && !c.is_terminal && c.port_forward.length == 0;
var show_accept_btn = handler.get_option('approve-mode') != 'password';
@@ -62,16 +58,15 @@ class Body: Reactor.Component
</div>
<div />
{c.is_file_transfer || c.is_terminal || c.port_forward || disconnected ? "" : <div>{translate('Permissions')}</div>}
{c.is_file_transfer || c.is_terminal || c.port_forward || disconnected ? "" : <div> <div class={permissions_locked ? "permissions locked" : "permissions"} style={permissions_locked ? "opacity:0.6;" : ""}>
{c.is_file_transfer || c.is_terminal || c.port_forward || disconnected ? "" : <div> <div .permissions>
<div class={!c.keyboard ? "disabled" : ""} title={translate('Enable keyboard/mouse')}><icon .keyboard /></div>
<div class={!c.clipboard ? "disabled" : ""} title={translate('Enable clipboard')}><icon .clipboard /></div>
<div class={!c.audio ? "disabled" : ""} title={translate('Enable audio')}><icon .audio /></div>
<div class={!c.file ? "disabled" : ""} title={translate('Enable file copy and paste')}><icon .file /></div>
<div class={!c.restart ? "disabled" : ""} title={translate('Enable remote restart')}><icon .restart /></div>
</div> <div class={permissions_locked ? "permissions locked" : "permissions"} style={permissions_locked ? "margin-top:8px;opacity:0.6;" : "margin-top:8px;"} >
</div> <div .permissions style="margin-top:8px;" >
<div class={!c.recording ? "disabled" : ""} title={translate('Enable recording session')}><icon .recording /></div>
<div class={!c.block_input ? "disabled" : ""} title={translate('Enable blocking user input')} style={is_win ? "" : "display:none;"}><icon .block_input /></div>
<div class={!c.privacy_mode ? "disabled" : ""} title={translate('Enable privacy mode')} style={is_privacy_mode_supported ? "" : "display:none;"}><icon .privacy_mode /></div>
</div></div>
}
{c.is_file_transfer ? <div>{translate('Transfer file')}</div> : ""}
@@ -108,7 +103,6 @@ class Body: Reactor.Component
}
event click $(icon.keyboard) (e) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.keyboard = !connection.keyboard;
@@ -118,7 +112,6 @@ class Body: Reactor.Component
}
event click $(icon.clipboard) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.clipboard = !connection.clipboard;
@@ -128,7 +121,6 @@ class Body: Reactor.Component
}
event click $(icon.audio) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.audio = !connection.audio;
@@ -138,7 +130,6 @@ class Body: Reactor.Component
}
event click $(icon.file) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.file = !connection.file;
@@ -148,7 +139,6 @@ class Body: Reactor.Component
}
event click $(icon.restart) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.restart = !connection.restart;
@@ -158,7 +148,6 @@ class Body: Reactor.Component
}
event click $(icon.recording) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.recording = !connection.recording;
@@ -168,7 +157,6 @@ class Body: Reactor.Component
}
event click $(icon.block_input) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.block_input = !connection.block_input;
@@ -177,16 +165,6 @@ class Body: Reactor.Component
});
}
event click $(icon.privacy_mode) {
if (!allow_perm_change_in_accept_window) return;
var { cid, connection } = this;
checkClickTime(function() {
connection.privacy_mode = !connection.privacy_mode;
body.update();
handler.switch_permission(cid, "privacy_mode", connection.privacy_mode);
});
}
event click $(button#accept) {
var { cid, connection } = this;
checkClickTime(function() {
@@ -390,7 +368,7 @@ function bring_to_top(idx=-1) {
}
}
handler.addConnection = function(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, restart, recording, block_input, privacy_mode) {
handler.addConnection = function(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, restart, recording, block_input) {
stdout.println("new connection #" + id + ": " + peer_id);
var conn;
connections.map(function(c) {
@@ -398,7 +376,6 @@ handler.addConnection = function(id, is_file_transfer, is_view_camera, is_termin
});
if (conn) {
conn.authorized = authorized;
conn.privacy_mode = privacy_mode;
update();
return;
}
@@ -414,7 +391,7 @@ handler.addConnection = function(id, is_file_transfer, is_view_camera, is_termin
name: name, authorized: authorized, time: new Date(), now: new Date(),
keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0,
audio: audio, file: file, restart: restart, recording: recording,
block_input:block_input, privacy_mode:privacy_mode,
block_input:block_input,
disconnected: false
};
if (idx < 0) {
@@ -503,21 +480,15 @@ function getElapsed(time, now) {
return out;
}
var ui_status_cache = ["", ""];
var ui_status_cache = [""];
function check_update_ui() {
self.timer(1s, function() {
var approve_mode = handler.get_option('approve-mode');
var allow_perm_change = handler.get_builtin_option('enable-perm-change-in-accept-window');
var changed = false;
if (ui_status_cache[0] != approve_mode) {
ui_status_cache[0] = approve_mode;
changed = true;
}
if (ui_status_cache[1] != allow_perm_change) {
ui_status_cache[1] = allow_perm_change;
allow_perm_change_in_accept_window = allow_perm_change != 'N';
changed = true;
}
if (changed) update();
check_update_ui();
});

View File

@@ -218,7 +218,7 @@ class Header: Reactor.Component {
{is_file_copy_paste_supported && file_enabled ? <li #enable-file-copy-paste .toggle-option><span>{svg_checkmark}</span>{translate('Enable file copy and paste')}</li> : ""}
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
{(pi.platform == "Windows" || pi.platform == "Mac OS") && (handler.get_toggle_option("privacy-mode") || (keyboard_enabled && privacy_mode_enabled)) ? <li #privacy-mode><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
{keyboard_enabled && pi.platform == "Windows" ? <li #privacy-mode><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
{keyboard_enabled && ((is_osx && pi.platform != "Mac OS") || (!is_osx && pi.platform == "Mac OS")) ? <li #allow_swap_key .toggle-option><span>{svg_checkmark}</span>{translate('Swap control-command key')}</li> : ""}
{handler.version_cmp(pi.version, '1.2.4') >= 0 ? <li #i444><span>{svg_checkmark}</span>{translate('True color (4:4:4)')}</li> : ""}
</menu>

View File

@@ -521,7 +521,6 @@ class MyIdMenu: Reactor.Component {
{!disable_settings && <li #enable-remote-restart><span>{svg_checkmark}</span>{translate('Enable remote restart')}</li>}
{!disable_settings && <li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP tunneling')}</li>}
{!disable_settings && is_win ? <li #enable-block-input><span>{svg_checkmark}</span>{translate('Enable blocking user input')}</li> : ""}
{!disable_settings && (handler.get_supported_privacy_mode_impls() != '[]') && <li #enable-privacy-mode><span>{svg_checkmark}</span>{translate('Enable privacy mode')}</li>}
{!disable_settings && <li #enable-lan-discovery><span>{svg_checkmark}</span>{translate('Enable LAN discovery')}</li>}
<AudioInputs />
<Enhancements />

View File

@@ -17,7 +17,6 @@ var audio_enabled = true; // server side
var file_enabled = true; // server side
var restart_enabled = true; // server side
var recording_enabled = true; // server side
var privacy_mode_enabled = true; // server side
var scroll_body = $(body);
var peer_platform = "";
@@ -589,7 +588,6 @@ handler.setPermission = function(name, enabled) {
if (name == "clipboard") clipboard_enabled = enabled;
if (name == "restart") restart_enabled = enabled;
if (name == "recording") recording_enabled = enabled;
if (name == "privacy_mode") privacy_mode_enabled = enabled;
input_blocked = false;
header.update();
});

View File

@@ -12,10 +12,7 @@ use hbb_common::fs::serialize_transfer_job;
use hbb_common::tokio::sync::mpsc::unbounded_channel;
use hbb_common::{
allow_err, bail,
config::{
keys::{OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW, OPTION_FILE_TRANSFER_MAX_FILES},
option2bool, Config,
},
config::{keys::OPTION_FILE_TRANSFER_MAX_FILES, Config},
fs::{self, get_string, is_write_need_confirmation, new_send_confirm, DigestCheckResult},
log,
message_proto::*,
@@ -28,7 +25,10 @@ use hbb_common::{
ResultType,
};
#[cfg(target_os = "windows")]
use hbb_common::{config::keys::*, tokio::sync::Mutex as TokioMutex};
use hbb_common::{
config::{keys::*, option2bool},
tokio::sync::Mutex as TokioMutex,
};
use serde_derive::Serialize;
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
use std::iter::FromIterator;
@@ -143,7 +143,6 @@ pub struct Client {
pub restart: bool,
pub recording: bool,
pub block_input: bool,
pub privacy_mode: bool,
pub from_switch: bool,
pub in_voice_call: bool,
pub incoming_voice_call: bool,
@@ -231,7 +230,6 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
restart: bool,
recording: bool,
block_input: bool,
privacy_mode: bool,
from_switch: bool,
#[cfg(not(any(target_os = "ios")))] tx: mpsc::UnboundedSender<Data>,
) {
@@ -253,7 +251,6 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
restart,
recording,
block_input,
privacy_mode,
from_switch,
#[cfg(not(any(target_os = "ios")))]
tx,
@@ -395,23 +392,6 @@ pub fn send_chat(id: i32, text: String) {
#[inline]
#[cfg(not(any(target_os = "ios")))]
pub fn switch_permission(id: i32, name: String, enabled: bool) {
#[cfg(target_os = "android")]
let is_keyboard_permission = name == "keyboard";
#[cfg(not(target_os = "android"))]
let is_keyboard_permission = false;
if !option2bool(
OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
) && !is_keyboard_permission
{
log::info!(
"blocked cm switch_permission by policy, conn_id={}, permission={}, enabled={}",
id,
name,
enabled
);
return;
}
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
allow_err!(client.tx.send(Data::SwitchPermission { name, enabled }));
};
@@ -420,19 +400,6 @@ pub fn switch_permission(id: i32, name: String, enabled: bool) {
#[inline]
#[cfg(target_os = "android")]
pub fn switch_permission_all(name: String, enabled: bool) {
if name != "keyboard"
&& !option2bool(
OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW,
&crate::get_builtin_option(OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW),
)
{
log::info!(
"blocked cm switch_permission_all by policy, permission={}, enabled={}",
name,
enabled
);
return;
}
for (_, client) in CLIENTS.read().unwrap().iter() {
allow_err!(client.tx.send(Data::SwitchPermission {
name: name.clone(),
@@ -455,16 +422,9 @@ pub fn get_clients_length() -> usize {
clients.len()
}
#[inline]
#[cfg(target_os = "android")]
pub fn has_active_clients() -> bool {
let clients = CLIENTS.read().unwrap();
clients.values().any(|c| !c.disconnected)
}
#[inline]
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(not(any(target_os = "ios")))]
pub fn switch_back(id: i32) {
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
allow_err!(client.tx.send(Data::SwitchSidesBack));
@@ -543,9 +503,9 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
}
Ok(Some(data)) => {
match data {
Data::Login{id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording, block_input, privacy_mode, from_switch} => {
Data::Login{id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording, block_input, from_switch} => {
log::debug!("conn_id: {}", id);
self.cm.add_connection(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, restart, recording, block_input, privacy_mode, from_switch, self.tx.clone());
self.cm.add_connection(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, restart, recording, block_input, from_switch, self.tx.clone());
self.conn_id = id;
#[cfg(target_os = "windows")]
{
@@ -573,26 +533,6 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
Data::ChatMessage { text } => {
self.cm.new_message(self.conn_id, text);
}
Data::SwitchPermission { name, enabled } => {
// Keep this branch scoped to privacy mode rollback.
// Other CM permission toggles are updated optimistically by the UI itself.
// The backend currently sends SwitchPermission back to CM only when
// privacy-mode turn-off fails and the UI state must be restored.
if name == "privacy_mode" {
let client = {
let mut clients = CLIENTS.write().unwrap();
clients.get_mut(&self.conn_id).map(|c| {
c.privacy_mode = enabled;
c.clone()
})
};
if let Some(client) = client {
// This reuses add_connection(), and cm.tis only selectively updates
// existing rows (authorized/privacy_mode) for this fallback path.
self.cm.ui_handler.add_connection(&client);
}
}
}
Data::FS(mut fs) => {
if let ipc::FS::WriteBlock { id, file_num, data: _, compressed } = fs {
if let Ok(bytes) = self.stream.next_raw().await {
@@ -895,7 +835,6 @@ pub async fn start_listen<T: InvokeUiCM>(
restart,
recording,
block_input,
privacy_mode,
from_switch,
..
}) => {
@@ -917,7 +856,6 @@ pub async fn start_listen<T: InvokeUiCM>(
restart,
recording,
block_input,
privacy_mode,
from_switch,
tx.clone(),
);

View File

@@ -870,14 +870,12 @@ impl<T: InvokeUiSession> Session<T> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn enter(&self, keyboard_mode: String) {
let session_id = self.lc.read().unwrap().session_id as u128;
keyboard::client::change_grab_status(GrabState::Run, &keyboard_mode, session_id);
keyboard::client::change_grab_status(GrabState::Run, &keyboard_mode);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn leave(&self, keyboard_mode: String) {
let session_id = self.lc.read().unwrap().session_id as u128;
keyboard::client::change_grab_status(GrabState::Wait, &keyboard_mode, session_id);
keyboard::client::change_grab_status(GrabState::Wait, &keyboard_mode);
}
// flutter only TODO new input
@@ -1464,11 +1462,10 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::ElevateWithLogon(username, password));
}
#[cfg(any(target_os = "android", target_os = "ios", not(feature = "flutter")))]
#[cfg(any(target_os = "ios"))]
pub fn switch_sides(&self) {}
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(not(any(target_os = "ios")))]
#[tokio::main(flavor = "current_thread")]
pub async fn switch_sides(&self) {
match crate::ipc::connect(1000, "").await {