mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-27 16:08:41 +03:00
Compare commits
12 Commits
deploy
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
716a4c9a11 | ||
|
|
6ad56075d6 | ||
|
|
b81ae6c894 | ||
|
|
546e9f1702 | ||
|
|
bb51c6aa42 | ||
|
|
78e8134ad5 | ||
|
|
bc2c36215d | ||
|
|
377547fa11 | ||
|
|
472c4fc03a | ||
|
|
9f8f726f12 | ||
|
|
701a9c6cdc | ||
|
|
0d40cf2101 |
@@ -142,6 +142,10 @@ const String kOptionSwapLeftRightMouse = "swap-left-right-mouse";
|
||||
const String kOptionCodecPreference = "codec-preference";
|
||||
const String kOptionRemoteMenubarDragLeft = "remote-menubar-drag-left";
|
||||
const String kOptionRemoteMenubarDragRight = "remote-menubar-drag-right";
|
||||
const String kOptionRemoteMenubarEdge = "remote-menubar-edge";
|
||||
const String kOptionRemoteMenubarFraction = "remote-menubar-frac";
|
||||
const String kOptionAllowMultiEdgeToolbarDock =
|
||||
"allow-multi-edge-toolbar-dock";
|
||||
const String kOptionHideAbTagsPanel = "hideAbTagsPanel";
|
||||
const String kOptionRemoteMenubarState = "remoteMenubarState";
|
||||
const String kOptionPeerSorting = "peer-sorting";
|
||||
|
||||
@@ -488,6 +488,16 @@ class _GeneralState extends State<_General> {
|
||||
_OptionCheckBox(context, 'Confirm before closing multiple tabs',
|
||||
kOptionEnableConfirmClosingTabs,
|
||||
isServer: false),
|
||||
if (!bind.isIncomingOnly())
|
||||
_OptionCheckBox(
|
||||
context,
|
||||
'allow-remote-toolbar-docking-any-edge',
|
||||
kOptionAllowMultiEdgeToolbarDock,
|
||||
isServer: false,
|
||||
update: (_) {
|
||||
reloadAllWindows();
|
||||
},
|
||||
),
|
||||
_OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr),
|
||||
if (!isWeb) wallpaper(),
|
||||
if (!isWeb && !bind.isIncomingOnly()) ...[
|
||||
|
||||
@@ -28,6 +28,220 @@ import './kb_layout_type_chooser.dart';
|
||||
import 'package:flutter_hbb/utils/scale.dart';
|
||||
import 'package:flutter_hbb/common/widgets/custom_scale_base.dart';
|
||||
|
||||
enum _ToolbarEdge { top, right, bottom, left }
|
||||
|
||||
_ToolbarEdge _parseToolbarEdge(String? s) {
|
||||
switch (s) {
|
||||
case 'right':
|
||||
return _ToolbarEdge.right;
|
||||
case 'bottom':
|
||||
return _ToolbarEdge.bottom;
|
||||
case 'left':
|
||||
return _ToolbarEdge.left;
|
||||
default:
|
||||
return _ToolbarEdge.top;
|
||||
}
|
||||
}
|
||||
|
||||
String _toolbarEdgeToString(_ToolbarEdge e) {
|
||||
switch (e) {
|
||||
case _ToolbarEdge.top:
|
||||
return 'top';
|
||||
case _ToolbarEdge.right:
|
||||
return 'right';
|
||||
case _ToolbarEdge.bottom:
|
||||
return 'bottom';
|
||||
case _ToolbarEdge.left:
|
||||
return 'left';
|
||||
}
|
||||
}
|
||||
|
||||
bool _isHorizontalEdge(_ToolbarEdge e) =>
|
||||
e == _ToolbarEdge.top || e == _ToolbarEdge.bottom;
|
||||
|
||||
const _legacyRemoteMenubarDragX = 'remote-menubar-drag-x';
|
||||
|
||||
double _clampToolbarFraction(double fraction, double left, double right) {
|
||||
if (fraction < left) fraction = left;
|
||||
if (fraction > right) fraction = right;
|
||||
return fraction;
|
||||
}
|
||||
|
||||
Size _toolbarSizeForEdge(_ToolbarEdge edge, Size? measured) {
|
||||
final isHorizontal = _isHorizontalEdge(edge);
|
||||
final fallback = isHorizontal ? const Size(360, 40) : const Size(40, 360);
|
||||
final size = measured ?? fallback;
|
||||
final long = size.longestSide;
|
||||
final short = size.shortestSide;
|
||||
return Size(isHorizontal ? long : short, isHorizontal ? short : long);
|
||||
}
|
||||
|
||||
Offset _toolbarOffsetForEdge({
|
||||
required _ToolbarEdge edge,
|
||||
required double fraction,
|
||||
required Size parentSize,
|
||||
required Size toolbarSize,
|
||||
}) {
|
||||
final xTravel = parentSize.width - toolbarSize.width;
|
||||
final yTravel = parentSize.height - toolbarSize.height;
|
||||
switch (edge) {
|
||||
case _ToolbarEdge.top:
|
||||
return Offset(xTravel * fraction, 0);
|
||||
case _ToolbarEdge.bottom:
|
||||
return Offset(xTravel * fraction, yTravel);
|
||||
case _ToolbarEdge.left:
|
||||
return Offset(0, yTravel * fraction);
|
||||
case _ToolbarEdge.right:
|
||||
return Offset(xTravel, yTravel * fraction);
|
||||
}
|
||||
}
|
||||
|
||||
double _fractionForAlignedDrag({
|
||||
required double cursor,
|
||||
required double grabOffset,
|
||||
required double parentExtent,
|
||||
required double toolbarExtent,
|
||||
required double left,
|
||||
required double right,
|
||||
}) {
|
||||
final travelExtent = parentExtent - toolbarExtent;
|
||||
if (travelExtent <= 0) {
|
||||
return _clampToolbarFraction(0.5, left, right);
|
||||
}
|
||||
return _clampToolbarFraction(
|
||||
(cursor - grabOffset) / travelExtent, left, right);
|
||||
}
|
||||
|
||||
({double left, double right}) _fractionBoundsForEdge(
|
||||
_ToolbarEdge edge,
|
||||
double left,
|
||||
double right,
|
||||
) {
|
||||
return _isHorizontalEdge(edge)
|
||||
? (left: left, right: right)
|
||||
: (left: 0, right: 1);
|
||||
}
|
||||
|
||||
String _toolbarRawFraction({
|
||||
required bool multiEdgeEnabled,
|
||||
required _ToolbarEdge edge,
|
||||
required String? savedFraction,
|
||||
required String? legacyFraction,
|
||||
}) {
|
||||
if (!multiEdgeEnabled) {
|
||||
return (legacyFraction != null && legacyFraction.isNotEmpty)
|
||||
? legacyFraction
|
||||
: '0.5';
|
||||
}
|
||||
if (savedFraction != null && savedFraction.isNotEmpty) {
|
||||
return savedFraction;
|
||||
}
|
||||
if (edge == _ToolbarEdge.top &&
|
||||
legacyFraction != null &&
|
||||
legacyFraction.isNotEmpty) {
|
||||
return legacyFraction;
|
||||
}
|
||||
return '0.5';
|
||||
}
|
||||
|
||||
// Returns the alignment for the wrapper Align that positions the entire
|
||||
// toolbar against the given edge at the given fraction along that edge.
|
||||
// Alignment uses [-1, 1] coordinates (0 = center).
|
||||
Alignment _alignmentForEdge(_ToolbarEdge edge, double fraction) {
|
||||
final f = fraction * 2 - 1;
|
||||
switch (edge) {
|
||||
case _ToolbarEdge.top:
|
||||
return Alignment(f, -1);
|
||||
case _ToolbarEdge.bottom:
|
||||
return Alignment(f, 1);
|
||||
case _ToolbarEdge.left:
|
||||
return Alignment(-1, f);
|
||||
case _ToolbarEdge.right:
|
||||
return Alignment(1, f);
|
||||
}
|
||||
}
|
||||
|
||||
// The drag handle hangs off the side of the toolbar facing away from the
|
||||
// docked edge, so the icons themselves sit flush against that edge.
|
||||
BorderRadius _collapseHandleBorderRadius(_ToolbarEdge edge) {
|
||||
const r = Radius.circular(5);
|
||||
switch (edge) {
|
||||
case _ToolbarEdge.top:
|
||||
return const BorderRadius.vertical(bottom: r);
|
||||
case _ToolbarEdge.bottom:
|
||||
return const BorderRadius.vertical(top: r);
|
||||
case _ToolbarEdge.left:
|
||||
return const BorderRadius.horizontal(right: r);
|
||||
case _ToolbarEdge.right:
|
||||
return const BorderRadius.horizontal(left: r);
|
||||
}
|
||||
}
|
||||
|
||||
int _monitorMenuQuarterTurns(_ToolbarEdge edge) {
|
||||
switch (edge) {
|
||||
case _ToolbarEdge.left:
|
||||
return 1;
|
||||
case _ToolbarEdge.right:
|
||||
return 3;
|
||||
case _ToolbarEdge.top:
|
||||
case _ToolbarEdge.bottom:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
IconData _toolbarCollapseIcon(_ToolbarEdge edge, bool isCollapsed) {
|
||||
switch (edge) {
|
||||
case _ToolbarEdge.top:
|
||||
return isCollapsed ? Icons.expand_more : Icons.expand_less;
|
||||
case _ToolbarEdge.bottom:
|
||||
return isCollapsed ? Icons.expand_less : Icons.expand_more;
|
||||
case _ToolbarEdge.left:
|
||||
return isCollapsed ? Icons.chevron_right : Icons.chevron_left;
|
||||
case _ToolbarEdge.right:
|
||||
return isCollapsed ? Icons.chevron_left : Icons.chevron_right;
|
||||
}
|
||||
}
|
||||
|
||||
class _ToolbarDockingOptions {
|
||||
_ToolbarDockingOptions({
|
||||
required this.edge,
|
||||
required this.fraction,
|
||||
required this.multiEdgeEnabled,
|
||||
});
|
||||
|
||||
_ToolbarEdge edge;
|
||||
double fraction;
|
||||
bool multiEdgeEnabled;
|
||||
}
|
||||
|
||||
final _toolbarDockingOptionsBySession = <String, _ToolbarDockingOptions>{};
|
||||
|
||||
String _toolbarDockingCacheKey(SessionID sessionId) => sessionId.toString();
|
||||
|
||||
_ToolbarDockingOptions? _cachedToolbarDockingOptions(SessionID sessionId) =>
|
||||
_toolbarDockingOptionsBySession[_toolbarDockingCacheKey(sessionId)];
|
||||
|
||||
void _cacheToolbarDockingOptions({
|
||||
required SessionID sessionId,
|
||||
required _ToolbarEdge edge,
|
||||
required double fraction,
|
||||
required bool multiEdgeEnabled,
|
||||
}) {
|
||||
final key = _toolbarDockingCacheKey(sessionId);
|
||||
final cached = _toolbarDockingOptionsBySession[key];
|
||||
if (cached == null) {
|
||||
_toolbarDockingOptionsBySession[key] = _ToolbarDockingOptions(
|
||||
edge: edge,
|
||||
fraction: fraction,
|
||||
multiEdgeEnabled: multiEdgeEnabled,
|
||||
);
|
||||
return;
|
||||
}
|
||||
cached.edge = edge;
|
||||
cached.fraction = fraction;
|
||||
cached.multiEdgeEnabled = multiEdgeEnabled;
|
||||
}
|
||||
|
||||
class ToolbarState {
|
||||
late RxBool _pin;
|
||||
|
||||
@@ -250,8 +464,26 @@ class RemoteToolbar extends StatefulWidget {
|
||||
class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
late Debouncer<int> _debouncerHide;
|
||||
bool _isCursorOverImage = false;
|
||||
final _fractionX = 0.5.obs;
|
||||
final _fraction = 0.5.obs;
|
||||
final _edge = _ToolbarEdge.top.obs;
|
||||
final _dragging = false.obs;
|
||||
// Live drag preview: where the toolbar would dock if the user dropped now.
|
||||
final _previewEdge = Rxn<_ToolbarEdge>();
|
||||
final _previewFraction = Rxn<double>();
|
||||
// Measured size of the live toolbar, so the preview ghost matches reality
|
||||
// (collapsed handle vs expanded toolbar). Updated after every layout pass.
|
||||
final _toolbarSize = Rxn<Size>();
|
||||
final _toolbarKey = GlobalKey(debugLabel: 'remote_toolbar_root');
|
||||
// When false (default), the toolbar stays on the top edge and the drag
|
||||
// handle just slides it horizontally — preserving long-standing UX while
|
||||
// still fixing the bug where dragging only moved the handle. When true,
|
||||
// the user has opted into multi-edge docking with nearest-edge snap.
|
||||
// Kept in sync after settings-triggered rebuilds.
|
||||
final _multiEdgeEnabled = false.obs;
|
||||
final _dockingOptionsInitialized = false.obs;
|
||||
bool _pendingDockingOptionSync = false;
|
||||
int _dockingOptionSyncSerial = 0;
|
||||
int _dragEpoch = 0;
|
||||
|
||||
int get windowId => stateGlobal.windowId;
|
||||
|
||||
@@ -273,16 +505,144 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
void _minimize() async =>
|
||||
await WindowController.fromWindowId(windowId).minimize();
|
||||
|
||||
Future<void> _syncDockingOptions({required bool force}) async {
|
||||
final syncSerial = ++_dockingOptionSyncSerial;
|
||||
if (_dragging.isTrue) {
|
||||
_deferDockingOptionsSync();
|
||||
return;
|
||||
}
|
||||
final dragEpoch = _dragEpoch;
|
||||
|
||||
// Use the canonical helper so the option's documented default semantics
|
||||
// apply (allow-* prefix => default false). Keeping it raw-string would
|
||||
// diverge from how _OptionCheckBox displays the same key.
|
||||
final multiEdgeEnabled =
|
||||
mainGetLocalBoolOptionSync(kOptionAllowMultiEdgeToolbarDock);
|
||||
final cached = _cachedToolbarDockingOptions(widget.ffi.sessionId);
|
||||
if (cached == null && pi.isSet.isFalse) {
|
||||
return;
|
||||
}
|
||||
final hadDockingOptions = cached != null;
|
||||
final wasMultiEdgeEnabled =
|
||||
cached?.multiEdgeEnabled ?? _multiEdgeEnabled.value;
|
||||
if (!force &&
|
||||
hadDockingOptions &&
|
||||
wasMultiEdgeEnabled == multiEdgeEnabled) {
|
||||
_pendingDockingOptionSync = false;
|
||||
return;
|
||||
}
|
||||
|
||||
final savedFraction = await bind.sessionGetOption(
|
||||
sessionId: widget.ffi.sessionId, arg: kOptionRemoteMenubarFraction);
|
||||
// Backward compat: legacy horizontal-only position.
|
||||
final legacyFraction = await bind.sessionGetOption(
|
||||
sessionId: widget.ffi.sessionId, arg: _legacyRemoteMenubarDragX);
|
||||
if (!mounted || syncSerial != _dockingOptionSyncSerial) return;
|
||||
|
||||
var nextEdge = _edge.value;
|
||||
var savedFractionForNextEdge = savedFraction;
|
||||
var keepCurrentPosition = false;
|
||||
if (!multiEdgeEnabled) {
|
||||
nextEdge = _ToolbarEdge.top;
|
||||
} else if (force || wasMultiEdgeEnabled || cached == null) {
|
||||
final edgeStr = await bind.sessionGetOption(
|
||||
sessionId: widget.ffi.sessionId, arg: kOptionRemoteMenubarEdge);
|
||||
if (!mounted || syncSerial != _dockingOptionSyncSerial) return;
|
||||
nextEdge = _parseToolbarEdge(edgeStr);
|
||||
} else {
|
||||
// The setting changed from top-only to multi-edge while this toolbar is
|
||||
// already visible. Keep its current position instead of jumping to the
|
||||
// last saved multi-edge dock.
|
||||
nextEdge = cached.edge;
|
||||
savedFractionForNextEdge = cached.fraction.toString();
|
||||
keepCurrentPosition = true;
|
||||
}
|
||||
|
||||
final rawFraction = _toolbarRawFraction(
|
||||
multiEdgeEnabled: multiEdgeEnabled,
|
||||
edge: nextEdge,
|
||||
savedFraction: savedFractionForNextEdge,
|
||||
legacyFraction: legacyFraction,
|
||||
);
|
||||
// Clamp to the saved drag-bound contract so a corrupted or out-of-range
|
||||
// saved value can't bypass it until the user drags again.
|
||||
final dragLeft = double.tryParse(
|
||||
bind.mainGetLocalOption(key: kOptionRemoteMenubarDragLeft)) ??
|
||||
0.0;
|
||||
final dragRight = double.tryParse(
|
||||
bind.mainGetLocalOption(key: kOptionRemoteMenubarDragRight)) ??
|
||||
1.0;
|
||||
final fractionBounds =
|
||||
_fractionBoundsForEdge(nextEdge, dragLeft, dragRight);
|
||||
final nextFraction = (double.tryParse(rawFraction) ?? 0.5)
|
||||
.clamp(fractionBounds.left, fractionBounds.right)
|
||||
.toDouble();
|
||||
if (!mounted || syncSerial != _dockingOptionSyncSerial) return;
|
||||
if (_dragging.isTrue || dragEpoch != _dragEpoch) {
|
||||
_deferDockingOptionsSync();
|
||||
return;
|
||||
}
|
||||
_edge.value = nextEdge;
|
||||
_fraction.value = nextFraction;
|
||||
_multiEdgeEnabled.value = multiEdgeEnabled;
|
||||
_dockingOptionsInitialized.value = true;
|
||||
_cacheToolbarDockingOptions(
|
||||
sessionId: widget.ffi.sessionId,
|
||||
edge: nextEdge,
|
||||
fraction: nextFraction,
|
||||
multiEdgeEnabled: multiEdgeEnabled,
|
||||
);
|
||||
_pendingDockingOptionSync = false;
|
||||
if (!multiEdgeEnabled || keepCurrentPosition) {
|
||||
bind.sessionPeerOption(
|
||||
sessionId: widget.ffi.sessionId,
|
||||
name: kOptionRemoteMenubarEdge,
|
||||
value: _toolbarEdgeToString(nextEdge),
|
||||
);
|
||||
bind.sessionPeerOption(
|
||||
sessionId: widget.ffi.sessionId,
|
||||
name: kOptionRemoteMenubarFraction,
|
||||
value: nextFraction.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _deferDockingOptionsSync() {
|
||||
_pendingDockingOptionSync = true;
|
||||
if (_dragging.isFalse) {
|
||||
_syncDockingOptionsAfterDragIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
void _markToolbarDragEpoch() {
|
||||
++_dragEpoch;
|
||||
}
|
||||
|
||||
void _syncDockingOptionsAfterDragIfNeeded() {
|
||||
if (!_pendingDockingOptionSync) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _syncDockingOptions(force: false);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
|
||||
final cached = _cachedToolbarDockingOptions(widget.ffi.sessionId);
|
||||
final multiEdgeEnabled =
|
||||
mainGetLocalBoolOptionSync(kOptionAllowMultiEdgeToolbarDock);
|
||||
final shouldResetToTop =
|
||||
cached != null && cached.multiEdgeEnabled && !multiEdgeEnabled;
|
||||
if (cached != null && !shouldResetToTop) {
|
||||
_edge.value = cached.edge;
|
||||
_fraction.value = cached.fraction;
|
||||
_multiEdgeEnabled.value = multiEdgeEnabled;
|
||||
_dockingOptionsInitialized.value = true;
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
_fractionX.value = double.tryParse(await bind.sessionGetOption(
|
||||
sessionId: widget.ffi.sessionId,
|
||||
arg: 'remote-menubar-drag-x') ??
|
||||
'0.5') ??
|
||||
0.5;
|
||||
await _syncDockingOptions(force: cached == null || shouldResetToTop);
|
||||
// Initialize toolbar states (collapse, hide) from session options
|
||||
widget.state.init(widget.ffi.sessionId);
|
||||
});
|
||||
@@ -303,6 +663,14 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant RemoteToolbar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _syncDockingOptions(force: false);
|
||||
});
|
||||
}
|
||||
|
||||
_debouncerHideProc(int v) {
|
||||
if (!pin && collapse.isFalse && _isCursorOverImage && _dragging.isFalse) {
|
||||
collapse.value = true;
|
||||
@@ -311,64 +679,130 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
++_dockingOptionSyncSerial;
|
||||
widget.onEnterOrLeaveImageCleaner(identityHashCode(this));
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
// Wait for initialization to complete to prevent flickering
|
||||
if (!widget.state.initialized.value) {
|
||||
if (!widget.state.initialized.value ||
|
||||
!_dockingOptionsInitialized.value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
// If toolbar is hidden, return empty widget
|
||||
if (hide.value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: collapse.isFalse
|
||||
? _buildToolbar(context)
|
||||
: _buildDraggableCollapse(context),
|
||||
final edge = _edge.value;
|
||||
final isHorizontal = _isHorizontalEdge(edge);
|
||||
|
||||
// Measure the live toolbar after every layout so the preview ghost can
|
||||
// match its actual footprint (collapsed handle vs expanded toolbar).
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_dragging.isTrue) return;
|
||||
final ro = _toolbarKey.currentContext?.findRenderObject();
|
||||
if (ro is RenderBox && ro.hasSize) {
|
||||
final s = ro.size;
|
||||
if (_toolbarSize.value != s) _toolbarSize.value = s;
|
||||
}
|
||||
});
|
||||
|
||||
final toolbar = Align(
|
||||
alignment: _alignmentForEdge(edge, _fraction.value),
|
||||
child: KeyedSubtree(
|
||||
key: _toolbarKey,
|
||||
child: collapse.isFalse
|
||||
? _buildToolbar(context, edge, isHorizontal)
|
||||
: _buildDraggableCollapse(context, edge, isHorizontal),
|
||||
),
|
||||
);
|
||||
|
||||
// Always return the Stack — even when not dragging — so the toolbar's
|
||||
// position in the Element tree stays stable. Wrapping/unwrapping it
|
||||
// mid-drag was killing the Draggable's gesture state.
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
IgnorePointer(
|
||||
child: Obx(() {
|
||||
final pe = _previewEdge.value;
|
||||
final pf = _previewFraction.value;
|
||||
if (!_dragging.isTrue || pe == null || pf == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _buildDragPreview(context, pe, pf, _toolbarSize.value);
|
||||
}),
|
||||
),
|
||||
toolbar,
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildDraggableCollapse(BuildContext context) {
|
||||
Widget _buildDragPreview(BuildContext context, _ToolbarEdge edge,
|
||||
double fraction, Size? measured) {
|
||||
final color = Theme.of(context).colorScheme.primary;
|
||||
// Use the measured live toolbar size so collapsed vs expanded looks
|
||||
// right. The current orientation may differ from the preview orientation
|
||||
// (e.g. dragging a top-docked toolbar toward the left edge), so swap the
|
||||
// long/short axes when previewing a different orientation.
|
||||
final previewSize = _toolbarSizeForEdge(edge, measured);
|
||||
return Align(
|
||||
alignment: _alignmentForEdge(edge, fraction),
|
||||
child: Container(
|
||||
width: previewSize.width,
|
||||
height: previewSize.height,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.10),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: color.withOpacity(0.55), width: 1.5),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDraggableCollapse(
|
||||
BuildContext context, _ToolbarEdge edge, bool isHorizontal) {
|
||||
return Obx(() {
|
||||
if (collapse.isFalse && _dragging.isFalse) {
|
||||
triggerAutoHide();
|
||||
}
|
||||
final borderRadius = BorderRadius.vertical(
|
||||
bottom: Radius.circular(5),
|
||||
);
|
||||
return Align(
|
||||
alignment: FractionalOffset(_fractionX.value, 0),
|
||||
child: Offstage(
|
||||
offstage: _dragging.isTrue,
|
||||
child: Material(
|
||||
elevation: _ToolbarTheme.elevation,
|
||||
shadowColor: MyTheme.color(context).shadow,
|
||||
final borderRadius = _collapseHandleBorderRadius(edge);
|
||||
return Offstage(
|
||||
offstage: _dragging.isTrue,
|
||||
child: Material(
|
||||
elevation: _ToolbarTheme.elevation,
|
||||
shadowColor: MyTheme.color(context).shadow,
|
||||
borderRadius: borderRadius,
|
||||
child: _DraggableShowHide(
|
||||
id: widget.id,
|
||||
sessionId: widget.ffi.sessionId,
|
||||
dragging: _dragging,
|
||||
fraction: _fraction,
|
||||
edge: _edge,
|
||||
previewEdge: _previewEdge,
|
||||
previewFraction: _previewFraction,
|
||||
toolbarSize: _toolbarSize,
|
||||
markDragEpoch: _markToolbarDragEpoch,
|
||||
syncDockingOptionsAfterDragIfNeeded:
|
||||
_syncDockingOptionsAfterDragIfNeeded,
|
||||
isHorizontal: isHorizontal,
|
||||
multiEdgeEnabled: _multiEdgeEnabled.value,
|
||||
toolbarState: widget.state,
|
||||
setFullscreen: _setFullscreen,
|
||||
setMinimize: _minimize,
|
||||
borderRadius: borderRadius,
|
||||
child: _DraggableShowHide(
|
||||
id: widget.id,
|
||||
sessionId: widget.ffi.sessionId,
|
||||
dragging: _dragging,
|
||||
fractionX: _fractionX,
|
||||
toolbarState: widget.state,
|
||||
setFullscreen: _setFullscreen,
|
||||
setMinimize: _minimize,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildToolbar(BuildContext context) {
|
||||
Widget _buildToolbar(
|
||||
BuildContext context, _ToolbarEdge edge, bool isHorizontal) {
|
||||
final List<Widget> toolbarItems = [];
|
||||
toolbarItems.add(_PinMenu(state: widget.state));
|
||||
if (!isWebDesktop) {
|
||||
@@ -382,6 +816,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
return _MonitorMenu(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
edge: edge,
|
||||
setRemoteState: widget.setRemoteState);
|
||||
} else {
|
||||
return Offstage();
|
||||
@@ -407,37 +842,53 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
if (!isWeb) toolbarItems.add(_RecordMenu());
|
||||
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
|
||||
final toolbarBorderRadius = BorderRadius.all(Radius.circular(4.0));
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Material(
|
||||
elevation: _ToolbarTheme.elevation,
|
||||
shadowColor: MyTheme.color(context).shadow,
|
||||
borderRadius: toolbarBorderRadius,
|
||||
color: Theme.of(context)
|
||||
.menuBarTheme
|
||||
.style
|
||||
?.backgroundColor
|
||||
?.resolve(MaterialState.values.toSet()),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Theme(
|
||||
data: themeData(),
|
||||
child: _ToolbarTheme.borderWrapper(
|
||||
context,
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: _ToolbarTheme.buttonHMargin * 2),
|
||||
...toolbarItems,
|
||||
SizedBox(width: _ToolbarTheme.buttonHMargin * 2)
|
||||
],
|
||||
),
|
||||
toolbarBorderRadius),
|
||||
),
|
||||
),
|
||||
// innerAxis: how the toolbar icons themselves flow.
|
||||
// outerAxis: how the toolbar block and the handle stack against each other
|
||||
// (perpendicular to the dock edge, so the handle hangs off the interior face).
|
||||
final innerAxis = isHorizontal ? Axis.horizontal : Axis.vertical;
|
||||
final outerAxis = isHorizontal ? Axis.vertical : Axis.horizontal;
|
||||
final spacer = isHorizontal
|
||||
? SizedBox(width: _ToolbarTheme.buttonHMargin * 2)
|
||||
: SizedBox(height: _ToolbarTheme.buttonHMargin * 2);
|
||||
final toolbarMaterial = Material(
|
||||
elevation: _ToolbarTheme.elevation,
|
||||
shadowColor: MyTheme.color(context).shadow,
|
||||
borderRadius: toolbarBorderRadius,
|
||||
color: Theme.of(context)
|
||||
.menuBarTheme
|
||||
.style
|
||||
?.backgroundColor
|
||||
?.resolve(MaterialState.values.toSet()),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: innerAxis,
|
||||
child: Theme(
|
||||
data: themeData(),
|
||||
child: _ToolbarTheme.borderWrapper(
|
||||
context,
|
||||
Flex(
|
||||
direction: innerAxis,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
spacer,
|
||||
...toolbarItems,
|
||||
spacer,
|
||||
],
|
||||
),
|
||||
toolbarBorderRadius),
|
||||
),
|
||||
_buildDraggableCollapse(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
final handle = _buildDraggableCollapse(context, edge, isHorizontal);
|
||||
// The handle hangs off the interior face of the toolbar (away from the
|
||||
// docked edge), centered along that face by the Flex's default cross-axis
|
||||
// alignment, so the icons themselves sit flush against the docked edge.
|
||||
final children = (edge == _ToolbarEdge.top || edge == _ToolbarEdge.left)
|
||||
? [toolbarMaterial, handle]
|
||||
: [handle, toolbarMaterial];
|
||||
return Flex(
|
||||
direction: outerAxis,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -516,11 +967,13 @@ class _MobileActionMenu extends StatelessWidget {
|
||||
class _MonitorMenu extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
final _ToolbarEdge edge;
|
||||
final Function(VoidCallback) setRemoteState;
|
||||
const _MonitorMenu({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
required this.edge,
|
||||
required this.setRemoteState,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -531,9 +984,17 @@ class _MonitorMenu extends StatelessWidget {
|
||||
!isWeb && ffi.ffiModel.pi.isSupportMultiDisplay;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => showMonitorsToolbar
|
||||
? buildMultiMonitorMenu(context)
|
||||
: Obx(() => buildMonitorMenu(context));
|
||||
Widget build(BuildContext context) {
|
||||
final child = showMonitorsToolbar
|
||||
? buildMultiMonitorMenu(context)
|
||||
: Obx(() => buildMonitorMenu(context));
|
||||
final quarterTurns = _monitorMenuQuarterTurns(edge);
|
||||
if (quarterTurns == 0) return child;
|
||||
return RotatedBox(
|
||||
quarterTurns: quarterTurns,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildMonitorMenu(BuildContext context) {
|
||||
final width = SimpleWrapper<double>(0);
|
||||
@@ -665,7 +1126,8 @@ class _MonitorMenu extends StatelessWidget {
|
||||
}
|
||||
|
||||
final scale = _ToolbarTheme.buttonSize / rect.height * 0.75;
|
||||
final startY = (_ToolbarTheme.buttonSize - rect.height * scale) * 0.5;
|
||||
final height = rect.height * scale;
|
||||
final startY = (_ToolbarTheme.buttonSize - height) * 0.5;
|
||||
final startX = startY;
|
||||
|
||||
final children = <Widget>[];
|
||||
@@ -708,7 +1170,7 @@ class _MonitorMenu extends StatelessWidget {
|
||||
width.value = rect.width * scale + startX * 2;
|
||||
return SizedBox(
|
||||
width: width.value,
|
||||
height: rect.height * scale + startY * 2,
|
||||
height: height + startY * 2,
|
||||
child: Stack(
|
||||
children: children,
|
||||
),
|
||||
@@ -2519,7 +2981,18 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
class _DraggableShowHide extends StatefulWidget {
|
||||
final String id;
|
||||
final SessionID sessionId;
|
||||
final RxDouble fractionX;
|
||||
final RxDouble fraction;
|
||||
final Rx<_ToolbarEdge> edge;
|
||||
final Rxn<_ToolbarEdge> previewEdge;
|
||||
final Rxn<double> previewFraction;
|
||||
final Rxn<Size> toolbarSize;
|
||||
final VoidCallback markDragEpoch;
|
||||
final VoidCallback syncDockingOptionsAfterDragIfNeeded;
|
||||
final bool isHorizontal;
|
||||
// Whether multi-edge docking is enabled for this session (toggled in
|
||||
// Settings -> Other). When false, the drag handle slides the toolbar
|
||||
// horizontally on the top edge and never switches edges.
|
||||
final bool multiEdgeEnabled;
|
||||
final RxBool dragging;
|
||||
final ToolbarState toolbarState;
|
||||
final BorderRadius borderRadius;
|
||||
@@ -2531,7 +3004,15 @@ class _DraggableShowHide extends StatefulWidget {
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.sessionId,
|
||||
required this.fractionX,
|
||||
required this.fraction,
|
||||
required this.edge,
|
||||
required this.previewEdge,
|
||||
required this.previewFraction,
|
||||
required this.toolbarSize,
|
||||
required this.markDragEpoch,
|
||||
required this.syncDockingOptionsAfterDragIfNeeded,
|
||||
required this.isHorizontal,
|
||||
required this.multiEdgeEnabled,
|
||||
required this.dragging,
|
||||
required this.toolbarState,
|
||||
required this.setFullscreen,
|
||||
@@ -2544,10 +3025,12 @@ class _DraggableShowHide extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
Offset position = Offset.zero;
|
||||
Size size = Size.zero;
|
||||
double left = 0.0;
|
||||
double right = 1.0;
|
||||
Offset? _lastPointerDown;
|
||||
Offset? _dragGrabOffset;
|
||||
double? _dragLongAxisGrabOffset;
|
||||
Size? _dragToolbarSize;
|
||||
|
||||
RxBool get collapse => widget.toolbarState.collapse;
|
||||
|
||||
@@ -2573,41 +3056,174 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
}
|
||||
}
|
||||
|
||||
// Bias applied to the currently-previewed edge so a drag hovering between
|
||||
// two edges doesn't flicker. Only relevant when multi-edge is enabled.
|
||||
static const double _switchHysteresisPx = 50.0;
|
||||
|
||||
_ToolbarEdge _nearestToolbarEdge(Offset cursor, Size mediaSize) {
|
||||
if (!widget.multiEdgeEnabled) return widget.edge.value;
|
||||
|
||||
double rawDist(_ToolbarEdge e) {
|
||||
switch (e) {
|
||||
case _ToolbarEdge.top:
|
||||
return cursor.dy;
|
||||
case _ToolbarEdge.bottom:
|
||||
return mediaSize.height - cursor.dy;
|
||||
case _ToolbarEdge.left:
|
||||
return cursor.dx;
|
||||
case _ToolbarEdge.right:
|
||||
return mediaSize.width - cursor.dx;
|
||||
}
|
||||
}
|
||||
|
||||
final previewed = widget.previewEdge.value;
|
||||
var winner = widget.edge.value;
|
||||
var best = double.infinity;
|
||||
for (final e in _ToolbarEdge.values) {
|
||||
final biased =
|
||||
e == previewed ? rawDist(e) - _switchHysteresisPx : rawDist(e);
|
||||
if (biased < best) {
|
||||
best = biased;
|
||||
winner = e;
|
||||
}
|
||||
}
|
||||
return winner;
|
||||
}
|
||||
|
||||
void _ensureDragGrabOffset(Offset cursor) {
|
||||
if (_dragGrabOffset != null) return;
|
||||
final mediaSize = MediaQueryData.fromView(View.of(context)).size;
|
||||
final toolbarSize =
|
||||
_toolbarSizeForEdge(widget.edge.value, widget.toolbarSize.value);
|
||||
_dragToolbarSize = toolbarSize;
|
||||
final toolbarOffset = _toolbarOffsetForEdge(
|
||||
edge: widget.edge.value,
|
||||
fraction: widget.fraction.value,
|
||||
parentSize: mediaSize,
|
||||
toolbarSize: toolbarSize,
|
||||
);
|
||||
_dragGrabOffset = cursor - toolbarOffset;
|
||||
_dragLongAxisGrabOffset = _isHorizontalEdge(widget.edge.value)
|
||||
? _dragGrabOffset?.dx
|
||||
: _dragGrabOffset?.dy;
|
||||
}
|
||||
|
||||
double _dragGrabOffsetForEdge(_ToolbarEdge edge, Size toolbarSize) {
|
||||
final offset = _dragLongAxisGrabOffset ?? 0;
|
||||
final extent =
|
||||
_isHorizontalEdge(edge) ? toolbarSize.width : toolbarSize.height;
|
||||
return _clampToolbarFraction(offset, 0, extent);
|
||||
}
|
||||
|
||||
void _updatePreview(Offset cursor) {
|
||||
_ensureDragGrabOffset(cursor);
|
||||
final mediaSize = MediaQueryData.fromView(View.of(context)).size;
|
||||
final winner = _nearestToolbarEdge(cursor, mediaSize);
|
||||
widget.previewEdge.value = winner;
|
||||
|
||||
final toolbarSize = _toolbarSizeForEdge(winner, _dragToolbarSize);
|
||||
final grabOffset = _dragGrabOffsetForEdge(winner, toolbarSize);
|
||||
final double frac;
|
||||
if (winner == _ToolbarEdge.top || winner == _ToolbarEdge.bottom) {
|
||||
frac = _fractionForAlignedDrag(
|
||||
cursor: cursor.dx,
|
||||
grabOffset: grabOffset,
|
||||
parentExtent: mediaSize.width,
|
||||
toolbarExtent: toolbarSize.width,
|
||||
left: left,
|
||||
right: right,
|
||||
);
|
||||
} else {
|
||||
final fractionBounds = _fractionBoundsForEdge(winner, left, right);
|
||||
frac = _fractionForAlignedDrag(
|
||||
cursor: cursor.dy,
|
||||
grabOffset: grabOffset,
|
||||
parentExtent: mediaSize.height,
|
||||
toolbarExtent: toolbarSize.height,
|
||||
left: fractionBounds.left,
|
||||
right: fractionBounds.right,
|
||||
);
|
||||
}
|
||||
widget.previewFraction.value = frac;
|
||||
}
|
||||
|
||||
void _resetDragTracking() {
|
||||
_lastPointerDown = null;
|
||||
_dragGrabOffset = null;
|
||||
_dragLongAxisGrabOffset = null;
|
||||
_dragToolbarSize = null;
|
||||
}
|
||||
|
||||
void _commitPreview() {
|
||||
final newEdge = widget.previewEdge.value;
|
||||
final frac = widget.previewFraction.value;
|
||||
widget.previewEdge.value = null;
|
||||
widget.previewFraction.value = null;
|
||||
widget.dragging.value = false;
|
||||
widget.markDragEpoch();
|
||||
_resetDragTracking();
|
||||
widget.syncDockingOptionsAfterDragIfNeeded();
|
||||
if (newEdge == null || frac == null) return;
|
||||
widget.edge.value = newEdge;
|
||||
widget.fraction.value = frac;
|
||||
_cacheToolbarDockingOptions(
|
||||
sessionId: widget.sessionId,
|
||||
edge: newEdge,
|
||||
fraction: frac,
|
||||
multiEdgeEnabled: widget.multiEdgeEnabled,
|
||||
);
|
||||
bind.sessionPeerOption(
|
||||
sessionId: widget.sessionId,
|
||||
name: kOptionRemoteMenubarEdge,
|
||||
value: _toolbarEdgeToString(newEdge),
|
||||
);
|
||||
bind.sessionPeerOption(
|
||||
sessionId: widget.sessionId,
|
||||
name: kOptionRemoteMenubarFraction,
|
||||
value: frac.toString(),
|
||||
);
|
||||
if (widget.multiEdgeEnabled) {
|
||||
return;
|
||||
}
|
||||
bind.sessionPeerOption(
|
||||
sessionId: widget.sessionId,
|
||||
name: _legacyRemoteMenubarDragX,
|
||||
value: frac.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDraggable(BuildContext context) {
|
||||
return Draggable(
|
||||
axis: Axis.horizontal,
|
||||
child: Icon(
|
||||
Icons.drag_indicator,
|
||||
size: 20,
|
||||
color: MyTheme.color(context).drag_indicator,
|
||||
return Listener(
|
||||
onPointerDown: (event) => _lastPointerDown = event.position,
|
||||
child: Draggable(
|
||||
// When multi-edge docking is off the toolbar stays on the top edge,
|
||||
// so lock the feedback to horizontal motion — otherwise the handle
|
||||
// floats away from the top while dragging and the toolbar looks
|
||||
// unmoored. When multi-edge is on we need 2D drag for snap-to-edge.
|
||||
axis: widget.multiEdgeEnabled ? null : Axis.horizontal,
|
||||
child: Icon(
|
||||
widget.isHorizontal ? Icons.drag_indicator : Icons.drag_handle,
|
||||
size: 20,
|
||||
color: MyTheme.color(context).drag_indicator,
|
||||
),
|
||||
feedback: widget,
|
||||
onDragStarted: () {
|
||||
widget.markDragEpoch();
|
||||
final pointerDown = _lastPointerDown;
|
||||
if (pointerDown != null) {
|
||||
_ensureDragGrabOffset(pointerDown);
|
||||
}
|
||||
widget.dragging.value = true;
|
||||
// Seed the preview at the current docked edge/fraction so something
|
||||
// shows the instant the drag begins, before the first onDragUpdate.
|
||||
widget.previewEdge.value = widget.edge.value;
|
||||
widget.previewFraction.value = widget.fraction.value;
|
||||
},
|
||||
onDragUpdate: (details) {
|
||||
_updatePreview(details.globalPosition);
|
||||
},
|
||||
onDragEnd: (_) => _commitPreview(),
|
||||
),
|
||||
feedback: widget,
|
||||
onDragStarted: (() {
|
||||
final RenderObject? renderObj = context.findRenderObject();
|
||||
if (renderObj != null) {
|
||||
final RenderBox renderBox = renderObj as RenderBox;
|
||||
size = renderBox.size;
|
||||
position = renderBox.localToGlobal(Offset.zero);
|
||||
}
|
||||
widget.dragging.value = true;
|
||||
}),
|
||||
onDragEnd: (details) {
|
||||
final mediaSize = MediaQueryData.fromView(View.of(context)).size;
|
||||
widget.fractionX.value +=
|
||||
(details.offset.dx - position.dx) / (mediaSize.width - size.width);
|
||||
if (widget.fractionX.value < left) {
|
||||
widget.fractionX.value = left;
|
||||
}
|
||||
if (widget.fractionX.value > right) {
|
||||
widget.fractionX.value = right;
|
||||
}
|
||||
bind.sessionPeerOption(
|
||||
sessionId: widget.sessionId,
|
||||
name: 'remote-menubar-drag-x',
|
||||
value: widget.fractionX.value.toString(),
|
||||
);
|
||||
widget.dragging.value = false;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2637,7 +3253,9 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
);
|
||||
}
|
||||
|
||||
final child = Row(
|
||||
final axis = widget.isHorizontal ? Axis.horizontal : Axis.vertical;
|
||||
final child = Flex(
|
||||
direction: axis,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildDraggable(context),
|
||||
@@ -2678,7 +3296,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
message: translate(
|
||||
collapse.isFalse ? 'Hide Toolbar' : 'Show Toolbar'),
|
||||
child: Icon(
|
||||
collapse.isFalse ? Icons.expand_less : Icons.expand_more,
|
||||
_toolbarCollapseIcon(widget.edge.value, collapse.isTrue),
|
||||
size: iconSize,
|
||||
),
|
||||
))),
|
||||
@@ -2720,7 +3338,8 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
borderRadius: widget.borderRadius,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
height: widget.isHorizontal ? 20 : null,
|
||||
width: widget.isHorizontal ? null : 20,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -346,7 +346,7 @@ class InputModel {
|
||||
/// which runs per-engine, so each isolate registers its own handler tied
|
||||
/// to its own set of InputModels.
|
||||
static void initSideButtonChannel() {
|
||||
if (!Platform.isLinux) return;
|
||||
if (!isLinux) return;
|
||||
if (_sideButtonChannelInitialized) return;
|
||||
_sideButtonChannelInitialized = true;
|
||||
|
||||
|
||||
Submodule libs/hbb_common updated: c8cbb6be28...822701e416
@@ -276,12 +276,21 @@ impl PipeWireRecorder {
|
||||
// see: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/982
|
||||
src.set_property("always-copy", &true)?;
|
||||
|
||||
// COSMIC/Wayland fix: insert videoconvert between pipewiresrc and appsink.
|
||||
// xdg-desktop-portal-cosmic's modifier negotiation fails when the downstream
|
||||
// format set is too narrow (appsink only accepts BGRx/RGBx), producing
|
||||
// "no more output formats" / not-negotiated (-4). videoconvert accepts any
|
||||
// system-memory video/x-raw format, widening negotiation so the portal can
|
||||
// settle on a format it can deliver via its SHM path.
|
||||
let convert = gst::ElementFactory::make("videoconvert", None)?;
|
||||
|
||||
let sink = gst::ElementFactory::make("appsink", None)?;
|
||||
sink.set_property("drop", &true)?;
|
||||
sink.set_property("max-buffers", &1u32)?;
|
||||
|
||||
pipeline.add_many(&[&src, &sink])?;
|
||||
src.link(&sink)?;
|
||||
pipeline.add_many(&[&src, &convert, &sink])?;
|
||||
src.link(&convert)?;
|
||||
convert.link(&sink)?;
|
||||
|
||||
let appsink = sink
|
||||
.dynamic_cast::<AppSink>()
|
||||
|
||||
@@ -199,6 +199,20 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
}
|
||||
std::thread::spawn(move || crate::start_server(false, no_server));
|
||||
} else {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
// Root CLI management commands must talk to the user `--server` main IPC.
|
||||
// Example: `sudo rustdesk --option custom-rendezvous-server` should query the
|
||||
// user's IPC instead of root's `/tmp/<app>-0/ipc`; `connect()` still limits this
|
||||
// routing to empty-postfix main IPC only.
|
||||
let _user_main_ipc_scope = if crate::platform::is_installed()
|
||||
&& is_root()
|
||||
&& is_user_main_ipc_scope_cli_command(&args)
|
||||
{
|
||||
Some(crate::ipc::UserMainIpcScope::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use crate::platform;
|
||||
@@ -938,6 +952,57 @@ fn is_root() -> bool {
|
||||
crate::platform::is_root()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos", test))]
|
||||
fn is_user_main_ipc_scope_cli_command(args: &[String]) -> bool {
|
||||
matches!(
|
||||
args.first().map(String::as_str),
|
||||
Some("--password")
|
||||
| Some("--set-unlock-pin")
|
||||
| Some("--get-id")
|
||||
| Some("--set-id")
|
||||
| Some("--config")
|
||||
| Some("--option")
|
||||
| Some("--assign")
|
||||
| Some("--deploy")
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn args(values: &[&str]) -> Vec<String> {
|
||||
values.iter().map(|value| value.to_string()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_main_ipc_scope_cli_command_matches_management_commands_only() {
|
||||
for command in [
|
||||
"--password",
|
||||
"--set-unlock-pin",
|
||||
"--get-id",
|
||||
"--set-id",
|
||||
"--config",
|
||||
"--option",
|
||||
"--assign",
|
||||
"--deploy",
|
||||
] {
|
||||
assert!(is_user_main_ipc_scope_cli_command(&args(&[command])));
|
||||
}
|
||||
|
||||
for command in [
|
||||
"--service",
|
||||
"--server",
|
||||
"--tray",
|
||||
"--cm",
|
||||
"--check-hwcodec-config",
|
||||
"--connect",
|
||||
] {
|
||||
assert!(!is_user_main_ipc_scope_cli_command(&args(&[command])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the executable is a Quick Support version.
|
||||
/// Note: This function must be kept in sync with `libs/portable/src/main.rs`.
|
||||
#[cfg(windows)]
|
||||
|
||||
210
src/ipc.rs
210
src/ipc.rs
@@ -33,25 +33,25 @@ use hbb_common::{
|
||||
tokio_util::codec::Framed,
|
||||
ResultType,
|
||||
};
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use ipc_auth::authorize_service_scoped_ipc_connection;
|
||||
#[cfg(windows)]
|
||||
pub(crate) use ipc_auth::authorize_windows_portable_service_ipc_connection;
|
||||
#[cfg(windows)]
|
||||
pub(crate) use ipc_auth::ensure_peer_executable_matches_current_by_pid_opt;
|
||||
#[cfg(windows)]
|
||||
pub(crate) use ipc_auth::log_rejected_windows_ipc_connection;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) use ipc_auth::{
|
||||
active_uid, ensure_peer_executable_matches_current_by_fd, is_allowed_service_peer_uid,
|
||||
log_rejected_uinput_connection, peer_uid_from_fd,
|
||||
};
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use ipc_auth::{active_uid, authorize_service_scoped_ipc_connection};
|
||||
#[cfg(windows)]
|
||||
use ipc_auth::{
|
||||
authorize_windows_main_ipc_connection, portable_service_listener_security_attributes,
|
||||
should_allow_everyone_create_on_windows,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) use ipc_auth::{
|
||||
ensure_peer_executable_matches_current_by_fd, is_allowed_service_peer_uid,
|
||||
log_rejected_uinput_connection, peer_uid_from_fd,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
use ipc_fs::terminal_count_candidate_uids;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use ipc_fs::{
|
||||
@@ -63,6 +63,8 @@ use parity_tokio_ipc::{
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use std::cell::Cell;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -71,12 +73,47 @@ use std::{
|
||||
|
||||
// IPC actions here.
|
||||
pub const IPC_ACTION_CLOSE: &str = "close";
|
||||
#[cfg(target_os = "windows")]
|
||||
const PORTABLE_SERVICE_IPC_HANDSHAKE_TIMEOUT_MS: u64 = 3_000;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) const IPC_TOKEN_LEN: usize = 64;
|
||||
#[cfg(target_os = "windows")]
|
||||
const IPC_TOKEN_RANDOM_BYTES: usize = IPC_TOKEN_LEN / 2;
|
||||
#[cfg(target_os = "windows")]
|
||||
const _: () = assert!(IPC_TOKEN_LEN % 2 == 0);
|
||||
pub static EXIT_RECV_CLOSE: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
thread_local! {
|
||||
static USE_USER_MAIN_IPC: Cell<bool> = Cell::new(false);
|
||||
}
|
||||
|
||||
#[must_use = "bind this guard to a local variable to keep the IPC scope active"]
|
||||
/// Thread-local guard for routing root main IPC to the active user on Linux/macOS.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub(crate) struct UserMainIpcScope {
|
||||
previous: bool,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl UserMainIpcScope {
|
||||
pub(crate) fn new() -> Self {
|
||||
let previous = USE_USER_MAIN_IPC.with(|use_user_main| {
|
||||
let previous = use_user_main.get();
|
||||
use_user_main.set(true);
|
||||
previous
|
||||
});
|
||||
Self { previous }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl Drop for UserMainIpcScope {
|
||||
fn drop(&mut self) {
|
||||
USE_USER_MAIN_IPC.with(|use_user_main| use_user_main.set(self.previous));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn connect_service(ms_timeout: u64) -> ResultType<ConnectionTmpl<ConnClient>> {
|
||||
connect(ms_timeout, crate::POSTFIX_SERVICE).await
|
||||
@@ -1112,11 +1149,7 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn connect(ms_timeout: u64, postfix: &str) -> ResultType<ConnectionTmpl<ConnClient>> {
|
||||
let path = Config::ipc_path(postfix);
|
||||
connect_with_path(ms_timeout, &path).await
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn generate_one_time_ipc_token() -> ResultType<String> {
|
||||
use hbb_common::rand::{rngs::OsRng, RngCore as _};
|
||||
use std::fmt::Write as _;
|
||||
@@ -1137,6 +1170,7 @@ pub(crate) fn generate_one_time_ipc_token() -> ResultType<String> {
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn constant_time_ipc_token_eq(expected: &str, candidate: &str) -> bool {
|
||||
if expected.len() != IPC_TOKEN_LEN || candidate.len() != IPC_TOKEN_LEN {
|
||||
return false;
|
||||
@@ -1149,6 +1183,7 @@ pub(crate) fn constant_time_ipc_token_eq(expected: &str, candidate: &str) -> boo
|
||||
== 0
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) async fn portable_service_ipc_handshake_as_client<T>(
|
||||
stream: &mut ConnectionTmpl<T>,
|
||||
token: &str,
|
||||
@@ -1173,6 +1208,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) async fn portable_service_ipc_handshake_as_server<T, F>(
|
||||
stream: &mut ConnectionTmpl<T>,
|
||||
mut validate_token: F,
|
||||
@@ -1209,6 +1245,103 @@ async fn connect_with_path(ms_timeout: u64, path: &str) -> ResultType<Connection
|
||||
Ok(ConnectionTmpl::new(client))
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[inline]
|
||||
fn select_server_uid_for_user_main_ipc(
|
||||
server_uids: &[u32],
|
||||
active_uid: Option<u32>,
|
||||
prefer_root: bool,
|
||||
) -> ResultType<u32> {
|
||||
let mut server_uids = server_uids.to_vec();
|
||||
server_uids.sort_unstable();
|
||||
server_uids.dedup();
|
||||
|
||||
match server_uids.as_slice() {
|
||||
[] => {
|
||||
if let Some(uid) = active_uid {
|
||||
// If no `--server` processes are found but the active user is identifiable,
|
||||
// try the active user anyway because the main process may also listen on "" IPC.
|
||||
return Ok(uid);
|
||||
} else {
|
||||
bail!("No --server process found for user main IPC")
|
||||
}
|
||||
}
|
||||
[uid] => return Ok(*uid),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if prefer_root && server_uids.contains(&0) {
|
||||
return Ok(0);
|
||||
}
|
||||
if let Some(active_uid) = active_uid.filter(|uid| server_uids.contains(uid)) {
|
||||
return Ok(active_uid);
|
||||
}
|
||||
bail!("Multiple --server processes found for user main IPC");
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn running_server_uids_for_current_exe() -> ResultType<Vec<u32>> {
|
||||
let current_exe = std::env::current_exe()?;
|
||||
let current_exe_path = std::fs::canonicalize(¤t_exe)?;
|
||||
let current_pid = hbb_common::sysinfo::Pid::from_u32(std::process::id());
|
||||
let mut sys = hbb_common::sysinfo::System::new();
|
||||
sys.refresh_processes();
|
||||
let mut server_uids = Vec::new();
|
||||
for process in sys.processes().values() {
|
||||
if process.pid() == current_pid {
|
||||
continue;
|
||||
}
|
||||
if process.cmd().get(1).map_or(true, |arg| arg != "--server") {
|
||||
continue;
|
||||
}
|
||||
let Ok(process_path) = std::fs::canonicalize(process.exe()) else {
|
||||
continue;
|
||||
};
|
||||
if process_path != current_exe_path {
|
||||
continue;
|
||||
}
|
||||
let Some(uid) = process.user_id().map(|uid| **uid as u32) else {
|
||||
// Root CLI management commands need a stable matching `--server` target.
|
||||
// If this key process races during enumeration, failing the command is clearer
|
||||
// than silently skipping it; `--server` is not expected to exit frequently.
|
||||
bail!("Failed to read --server process uid");
|
||||
};
|
||||
server_uids.push(uid);
|
||||
}
|
||||
Ok(server_uids)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn user_main_ipc_server_uid() -> ResultType<u32> {
|
||||
let server_uids = running_server_uids_for_current_exe()?;
|
||||
#[cfg(target_os = "linux")]
|
||||
let prefer_root = crate::platform::linux::is_login_screen_wayland();
|
||||
#[cfg(target_os = "macos")]
|
||||
let prefer_root = false;
|
||||
select_server_uid_for_user_main_ipc(&server_uids, active_uid(), prefer_root)
|
||||
}
|
||||
|
||||
pub async fn connect(ms_timeout: u64, postfix: &str) -> ResultType<ConnectionTmpl<ConnClient>> {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let use_user_main_ipc = USE_USER_MAIN_IPC.with(|use_user_main| use_user_main.get());
|
||||
let is_root_main_ipc =
|
||||
unsafe { hbb_common::libc::geteuid() == 0 } && postfix.is_empty() && use_user_main_ipc;
|
||||
if is_root_main_ipc {
|
||||
let uid = user_main_ipc_server_uid()?;
|
||||
let path = Config::ipc_path_for_uid(uid, postfix);
|
||||
return connect_with_path(ms_timeout, &path).await;
|
||||
}
|
||||
let path = Config::ipc_path(postfix);
|
||||
return connect_with_path(ms_timeout, &path).await;
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
let path = Config::ipc_path(postfix);
|
||||
connect_with_path(ms_timeout, &path).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn connect_for_uid(
|
||||
ms_timeout: u64,
|
||||
@@ -2002,7 +2135,16 @@ mod test {
|
||||
assert!(std::mem::size_of::<Data>() <= 120);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[test]
|
||||
fn test_service_ipc_path_is_shared_across_uids() {
|
||||
assert_eq!(
|
||||
Config::ipc_path_for_uid(0, crate::POSTFIX_SERVICE),
|
||||
Config::ipc_path_for_uid(501, crate::POSTFIX_SERVICE)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[test]
|
||||
fn test_ipc_path_differs_by_uid_for_cm() {
|
||||
let effective_uid = unsafe { hbb_common::libc::geteuid() as u32 };
|
||||
@@ -2021,4 +2163,46 @@ mod test {
|
||||
Config::ipc_path_for_uid(other_uid, postfix)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[test]
|
||||
fn test_select_server_uid_uses_active_uid_when_no_server_found() {
|
||||
assert_eq!(
|
||||
select_server_uid_for_user_main_ipc(&[], Some(501), false).unwrap(),
|
||||
501
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[test]
|
||||
fn test_select_server_uid_uses_single_server_uid() {
|
||||
assert_eq!(
|
||||
select_server_uid_for_user_main_ipc(&[501], None, false).unwrap(),
|
||||
501
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[test]
|
||||
fn test_select_server_uid_prefers_active_uid_with_multiple_servers() {
|
||||
assert_eq!(
|
||||
select_server_uid_for_user_main_ipc(&[0, 501], Some(501), false).unwrap(),
|
||||
501
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[test]
|
||||
fn test_select_server_uid_prefers_root_on_wayland_login_screen() {
|
||||
assert_eq!(
|
||||
select_server_uid_for_user_main_ipc(&[0, 501], Some(501), true).unwrap(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
#[test]
|
||||
fn test_select_server_uid_fails_when_multiple_servers_are_ambiguous() {
|
||||
assert!(select_server_uid_for_user_main_ipc(&[501, 502], None, false).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,27 +607,30 @@ pub(crate) fn log_rejected_windows_ipc_connection(
|
||||
peer_session_id: Option<u32>,
|
||||
expected_session_id: Option<u32>,
|
||||
peer_is_system: Option<bool>,
|
||||
peer_is_elevated: Option<bool>,
|
||||
) {
|
||||
static LOG_THROTTLE: OnceLock<Mutex<UnauthorizedIpcLogThrottle>> = OnceLock::new();
|
||||
throttled_unauthorized_ipc_log(&LOG_THROTTLE, |suppressed| {
|
||||
if suppressed > 0 {
|
||||
log::warn!(
|
||||
"Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?} (suppressed {} similar events)",
|
||||
"Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?}, peer_is_elevated={:?} (suppressed {} similar events)",
|
||||
postfix,
|
||||
peer_pid,
|
||||
peer_session_id,
|
||||
expected_session_id,
|
||||
peer_is_system,
|
||||
peer_is_elevated,
|
||||
suppressed
|
||||
);
|
||||
} else {
|
||||
log::warn!(
|
||||
"Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?}",
|
||||
"Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?}, peer_is_elevated={:?}",
|
||||
postfix,
|
||||
peer_pid,
|
||||
peer_session_id,
|
||||
expected_session_id,
|
||||
peer_is_system
|
||||
peer_is_system,
|
||||
peer_is_elevated
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -655,8 +658,14 @@ pub(crate) fn authorize_service_scoped_ipc_connection(stream: &Connection, postf
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn authorize_windows_main_ipc_connection(stream: &Connection, postfix: &str) -> bool {
|
||||
let (authorized, peer_pid, peer_session_id, server_session_id, peer_is_system) =
|
||||
stream.server_authorization_status();
|
||||
let (
|
||||
authorized,
|
||||
peer_pid,
|
||||
peer_session_id,
|
||||
server_session_id,
|
||||
peer_is_system,
|
||||
peer_is_elevated,
|
||||
) = stream.server_authorization_status();
|
||||
if !authorized {
|
||||
log_rejected_windows_ipc_connection(
|
||||
postfix,
|
||||
@@ -664,6 +673,7 @@ pub(crate) fn authorize_windows_main_ipc_connection(stream: &Connection, postfix
|
||||
peer_session_id,
|
||||
server_session_id,
|
||||
peer_is_system,
|
||||
peer_is_elevated,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -776,7 +786,14 @@ impl ConnectionTmpl<parity_tokio_ipc::Connection> {
|
||||
|
||||
fn server_authorization_status(
|
||||
&self,
|
||||
) -> (bool, Option<u32>, Option<u32>, Option<u32>, Option<bool>) {
|
||||
) -> (
|
||||
bool,
|
||||
Option<u32>,
|
||||
Option<u32>,
|
||||
Option<u32>,
|
||||
Option<bool>,
|
||||
Option<bool>,
|
||||
) {
|
||||
let peer_pid = self.peer_pid();
|
||||
let server_session_id = crate::platform::windows::get_current_process_session_id();
|
||||
let peer_session_id =
|
||||
@@ -786,20 +803,34 @@ impl ConnectionTmpl<parity_tokio_ipc::Connection> {
|
||||
let peer_is_system = peer_is_system_result
|
||||
.as_ref()
|
||||
.and_then(|r| r.as_ref().ok().copied());
|
||||
if server_session_id.is_none() && !peer_is_system.unwrap_or(false) {
|
||||
// When the server session id cannot be determined, the session-id allow-path is
|
||||
// disabled and only SYSTEM peers can be authorized.
|
||||
log::debug!(
|
||||
"IPC authorization: server session id unavailable; rejecting non-SYSTEM peer, peer_pid={:?}, peer_session_id={:?}",
|
||||
peer_pid,
|
||||
peer_session_id
|
||||
);
|
||||
}
|
||||
let authorized = is_allowed_windows_session_scoped_peer(
|
||||
let session_authorized = is_allowed_windows_session_scoped_peer(
|
||||
peer_is_system.unwrap_or(false),
|
||||
peer_session_id,
|
||||
server_session_id,
|
||||
);
|
||||
let peer_is_elevated_result = if session_authorized {
|
||||
None
|
||||
} else {
|
||||
peer_pid.map(|pid| crate::platform::windows::is_elevated(Some(pid)))
|
||||
};
|
||||
let peer_is_elevated = peer_is_elevated_result
|
||||
.as_ref()
|
||||
.and_then(|r| r.as_ref().ok().copied());
|
||||
if server_session_id.is_none()
|
||||
&& !peer_is_system.unwrap_or(false)
|
||||
&& !peer_is_elevated.unwrap_or(false)
|
||||
{
|
||||
// When the server session id cannot be determined, the session-id allow-path is
|
||||
// disabled and only privileged peers can be authorized.
|
||||
log::debug!(
|
||||
"IPC authorization: server session id unavailable; rejecting non-privileged peer, peer_pid={:?}, peer_session_id={:?}",
|
||||
peer_pid,
|
||||
peer_session_id
|
||||
);
|
||||
}
|
||||
// Main IPC trusts same-session peers, LocalSystem, and elevated administrators.
|
||||
// Service-scoped IPC channels keep their own stricter authorization paths.
|
||||
let authorized = session_authorized || peer_is_elevated.unwrap_or(false);
|
||||
if !authorized {
|
||||
if let (Some(pid), Some(Err(err))) = (peer_pid, peer_is_system_result.as_ref()) {
|
||||
log::debug!(
|
||||
@@ -808,6 +839,13 @@ impl ConnectionTmpl<parity_tokio_ipc::Connection> {
|
||||
err
|
||||
);
|
||||
}
|
||||
if let (Some(pid), Some(Err(err))) = (peer_pid, peer_is_elevated_result.as_ref()) {
|
||||
log::debug!(
|
||||
"Failed to determine whether peer process is elevated, pid={}, err={}",
|
||||
pid,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
(
|
||||
authorized,
|
||||
@@ -815,6 +853,7 @@ impl ConnectionTmpl<parity_tokio_ipc::Connection> {
|
||||
peer_session_id,
|
||||
server_session_id,
|
||||
peer_is_system,
|
||||
peer_is_elevated,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "كلمة المرور مخفية"),
|
||||
("preset-password-in-use-tip", "كلمة المرور المحددة مسبقًا قيد الاستخدام"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "Зададзены пастаянны пароль (скрыты)."),
|
||||
("preset-password-in-use-tip", "Пададзены пароль цяпер выкарыстоўваецца"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "永久密码已设置(已隐藏)"),
|
||||
("preset-password-in-use-tip", "当前使用预设密码"),
|
||||
("Enable privacy mode", "允许隐私模式"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -274,5 +274,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("keep-awake-during-incoming-sessions-label", "Keep screen awake during incoming sessions"),
|
||||
("password-hidden-tip", "Permanent password is set (hidden)."),
|
||||
("preset-password-in-use-tip", "Preset password is currently in use."),
|
||||
("allow-remote-toolbar-docking-any-edge", "Allow docking remote toolbar to any window edge"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by the peer", "Cerrado manualmente por el par"),
|
||||
("Enable remote configuration modification", "Habilitar modificación remota de configuración"),
|
||||
("Run without install", "Ejecutar sin instalar"),
|
||||
("Connect via relay", ""),
|
||||
("Connect via relay", "Conectar a través de relay"),
|
||||
("Always connect via relay", "Conéctese siempre a través de relay"),
|
||||
("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"),
|
||||
("Login", "Iniciar sesión"),
|
||||
@@ -228,7 +228,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Olvidó su nombre de usuario"),
|
||||
("Password missed", "Olvidó su contraseña"),
|
||||
("Wrong credentials", "Credenciales incorrectas"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("The verification code is incorrect or has expired", "El código de verificación es incorrecto o ha caducado"),
|
||||
("Edit Tag", "Editar tag"),
|
||||
("Forget Password", "Olvidar contraseña"),
|
||||
("Favorites", "Favoritos"),
|
||||
@@ -302,8 +302,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
|
||||
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
|
||||
("android_open_battery_optimizations_tip", "Si deseas deshabilitar esta característica, por favor, ve a la página siguiente de ajustes, busca y entra en [Batería] y desmarca [Sin restricción]"),
|
||||
("Start on boot", ""),
|
||||
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||
("Start on boot", "Iniciar al arrancar"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Iniciar el servicio de pantalla compartida al arrancar, requiere permisos especiales"),
|
||||
("Connection not allowed", "Conexión no disponible"),
|
||||
("Legacy mode", "Modo heredado"),
|
||||
("Map mode", "Modo mapa"),
|
||||
@@ -326,8 +326,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Ratio", "Relación"),
|
||||
("Image Quality", "Calidad de imagen"),
|
||||
("Scroll Style", "Estilo de desplazamiento"),
|
||||
("Show Toolbar", ""),
|
||||
("Hide Toolbar", ""),
|
||||
("Show Toolbar", "Mostrar herramientas"),
|
||||
("Hide Toolbar", "Ocultar herramientas"),
|
||||
("Direct Connection", "Conexión directa"),
|
||||
("Relay Connection", "Conexión Relay"),
|
||||
("Secure Connection", "Conexión segura"),
|
||||
@@ -338,7 +338,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Security", "Seguridad"),
|
||||
("Theme", "Tema"),
|
||||
("Dark Theme", "Tema Oscuro"),
|
||||
("Light Theme", ""),
|
||||
("Light Theme", "Tema claro"),
|
||||
("Dark", "Oscuro"),
|
||||
("Light", "Claro"),
|
||||
("Follow System", "Tema del sistema"),
|
||||
@@ -355,12 +355,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input Device", "Dispositivo de entrada de audio"),
|
||||
("Use IP Whitelisting", "Usar lista de IPs admitidas"),
|
||||
("Network", "Red"),
|
||||
("Pin Toolbar", ""),
|
||||
("Unpin Toolbar", ""),
|
||||
("Pin Toolbar", "Anclar herramientas"),
|
||||
("Unpin Toolbar", "Desanclar herramientas"),
|
||||
("Recording", "Grabando"),
|
||||
("Directory", "Directorio"),
|
||||
("Automatically record incoming sessions", "Grabación automática de sesiones entrantes"),
|
||||
("Automatically record outgoing sessions", ""),
|
||||
("Automatically record outgoing sessions", "Grabación automática de sesiones salientes"),
|
||||
("Change", "Cambiar"),
|
||||
("Start session recording", "Comenzar grabación de sesión"),
|
||||
("Stop session recording", "Detener grabación de sesión"),
|
||||
@@ -368,7 +368,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable LAN discovery", "Habilitar descubrimiento de LAN"),
|
||||
("Deny LAN discovery", "Denegar descubrimiento de LAN"),
|
||||
("Write a message", "Escribir un mensaje"),
|
||||
("Prompt", ""),
|
||||
("Prompt", "Solicitud"),
|
||||
("Please wait for confirmation of UAC...", "Por favor, espera confirmación de UAC"),
|
||||
("elevated_foreground_window_tip", "La ventana actual del escritorio remoto necesita privilegios elevados para funcionar, así que no puedes usar ratón y teclado temporalmente. Puedes solicitar al usuario remoto que minimize la ventana actual o hacer clic en el botón de elevación de la ventana de gestión de conexión. Para evitar este problema, se recomienda instalar el programa en el dispositivo remto."),
|
||||
("Disconnected", "Desconectado"),
|
||||
@@ -616,9 +616,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("During service is on", "Mientras el servicio está activo"),
|
||||
("Capture screen using DirectX", "Capturar pantalla con DirectX"),
|
||||
("Back", "Atrás"),
|
||||
("Apps", ""),
|
||||
("Volume up", "Bajar volumen"),
|
||||
("Volume down", "Subir volumen"),
|
||||
("Apps", "Aplicaciones"),
|
||||
("Volume up", "Subir volumen"),
|
||||
("Volume down", "Bajar volumen"),
|
||||
("Power", "Encendido"),
|
||||
("Telegram bot", "Bot de Telegram"),
|
||||
("enable-bot-tip", "Si activas esta característica puedes recibir código 2FA de tu bot. También puede funcionar como notificación de conexión."),
|
||||
@@ -651,7 +651,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Update client clipboard", "Actualizar portapapeles del cliente"),
|
||||
("Untagged", "Sin itiquetar"),
|
||||
("new-version-of-{}-tip", "Hay una nueva versión de {} disponible"),
|
||||
("Accessible devices", ""),
|
||||
("Accessible devices", "Dispositivos accesibles"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "Por favor, actualiza el cliente RustDesk a la versión {} o superior en el lado remoto"),
|
||||
("d3d_render_tip", "Al activar el renderizado D3D, la pantalla de control remoto puede verse negra en algunos equipos."),
|
||||
("Use D3D rendering", "Usar renderizado D3D"),
|
||||
@@ -689,9 +689,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Use WebSocket", "Usar WebSocket"),
|
||||
("Trackpad speed", "Velocidad de trackpad"),
|
||||
("Default trackpad speed", "Velocidad predeterminada de trackpad"),
|
||||
("Numeric one-time password", ""),
|
||||
("Enable IPv6 P2P connection", ""),
|
||||
("Enable UDP hole punching", ""),
|
||||
("Numeric one-time password", "Contraseña numérica de un solo uso"),
|
||||
("Enable IPv6 P2P connection", "Habilitar conexión IPv6 P2P"),
|
||||
("Enable UDP hole punching", "Habilitar perforación de agujero UDP"),
|
||||
("View camera", "Ver cámara"),
|
||||
("Enable camera", "Habilitar cámara"),
|
||||
("No cameras", "No hay cámaras"),
|
||||
@@ -708,8 +708,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to check if the user is an administrator.", "No se ha podido comprobar si el usuario es un administrador."),
|
||||
("Supported only in the installed version.", "Soportado solo en la versión instalada."),
|
||||
("elevation_username_tip", "Introduzca el nombre de usuario o dominio\\NombreDeUsuario"),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Preparing for installation ...", "Preparando instlación..."),
|
||||
("Show my cursor", "Mostrar mi cursor"),
|
||||
("Scale custom", "Escala personalizada"),
|
||||
("Custom scale slider", "Control deslizante de escala personalizada"),
|
||||
("Decrease", "Disminuir"),
|
||||
@@ -721,28 +721,29 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show virtual joystick", "Mostrar joystick virtual"),
|
||||
("Edit note", "Editar nota"),
|
||||
("Alias", ""),
|
||||
("ScrollEdge", ""),
|
||||
("Allow insecure TLS fallback", ""),
|
||||
("allow-insecure-tls-fallback-tip", ""),
|
||||
("Disable UDP", ""),
|
||||
("disable-udp-tip", ""),
|
||||
("server-oss-not-support-tip", ""),
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("ScrollEdge", "Desplazamiento de pantalla"),
|
||||
("Allow insecure TLS fallback", "Permitir conexión TLS insegura de respaldo"),
|
||||
("allow-insecure-tls-fallback-tip", "De forma predeterminada, RustDesk verifica el certificado de servidor para protocolos que usen TLS.\nCon esta opción habilitada, Rustdesk volverá al paso de omisión de verificación y procederá en caso de fallo de verificación."),
|
||||
("Disable UDP", "Inhabilitar UDP"),
|
||||
("disable-udp-tip", "Controla si se usa TCP solamente.\nCuando esta opción está activa, RustDesk no usará más el puerto UDP 21116, en su lugar se usará el TCP 21116."),
|
||||
("server-oss-not-support-tip", "NOTA: El servidor RustDesk OSS no incluye esta característica."),
|
||||
("input note here", "Introducir nota aquí"),
|
||||
("note-at-conn-end-tip", "Pedir nota al finalizar la conexión"),
|
||||
("Show terminal extra keys", "Mostrar teclas extra del terminal"),
|
||||
("Relative mouse mode", "Modo de ratón relativo"),
|
||||
("rel-mouse-not-supported-peer-tip", "El modo relativo de ratón no está soportado por el par."),
|
||||
("rel-mouse-not-ready-tip", "El modo relativo de ratón aún no está preparado. Por favor, inténtalo de nuevo."),
|
||||
("rel-mouse-lock-failed-tip", "Ha fallado el bloqueo del cursor. El modo relativo del ratón ha sido inhabilitado."),
|
||||
("rel-mouse-exit-{}-tip", "Pulsa {} para salir."),
|
||||
("rel-mouse-permission-lost-tip", "Permiso de teclado revocado. El modo relativo del ratón ha sido inhabilitado."),
|
||||
("Changelog", "Registro de cambios"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Mantener la pantalla activa durante sesiones salientes"),
|
||||
("keep-awake-during-incoming-sessions-label", "Mantener la pantalla activa durante sesiones entrantes"),
|
||||
("Continue with {}", "Continuar con {}"),
|
||||
("Display Name", ""),
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("Display Name", "Nombre de pantalla"),
|
||||
("password-hidden-tip", "La contraseña permanente está ajustada a (oculta)."),
|
||||
("preset-password-in-use-tip", "Se está usando la contraseña predeterminada."),
|
||||
("Enable privacy mode", "Habilitar modo privado"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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é"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -654,6 +654,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accessible devices", "એક્સેસિબલ ઉપકરણો"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "રિમોટ ક્લાયન્ટને {} માં અપગ્રેડ કરો"),
|
||||
("d3d_render_tip", "D3D રેન્ડરિંગ વાપરો"),
|
||||
("Use D3D rendering", ""),
|
||||
("Printer", "પ્રિન્ટર"),
|
||||
("printer-os-requirement-tip", "પ્રિન્ટિંગ માટે Windows જરૂરી છે."),
|
||||
("printer-requires-installed-{}-client-tip", "આ માટે {} ક્લાયન્ટ ઇન્સ્ટોલ હોવું જોઈએ."),
|
||||
@@ -743,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "સુરક્ષા માટે પાસવર્ડ છુપાવેલ છે."),
|
||||
("preset-password-in-use-tip", "પ્રીસેટ પાસવર્ડ વપરાશમાં છે."),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -654,6 +654,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accessible devices", "सुलभ डिवाइस"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "रिमोट RustDesk क्लाइंट को संस्करण {} में अपग्रेड करें"),
|
||||
("d3d_render_tip", "D3D रेंडरिंग का उपयोग करें"),
|
||||
("Use D3D rendering", ""),
|
||||
("Printer", "प्रिंटर"),
|
||||
("printer-os-requirement-tip", "प्रिंटिंग के लिए Windows आवश्यक है।"),
|
||||
("printer-requires-installed-{}-client-tip", "इसके लिए क्लाइंट साइड पर {} इंस्टॉल होना चाहिए।"),
|
||||
@@ -742,5 +743,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Display Name", "प्रदर्शित नाम"),
|
||||
("password-hidden-tip", "पासवर्ड सुरक्षा के लिए छिपा हुआ है।"),
|
||||
("preset-password-in-use-tip", "पूर्व-निर्धारित पासवर्ड उपयोग में है।"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", "Adatvédelmi mód aktiválása"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "永続的なパスワードが設定されています (非表示)"),
|
||||
("preset-password-in-use-tip", "プリセットパスワードが現在使用されています"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "영구 비밀번호가 설정되었습니다 (숨김)."),
|
||||
("preset-password-in-use-tip", "현재 사전 설정된 비밀번호가 사용 중입니다."),
|
||||
("Enable privacy mode", "개인정보 보호 모드 사용함"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -654,6 +654,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accessible devices", "ലഭ്യമായ ഉപകരണങ്ങൾ"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "റിമോട്ട് പതിപ്പ് {} ലേക്ക് മാറ്റുക"),
|
||||
("d3d_render_tip", "D3D റെൻഡറിംഗ് ഉപയോഗിക്കുക"),
|
||||
("Use D3D rendering", ""),
|
||||
("Printer", "പ്രിന്റർ"),
|
||||
("printer-os-requirement-tip", "പ്രിന്റിംഗിന് വിൻഡോസ് വേണം."),
|
||||
("printer-requires-installed-{}-client-tip", "ഇതിന് {} ക്ലയന്റ് ഇൻസ്റ്റാൾ ചെയ്യണം."),
|
||||
@@ -742,5 +743,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Display Name", "ഡിസ്പ്ലേ പേര്"),
|
||||
("password-hidden-tip", "സുരക്ഷയ്ക്കായി പാസ്വേഡ് മറച്ചിരിക്കുന്നു."),
|
||||
("preset-password-in-use-tip", "പ്രീസെറ്റ് പാസ്വേഡ് ഉപയോഗത്തിലാണ്."),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -743,6 +743,7 @@ 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", "Schakel privacymodus in"),
|
||||
("Enable privacy mode", "Privacymodus inschakelen"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "Ustawiono (ukryto) stare hasło."),
|
||||
("preset-password-in-use-tip", "Obecnie używane jest hasło domyślne."),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -740,9 +740,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("keep-awake-during-outgoing-sessions-label", "Manter tela ativa durante sessões de saída"),
|
||||
("keep-awake-during-incoming-sessions-label", "Manter tela ativa durante sessões de entrada"),
|
||||
("Continue with {}", "Continuar com {}"),
|
||||
("Display Name", ""),
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("Display Name", "Nome de Exibição"),
|
||||
("password-hidden-tip", "A senha permanente está definida como (oculta)."),
|
||||
("preset-password-in-use-tip", "A senha predefinida está sendo usada."),
|
||||
("Enable privacy mode", "Habilitar modo de privacidade"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -540,7 +540,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("auto_disconnect_option_tip", "Deconectează automat sesiunile de la distanță după o perioadă de inactivitate."),
|
||||
("Connection failed due to inactivity", "Conexiunea a eșuat din cauza inactivității"),
|
||||
("Check for software update on startup", "Verifică actualizări la pornire"),
|
||||
("upgrade_rustdesk_server_pro_{}_tip", "Versiunea serverului RustDesk Pro este mai mică decât {}. Te rugăm să o actualizezi."),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Versiunea serverului RustDesk Pro este mai mică decât {}. Te rugăm să o actualizezi."),
|
||||
("pull_group_failed_tip", "Sincronizarea grupului a eșuat. Verifică conexiunea la rețea sau autentifică-te din nou."),
|
||||
("Filter by intersection", "Filtrează prin intersecție"),
|
||||
("Remove wallpaper during incoming sessions", "Elimină imaginea de fundal în timpul sesiunilor primite"),
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "Установлен постоянный пароль (скрытый)."),
|
||||
("preset-password-in-use-tip", "Установленный пароль сейчас используется."),
|
||||
("Enable privacy mode", "Использовать режим конфиденциальности"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "Parola gizli"),
|
||||
("preset-password-in-use-tip", "Önceden ayarlanmış parola kullanılıyor"),
|
||||
("Enable privacy mode", "Gizlilik modunu etkinleştir"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "固定密碼已設定(已隱藏)"),
|
||||
("preset-password-in-use-tip", "目前正在使用預設密碼"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -744,5 +744,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -614,6 +614,7 @@ fn authorize_service_scoped_ipc_connection(
|
||||
peer_session_id,
|
||||
expected_active_session_id,
|
||||
peer_is_system,
|
||||
None,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user