mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-19 06:15:02 +03:00
Compare commits
11 Commits
a658e987b7
...
1.4.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c574a4182 | ||
|
|
311d4708e5 | ||
|
|
5cf4323d07 | ||
|
|
3976701ac6 | ||
|
|
9ded8d6ab2 | ||
|
|
cd2fff0655 | ||
|
|
10f61ffdc2 | ||
|
|
d72952bf93 | ||
|
|
a7c55db9ac | ||
|
|
bff47e2b81 | ||
|
|
3d478c4935 |
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -1324,7 +1324,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "clipboard-master"
|
||||
version = "4.0.0-beta.6"
|
||||
source = "git+https://github.com/rustdesk-org/clipboard-master#ddc39f00a6211959489ae683aa6ae6eedf03a809"
|
||||
source = "git+https://github.com/rustdesk-org/clipboard-master#7762d74e38db37cfeb6ded88c964b9cdbddfb6db"
|
||||
dependencies = [
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
@@ -6920,7 +6920,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rdev"
|
||||
version = "0.5.0-2"
|
||||
source = "git+https://github.com/rustdesk-org/rdev#f9b60b1dd0f3300a1b797d7a74c116683cd232c8"
|
||||
source = "git+https://github.com/rustdesk-org/rdev#871bf1c856d6a30af2f56ab8848396a025140855"
|
||||
dependencies = [
|
||||
"cocoa 0.24.1",
|
||||
"core-foundation 0.9.4",
|
||||
@@ -9733,9 +9733,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.3"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953"
|
||||
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"wayland-backend",
|
||||
@@ -10838,16 +10838,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wl-clipboard-rs"
|
||||
version = "0.9.0"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4de22eebb1d1e2bad2d970086e96da0e12cde0b411321e5b0f7b2a1f876aa26f"
|
||||
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"os_pipe",
|
||||
"rustix 0.38.34",
|
||||
"tempfile",
|
||||
"thiserror 1.0.61",
|
||||
"rustix 1.1.2",
|
||||
"thiserror 2.0.17",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
|
||||
7
flutter/assets/display_switcher.svg
Normal file
7
flutter/assets/display_switcher.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<g fill="#000000" fill-rule="evenodd">
|
||||
<rect x="4" y="6" width="24" height="16" rx="3"/>
|
||||
<rect x="14.5" y="22" width="3" height="2"/>
|
||||
<rect x="9.5" y="24" width="13" height="2.5" rx="1.25"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 303 B |
@@ -174,6 +174,8 @@ const String kOptionShowVirtualMouse = "show-virtual-mouse";
|
||||
const String kOptionVirtualMouseScale = "virtual-mouse-scale";
|
||||
const String kOptionShowVirtualJoystick = "show-virtual-joystick";
|
||||
const String kOptionAllowAskForNoteAtEndOfConnection = "allow-ask-for-note";
|
||||
const String kOptionAllowMonitorSwitchMainToolbar = "allow-monitor-switch-main-toolbar";
|
||||
const String kOptionAllowMonitorSwitchMinToolbar = "allow-monitor-switch-min-toolbar";
|
||||
const String kOptionEnableShowTerminalExtraKeys = "enable-show-terminal-extra-keys";
|
||||
|
||||
// network options
|
||||
|
||||
@@ -407,6 +407,7 @@ class _GeneralState extends State<_General> {
|
||||
final RxBool serviceStop =
|
||||
isWeb ? RxBool(false) : Get.find<RxBool>(tag: 'stop-service');
|
||||
RxBool serviceBtnEnabled = true.obs;
|
||||
final GlobalKey _minToolbarOptionKey = GlobalKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -605,6 +606,47 @@ class _GeneralState extends State<_General> {
|
||||
},
|
||||
));
|
||||
}
|
||||
children.add(_OptionCheckBox(
|
||||
context,
|
||||
'Show monitor switch button on the main toolbar',
|
||||
kOptionAllowMonitorSwitchMainToolbar,
|
||||
isServer: false,
|
||||
update: (enabled) async {
|
||||
if (!enabled) {
|
||||
await mainSetLocalBoolOption(
|
||||
kOptionAllowMonitorSwitchMinToolbar, false);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
reloadAllWindows();
|
||||
if (enabled) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final ctx = _minToolbarOptionKey.currentContext;
|
||||
if (ctx != null) {
|
||||
Scrollable.ensureVisible(
|
||||
ctx,
|
||||
alignment: 0.5,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
));
|
||||
if (mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMainToolbar)) {
|
||||
children.add(KeyedSubtree(
|
||||
key: _minToolbarOptionKey,
|
||||
child: _OptionCheckBox(
|
||||
context,
|
||||
'Show on the minimized toolbar',
|
||||
kOptionAllowMonitorSwitchMinToolbar,
|
||||
isServer: false,
|
||||
update: (_) {
|
||||
reloadAllWindows();
|
||||
},
|
||||
).marginOnly(left: _kCheckBoxLeftMargin * 3),
|
||||
));
|
||||
}
|
||||
return _Card(title: 'Other', children: children);
|
||||
}
|
||||
|
||||
|
||||
@@ -779,6 +779,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
borderRadius: borderRadius,
|
||||
child: _DraggableShowHide(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
sessionId: widget.ffi.sessionId,
|
||||
dragging: _dragging,
|
||||
fraction: _fraction,
|
||||
@@ -805,6 +806,17 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
BuildContext context, _ToolbarEdge edge, bool isHorizontal) {
|
||||
final List<Widget> toolbarItems = [];
|
||||
toolbarItems.add(_PinMenu(state: widget.state));
|
||||
toolbarItems.add(Obx(() {
|
||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||
if ((privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
|
||||
pi.displaysCount.value > 1 &&
|
||||
mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMainToolbar)) {
|
||||
return _MainMonitorSwitchButton(id: widget.id, ffi: widget.ffi);
|
||||
} else {
|
||||
return const Offstage();
|
||||
}
|
||||
}));
|
||||
if (!isWebDesktop) {
|
||||
toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
|
||||
}
|
||||
@@ -965,6 +977,88 @@ class _MobileActionMenu extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _MonitorCycle {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
const _MonitorCycle(this.id, this.ffi);
|
||||
|
||||
PeerInfo get _pi => ffi.ffiModel.pi;
|
||||
int get total => _pi.displays.length;
|
||||
int get _current => CurrentDisplayState.find(id).value;
|
||||
bool get _inRange => _current >= 0 && _current < total;
|
||||
|
||||
String get label => _inRange ? '${_current + 1}' : '*';
|
||||
String get tooltip => '${translate('Switch display')} ($label/$total)';
|
||||
|
||||
void next() {
|
||||
final t = total;
|
||||
if (t < 2) return;
|
||||
final from = _inRange ? _current : -1;
|
||||
final target = (from + 1) % t;
|
||||
final isChooseDisplayToOpenInNewWindow = _pi.isSupportMultiDisplay &&
|
||||
bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) ==
|
||||
'Y';
|
||||
if (isChooseDisplayToOpenInNewWindow) {
|
||||
openMonitorInNewTabOrWindow(target, ffi.id, _pi);
|
||||
} else {
|
||||
openMonitorInTheSameTab(target, ffi, _pi, updateCursorPos: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _MainMonitorSwitchButton extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
|
||||
const _MainMonitorSwitchButton({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cycle = _MonitorCycle(id, ffi);
|
||||
return Obx(() {
|
||||
if (cycle.total < 2) return const Offstage();
|
||||
final label = cycle.label;
|
||||
|
||||
return _IconMenuButton(
|
||||
tooltip: cycle.tooltip,
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
onPressed: cycle.next,
|
||||
icon: SizedBox(
|
||||
width: _ToolbarTheme.buttonSize,
|
||||
height: _ToolbarTheme.buttonSize,
|
||||
child: Stack(
|
||||
alignment: const Alignment(0, -0.125),
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/display_switcher.svg',
|
||||
colorFilter:
|
||||
const ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
width: _ToolbarTheme.buttonSize,
|
||||
height: _ToolbarTheme.buttonSize,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 11,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _MonitorMenu extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
@@ -2971,6 +3065,7 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
|
||||
class _DraggableShowHide extends StatefulWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
final SessionID sessionId;
|
||||
final RxDouble fraction;
|
||||
final Rx<_ToolbarEdge> edge;
|
||||
@@ -2994,6 +3089,7 @@ class _DraggableShowHide extends StatefulWidget {
|
||||
const _DraggableShowHide({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
required this.sessionId,
|
||||
required this.fraction,
|
||||
required this.edge,
|
||||
@@ -3250,6 +3346,9 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildDraggable(context),
|
||||
Obx(() => collapse.isTrue
|
||||
? _MinimizedMonitorSwitchButton(id: widget.id, ffi: widget.ffi)
|
||||
: const Offstage()),
|
||||
Obx(() => buttonWrapper(
|
||||
() {
|
||||
widget.setFullscreen(!isFullscreen.value);
|
||||
@@ -3410,3 +3509,73 @@ class EdgeThicknessControl extends StatelessWidget {
|
||||
return slider;
|
||||
}
|
||||
}
|
||||
|
||||
class _MinimizedMonitorSwitchButton extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
|
||||
const _MinimizedMonitorSwitchButton({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double iconSize = 20;
|
||||
final cycle = _MonitorCycle(id, ffi);
|
||||
|
||||
return Obx(() {
|
||||
final label = cycle.label;
|
||||
if (!mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMainToolbar) ||
|
||||
!mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMinToolbar)) {
|
||||
return const Offstage();
|
||||
}
|
||||
if (cycle.total < 2) return const Offstage();
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if (privacyModeState.isNotEmpty &&
|
||||
!allowDisplaySwitchInPrivacyMode(
|
||||
ffi.ffiModel.pi, privacyModeState.value)) {
|
||||
return const Offstage();
|
||||
}
|
||||
|
||||
return Tooltip(
|
||||
message: cycle.tooltip,
|
||||
child: TextButton(
|
||||
onPressed: cycle.next,
|
||||
style: ButtonStyle(
|
||||
minimumSize: MaterialStateProperty.all(const Size(0, 0)),
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return _ToolbarTheme.blueColor.withOpacity(0.15);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: const Alignment(0, -0.125),
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/display_switcher.svg',
|
||||
colorFilter:
|
||||
ColorFilter.mode(_ToolbarTheme.blueColor, BlendMode.srcIn),
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1307,7 +1307,8 @@ class InputModel {
|
||||
}
|
||||
if (isPhysicalMouse.value) {
|
||||
if (!_relativeMouse.handleRelativeMouseMove(e.localPosition)) {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position,
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), canvasPosition,
|
||||
edgeScroll: useEdgeScroll);
|
||||
}
|
||||
}
|
||||
@@ -1548,7 +1549,8 @@ class InputModel {
|
||||
_relativeMouse
|
||||
.sendRelativeMouseButton(_getMouseEvent(e, _kMouseEventDown));
|
||||
} else {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventDown), e.position);
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventDown), canvasPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,7 +1572,8 @@ class InputModel {
|
||||
_relativeMouse
|
||||
.sendRelativeMouseButton(_getMouseEvent(e, _kMouseEventUp));
|
||||
} else {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position);
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventUp), canvasPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1592,12 +1595,40 @@ class InputModel {
|
||||
}
|
||||
if (isPhysicalMouse.value) {
|
||||
if (!_relativeMouse.handleRelativeMouseMove(e.localPosition)) {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position,
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), canvasPosition,
|
||||
edgeScroll: useEdgeScroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert pointer coordinates into the visible remote canvas space.
|
||||
///
|
||||
/// On mobile, the remote page body is wrapped in `SafeArea`, but the pointer
|
||||
/// listener that feeds these events sits outside that subtree. As a result,
|
||||
/// `event.localPosition` still includes the top/left safe-area inset.
|
||||
///
|
||||
/// When the keyboard-visible path shows `KeyHelpTools`, the remote canvas is
|
||||
/// also shifted downward by `CanvasModel.getAdjustY()`. The downstream mouse
|
||||
/// mapping logic expects coordinates relative to the visible canvas area, so
|
||||
/// we subtract both the mobile safe-area padding and the current canvas
|
||||
/// adjustment before passing the position into mouse mapping.
|
||||
///
|
||||
/// Desktop and web desktop continue to use the global position directly
|
||||
/// because their pointer mapping is window-based.
|
||||
Offset _pointerPositionForRemoteCanvas(PointerEvent event) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
return event.position;
|
||||
}
|
||||
final mediaData = MediaQueryData.fromView(
|
||||
WidgetsBinding.instance.platformDispatcher.views.first);
|
||||
final adjustY = parent.target?.canvasModel.getAdjustY() ?? 0.0;
|
||||
return Offset(
|
||||
event.localPosition.dx - mediaData.padding.left,
|
||||
event.localPosition.dy - mediaData.padding.top - adjustY,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Rect?> fillRemoteCoordsAndGetCurFrame(
|
||||
List<RemoteWindowCoords> remoteWindowCoords) async {
|
||||
final coords =
|
||||
|
||||
@@ -868,6 +868,7 @@ pub mod clipboard_listener {
|
||||
.unwrap()
|
||||
.insert(name.clone(), tx);
|
||||
|
||||
cleanup_stale_listener(&mut listener_lock);
|
||||
if listener_lock.handle.is_none() {
|
||||
log::info!("Start clipboard listener thread");
|
||||
let handler = Handler {
|
||||
@@ -893,6 +894,24 @@ pub mod clipboard_listener {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_stale_listener(listener: &mut ClipboardListener) {
|
||||
if !listener
|
||||
.handle
|
||||
.as_ref()
|
||||
.map(|(_, h)| h.is_finished())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some((shutdown, h)) = listener.handle.take() {
|
||||
log::warn!("Cleaning up stale clipboard listener handle");
|
||||
if let Err(e) = h.join() {
|
||||
log::error!("Clipboard listener thread panicked during stale cleanup: {:?}", e);
|
||||
}
|
||||
drop(shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsubscribe(name: &str) {
|
||||
log::info!("Unsubscribe clipboard listener: {}", name);
|
||||
let mut listener_lock = CLIPBOARD_LISTENER.lock().unwrap();
|
||||
|
||||
@@ -1245,11 +1245,49 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
|
||||
|
||||
#[inline]
|
||||
pub fn map_keyboard_mode(_peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
|
||||
if let Some(evt) = windows_peer_special_key(_peer, event) {
|
||||
return vec![evt];
|
||||
}
|
||||
|
||||
_map_keyboard_mode(_peer, event, key_event)
|
||||
.map(|e| vec![e])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn windows_peer_special_key(peer: &str, event: &Event) -> Option<KeyEvent> {
|
||||
if peer != OS_LOWER_WINDOWS {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (key, down) = match event.event_type {
|
||||
EventType::KeyPress(key) => (key, true),
|
||||
EventType::KeyRelease(key) => (key, false),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Handle only `Pause` for Windows peers for now.
|
||||
// Windows has no normal scan code for `Pause`, so send it as a legacy control key.
|
||||
#[cfg(target_os = "windows")]
|
||||
let is_pause = {
|
||||
// The Windows scan code can look like `NumLock`; VK_PAUSE distinguishes it.
|
||||
let pause_vk_code = rdev::win_code_from_key(Key::Pause);
|
||||
key == Key::Pause || pause_vk_code == Some(event.platform_code as _)
|
||||
};
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let is_pause = key == Key::Pause;
|
||||
if !is_pause {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.mode = KeyboardMode::Legacy.into();
|
||||
key_event.down = down;
|
||||
key_event.set_control_key(ControlKey::Pause);
|
||||
let (alt, ctrl, shift, command) = client::get_modifiers_state(false, false, false, false);
|
||||
client::legacy_modifiers(&mut key_event, alt, ctrl, shift, command);
|
||||
Some(key_event)
|
||||
}
|
||||
|
||||
fn _map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
|
||||
match event.event_type {
|
||||
EventType::KeyPress(..) => {
|
||||
@@ -1421,6 +1459,11 @@ fn is_press(event: &Event) -> bool {
|
||||
pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
|
||||
let mut events: Vec<KeyEvent> = Vec::new();
|
||||
|
||||
if let Some(evt) = windows_peer_special_key(peer, event) {
|
||||
events.push(evt);
|
||||
return events;
|
||||
}
|
||||
|
||||
if let Some(unicode_info) = &event.unicode {
|
||||
if unicode_info.is_dead {
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "إعادة تعيين اختيار إدخال لوحة المفاتيح"),
|
||||
("remember-wayland-keyboard-choice-tip", "لا تسأل مرة أخرى لهذا الكمبيوتر البعيد"),
|
||||
("Why this happens", "سبب حدوث ذلك"),
|
||||
("Switch display", "تبديل الشاشة"),
|
||||
("Show monitor switch button on the main toolbar", "إظهار زر تبديل الشاشة على شريط الأدوات الرئيسي"),
|
||||
("Show on the minimized toolbar", "الإظهار على شريط الأدوات المُصغّر"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Скінуць выбар уводу з клавіятуры"),
|
||||
("remember-wayland-keyboard-choice-tip", "Не пытацца зноў для гэтага аддаленага кампутара"),
|
||||
("Why this happens", "Чаму гэта адбываецца"),
|
||||
("Switch display", "Пераключыць дысплэй"),
|
||||
("Show monitor switch button on the main toolbar", "Паказваць кнопку пераключэння манітора на галоўнай панэлі інструментаў"),
|
||||
("Show on the minimized toolbar", "Паказваць на згорнутай панэлі інструментаў"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Нулиране на избора за въвеждане от клавиатура"),
|
||||
("remember-wayland-keyboard-choice-tip", "Не питай отново за този отдалечен компютър"),
|
||||
("Why this happens", "Защо се случва това"),
|
||||
("Switch display", "Превключване на дисплея"),
|
||||
("Show monitor switch button on the main toolbar", "Показване на бутона за превключване на монитора в главната лента с инструменти"),
|
||||
("Show on the minimized toolbar", "Показване в минимизираната лента с инструменти"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Restableix l'opció d'entrada de teclat"),
|
||||
("remember-wayland-keyboard-choice-tip", "No tornis a preguntar-ho per a aquest equip remot"),
|
||||
("Why this happens", "Per què passa això"),
|
||||
("Switch display", "Canvia de pantalla"),
|
||||
("Show monitor switch button on the main toolbar", "Mostra el botó de canvi de monitor a la barra d’eines principal"),
|
||||
("Show on the minimized toolbar", "Mostra a la barra d’eines minimitzada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "重置键盘输入选择"),
|
||||
("remember-wayland-keyboard-choice-tip", "以后对这台远程电脑不再询问"),
|
||||
("Why this happens", "了解原因"),
|
||||
("Switch display", "切换显示器"),
|
||||
("Show monitor switch button on the main toolbar", "在主工具栏上显示显示器切换按钮"),
|
||||
("Show on the minimized toolbar", "在最小化工具栏上显示"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Resetovat volbu vstupu z klávesnice"),
|
||||
("remember-wayland-keyboard-choice-tip", "Pro tento vzdálený počítač se již neptat"),
|
||||
("Why this happens", "Proč k tomu dochází"),
|
||||
("Switch display", "Přepnout obrazovku"),
|
||||
("Show monitor switch button on the main toolbar", "Zobrazit tlačítko přepnutí monitoru na hlavním panelu nástrojů"),
|
||||
("Show on the minimized toolbar", "Zobrazit na minimalizovaném panelu nástrojů"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Nulstil valg for tastaturinput"),
|
||||
("remember-wayland-keyboard-choice-tip", "Spørg ikke igen for denne fjerncomputer"),
|
||||
("Why this happens", "Hvorfor dette sker"),
|
||||
("Switch display", "Skift skærm"),
|
||||
("Show monitor switch button on the main toolbar", "Vis knap til skærmskift på hovedværktøjslinjen"),
|
||||
("Show on the minimized toolbar", "Vis på den minimerede værktøjslinje"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Auswahl der Tastatureingabe zurücksetzen"),
|
||||
("remember-wayland-keyboard-choice-tip", "Für diesen entfernten Computer nicht erneut fragen"),
|
||||
("Why this happens", "Warum dies passiert"),
|
||||
("Switch display", "Anzeige wechseln"),
|
||||
("Show monitor switch button on the main toolbar", "Schaltfläche zum Monitorwechsel in der Haupt-Symbolleiste anzeigen"),
|
||||
("Show on the minimized toolbar", "In der minimierten Symbolleiste anzeigen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Επαναφορά επιλογής εισαγωγής από πληκτρολόγιο"),
|
||||
("remember-wayland-keyboard-choice-tip", "Να μην ερωτηθώ ξανά για αυτόν τον απομακρυσμένο υπολογιστή"),
|
||||
("Why this happens", "Γιατί συμβαίνει αυτό"),
|
||||
("Switch display", "Εναλλαγή οθόνης"),
|
||||
("Show monitor switch button on the main toolbar", "Εμφάνιση κουμπιού εναλλαγής οθόνης στην κύρια γραμμή εργαλείων"),
|
||||
("Show on the minimized toolbar", "Εμφάνιση στην ελαχιστοποιημένη γραμμή εργαλείων"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Restarigi la elekton de klavara enigo"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne demandi denove por ĉi tiu fora komputilo"),
|
||||
("Why this happens", "Kial ĉi tio okazas"),
|
||||
("Switch display", "Ŝalti ekranon"),
|
||||
("Show monitor switch button on the main toolbar", "Montri ekran-ŝaltan butonon en la ĉefa ilobreto"),
|
||||
("Show on the minimized toolbar", "Montri en la minimumigita ilobreto"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Restablecer la opción de entrada del teclado"),
|
||||
("remember-wayland-keyboard-choice-tip", "No volver a preguntar para este equipo remoto"),
|
||||
("Why this happens", "Por qué ocurre esto"),
|
||||
("Switch display", "Cambiar de pantalla"),
|
||||
("Show monitor switch button on the main toolbar", "Mostrar el botón de cambio de monitor en la barra de herramientas principal"),
|
||||
("Show on the minimized toolbar", "Mostrar en la barra de herramientas minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Lähtesta klaviatuurisisestuse valik"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ära küsi selle kaugarvuti puhul uuesti"),
|
||||
("Why this happens", "Miks see juhtub"),
|
||||
("Switch display", "Vaheta kuva"),
|
||||
("Show monitor switch button on the main toolbar", "Näita monitori vahetamise nuppu peamisel tööriistaribal"),
|
||||
("Show on the minimized toolbar", "Näita minimeeritud tööriistaribal"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Berrezarri teklatuko sarreraren aukera"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ez galdetu berriro urruneko ordenagailu honetarako"),
|
||||
("Why this happens", "Zergatik gertatzen den hau"),
|
||||
("Switch display", "Aldatu pantaila"),
|
||||
("Show monitor switch button on the main toolbar", "Erakutsi monitorea aldatzeko botoia tresna-barra nagusian"),
|
||||
("Show on the minimized toolbar", "Erakutsi minimizatutako tresna-barran"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "بازنشانی انتخاب ورودی صفحه کلید"),
|
||||
("remember-wayland-keyboard-choice-tip", "برای این رایانه از راه دور دوباره نپرس"),
|
||||
("Why this happens", "چرا این اتفاق میافتد"),
|
||||
("Switch display", "تعویض نمایشگر"),
|
||||
("Show monitor switch button on the main toolbar", "نمایش دکمه تعویض نمایشگر در نوار ابزار اصلی"),
|
||||
("Show on the minimized toolbar", "نمایش در نوار ابزار کوچکشده"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Nollaa näppäimistösyötteen valinta"),
|
||||
("remember-wayland-keyboard-choice-tip", "Älä kysy uudelleen tältä etätietokoneelta"),
|
||||
("Why this happens", "Miksi näin tapahtuu"),
|
||||
("Switch display", "Vaihda näyttöä"),
|
||||
("Show monitor switch button on the main toolbar", "Näytä näytön vaihtopainike päätyökalurivillä"),
|
||||
("Show on the minimized toolbar", "Näytä pienennetyssä työkalurivissä"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Réinitialiser le choix de la saisie au clavier"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne plus demander pour cet appareil distant"),
|
||||
("Why this happens", "Pourquoi cela se produit"),
|
||||
("Switch display", "Changer d’écran"),
|
||||
("Show monitor switch button on the main toolbar", "Afficher le bouton de changement d’écran dans la barre d’outils principale"),
|
||||
("Show on the minimized toolbar", "Afficher dans la barre d’outils réduite"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "კლავიატურის შეყვანის არჩევანის ჩამოყრა"),
|
||||
("remember-wayland-keyboard-choice-tip", "აღარ მკითხო ამ დისტანციური კომპიუტერისთვის"),
|
||||
("Why this happens", "რატომ ხდება ეს"),
|
||||
("Switch display", "ეკრანის გადართვა"),
|
||||
("Show monitor switch button on the main toolbar", "მონიტორის გადართვის ღილაკის ჩვენება მთავარ ხელსაწყოთა ზოლზე"),
|
||||
("Show on the minimized toolbar", "ჩვენება ჩაკეცილ ხელსაწყოთა ზოლზე"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "કીબોર્ડ ઇનપુટ પસંદગી રિસેટ કરો"),
|
||||
("remember-wayland-keyboard-choice-tip", "આ રિમોટ કમ્પ્યુટર માટે ફરીથી પૂછશો નહીં"),
|
||||
("Why this happens", "આવું શા માટે થાય છે"),
|
||||
("Switch display", "ડિસ્પ્લે બદલો"),
|
||||
("Show monitor switch button on the main toolbar", "મુખ્ય ટૂલબાર પર મોનિટર સ્વિચ બટન બતાવો"),
|
||||
("Show on the minimized toolbar", "ન્યૂનતમ કરેલા ટૂલબાર પર બતાવો"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "אפס את בחירת קלט המקלדת"),
|
||||
("remember-wayland-keyboard-choice-tip", "אל תשאל שוב עבור מחשב מרוחק זה"),
|
||||
("Why this happens", "מדוע זה קורה"),
|
||||
("Switch display", "החלפת צג"),
|
||||
("Show monitor switch button on the main toolbar", "הצגת לחצן החלפת צג בסרגל הכלים הראשי"),
|
||||
("Show on the minimized toolbar", "הצגה בסרגל הכלים הממוזער"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "कीबोर्ड इनपुट चयन रीसेट करें"),
|
||||
("remember-wayland-keyboard-choice-tip", "इस रिमोट कंप्यूटर के लिए दोबारा न पूछें"),
|
||||
("Why this happens", "ऐसा क्यों होता है"),
|
||||
("Switch display", "डिस्प्ले बदलें"),
|
||||
("Show monitor switch button on the main toolbar", "मुख्य टूलबार पर मॉनिटर स्विच बटन दिखाएं"),
|
||||
("Show on the minimized toolbar", "न्यूनतम किए गए टूलबार पर दिखाएं"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Poništi izbor unosa tipkovnicom"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne pitaj ponovno za ovo udaljeno računalo"),
|
||||
("Why this happens", "Zašto se ovo događa"),
|
||||
("Switch display", "Promijeni zaslon"),
|
||||
("Show monitor switch button on the main toolbar", "Prikaži gumb za prebacivanje monitora na glavnoj alatnoj traci"),
|
||||
("Show on the minimized toolbar", "Prikaži na minimiziranoj alatnoj traci"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Billentyűzetbevitel választásának visszaállítása"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne kérdezze meg újra ennél a távoli számítógépnél"),
|
||||
("Why this happens", "Miért történik ez"),
|
||||
("Switch display", "Kijelző váltása"),
|
||||
("Show monitor switch button on the main toolbar", "Monitorváltó gomb megjelenítése a fő eszköztáron"),
|
||||
("Show on the minimized toolbar", "Megjelenítés a kis méretű eszköztáron"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Setel ulang pilihan masukan keyboard"),
|
||||
("remember-wayland-keyboard-choice-tip", "Jangan tanya lagi untuk komputer jarak jauh ini"),
|
||||
("Why this happens", "Mengapa ini terjadi"),
|
||||
("Switch display", "Ganti tampilan"),
|
||||
("Show monitor switch button on the main toolbar", "Tampilkan tombol pengalih monitor di bilah alat utama"),
|
||||
("Show on the minimized toolbar", "Tampilkan di bilah alat yang diperkecil"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Ripristina scelta input da tastiera"),
|
||||
("remember-wayland-keyboard-choice-tip", "Non chiedere più per questo computer remoto"),
|
||||
("Why this happens", "Perché accade questo"),
|
||||
("Switch display", "Cambia schermo"),
|
||||
("Show monitor switch button on the main toolbar", "Visualizza nella barra strumenti principale il pulsante per il cambio schermo"),
|
||||
("Show on the minimized toolbar", "Visualizza nella barra strumenti ridotta a icona"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -197,9 +197,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please enter the folder name", "フォルダー名を入力してください"),
|
||||
("Fix it", "修復する"),
|
||||
("Warning", "警告"),
|
||||
("Login screen using Wayland is not supported", "Wayland を使用したログインスクリーンはサポートされていません"),
|
||||
("Login screen using Wayland is not supported", "Wayland を使用したログイン画面は対応していません"),
|
||||
("Reboot required", "再起動が必要です"),
|
||||
("Unsupported display server", "サポートされていないディスプレイサーバー"),
|
||||
("Unsupported display server", "非対応のディスプレイサーバー"),
|
||||
("x11 expected", "X11 が必要です"),
|
||||
("Port", "ポート"),
|
||||
("Settings", "設定"),
|
||||
@@ -268,11 +268,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Share screen", "画面を共有"),
|
||||
("Chat", "チャット"),
|
||||
("Total", "合計"),
|
||||
("items", "個のアイテム"),
|
||||
("items", "個の項目"),
|
||||
("Selected", "選択済み"),
|
||||
("Screen Capture", "画面キャプチャ"),
|
||||
("Screen Capture", "画面をキャプチャ"),
|
||||
("Input Control", "入力操作"),
|
||||
("Audio Capture", "音声キャプチャ"),
|
||||
("Audio Capture", "オーディオをキャプチャ"),
|
||||
("Do you accept?", "許可しますか?"),
|
||||
("Open System Setting", "システム設定を開く"),
|
||||
("How to get Android input permission?", "Android の入力権限を取得するには?"),
|
||||
@@ -281,7 +281,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_new_connection_tip", "新しい操作リクエストが届きました。この端末を操作しようとしています。"),
|
||||
("android_service_will_start_tip", "「画面キャプチャ」を有効にするとサービスが自動的に開始され、他の端末がこの端末への接続をリクエストできるようになります。"),
|
||||
("android_stop_service_tip", "サービスを停止すると、自動的に現在のセッションがすべて閉じられます。"),
|
||||
("android_version_audio_tip", "現在の Android バージョンでは音声キャプチャはサポートされていません。Android 10 以降に更新してください。"),
|
||||
("android_version_audio_tip", "使用している Android はオーディオキャプチャに対応していません。Android 10 以降に更新してください。"),
|
||||
("android_start_service_tip", "「サービスを開始」をタップするか、「画面キャプチャ」の許可を有効にすると、画面共有サービスが開始されます。"),
|
||||
("android_permission_may_not_change_tip", "権限の変更は現在のセッションには適用されません。再接続後に適用されます。"),
|
||||
("Account", "アカウント"),
|
||||
@@ -292,7 +292,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
("Someone turns on privacy mode, exit", "プライバシーモードがオンになりました。終了します。"),
|
||||
("Unsupported", "サポートされていません"),
|
||||
("Unsupported", "対応していません"),
|
||||
("Peer denied", "リモートホストに拒否されました"),
|
||||
("Please install plugins", "プラグインをインストールしてください"),
|
||||
("Peer exit", "リモートホストが退出しました"),
|
||||
@@ -376,10 +376,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Confirm before closing multiple tabs", "複数のタブを閉じる前に確認する"),
|
||||
("Keyboard Settings", "キーボードの設定"),
|
||||
("Full Access", "フルアクセス"),
|
||||
("Screen Share", "画面共有"),
|
||||
("Screen Share", "画面を共有"),
|
||||
("ubuntu-21-04-required", "Wayland を使用するには、Ubuntu 21.04 以降のバージョンが必要です。"),
|
||||
("wayland-requires-higher-linux-version", "Wayland を使用するには、より新しい Linux ディストリビューションが必要です。 X11 デスクトップを試すか、OS を変更してください。"),
|
||||
("xdp-portal-unavailable", "Wayland の画面キャプチャに失敗しました。XDG Desktop Portal がクラッシュしたか、利用できない可能性があります。`systemctl --user restart xdg-desktop-portal` で再起動してみてください。"),
|
||||
("xdp-portal-unavailable", "Wayland の画面キャプチャに失敗しました。XDG デスクトップポータルがクラッシュしたか、利用できない可能性があります。`systemctl --user restart xdg-desktop-portal` で再起動してみてください。"),
|
||||
("JumpLink", "表示"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "共有する画面を選択してください(リモートコンピューターが操作します)"),
|
||||
("Show RustDesk", "RustDesk を表示"),
|
||||
@@ -397,7 +397,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Request access to your device", "デバイスへのアクセス要求"),
|
||||
("Hide connection management window", "接続管理画面を隠す"),
|
||||
("hide_cm_tip", "パスワードによるセッションを許可し、固定パスワードを使用する場合にのみ、管理画面の非表示を許可する。"),
|
||||
("wayland_experiment_tip", "Wayland のサポートは試験的なものです。無人アクセスを使用する場合はX11デスクトップをご利用ください。"),
|
||||
("wayland_experiment_tip", "Wayland の対応は試験的なものです。無人アクセスを使用する場合はX11デスクトップをご利用ください。"),
|
||||
("Right click to select tabs", "右クリックでタブを選択"),
|
||||
("Skipped", "スキップ"),
|
||||
("Add to address book", "アドレス帳に追加"),
|
||||
@@ -568,7 +568,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input_source_1_tip", "入力ソース 1"),
|
||||
("input_source_2_tip", "入力ソース 2"),
|
||||
("Swap control-command key", "ctrl と command キーを入れ替える"),
|
||||
("swap-left-right-mouse", "マウスのクリックを入れ替える"),
|
||||
("swap-left-right-mouse", "マウスクリックを入れ替える"),
|
||||
("2FA code", "二要素認証コード"),
|
||||
("More", "詳細"),
|
||||
("enable-2fa-title", "二要素認証を有効化する"),
|
||||
@@ -601,10 +601,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("default_proxy_tip", "既定のプロトコルとポートは Socks5 と 1080 です。"),
|
||||
("no_audio_input_device_tip", "オーディオ入力デバイスが見つかりません。"),
|
||||
("Incoming", "受信"),
|
||||
("Outgoing", "発信"),
|
||||
("Clear Wayland screen selection", "Wayland の画面選択をクリア"),
|
||||
("clear_Wayland_screen_selection_tip", "画面選択をクリア後、共有画面を再び選択できます。"),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "本当に Wayland の画面選択をクリアしますか?"),
|
||||
("Outgoing", "送信"),
|
||||
("Clear Wayland screen selection", "Wayland の画面選択を消去"),
|
||||
("clear_Wayland_screen_selection_tip", "画面選択を消去後、共有画面を再び選択できます。"),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "本当に Wayland の画面選択を消去しますか?"),
|
||||
("android_new_voice_call_tip", "新しい音声通話リクエストを受信しました。承認すると音声通話に切り替わります。"),
|
||||
("texture_render_tip", "テクスチャレンダリングを使用し、画像をより滑らかに描画します。レンダリングの問題が発生した場合は無効にしてみてください。"),
|
||||
("Use texture rendering", "テクスチャレンダリングを使用する"),
|
||||
@@ -643,7 +643,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("one-way-file-transfer-tip", "コントロールをされる側では一方向のファイル転送が有効になります。"),
|
||||
("Authentication Required", "認証が必要です"),
|
||||
("Authenticate", "認証"),
|
||||
("web_id_input_tip", "同じサーバー内の ID を入力できます。Web クライアントでは直接 IP アドレスによるアクセスはサポートされていません。\n別のサーバー上のデバイスにアクセスする場合は、サーバーアドレス (<id>@<server_address>?key=<key_value>) を入力してください。\n 例: 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\nパブリックサーバー上のデバイスにアクセスする場合は、「<id>@public」と入力してください。パブリックサーバーはキーは不要です。"),
|
||||
("web_id_input_tip", "同じサーバー内の ID を入力できます。Web クライアントでは IP アドレスによる直接アクセスに対応していません。\n別のサーバー上のデバイスにアクセスする場合は、サーバーアドレス (<id>@<server_address>?key=<key_value>) を入力してください。\n 例: 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\nパブリックサーバー上のデバイスにアクセスする場合は、「<id>@public」と入力してください。パブリックサーバーはキーは不要です。"),
|
||||
("Download", "ダウンロード"),
|
||||
("Upload folder", "フォルダーをアップロード"),
|
||||
("Upload files", "ファイルをアップロード"),
|
||||
@@ -674,7 +674,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("dont-show-again-tip", "今後は表示しない"),
|
||||
("Take screenshot", "スクリーンショットを撮影"),
|
||||
("Taking screenshot", "スクリーンショットを撮影中"),
|
||||
("screenshot-merged-screen-not-supported-tip", "複数のディスプレイのスクリーンショットの結合は、現在サポートされていません。単一のディスプレイに切り替えてもう一度お試しください。"),
|
||||
("screenshot-merged-screen-not-supported-tip", "複数のディスプレイのスクリーンショットの結合は、現在非対応です。単一のディスプレイに切り替えてもう一度お試しください。"),
|
||||
("screenshot-action-tip", "スクリーンショットを続行する方法を選択してください。"),
|
||||
("Save as", "保存先"),
|
||||
("Copy to clipboard", "クリップボードにコピー"),
|
||||
@@ -685,7 +685,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("download-new-version-failed-tip", "ダウンロードに失敗しました。もう一度お試しいただくか、「ダウンロード」ボタンをクリックしてリリースページからダウンロードし、手動でアップグレードしてください。"),
|
||||
("Auto update", "ソフトウェアの自動更新を行う"),
|
||||
("update-failed-check-msi-tip", "インストール方法の確認に失敗しました。「ダウンロード」ボタンをクリックしてリリースページからダウンロードし、手動でアップグレードしてください。"),
|
||||
("websocket_tip", "WebSocket を使用する場合、リレー接続のみがサポートされます。"),
|
||||
("websocket_tip", "WebSocket を使用する場合、リレー接続のみ対応しています。"),
|
||||
("Use WebSocket", "WebSocket を使用する"),
|
||||
("Trackpad speed", "トラックパッドの速度"),
|
||||
("Default trackpad speed", "既定のトラックパッドの速度"),
|
||||
@@ -695,7 +695,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("View camera", "カメラを表示"),
|
||||
("Enable camera", "カメラを有効化する"),
|
||||
("No cameras", "カメラなし"),
|
||||
("view_camera_unsupported_tip", "リモートデバイスはカメラの表示をサポートしていません。"),
|
||||
("view_camera_unsupported_tip", "リモートデバイスはカメラの表示に対応していません"),
|
||||
("Terminal", "ターミナル"),
|
||||
("Enable terminal", "ターミナルを有効化する"),
|
||||
("New tab", "新しいタブ"),
|
||||
@@ -706,7 +706,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Incorrect username or password.", "ユーザー名またはパスワードが正しくありません。"),
|
||||
("The user is not an administrator.", "このユーザーは管理者ではありません。"),
|
||||
("Failed to check if the user is an administrator.", "ユーザーが管理者であるかどうかを確認できませんでした。"),
|
||||
("Supported only in the installed version.", "インストールされたバージョンでのみサポートされます。"),
|
||||
("Supported only in the installed version.", "インストールされたバージョンでのみ対応しています。"),
|
||||
("elevation_username_tip", "ユーザー名またはドメインのユーザー名を入力してください。"),
|
||||
("Preparing for installation ...", "インストールの準備中です..."),
|
||||
("Show my cursor", "自分のカーソルを表示する"),
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "キーボード入力の選択をリセット"),
|
||||
("remember-wayland-keyboard-choice-tip", "このリモートコンピューターでは今後確認しない"),
|
||||
("Why this happens", "この問題が起こる理由"),
|
||||
("Switch display", "ディスプレイを切り替え"),
|
||||
("Show monitor switch button on the main toolbar", "メインツールバーにモニター切り替えボタンを表示"),
|
||||
("Show on the minimized toolbar", "最小化したツールバーに表示"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "키보드 입력 선택 초기화"),
|
||||
("remember-wayland-keyboard-choice-tip", "이 원격 컴퓨터에 대해 다시 묻지 않기"),
|
||||
("Why this happens", "이런 현상이 발생하는 이유"),
|
||||
("Switch display", "디스플레이 전환"),
|
||||
("Show monitor switch button on the main toolbar", "기본 도구 모음에 모니터 전환 버튼 표시"),
|
||||
("Show on the minimized toolbar", "최소화된 도구 모음에 표시"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Пернетақта еңгізу таңдауын қалпына келтіру"),
|
||||
("remember-wayland-keyboard-choice-tip", "Осы қашықтағы компьютер үшін қайта сұрамау"),
|
||||
("Why this happens", "Бұл неге болады"),
|
||||
("Switch display", "Дисплейді ауыстыру"),
|
||||
("Show monitor switch button on the main toolbar", "Негізгі құралдар тақтасында мониторды ауыстыру түймесін көрсету"),
|
||||
("Show on the minimized toolbar", "Кішірейтілген құралдар тақтасында көрсету"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Atstatyti klaviatūros įvesties pasirinkimą"),
|
||||
("remember-wayland-keyboard-choice-tip", "Daugiau neklausti dėl šio nuotolinio kompiuterio"),
|
||||
("Why this happens", "Kodėl taip nutinka"),
|
||||
("Switch display", "Perjungti ekraną"),
|
||||
("Show monitor switch button on the main toolbar", "Rodyti monitoriaus perjungimo mygtuką pagrindinėje įrankių juostoje"),
|
||||
("Show on the minimized toolbar", "Rodyti sumažintoje įrankių juostoje"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Atiestatīt tastatūras ievades izvēli"),
|
||||
("remember-wayland-keyboard-choice-tip", "Vairs nejautāt par šo attālo datoru"),
|
||||
("Why this happens", "Kāpēc tas notiek"),
|
||||
("Switch display", "Pārslēgt displeju"),
|
||||
("Show monitor switch button on the main toolbar", "Rādīt monitora pārslēgšanas pogu galvenajā rīkjoslā"),
|
||||
("Show on the minimized toolbar", "Rādīt minimizētajā rīkjoslā"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "കീബോർഡ് ഇൻപുട്ട് തിരഞ്ഞെടുപ്പ് റീസെറ്റ് ചെയ്യുക"),
|
||||
("remember-wayland-keyboard-choice-tip", "ഈ റിമോട്ട് കമ്പ്യൂട്ടറിനായി ഇനി ചോദിക്കരുത്"),
|
||||
("Why this happens", "ഇത് എന്തുകൊണ്ട് സംഭവിക്കുന്നു"),
|
||||
("Switch display", "ഡിസ്പ്ലേ മാറ്റുക"),
|
||||
("Show monitor switch button on the main toolbar", "പ്രധാന ടൂൾബാറിൽ മോണിറ്റർ സ്വിച്ച് ബട്ടൺ കാണിക്കുക"),
|
||||
("Show on the minimized toolbar", "ചെറുതാക്കിയ ടൂൾബാറിൽ കാണിക്കുക"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Tilbakestill valg for tastaturinndata"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ikke spør igjen for denne eksterne datamaskinen"),
|
||||
("Why this happens", "Hvorfor dette skjer"),
|
||||
("Switch display", "Bytt skjerm"),
|
||||
("Show monitor switch button on the main toolbar", "Vis knapp for skjermbytte på hovedverktøylinjen"),
|
||||
("Show on the minimized toolbar", "Vis på den minimerte verktøylinjen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Keuze voor toetsenbordinvoer opnieuw instellen"),
|
||||
("remember-wayland-keyboard-choice-tip", "Niet meer vragen voor deze externe computer"),
|
||||
("Why this happens", "Waarom dit gebeurt"),
|
||||
("Switch display", "Beeldscherm wisselen"),
|
||||
("Show monitor switch button on the main toolbar", "Knop voor monitorwisseling weergeven op de hoofdwerkbalk"),
|
||||
("Show on the minimized toolbar", "Weergeven op de geminimaliseerde werkbalk"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Zresetuj wybór dotyczący wprowadzania z klawiatury"),
|
||||
("remember-wayland-keyboard-choice-tip", "Nie pytaj ponownie dla tego zdalnego komputera"),
|
||||
("Why this happens", "Dlaczego tak się dzieje"),
|
||||
("Switch display", "Przełącz ekran"),
|
||||
("Show monitor switch button on the main toolbar", "Pokaż przycisk przełączania monitora na głównym pasku narzędzi"),
|
||||
("Show on the minimized toolbar", "Pokaż na zminimalizowanym pasku narzędzi"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Repor escolha de entrada de teclado"),
|
||||
("remember-wayland-keyboard-choice-tip", "Não voltar a perguntar para este computador remoto"),
|
||||
("Why this happens", "Porque é que isto acontece"),
|
||||
("Switch display", "Trocar de ecrã"),
|
||||
("Show monitor switch button on the main toolbar", "Mostrar o botão de troca de monitor na barra de ferramentas principal"),
|
||||
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -16,18 +16,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Control Remote Desktop", "Controle um Computador Remoto"),
|
||||
("Transfer file", "Transferir arquivos"),
|
||||
("Connect", "Conectar"),
|
||||
("Recent sessions", "Sessões Recentes"),
|
||||
("Address book", "Lista de Endereços"),
|
||||
("Recent sessions", "Sessões recentes"),
|
||||
("Address book", "Lista de endereços"),
|
||||
("Confirmation", "Confirmação"),
|
||||
("TCP tunneling", "Tunelamento TCP"),
|
||||
("Remove", "Remover"),
|
||||
("Refresh random password", "Atualizar senha aleatória"),
|
||||
("Set your own password", "Configure sua própria senha"),
|
||||
("Refresh random password", "Gerar nova senha aleatória"),
|
||||
("Set your own password", "Definir sua própria senha"),
|
||||
("Enable keyboard/mouse", "Habilitar teclado/mouse"),
|
||||
("Enable clipboard", "Habilitar área de transferência"),
|
||||
("Enable file transfer", "Habilitar transferência de arquivos"),
|
||||
("Enable TCP tunneling", "Habilitar tunelamento TCP"),
|
||||
("IP Whitelisting", "Lista de IPs Confiáveis"),
|
||||
("IP Whitelisting", "Lista de IPs Permitidos"),
|
||||
("ID/Relay Server", "Servidor ID/Relay"),
|
||||
("Import server config", "Importar Configuração do Servidor"),
|
||||
("Export Server Config", "Exportar Configuração do Servidor"),
|
||||
@@ -320,12 +320,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Exit Fullscreen", "Sair da Tela Cheia"),
|
||||
("Fullscreen", "Tela Cheia"),
|
||||
("Mobile Actions", "Ações móveis"),
|
||||
("Select Monitor", "Selecionar monitor"),
|
||||
("Select Monitor", "Selecionar tela"),
|
||||
("Control Actions", "Controlar ações"),
|
||||
("Display Settings", "Configurações de exibição"),
|
||||
("Ratio", "Proporção"),
|
||||
("Image Quality", "Qualidade de imagem"),
|
||||
("Scroll Style", "Estilo de Rolagem"),
|
||||
("Scroll Style", "Estilo de rolagem"),
|
||||
("Show Toolbar", "Mostrar barra de ferramentas"),
|
||||
("Hide Toolbar", "Ocultar barra de ferramentas"),
|
||||
("Direct Connection", "Conexão Direta"),
|
||||
@@ -353,7 +353,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Disconnect all devices?", "Desconectar todos os dispositivos?"),
|
||||
("Clear", "Limpar"),
|
||||
("Audio Input Device", "Dispositivo de entrada de áudio"),
|
||||
("Use IP Whitelisting", "Utilizar lista de IPs confiáveis"),
|
||||
("Use IP Whitelisting", "Utilizar lista de IPs permitidos"),
|
||||
("Network", "Rede"),
|
||||
("Pin Toolbar", "Fixar barra de ferramentas"),
|
||||
("Unpin Toolbar", "Desafixar barra de ferramentas"),
|
||||
@@ -463,7 +463,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Password", "Senha Vazia"),
|
||||
("Me", "Eu"),
|
||||
("identical_file_tip", "Este arquivo é idêntico ao do parceiro."),
|
||||
("show_monitors_tip", "Mostrar monitores na barra de ferramentas"),
|
||||
("show_monitors_tip", "Mostrar telas na barra de ferramentas"),
|
||||
("View Mode", "Modo de visualização"),
|
||||
("login_linux_tip", "Você precisa fazer login na conta Linux remota para habilitar uma sessão de desktop X"),
|
||||
("verify_rustdesk_password_tip", "Verifique a senha do RustDesk"),
|
||||
@@ -674,7 +674,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("dont-show-again-tip", "Não mostrar novamente"),
|
||||
("Take screenshot", "Capturar tela"),
|
||||
("Taking screenshot", "Capturando tela"),
|
||||
("screenshot-merged-screen-not-supported-tip", "Mesclar a captura de tela de múltiplos monitores não é suportada no momento. Por favor, alterne para um único monitor e tente novamente."),
|
||||
("screenshot-merged-screen-not-supported-tip", "A captura de tela de múltiplas telas não é suportada no momento. Por favor, alterne para uma única tela e tente novamente."),
|
||||
("screenshot-action-tip", "Por favor, selecione como deseja continuar com a captura de tela."),
|
||||
("Save as", "Salvar como"),
|
||||
("Copy to clipboard", "Copiar para área de transferência"),
|
||||
@@ -694,7 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable UDP hole punching", "Habilitar UDP hole punching"),
|
||||
("View camera", "Visualizar câmera"),
|
||||
("Enable camera", "Habilitar câmera"),
|
||||
("No cameras", "Nenhuma câmeras"),
|
||||
("No cameras", "Nenhuma câmera"),
|
||||
("view_camera_unsupported_tip", "O dispositivo remoto não suporta visualização da câmera."),
|
||||
("Terminal", "Terminal"),
|
||||
("Enable terminal", "Habilitar terminal"),
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Redefinir escolha de entrada do teclado"),
|
||||
("remember-wayland-keyboard-choice-tip", "Não perguntar novamente para este computador remoto"),
|
||||
("Why this happens", "Por que isso acontece"),
|
||||
("Switch display", "Trocar de tela"),
|
||||
("Show monitor switch button on the main toolbar", "Mostrar botão de troca de tela na barra de ferramentas"),
|
||||
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Resetează alegerea pentru introducerea de la tastatură"),
|
||||
("remember-wayland-keyboard-choice-tip", "Nu mai întreba pentru acest computer la distanță"),
|
||||
("Why this happens", "De ce se întâmplă acest lucru"),
|
||||
("Switch display", "Comută afișajul"),
|
||||
("Show monitor switch button on the main toolbar", "Afișează butonul de comutare a monitorului în bara de instrumente principală"),
|
||||
("Show on the minimized toolbar", "Afișează în bara de instrumente minimizată"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Сбросить выбор для ввода с клавиатуры"),
|
||||
("remember-wayland-keyboard-choice-tip", "Больше не спрашивать для этого удалённого компьютера"),
|
||||
("Why this happens", "Почему это происходит"),
|
||||
("Switch display", "Переключить дисплей"),
|
||||
("Show monitor switch button on the main toolbar", "Показывать кнопку переключения монитора на главной панели инструментов"),
|
||||
("Show on the minimized toolbar", "Показывать на свёрнутой панели инструментов"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Reseta s'isseberada de s'insertada cun su tecladu"),
|
||||
("remember-wayland-keyboard-choice-tip", "No torres a preguntare pro custu elaboradore remotu"),
|
||||
("Why this happens", "Pro ite custu càpitat"),
|
||||
("Switch display", "Càmbia ischermu"),
|
||||
("Show monitor switch button on the main toolbar", "Mustra su butone de càmbiu de monitor in sa barra de aina printzipale"),
|
||||
("Show on the minimized toolbar", "Mustra in sa barra de aina minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Obnoviť voľbu vstupu z klávesnice"),
|
||||
("remember-wayland-keyboard-choice-tip", "Nepýtať sa znova pre tento vzdialený počítač"),
|
||||
("Why this happens", "Prečo sa to deje"),
|
||||
("Switch display", "Prepnúť obrazovku"),
|
||||
("Show monitor switch button on the main toolbar", "Zobraziť tlačidlo prepnutia monitora na hlavnom paneli nástrojov"),
|
||||
("Show on the minimized toolbar", "Zobraziť na minimalizovanom paneli nástrojov"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Ponastavi izbiro vnosa s tipkovnice"),
|
||||
("remember-wayland-keyboard-choice-tip", "Za ta oddaljeni računalnik ne vprašaj več"),
|
||||
("Why this happens", "Zakaj se to dogaja"),
|
||||
("Switch display", "Preklopi zaslon"),
|
||||
("Show monitor switch button on the main toolbar", "Pokaži gumb za preklop monitorja v glavni orodni vrstici"),
|
||||
("Show on the minimized toolbar", "Pokaži v pomanjšani orodni vrstici"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Rivendos zgjedhjen e hyrjes nga tastiera"),
|
||||
("remember-wayland-keyboard-choice-tip", "Mos pyet më për këtë kompjuter në distancë"),
|
||||
("Why this happens", "Pse ndodh kjo"),
|
||||
("Switch display", "Ndërro ekranin"),
|
||||
("Show monitor switch button on the main toolbar", "Shfaq butonin e ndërrimit të monitorit te shiriti kryesor i veglave"),
|
||||
("Show on the minimized toolbar", "Shfaq te shiriti i minimizuar i veglave"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Resetuj izbor unosa sa tastature"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne pitaj ponovo za ovaj udaljeni računar"),
|
||||
("Why this happens", "Zašto se ovo dešava"),
|
||||
("Switch display", "Промени екран"),
|
||||
("Show monitor switch button on the main toolbar", "Прикажи дугме за пребацивање монитора на главној траци са алаткама"),
|
||||
("Show on the minimized toolbar", "Прикажи на умањеној траци са алаткама"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Återställ val av tangentbordsinmatning"),
|
||||
("remember-wayland-keyboard-choice-tip", "Fråga inte igen för den här fjärrdatorn"),
|
||||
("Why this happens", "Varför detta händer"),
|
||||
("Switch display", "Växla skärm"),
|
||||
("Show monitor switch button on the main toolbar", "Visa knapp för skärmväxling i huvudverktygsfältet"),
|
||||
("Show on the minimized toolbar", "Visa i det minimerade verktygsfältet"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "விசைப்பலகை உள்ளீட்டுத் தேர்வை மீட்டமை"),
|
||||
("remember-wayland-keyboard-choice-tip", "இந்தத் தொலை கணினிக்கு மீண்டும் கேட்க வேண்டாம்"),
|
||||
("Why this happens", "இது ஏன் நிகழ்கிறது"),
|
||||
("Switch display", "திரையை மாற்று"),
|
||||
("Show monitor switch button on the main toolbar", "முதன்மை கருவிப்பட்டையில் திரை மாற்று பொத்தானைக் காட்டு"),
|
||||
("Show on the minimized toolbar", "சிறிதாக்கப்பட்ட கருவிப்பட்டையில் காட்டு"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", ""),
|
||||
("remember-wayland-keyboard-choice-tip", ""),
|
||||
("Why this happens", ""),
|
||||
("Switch display", ""),
|
||||
("Show monitor switch button on the main toolbar", ""),
|
||||
("Show on the minimized toolbar", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "รีเซ็ตตัวเลือกการป้อนข้อมูลจากคีย์บอร์ด"),
|
||||
("remember-wayland-keyboard-choice-tip", "ไม่ต้องถามอีกสำหรับคอมพิวเตอร์ปลายทางนี้"),
|
||||
("Why this happens", "เหตุใดจึงเกิดขึ้น"),
|
||||
("Switch display", "สลับจอแสดงผล"),
|
||||
("Show monitor switch button on the main toolbar", "แสดงปุ่มสลับจอภาพบนแถบเครื่องมือหลัก"),
|
||||
("Show on the minimized toolbar", "แสดงบนแถบเครื่องมือที่ย่อเล็กสุด"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Klavye girişi seçimini sıfırla"),
|
||||
("remember-wayland-keyboard-choice-tip", "Bu uzak bilgisayar için bir daha sorma"),
|
||||
("Why this happens", "Bunun nedeni"),
|
||||
("Switch display", "Ekranı değiştir"),
|
||||
("Show monitor switch button on the main toolbar", "Ana araç çubuğunda monitör değiştirme düğmesini göster"),
|
||||
("Show on the minimized toolbar", "Simge durumuna küçültülmüş araç çubuğunda göster"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "重設鍵盤輸入選擇"),
|
||||
("remember-wayland-keyboard-choice-tip", "不要再為此遠端電腦詢問"),
|
||||
("Why this happens", "發生原因"),
|
||||
("Switch display", "切換螢幕"),
|
||||
("Show monitor switch button on the main toolbar", "在主工具列上顯示螢幕切換按鈕"),
|
||||
("Show on the minimized toolbar", "在最小化工具列上顯示"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Скинути вибір щодо введення з клавіатури"),
|
||||
("remember-wayland-keyboard-choice-tip", "Більше не запитувати для цього віддаленого комп'ютера"),
|
||||
("Why this happens", "Чому це відбувається"),
|
||||
("Switch display", "Перемкнути дисплей"),
|
||||
("Show monitor switch button on the main toolbar", "Показувати кнопку перемикання монітора на головній панелі інструментів"),
|
||||
("Show on the minimized toolbar", "Показувати на згорнутій панелі інструментів"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Đặt lại lựa chọn nhập bàn phím"),
|
||||
("remember-wayland-keyboard-choice-tip", "Không hỏi lại cho máy tính từ xa này"),
|
||||
("Why this happens", "Tại sao điều này xảy ra"),
|
||||
("Switch display", "Chuyển màn hình"),
|
||||
("Show monitor switch button on the main toolbar", "Hiển thị nút chuyển đổi màn hình trên thanh công cụ chính"),
|
||||
("Show on the minimized toolbar", "Hiển thị trên thanh công cụ thu nhỏ"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ fn check_desktop_manager() {
|
||||
pub fn start_xdesktop() {
|
||||
debug_assert!(crate::is_server());
|
||||
std::thread::spawn(|| {
|
||||
DesktopManager::recover_orphaned_session();
|
||||
*DESKTOP_MANAGER.lock().unwrap() = Some(DesktopManager::new());
|
||||
|
||||
let interval = time::Duration::from_millis(super::SERVICE_INTERVAL);
|
||||
@@ -462,10 +463,15 @@ impl DesktopManager {
|
||||
let (child_xorg, child_wm) = Self::start_x11(uid, gid, username, display_num, &envs)?;
|
||||
is_child_running.store(true, Ordering::SeqCst);
|
||||
|
||||
// capture the logind session scope (from a live child) for teardown and crash
|
||||
// recovery, see reap_session_scope and recover_orphaned_session.
|
||||
let scope_dir = Self::session_scope_dir(child_xorg.id());
|
||||
Self::save_orphaned_marker(&scope_dir, display_num);
|
||||
|
||||
log::info!("Start xorg and wm done, notify and wait xtop x11");
|
||||
allow_err!(tx_res.send("".to_owned()));
|
||||
|
||||
Self::wait_stop_x11(child_xorg, child_wm);
|
||||
Self::wait_stop_x11(child_xorg, child_wm, scope_dir, display_num);
|
||||
log::info!("Wait x11 stop done");
|
||||
Ok(())
|
||||
}
|
||||
@@ -665,7 +671,282 @@ impl DesktopManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_wait_stop_x11(child_xorg: &mut Child, child_wm: &mut Child) -> bool {
|
||||
// resolve the "session-<id>.scope" directory pam_systemd put the x session in, read
|
||||
// from a live child pid. cgroup v2 mounts every cgroup under /sys/fs/cgroup, v1/hybrid
|
||||
// keeps the scope under the systemd controller mount; pick by the controller field and
|
||||
// confirm the cgroup is real. empty if there is no such scope (e.g. no logind).
|
||||
fn session_scope_dir(pid: u32) -> String {
|
||||
let path = format!("/proc/{}/cgroup", pid);
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read {} to find session scope: {}", path, e);
|
||||
return "".to_owned();
|
||||
}
|
||||
};
|
||||
for line in content.lines() {
|
||||
// "<hierarchy>:<controllers>:<path>"; v2 unified is "0::<path>", the v1
|
||||
// systemd hierarchy is "<n>:name=systemd:<path>".
|
||||
let mut fields = line.splitn(3, ':');
|
||||
let (controllers, cgroup) = match (fields.next(), fields.next(), fields.next()) {
|
||||
(Some(_), Some(c), Some(p)) => (c, p),
|
||||
_ => continue,
|
||||
};
|
||||
let scope = match Self::session_scope(cgroup) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
let mount = if controllers.is_empty() {
|
||||
"/sys/fs/cgroup"
|
||||
} else if controllers.split(',').any(|c| c == "name=systemd") {
|
||||
"/sys/fs/cgroup/systemd"
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let dir = format!("{}{}", mount, scope);
|
||||
if Path::new(&format!("{}/cgroup.procs", dir)).exists() {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
// the "/.../session-<id>.scope" prefix of a cgroup path, dropping any nested child
|
||||
// cgroup below it so a descendant scope does not get mistaken for the session.
|
||||
fn session_scope(cgroup: &str) -> Option<String> {
|
||||
let mut scope = String::new();
|
||||
for comp in cgroup.split('/').filter(|c| !c.is_empty()) {
|
||||
scope.push('/');
|
||||
scope.push_str(comp);
|
||||
if comp.starts_with("session-") && comp.ends_with(".scope") {
|
||||
return Some(scope);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// on teardown reap the whole session scope subtree, not just the xorg + wm pids:
|
||||
// the per-session pipewire and other desktop children otherwise outlive them and
|
||||
// hold the logind session in "closing", leaking sockets + displays on reconnect
|
||||
// (rustdesk/rustdesk#15183). SIGTERM first so pipewire unlinks its sockets, then
|
||||
// SIGKILL stragglers; skip our own pid (pam put the service in the scope too).
|
||||
fn reap_session_scope(scope_dir: &str) {
|
||||
if scope_dir.is_empty() {
|
||||
return;
|
||||
}
|
||||
let me = std::process::id();
|
||||
// spare the --server's own children and any descendants of them sharing this scope
|
||||
// (see pid_is_spared); only the desktop session's leftovers are reaped.
|
||||
let spared: Vec<u32> = crate::server::CHILD_PROCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.id())
|
||||
.collect();
|
||||
for sig in [hbb_common::libc::SIGTERM, hbb_common::libc::SIGKILL] {
|
||||
let mut pids = Vec::new();
|
||||
Self::collect_scope_pids(Path::new(scope_dir), &mut pids);
|
||||
let mut any = false;
|
||||
for pid in pids {
|
||||
if pid == me || Self::pid_is_spared(pid, &spared, me) {
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
log::info!("Reaping leftover session process {} (signal {})", pid, sig);
|
||||
unsafe {
|
||||
if hbb_common::libc::kill(pid as hbb_common::libc::pid_t, sig) != 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
// ESRCH = it already exited (or did between snapshot and now).
|
||||
if err.raw_os_error() != Some(hbb_common::libc::ESRCH) {
|
||||
log::warn!("Failed to signal session process {}: {}", pid, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !any {
|
||||
break;
|
||||
}
|
||||
if sig == hbb_common::libc::SIGTERM {
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a tracked --server child (the sudo wrapper run_as_user spawns) or any descendant of
|
||||
// one: with use_pty sudo runs --cm-no-ui under a monitor with its own pid, so walk the
|
||||
// parent chain (stopping at the --server) to spare the worker, not just the wrapper.
|
||||
fn pid_is_spared(pid: u32, spared: &[u32], me: u32) -> bool {
|
||||
let mut cur = pid;
|
||||
for _ in 0..32 {
|
||||
if spared.contains(&cur) {
|
||||
return true;
|
||||
}
|
||||
if cur <= 1 || cur == me {
|
||||
return false;
|
||||
}
|
||||
match Self::parent_pid(cur) {
|
||||
Some(ppid) => cur = ppid,
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn parent_pid(pid: u32) -> Option<u32> {
|
||||
// /proc/<pid>/stat is "pid (comm) state ppid ..."; comm can contain spaces and ')',
|
||||
// so read the fields after the last ')'.
|
||||
let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
|
||||
stat.rsplit_once(')')?
|
||||
.1
|
||||
.split_whitespace()
|
||||
.nth(1)?
|
||||
.parse()
|
||||
.ok()
|
||||
}
|
||||
|
||||
// collect every pid in the cgroup subtree rooted at dir. "cgroup.procs" lists only
|
||||
// the procs directly in a cgroup, so recurse into child cgroup directories to catch
|
||||
// processes the desktop session moved into descendant scopes.
|
||||
fn collect_scope_pids(dir: &Path, out: &mut Vec<u32>) {
|
||||
let procs = dir.join("cgroup.procs");
|
||||
match std::fs::read_to_string(&procs) {
|
||||
Ok(content) => {
|
||||
out.extend(content.lines().filter_map(|l| l.trim().parse::<u32>().ok()));
|
||||
}
|
||||
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
|
||||
log::warn!("Failed to read {}: {}", procs.display(), e);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
let entries = match std::fs::read_dir(dir) {
|
||||
Ok(e) => e,
|
||||
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
|
||||
log::warn!("Failed to list cgroup dir {}: {}", dir.display(), e);
|
||||
return;
|
||||
}
|
||||
Err(_) => return,
|
||||
};
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read entry under {}: {}", dir.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match entry.file_type() {
|
||||
Ok(t) if t.is_dir() => Self::collect_scope_pids(&entry.path(), out),
|
||||
Ok(_) => {}
|
||||
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
|
||||
log::warn!("Failed to stat {}: {}", entry.path().display(), e);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a SIGKILL'd Xorg (how wait_x11_children_exit ends it) leaves "/tmp/.X<n>-lock" and
|
||||
// "/tmp/.X11-unix/X<n>" behind, and get_avail_display() treats either file as "display
|
||||
// in use", so the number is never reused and climbs until none are free
|
||||
// (rustdesk/rustdesk#15183). a clean exit would remove them; do the same on teardown,
|
||||
// but skip it if a live process still holds the lock: another server could have taken
|
||||
// the number in the gap, and removing its files would break that display.
|
||||
fn cleanup_x_display_files(display_num: u32) {
|
||||
let lock = format!("/tmp/.X{}-lock", display_num);
|
||||
if let Ok(content) = std::fs::read_to_string(&lock) {
|
||||
if let Ok(pid) = content.trim().parse::<i32>() {
|
||||
if Self::pid_alive(pid) {
|
||||
log::info!("X display {} still held by pid {}, leaving its files", display_num, pid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for path in [lock, format!("/tmp/.X11-unix/X{}", display_num)] {
|
||||
if let Err(e) = std::fs::remove_file(&path) {
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
log::warn!("Failed to remove stale X file {}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// signal-0 probe: the pid exists if kill succeeds or fails with EPERM (alive but not
|
||||
// ours); only ESRCH means it is gone.
|
||||
fn pid_alive(pid: i32) -> bool {
|
||||
unsafe {
|
||||
if hbb_common::libc::kill(pid as hbb_common::libc::pid_t, 0) == 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::io::Error::last_os_error().raw_os_error() == Some(hbb_common::libc::EPERM)
|
||||
}
|
||||
|
||||
const ORPHANED_SESSION_KEY: &'static str = "headless-orphaned-session";
|
||||
|
||||
fn save_orphaned_marker(scope_dir: &str, display_num: u32) {
|
||||
// tag the marker with this boot's id: a logind session id is only unique within a
|
||||
// boot (the counter lives in /run and resets), so recovery must not reap a recorded
|
||||
// scope path after a reboot, when it may name a different live session.
|
||||
let boot_id = Self::current_boot_id().unwrap_or_default();
|
||||
hbb_common::config::LocalConfig::set_option(
|
||||
Self::ORPHANED_SESSION_KEY.to_owned(),
|
||||
format!("{};{};{}", scope_dir, display_num, boot_id),
|
||||
);
|
||||
}
|
||||
|
||||
fn current_boot_id() -> Option<String> {
|
||||
std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
|
||||
.ok()
|
||||
.map(|s| s.trim().to_owned())
|
||||
}
|
||||
|
||||
fn clear_orphaned_marker() {
|
||||
hbb_common::config::LocalConfig::set_option(
|
||||
Self::ORPHANED_SESSION_KEY.to_owned(),
|
||||
String::new(),
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_orphaned_marker(marker: &str) -> Option<(&str, u32, &str)> {
|
||||
let (rest, boot_id) = marker.rsplit_once(';')?;
|
||||
let (scope_dir, display) = rest.rsplit_once(';')?;
|
||||
Some((scope_dir, display.trim().parse::<u32>().ok()?, boot_id))
|
||||
}
|
||||
|
||||
// a run that dies before wait_stop_x11 (service or --server crash) leaks the headless
|
||||
// session scope + X lock files, the same as a missed teardown (rustdesk/rustdesk#15183).
|
||||
// reap exactly what the dead run recorded - never a scan, so unrelated sessions are safe.
|
||||
fn recover_orphaned_session() {
|
||||
let marker = hbb_common::config::LocalConfig::get_option(Self::ORPHANED_SESSION_KEY);
|
||||
if marker.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some((scope_dir, display_num, boot_id)) = Self::parse_orphaned_marker(&marker) {
|
||||
// only reap the recorded scope when the marker is from this same boot: a leaked
|
||||
// cgroup cannot outlive a reboot, so cross-boot there is nothing legitimate to
|
||||
// reap, and the recorded "session-N.scope" may by then name a different live
|
||||
// session. the X lock cleanup is pid-guarded, so run it either way.
|
||||
let same_boot = Self::current_boot_id().map_or(false, |b| b == boot_id);
|
||||
log::info!(
|
||||
"Recovering leaked headless session from a previous run: scope {}, display {} (same boot: {})",
|
||||
scope_dir,
|
||||
display_num,
|
||||
same_boot
|
||||
);
|
||||
if same_boot {
|
||||
Self::reap_session_scope(scope_dir);
|
||||
}
|
||||
Self::cleanup_x_display_files(display_num);
|
||||
}
|
||||
Self::clear_orphaned_marker();
|
||||
}
|
||||
|
||||
fn try_wait_stop_x11(
|
||||
child_xorg: &mut Child,
|
||||
child_wm: &mut Child,
|
||||
scope_dir: &str,
|
||||
display_num: u32,
|
||||
) -> bool {
|
||||
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
|
||||
let mut exited = true;
|
||||
if let Some(desktop_manager) = &mut (*desktop_manager) {
|
||||
@@ -677,6 +958,9 @@ impl DesktopManager {
|
||||
if exited {
|
||||
log::debug!("Wait x11 children exiting");
|
||||
Self::wait_x11_children_exit(child_xorg, child_wm);
|
||||
Self::reap_session_scope(scope_dir);
|
||||
Self::cleanup_x_display_files(display_num);
|
||||
Self::clear_orphaned_marker();
|
||||
desktop_manager
|
||||
.is_child_running
|
||||
.store(false, Ordering::SeqCst);
|
||||
@@ -686,9 +970,14 @@ impl DesktopManager {
|
||||
exited
|
||||
}
|
||||
|
||||
fn wait_stop_x11(mut child_xorg: Child, mut child_wm: Child) {
|
||||
fn wait_stop_x11(
|
||||
mut child_xorg: Child,
|
||||
mut child_wm: Child,
|
||||
scope_dir: String,
|
||||
display_num: u32,
|
||||
) {
|
||||
loop {
|
||||
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm) {
|
||||
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm, &scope_dir, display_num) {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
@@ -806,3 +1095,77 @@ fn pam_get_service_name() -> String {
|
||||
"gdm".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn session_scope_truncates_at_first_scope() {
|
||||
assert_eq!(
|
||||
DesktopManager::session_scope("/user.slice/user-1000.slice/session-3.scope").as_deref(),
|
||||
Some("/user.slice/user-1000.slice/session-3.scope")
|
||||
);
|
||||
// a nested child scope must not be mistaken for the session
|
||||
assert_eq!(
|
||||
DesktopManager::session_scope(
|
||||
"/user.slice/user-1000.slice/session-3.scope/app-foo.scope"
|
||||
)
|
||||
.as_deref(),
|
||||
Some("/user.slice/user-1000.slice/session-3.scope")
|
||||
);
|
||||
assert_eq!(
|
||||
DesktopManager::session_scope(
|
||||
"/user.slice/user-1000.slice/user@1000.service/app.slice/x.service"
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(DesktopManager::session_scope("/"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_scope_pids_walks_descendant_cgroups() {
|
||||
// regression for #15183: pids in descendant cgroups must be collected too
|
||||
let base = std::env::temp_dir().join(format!("rustdesk-cgtest-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
let scope = base.join("session-3.scope");
|
||||
let child = scope.join("app-foo.scope");
|
||||
let nested = child.join("deeper.scope");
|
||||
std::fs::create_dir_all(&nested).unwrap();
|
||||
std::fs::create_dir_all(scope.join("empty.scope")).unwrap();
|
||||
std::fs::write(scope.join("cgroup.procs"), "100\n101\n").unwrap();
|
||||
std::fs::write(scope.join("cgroup.controllers"), "memory pids\n").unwrap();
|
||||
std::fs::write(child.join("cgroup.procs"), "200\n").unwrap();
|
||||
std::fs::write(nested.join("cgroup.procs"), "300\n").unwrap();
|
||||
|
||||
let mut pids = Vec::new();
|
||||
DesktopManager::collect_scope_pids(&scope, &mut pids);
|
||||
pids.sort();
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
|
||||
assert_eq!(pids, vec![100, 101, 200, 300]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_orphaned_session_marker() {
|
||||
assert_eq!(
|
||||
DesktopManager::parse_orphaned_marker(
|
||||
"/sys/fs/cgroup/user.slice/user-1000.slice/session-3.scope;7;abc-123"
|
||||
),
|
||||
Some((
|
||||
"/sys/fs/cgroup/user.slice/user-1000.slice/session-3.scope",
|
||||
7,
|
||||
"abc-123"
|
||||
))
|
||||
);
|
||||
// an empty scope still carries the display so its stale X lock can be cleaned
|
||||
assert_eq!(DesktopManager::parse_orphaned_marker(";5;abc-123"), Some(("", 5, "abc-123")));
|
||||
// an empty boot id never matches the live one, so the scope reap is skipped
|
||||
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;5;"), Some(("/scope", 5, "")));
|
||||
assert_eq!(DesktopManager::parse_orphaned_marker(""), None);
|
||||
assert_eq!(DesktopManager::parse_orphaned_marker("garbage"), None);
|
||||
// the pre-boot-id two-field format no longer parses, recovery just skips it
|
||||
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;7"), None);
|
||||
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;notnum;abc"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user