mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-24 19:48:32 +08:00
Compare commits
25 Commits
copilot/fi
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50c62d5eac | ||
|
|
91ac48912e | ||
|
|
272a6604cd | ||
|
|
8a889d3ebb | ||
|
|
17a3f2ae52 | ||
|
|
6c3515588f | ||
|
|
4d2d2118a2 | ||
|
|
483fe80308 | ||
|
|
34ceeac36e | ||
|
|
20f11018ce | ||
|
|
9345fb754a | ||
|
|
779b7aaf02 | ||
|
|
b268aa1061 | ||
|
|
40f86fa639 | ||
|
|
980bc11e68 | ||
|
|
85db677982 | ||
|
|
2842315b1d | ||
|
|
6c541f7bfd | ||
|
|
067fab2b73 | ||
|
|
de6bf9dc7e | ||
|
|
54eae37038 | ||
|
|
0118e16132 | ||
|
|
626a091f55 | ||
|
|
4fa5e99e65 | ||
|
|
5ee9dcf42d |
2
build.py
2
build.py
@@ -299,7 +299,7 @@ Version: %s
|
||||
Architecture: %s
|
||||
Maintainer: rustdesk <info@rustdesk.com>
|
||||
Homepage: https://rustdesk.com
|
||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3 | libxdo4, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
||||
Recommends: libayatana-appindicator3-1
|
||||
Description: A remote control software.
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
],
|
||||
"finish-args": [
|
||||
"--share=ipc",
|
||||
"--socket=wayland",
|
||||
"--socket=x11",
|
||||
"--share=network",
|
||||
"--filesystem=home",
|
||||
|
||||
@@ -25,6 +25,7 @@ enum UserStatus { kDisabled, kNormal, kUnverified }
|
||||
// Is all the fields of the user needed?
|
||||
class UserPayload {
|
||||
String name = '';
|
||||
String displayName = '';
|
||||
String email = '';
|
||||
String note = '';
|
||||
String? verifier;
|
||||
@@ -33,6 +34,7 @@ class UserPayload {
|
||||
|
||||
UserPayload.fromJson(Map<String, dynamic> json)
|
||||
: name = json['name'] ?? '',
|
||||
displayName = json['display_name'] ?? '',
|
||||
email = json['email'] ?? '',
|
||||
note = json['note'] ?? '',
|
||||
verifier = json['verifier'],
|
||||
@@ -46,6 +48,7 @@ class UserPayload {
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> map = {
|
||||
'name': name,
|
||||
'display_name': displayName,
|
||||
'status': status == UserStatus.kDisabled
|
||||
? 0
|
||||
: status == UserStatus.kUnverified
|
||||
@@ -58,9 +61,14 @@ class UserPayload {
|
||||
Map<String, dynamic> toGroupCacheJson() {
|
||||
final Map<String, dynamic> map = {
|
||||
'name': name,
|
||||
'display_name': displayName,
|
||||
};
|
||||
return map;
|
||||
}
|
||||
|
||||
String get displayNameOrName {
|
||||
return displayName.trim().isEmpty ? name : displayName;
|
||||
}
|
||||
}
|
||||
|
||||
class PeerPayload {
|
||||
|
||||
@@ -103,7 +103,7 @@ class ButtonOP extends StatelessWidget {
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Center(
|
||||
child: Text('${translate("Continue with")} $opLabel')),
|
||||
child: Text(translate("Continue with {$opLabel}"))),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -158,12 +158,18 @@ class _MyGroupState extends State<MyGroup> {
|
||||
return Obx(() {
|
||||
final userItems = gFFI.groupModel.users.where((p0) {
|
||||
if (searchAccessibleItemNameText.isNotEmpty) {
|
||||
return p0.name
|
||||
.toLowerCase()
|
||||
.contains(searchAccessibleItemNameText.value.toLowerCase());
|
||||
final search = searchAccessibleItemNameText.value.toLowerCase();
|
||||
return p0.name.toLowerCase().contains(search) ||
|
||||
p0.displayNameOrName.toLowerCase().contains(search);
|
||||
}
|
||||
return true;
|
||||
}).toList();
|
||||
// Count occurrences of each displayNameOrName to detect duplicates
|
||||
final displayNameCount = <String, int>{};
|
||||
for (final u in userItems) {
|
||||
final dn = u.displayNameOrName;
|
||||
displayNameCount[dn] = (displayNameCount[dn] ?? 0) + 1;
|
||||
}
|
||||
final deviceGroupItems = gFFI.groupModel.deviceGroups.where((p0) {
|
||||
if (searchAccessibleItemNameText.isNotEmpty) {
|
||||
return p0.name
|
||||
@@ -177,7 +183,8 @@ class _MyGroupState extends State<MyGroup> {
|
||||
itemCount: deviceGroupItems.length + userItems.length,
|
||||
itemBuilder: (context, index) => index < deviceGroupItems.length
|
||||
? _buildDeviceGroupItem(deviceGroupItems[index])
|
||||
: _buildUserItem(userItems[index - deviceGroupItems.length]));
|
||||
: _buildUserItem(userItems[index - deviceGroupItems.length],
|
||||
displayNameCount));
|
||||
var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||
return Obx(() => stateGlobal.isPortrait.isFalse
|
||||
? listView(false)
|
||||
@@ -185,8 +192,14 @@ class _MyGroupState extends State<MyGroup> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildUserItem(UserPayload user) {
|
||||
Widget _buildUserItem(UserPayload user, Map<String, int> displayNameCount) {
|
||||
final username = user.name;
|
||||
final dn = user.displayNameOrName;
|
||||
final isDuplicate = (displayNameCount[dn] ?? 0) > 1;
|
||||
final displayName =
|
||||
isDuplicate && user.displayName.trim().isNotEmpty
|
||||
? '${user.displayName} (@$username)'
|
||||
: dn;
|
||||
return InkWell(onTap: () {
|
||||
isSelectedDeviceGroup.value = false;
|
||||
if (selectedAccessibleItemName.value != username) {
|
||||
@@ -222,14 +235,14 @@ class _MyGroupState extends State<MyGroup> {
|
||||
alignment: Alignment.center,
|
||||
child: Center(
|
||||
child: Text(
|
||||
username.characters.first.toUpperCase(),
|
||||
displayName.characters.first.toUpperCase(),
|
||||
style: TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
).marginOnly(right: 4),
|
||||
if (isMe) Flexible(child: Text(username)),
|
||||
if (isMe) Flexible(child: Text(displayName)),
|
||||
if (isMe)
|
||||
Flexible(
|
||||
child: Container(
|
||||
@@ -246,7 +259,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isMe) Expanded(child: Text(username)),
|
||||
if (!isMe) Expanded(child: Text(displayName)),
|
||||
],
|
||||
).paddingSymmetric(vertical: 4),
|
||||
),
|
||||
|
||||
@@ -570,11 +570,14 @@ class MyGroupPeerView extends BasePeersView {
|
||||
static bool filter(Peer peer) {
|
||||
final model = gFFI.groupModel;
|
||||
if (model.searchAccessibleItemNameText.isNotEmpty) {
|
||||
final text = model.searchAccessibleItemNameText.value;
|
||||
final searchPeersOfUser = peer.loginName.contains(text) &&
|
||||
model.users.any((user) => user.name == peer.loginName);
|
||||
final searchPeersOfDeviceGroup = peer.device_group_name.contains(text) &&
|
||||
model.deviceGroups.any((g) => g.name == peer.device_group_name);
|
||||
final text = model.searchAccessibleItemNameText.value.toLowerCase();
|
||||
final searchPeersOfUser = model.users.any((user) =>
|
||||
user.name == peer.loginName &&
|
||||
(user.name.toLowerCase().contains(text) ||
|
||||
user.displayNameOrName.toLowerCase().contains(text)));
|
||||
final searchPeersOfDeviceGroup =
|
||||
peer.device_group_name.toLowerCase().contains(text) &&
|
||||
model.deviceGroups.any((g) => g.name == peer.device_group_name);
|
||||
if (!searchPeersOfUser && !searchPeersOfDeviceGroup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2016,7 +2016,9 @@ class _AccountState extends State<_Account> {
|
||||
|
||||
Widget accountAction() {
|
||||
return Obx(() => _Button(
|
||||
gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',
|
||||
gFFI.userModel.userName.value.isEmpty
|
||||
? 'Login'
|
||||
: '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})',
|
||||
() => {
|
||||
gFFI.userModel.userName.value.isEmpty
|
||||
? loginDialog()
|
||||
@@ -2037,6 +2039,10 @@ class _AccountState extends State<_Account> {
|
||||
offstage: gFFI.userModel.userName.value.isEmpty,
|
||||
child: Column(
|
||||
children: [
|
||||
if (gFFI.userModel.displayName.value.trim().isNotEmpty &&
|
||||
gFFI.userModel.displayName.value.trim() !=
|
||||
gFFI.userModel.userName.value.trim())
|
||||
text('Display Name', gFFI.userModel.displayName.value.trim()),
|
||||
text('Username', gFFI.userModel.userName.value),
|
||||
// text('Group', gFFI.groupModel.groupName.value),
|
||||
],
|
||||
@@ -2130,7 +2136,9 @@ class _PluginState extends State<_Plugin> {
|
||||
|
||||
Widget accountAction() {
|
||||
return Obx(() => _Button(
|
||||
gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',
|
||||
gFFI.userModel.userName.value.isEmpty
|
||||
? 'Login'
|
||||
: '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})',
|
||||
() => {
|
||||
gFFI.userModel.userName.value.isEmpty
|
||||
? loginDialog()
|
||||
@@ -2538,6 +2546,49 @@ class WaylandCard extends StatefulWidget {
|
||||
|
||||
class _WaylandCardState extends State<WaylandCard> {
|
||||
final restoreTokenKey = 'wayland-restore-token';
|
||||
static const _kClearShortcutsInhibitorEventKey =
|
||||
'clear-gnome-shortcuts-inhibitor-permission-res';
|
||||
final _clearShortcutsInhibitorFailedMsg = ''.obs;
|
||||
// Don't show the shortcuts permission reset button for now.
|
||||
// Users can change it manually:
|
||||
// "Settings" -> "Apps" -> "RustDesk" -> "Permissions" -> "Inhibit Shortcuts".
|
||||
// For resetting(clearing) the permission from the portal permission store, you can
|
||||
// use (replace <desktop-id> with the RustDesk desktop file ID):
|
||||
// busctl --user call org.freedesktop.impl.portal.PermissionStore \
|
||||
// /org/freedesktop/impl/portal/PermissionStore org.freedesktop.impl.portal.PermissionStore \
|
||||
// DeletePermission sss "gnome" "shortcuts-inhibitor" "<desktop-id>"
|
||||
// On a native install this is typically "rustdesk.desktop"; on Flatpak it is usually
|
||||
// the exported desktop ID derived from the Flatpak app-id (e.g. "com.rustdesk.RustDesk.desktop").
|
||||
//
|
||||
// We may add it back in the future if needed.
|
||||
final showResetInhibitorPermission = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (showResetInhibitorPermission) {
|
||||
platformFFI.registerEventHandler(
|
||||
_kClearShortcutsInhibitorEventKey, _kClearShortcutsInhibitorEventKey,
|
||||
(evt) async {
|
||||
if (!mounted) return;
|
||||
if (evt['success'] == true) {
|
||||
setState(() {});
|
||||
} else {
|
||||
_clearShortcutsInhibitorFailedMsg.value =
|
||||
evt['msg'] as String? ?? 'Unknown error';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (showResetInhibitorPermission) {
|
||||
platformFFI.unregisterEventHandler(
|
||||
_kClearShortcutsInhibitorEventKey, _kClearShortcutsInhibitorEventKey);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -2545,9 +2596,16 @@ class _WaylandCardState extends State<WaylandCard> {
|
||||
future: bind.mainHandleWaylandScreencastRestoreToken(
|
||||
key: restoreTokenKey, value: "get"),
|
||||
hasData: (restoreToken) {
|
||||
final hasShortcutsPermission = showResetInhibitorPermission &&
|
||||
bind.mainGetCommonSync(
|
||||
key: "has-gnome-shortcuts-inhibitor-permission") ==
|
||||
"true";
|
||||
|
||||
final children = [
|
||||
if (restoreToken.isNotEmpty)
|
||||
_buildClearScreenSelection(context, restoreToken),
|
||||
if (hasShortcutsPermission)
|
||||
_buildClearShortcutsInhibitorPermission(context),
|
||||
];
|
||||
return Offstage(
|
||||
offstage: children.isEmpty,
|
||||
@@ -2592,6 +2650,50 @@ class _WaylandCardState extends State<WaylandCard> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildClearShortcutsInhibitorPermission(BuildContext context) {
|
||||
onConfirm() {
|
||||
_clearShortcutsInhibitorFailedMsg.value = '';
|
||||
bind.mainSetCommon(
|
||||
key: "clear-gnome-shortcuts-inhibitor-permission", value: "");
|
||||
gFFI.dialogManager.dismissAll();
|
||||
}
|
||||
|
||||
showConfirmMsgBox() => msgBoxCommon(
|
||||
gFFI.dialogManager,
|
||||
'Confirmation',
|
||||
Text(
|
||||
translate('confirm-clear-shortcuts-inhibitor-permission-tip'),
|
||||
),
|
||||
[
|
||||
dialogButton('OK', onPressed: onConfirm),
|
||||
dialogButton('Cancel',
|
||||
onPressed: () => gFFI.dialogManager.dismissAll())
|
||||
]);
|
||||
|
||||
return Column(children: [
|
||||
Obx(
|
||||
() => _clearShortcutsInhibitorFailedMsg.value.isEmpty
|
||||
? Offstage()
|
||||
: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text(_clearShortcutsInhibitorFailedMsg.value,
|
||||
style: DefaultTextStyle.of(context)
|
||||
.style
|
||||
.copyWith(color: Colors.red))
|
||||
.marginOnly(bottom: 10.0)),
|
||||
),
|
||||
_Button(
|
||||
'Reset keyboard shortcuts permission',
|
||||
showConfirmMsgBox,
|
||||
tip: 'clear-shortcuts-inhibitor-permission-tip',
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
Theme.of(context).colorScheme.error.withOpacity(0.75)),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
@@ -15,6 +16,7 @@ class TerminalPage extends StatefulWidget {
|
||||
required this.tabController,
|
||||
required this.isSharedPassword,
|
||||
required this.terminalId,
|
||||
required this.tabKey,
|
||||
this.forceRelay,
|
||||
this.connToken,
|
||||
}) : super(key: key);
|
||||
@@ -25,6 +27,8 @@ class TerminalPage extends StatefulWidget {
|
||||
final bool? isSharedPassword;
|
||||
final String? connToken;
|
||||
final int terminalId;
|
||||
/// Tab key for focus management, passed from parent to avoid duplicate construction
|
||||
final String tabKey;
|
||||
final SimpleWrapper<State<TerminalPage>?> _lastState = SimpleWrapper(null);
|
||||
|
||||
FFI get ffi => (_lastState.value! as _TerminalPageState)._ffi;
|
||||
@@ -42,11 +46,16 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
late FFI _ffi;
|
||||
late TerminalModel _terminalModel;
|
||||
double? _cellHeight;
|
||||
final FocusNode _terminalFocusNode = FocusNode(canRequestFocus: false);
|
||||
StreamSubscription<DesktopTabState>? _tabStateSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Listen for tab selection changes to request focus
|
||||
_tabStateSubscription = widget.tabController.state.listen(_onTabStateChanged);
|
||||
|
||||
// Use shared FFI instance from connection manager
|
||||
_ffi = TerminalConnectionManager.getConnection(
|
||||
peerId: widget.id,
|
||||
@@ -64,6 +73,13 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
_terminalModel.onResizeExternal = (w, h, pw, ph) {
|
||||
_cellHeight = ph * 1.0;
|
||||
|
||||
// Enable focus once terminal has valid dimensions (first valid resize)
|
||||
if (!_terminalFocusNode.canRequestFocus && w > 0 && h > 0) {
|
||||
_terminalFocusNode.canRequestFocus = true;
|
||||
// Auto-focus if this tab is currently selected
|
||||
_requestFocusIfSelected();
|
||||
}
|
||||
|
||||
// Schedule the setState for the next frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
@@ -99,14 +115,42 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Cancel tab state subscription to prevent memory leak
|
||||
_tabStateSubscription?.cancel();
|
||||
// Unregister terminal model from FFI
|
||||
_ffi.unregisterTerminalModel(widget.terminalId);
|
||||
_terminalModel.dispose();
|
||||
_terminalFocusNode.dispose();
|
||||
// Release connection reference instead of closing directly
|
||||
TerminalConnectionManager.releaseConnection(widget.id);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onTabStateChanged(DesktopTabState state) {
|
||||
// Check if this tab is now selected and request focus
|
||||
if (state.selected >= 0 && state.selected < state.tabs.length) {
|
||||
final selectedTab = state.tabs[state.selected];
|
||||
if (selectedTab.key == widget.tabKey && mounted) {
|
||||
_requestFocusIfSelected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _requestFocusIfSelected() {
|
||||
if (!mounted || !_terminalFocusNode.canRequestFocus) return;
|
||||
// Use post-frame callback to ensure widget is fully laid out in focus tree
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// Re-check conditions after frame: mounted, focusable, still selected, not already focused
|
||||
if (!mounted || !_terminalFocusNode.canRequestFocus || _terminalFocusNode.hasFocus) return;
|
||||
final state = widget.tabController.state.value;
|
||||
if (state.selected >= 0 && state.selected < state.tabs.length) {
|
||||
if (state.tabs[state.selected].key == widget.tabKey) {
|
||||
_terminalFocusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This method ensures that the number of visible rows is an integer by computing the
|
||||
// extra space left after dividing the available height by the height of a single
|
||||
// terminal row (`_cellHeight`) and distributing it evenly as top and bottom padding.
|
||||
@@ -131,7 +175,9 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
return TerminalView(
|
||||
_terminalModel.terminal,
|
||||
controller: _terminalModel.terminalController,
|
||||
autofocus: true,
|
||||
focusNode: _terminalFocusNode,
|
||||
// Note: autofocus is not used here because focus is managed manually
|
||||
// via _onTabStateChanged() to handle tab switching properly.
|
||||
backgroundOpacity: 0.7,
|
||||
padding: _calculatePadding(heightPx),
|
||||
onSecondaryTapDown: (details, offset) async {
|
||||
|
||||
@@ -34,6 +34,8 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
static const IconData selectedIcon = Icons.terminal;
|
||||
static const IconData unselectedIcon = Icons.terminal_outlined;
|
||||
int _nextTerminalId = 1;
|
||||
// Lightweight idempotency guard for async close operations
|
||||
final Set<String> _closingTabs = {};
|
||||
|
||||
_TerminalTabPageState(Map<String, dynamic> params) {
|
||||
Get.put(DesktopTabController(tabType: DesktopTabType.terminal));
|
||||
@@ -70,28 +72,12 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
label: tabLabel,
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon,
|
||||
onTabCloseButton: () async {
|
||||
if (await desktopTryShowTabAuditDialogCloseCancelled(
|
||||
id: tabKey,
|
||||
tabController: tabController,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
// Close the terminal session first
|
||||
final ffi = TerminalConnectionManager.getExistingConnection(peerId);
|
||||
if (ffi != null) {
|
||||
final terminalModel = ffi.terminalModels[terminalId];
|
||||
if (terminalModel != null) {
|
||||
await terminalModel.closeTerminal();
|
||||
}
|
||||
}
|
||||
// Then close the tab
|
||||
tabController.closeBy(tabKey);
|
||||
},
|
||||
onTabCloseButton: () => _closeTab(tabKey),
|
||||
page: TerminalPage(
|
||||
key: ValueKey(tabKey),
|
||||
id: peerId,
|
||||
terminalId: terminalId,
|
||||
tabKey: tabKey,
|
||||
password: password,
|
||||
isSharedPassword: isSharedPassword,
|
||||
tabController: tabController,
|
||||
@@ -101,6 +87,149 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Unified tab close handler for all close paths (button, shortcut, programmatic).
|
||||
/// Shows audit dialog, cleans up session if not persistent, then removes the UI tab.
|
||||
Future<void> _closeTab(String tabKey) async {
|
||||
// Idempotency guard: skip if already closing this tab
|
||||
if (_closingTabs.contains(tabKey)) return;
|
||||
_closingTabs.add(tabKey);
|
||||
|
||||
try {
|
||||
// Snapshot peerTabCount BEFORE any await to avoid race with concurrent
|
||||
// _closeAllTabs clearing tabController (which would make the live count
|
||||
// drop to 0 and incorrectly trigger session persistence).
|
||||
// Note: the snapshot may become stale if other individual tabs are closed
|
||||
// during the audit dialog, but this is an acceptable trade-off.
|
||||
int? snapshotPeerTabCount;
|
||||
final parsed = _parseTabKey(tabKey);
|
||||
if (parsed != null) {
|
||||
final (peerId, _) = parsed;
|
||||
snapshotPeerTabCount = tabController.state.value.tabs.where((t) {
|
||||
final p = _parseTabKey(t.key);
|
||||
return p != null && p.$1 == peerId;
|
||||
}).length;
|
||||
}
|
||||
|
||||
if (await desktopTryShowTabAuditDialogCloseCancelled(
|
||||
id: tabKey,
|
||||
tabController: tabController,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close terminal session if not in persistent mode.
|
||||
// Wrapped separately so session cleanup failure never blocks UI tab removal.
|
||||
try {
|
||||
await _closeTerminalSessionIfNeeded(tabKey,
|
||||
peerTabCount: snapshotPeerTabCount);
|
||||
} catch (e) {
|
||||
debugPrint('[TerminalTabPage] Session cleanup failed for $tabKey: $e');
|
||||
}
|
||||
// Always close the tab from UI, regardless of session cleanup result
|
||||
tabController.closeBy(tabKey);
|
||||
} catch (e) {
|
||||
debugPrint('[TerminalTabPage] Error closing tab $tabKey: $e');
|
||||
} finally {
|
||||
_closingTabs.remove(tabKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// Close all tabs with session cleanup.
|
||||
/// Used for window-level close operations (onDestroy, handleWindowCloseButton).
|
||||
/// UI tabs are removed immediately; session cleanup runs in parallel with a
|
||||
/// bounded timeout so window close is not blocked indefinitely.
|
||||
Future<void> _closeAllTabs() async {
|
||||
final tabKeys = tabController.state.value.tabs.map((t) => t.key).toList();
|
||||
// Remove all UI tabs immediately (same instant behavior as the old tabController.clear())
|
||||
tabController.clear();
|
||||
// Run session cleanup in parallel with bounded timeout (closeTerminal() has internal 3s timeout).
|
||||
// Skip tabs already being closed by a concurrent _closeTab() to avoid duplicate FFI calls.
|
||||
final futures = tabKeys
|
||||
.where((tabKey) => !_closingTabs.contains(tabKey))
|
||||
.map((tabKey) async {
|
||||
try {
|
||||
await _closeTerminalSessionIfNeeded(tabKey, persistAll: true);
|
||||
} catch (e) {
|
||||
debugPrint('[TerminalTabPage] Session cleanup failed for $tabKey: $e');
|
||||
}
|
||||
}).toList();
|
||||
if (futures.isNotEmpty) {
|
||||
await Future.wait(futures).timeout(
|
||||
const Duration(seconds: 4),
|
||||
onTimeout: () {
|
||||
debugPrint(
|
||||
'[TerminalTabPage] Session cleanup timed out for batch close');
|
||||
return [];
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the terminal session on server side based on persistent mode.
|
||||
///
|
||||
/// [persistAll] controls behavior when persistent mode is enabled:
|
||||
/// - `true` (window close): persist all sessions, don't close any.
|
||||
/// - `false` (tab close): only persist the last session for the peer,
|
||||
/// close others so only the most recent disconnected session survives.
|
||||
Future<void> _closeTerminalSessionIfNeeded(String tabKey,
|
||||
{bool persistAll = false, int? peerTabCount}) async {
|
||||
final parsed = _parseTabKey(tabKey);
|
||||
if (parsed == null) return;
|
||||
final (peerId, terminalId) = parsed;
|
||||
|
||||
final ffi = TerminalConnectionManager.getExistingConnection(peerId);
|
||||
if (ffi == null) return;
|
||||
|
||||
final isPersistent = bind.sessionGetToggleOptionSync(
|
||||
sessionId: ffi.sessionId,
|
||||
arg: kOptionTerminalPersistent,
|
||||
);
|
||||
|
||||
if (isPersistent) {
|
||||
if (persistAll) {
|
||||
// Window close: persist all sessions
|
||||
return;
|
||||
}
|
||||
// Tab close: only persist if this is the last tab for this peer.
|
||||
// Use the snapshot value if provided (avoids race with concurrent tab removal).
|
||||
final effectivePeerTabCount = peerTabCount ??
|
||||
tabController.state.value.tabs.where((t) {
|
||||
final p = _parseTabKey(t.key);
|
||||
return p != null && p.$1 == peerId;
|
||||
}).length;
|
||||
if (effectivePeerTabCount <= 1) {
|
||||
// Last tab for this peer — persist the session
|
||||
return;
|
||||
}
|
||||
// Not the last tab — fall through to close the session
|
||||
}
|
||||
|
||||
final terminalModel = ffi.terminalModels[terminalId];
|
||||
if (terminalModel != null) {
|
||||
// closeTerminal() has internal 3s timeout, no need for external timeout
|
||||
await terminalModel.closeTerminal();
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse tabKey (format: "peerId_terminalId") into its components.
|
||||
/// Note: peerId may contain underscores, so we use lastIndexOf('_').
|
||||
/// Returns null if tabKey format is invalid.
|
||||
(String peerId, int terminalId)? _parseTabKey(String tabKey) {
|
||||
final lastUnderscore = tabKey.lastIndexOf('_');
|
||||
if (lastUnderscore <= 0) {
|
||||
debugPrint('[TerminalTabPage] Invalid tabKey format: $tabKey');
|
||||
return null;
|
||||
}
|
||||
final terminalIdStr = tabKey.substring(lastUnderscore + 1);
|
||||
final terminalId = int.tryParse(terminalIdStr);
|
||||
if (terminalId == null) {
|
||||
debugPrint('[TerminalTabPage] Invalid terminalId in tabKey: $tabKey');
|
||||
return null;
|
||||
}
|
||||
final peerId = tabKey.substring(0, lastUnderscore);
|
||||
return (peerId, terminalId);
|
||||
}
|
||||
|
||||
Widget _tabMenuBuilder(String peerId, CancelFunc cancelFunc) {
|
||||
final List<MenuEntryBase<String>> menu = [];
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0);
|
||||
@@ -184,7 +313,8 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
} else if (call.method == kWindowEventRestoreTerminalSessions) {
|
||||
_restoreSessions(call.arguments);
|
||||
} else if (call.method == "onDestroy") {
|
||||
tabController.clear();
|
||||
// Clean up sessions before window destruction (bounded wait)
|
||||
await _closeAllTabs();
|
||||
} else if (call.method == kWindowActionRebuild) {
|
||||
reloadCurrentWindow();
|
||||
} else if (call.method == kWindowEventActiveSession) {
|
||||
@@ -194,7 +324,10 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
final currentTab = tabController.state.value.selectedTabInfo;
|
||||
assert(call.arguments is String,
|
||||
"Expected String arguments for kWindowEventActiveSession, got ${call.arguments.runtimeType}");
|
||||
if (currentTab.key.startsWith(call.arguments)) {
|
||||
// Use lastIndexOf to handle peerIds containing underscores
|
||||
final lastUnderscore = currentTab.key.lastIndexOf('_');
|
||||
if (lastUnderscore > 0 &&
|
||||
currentTab.key.substring(0, lastUnderscore) == call.arguments) {
|
||||
windowOnTop(windowId());
|
||||
return true;
|
||||
}
|
||||
@@ -265,7 +398,7 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
// macOS: Cmd+W (standard for close tab)
|
||||
final currentTab = tabController.state.value.selectedTabInfo;
|
||||
if (tabController.state.value.tabs.length > 1) {
|
||||
tabController.closeBy(currentTab.key);
|
||||
_closeTab(currentTab.key);
|
||||
return true;
|
||||
}
|
||||
} else if (!isMacOS &&
|
||||
@@ -274,7 +407,7 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
// Other platforms: Ctrl+Shift+W (to avoid conflict with Ctrl+W word delete)
|
||||
final currentTab = tabController.state.value.selectedTabInfo;
|
||||
if (tabController.state.value.tabs.length > 1) {
|
||||
tabController.closeBy(currentTab.key);
|
||||
_closeTab(currentTab.key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -329,7 +462,10 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
void _addNewTerminal(String peerId, {int? terminalId}) {
|
||||
// Find first tab for this peer to get connection parameters
|
||||
final firstTab = tabController.state.value.tabs.firstWhere(
|
||||
(tab) => tab.key.startsWith('$peerId\_'),
|
||||
(tab) {
|
||||
final last = tab.key.lastIndexOf('_');
|
||||
return last > 0 && tab.key.substring(0, last) == peerId;
|
||||
},
|
||||
);
|
||||
if (firstTab.page is TerminalPage) {
|
||||
final page = firstTab.page as TerminalPage;
|
||||
@@ -350,11 +486,10 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
|
||||
void _addNewTerminalForCurrentPeer({int? terminalId}) {
|
||||
final currentTab = tabController.state.value.selectedTabInfo;
|
||||
final parts = currentTab.key.split('_');
|
||||
if (parts.isNotEmpty) {
|
||||
final peerId = parts[0];
|
||||
_addNewTerminal(peerId, terminalId: terminalId);
|
||||
}
|
||||
final parsed = _parseTabKey(currentTab.key);
|
||||
if (parsed == null) return;
|
||||
final (peerId, _) = parsed;
|
||||
_addNewTerminal(peerId, terminalId: terminalId);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -368,10 +503,9 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
selectedBorderColor: MyTheme.accent,
|
||||
labelGetter: DesktopTab.tablabelGetter,
|
||||
tabMenuBuilder: (key) {
|
||||
// Extract peerId from tab key (format: "peerId_terminalId")
|
||||
final parts = key.split('_');
|
||||
if (parts.isEmpty) return Container();
|
||||
final peerId = parts[0];
|
||||
final parsed = _parseTabKey(key);
|
||||
if (parsed == null) return Container();
|
||||
final (peerId, _) = parsed;
|
||||
return _tabMenuBuilder(peerId, () {});
|
||||
},
|
||||
));
|
||||
@@ -426,7 +560,7 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
}
|
||||
}
|
||||
if (connLength <= 1) {
|
||||
tabController.clear();
|
||||
await _closeAllTabs();
|
||||
return true;
|
||||
} else {
|
||||
final bool res;
|
||||
@@ -437,7 +571,7 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
res = await closeConfirmDialog();
|
||||
}
|
||||
if (res) {
|
||||
tabController.clear();
|
||||
await _closeAllTabs();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1861,8 +1861,18 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pi.isWayland && mode.key != kKeyMapMode) {
|
||||
continue;
|
||||
if (pi.isWayland) {
|
||||
// Legacy mode is hidden on desktop control side because dead keys
|
||||
// don't work properly on Wayland. When the control side is mobile,
|
||||
// Legacy mode is used automatically (mobile always sends Legacy events).
|
||||
if (mode.key == kKeyLegacyMode) {
|
||||
continue;
|
||||
}
|
||||
// Translate mode requires server >= 1.4.6.
|
||||
if (mode.key == kKeyTranslateMode &&
|
||||
versionCmp(pi.version, '1.4.6') < 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var text = translate(mode.menu);
|
||||
|
||||
@@ -68,6 +68,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
double _viewInsetsBottom = 0;
|
||||
final _uniqueKey = UniqueKey();
|
||||
Timer? _timerDidChangeMetrics;
|
||||
Timer? _iosKeyboardWorkaroundTimer;
|
||||
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
|
||||
@@ -140,6 +141,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
await gFFI.close();
|
||||
_timer?.cancel();
|
||||
_timerDidChangeMetrics?.cancel();
|
||||
_iosKeyboardWorkaroundTimer?.cancel();
|
||||
gFFI.dialogManager.dismissAll();
|
||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
@@ -206,7 +208,24 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
gFFI.ffiModel.pi.version.isNotEmpty) {
|
||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||
}
|
||||
|
||||
// Workaround for iOS: physical keyboard input fails after virtual keyboard is hidden
|
||||
// https://github.com/flutter/flutter/issues/39900
|
||||
// https://github.com/rustdesk/rustdesk/discussions/11843#discussioncomment-13499698 - Virtual keyboard issue
|
||||
if (isIOS) {
|
||||
_iosKeyboardWorkaroundTimer?.cancel();
|
||||
_iosKeyboardWorkaroundTimer = Timer(Duration(milliseconds: 100), () {
|
||||
if (!mounted) return;
|
||||
_physicalFocusNode.unfocus();
|
||||
_iosKeyboardWorkaroundTimer = Timer(Duration(milliseconds: 50), () {
|
||||
if (!mounted) return;
|
||||
_physicalFocusNode.requestFocus();
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_iosKeyboardWorkaroundTimer?.cancel();
|
||||
_iosKeyboardWorkaroundTimer = null;
|
||||
_timer?.cancel();
|
||||
_timer = Timer(kMobileDelaySoftKeyboardFocus, () {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
|
||||
@@ -688,7 +688,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
SettingsTile(
|
||||
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
|
||||
? translate('Login')
|
||||
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
|
||||
: '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})')),
|
||||
leading: Icon(Icons.person),
|
||||
onPressed: (context) {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
|
||||
@@ -2215,10 +2215,32 @@ class CanvasModel with ChangeNotifier {
|
||||
double w = size.width - leftToEdge - rightToEdge;
|
||||
double h = size.height - topToEdge - bottomToEdge;
|
||||
if (isMobile) {
|
||||
// Account for horizontal safe area insets on both orientations.
|
||||
w = w - mediaData.padding.left - mediaData.padding.right;
|
||||
// Vertically, subtract the bottom keyboard inset (viewInsets.bottom) and any
|
||||
// bottom overlay (e.g. key-help tools) so the canvas is not covered.
|
||||
h = h -
|
||||
mediaData.viewInsets.bottom -
|
||||
(parent.target?.cursorModel.keyHelpToolsRectToAdjustCanvas?.bottom ??
|
||||
0);
|
||||
// Orientation-specific handling:
|
||||
// - Portrait: additionally subtract top padding (e.g. status bar / notch)
|
||||
// - Landscape: does not subtract mediaData.padding.top/bottom (home indicator auto-hides)
|
||||
final isPortrait = size.height > size.width;
|
||||
if (isPortrait) {
|
||||
// In portrait mode, subtract the top safe-area padding (e.g. status bar / notch)
|
||||
// so the remote image is not truncated, while keeping the bottom inset to avoid
|
||||
// introducing unnecessary blank space around the canvas.
|
||||
//
|
||||
// iOS -> Android, portrait, adjust mode:
|
||||
// h = h (no padding subtracted): top and bottom are truncated
|
||||
// https://github.com/user-attachments/assets/30ed4559-c27e-432b-847f-8fec23c9f998
|
||||
// h = h - top - bottom: extra blank spaces appear
|
||||
// https://github.com/user-attachments/assets/12a98817-3b4e-43aa-be0f-4b03cf364b7e
|
||||
// h = h - top (current): works fine
|
||||
// https://github.com/user-attachments/assets/95f047f2-7f47-4a36-8113-5023989a0c81
|
||||
h = h - mediaData.padding.top;
|
||||
}
|
||||
}
|
||||
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@ class TerminalModel with ChangeNotifier {
|
||||
bool _disposed = false;
|
||||
|
||||
final _inputBuffer = <String>[];
|
||||
// Buffer for output data received before terminal view has valid dimensions.
|
||||
// This prevents NaN errors when writing to terminal before layout is complete.
|
||||
final _pendingOutputChunks = <String>[];
|
||||
int _pendingOutputSize = 0;
|
||||
static const int _kMaxOutputBufferChars = 8 * 1024;
|
||||
// View ready state: true when terminal has valid dimensions, safe to write
|
||||
bool _terminalViewReady = false;
|
||||
|
||||
bool get isPeerWindows => parent.ffiModel.pi.platform == kPeerPlatformWindows;
|
||||
|
||||
@@ -74,6 +81,12 @@ class TerminalModel with ChangeNotifier {
|
||||
// This piece of code must be placed before the conditional check in order to initialize properly.
|
||||
onResizeExternal?.call(w, h, pw, ph);
|
||||
|
||||
// Mark terminal view as ready and flush any buffered output on first valid resize.
|
||||
// Must be after onResizeExternal so the view layer has valid dimensions before flushing.
|
||||
if (!_terminalViewReady) {
|
||||
_markViewReady();
|
||||
}
|
||||
|
||||
if (_terminalOpened) {
|
||||
// Notify remote terminal of resize
|
||||
try {
|
||||
@@ -141,7 +154,7 @@ class TerminalModel with ChangeNotifier {
|
||||
debugPrint('[TerminalModel] Error calling sessionOpenTerminal: $e');
|
||||
// Optionally show error to user
|
||||
if (e is TimeoutException) {
|
||||
terminal.write('Failed to open terminal: Connection timeout\r\n');
|
||||
_writeToTerminal('Failed to open terminal: Connection timeout\r\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +296,7 @@ class TerminalModel with ChangeNotifier {
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
terminal.write('Failed to open terminal: $message\r\n');
|
||||
_writeToTerminal('Failed to open terminal: $message\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,29 +340,83 @@ class TerminalModel with ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
terminal.write(text);
|
||||
_writeToTerminal(text);
|
||||
} catch (e) {
|
||||
debugPrint('[TerminalModel] Failed to process terminal data: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write text to terminal, buffering if the view is not yet ready.
|
||||
/// All terminal output should go through this method to avoid NaN errors
|
||||
/// from writing before the terminal view has valid layout dimensions.
|
||||
void _writeToTerminal(String text) {
|
||||
if (!_terminalViewReady) {
|
||||
// If a single chunk exceeds the cap, keep only its tail.
|
||||
// Note: truncation may split a multi-byte ANSI escape sequence,
|
||||
// which can cause a brief visual glitch on flush. This is acceptable
|
||||
// because it only affects the pre-layout buffering window and the
|
||||
// terminal will self-correct on subsequent output.
|
||||
if (text.length >= _kMaxOutputBufferChars) {
|
||||
final truncated =
|
||||
text.substring(text.length - _kMaxOutputBufferChars);
|
||||
_pendingOutputChunks
|
||||
..clear()
|
||||
..add(truncated);
|
||||
_pendingOutputSize = truncated.length;
|
||||
} else {
|
||||
_pendingOutputChunks.add(text);
|
||||
_pendingOutputSize += text.length;
|
||||
// Drop oldest chunks if exceeds limit (whole chunks to preserve ANSI sequences)
|
||||
while (_pendingOutputSize > _kMaxOutputBufferChars &&
|
||||
_pendingOutputChunks.length > 1) {
|
||||
final removed = _pendingOutputChunks.removeAt(0);
|
||||
_pendingOutputSize -= removed.length;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
terminal.write(text);
|
||||
}
|
||||
|
||||
void _flushOutputBuffer() {
|
||||
if (_pendingOutputChunks.isEmpty) return;
|
||||
debugPrint(
|
||||
'[TerminalModel] Flushing $_pendingOutputSize buffered chars (${_pendingOutputChunks.length} chunks)');
|
||||
for (final chunk in _pendingOutputChunks) {
|
||||
terminal.write(chunk);
|
||||
}
|
||||
_pendingOutputChunks.clear();
|
||||
_pendingOutputSize = 0;
|
||||
}
|
||||
|
||||
/// Mark terminal view as ready and flush buffered output.
|
||||
void _markViewReady() {
|
||||
if (_terminalViewReady) return;
|
||||
_terminalViewReady = true;
|
||||
_flushOutputBuffer();
|
||||
}
|
||||
|
||||
void _handleTerminalClosed(Map<String, dynamic> evt) {
|
||||
final int exitCode = evt['exit_code'] ?? 0;
|
||||
terminal.write('\r\nTerminal closed with exit code: $exitCode\r\n');
|
||||
_writeToTerminal('\r\nTerminal closed with exit code: $exitCode\r\n');
|
||||
_terminalOpened = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _handleTerminalError(Map<String, dynamic> evt) {
|
||||
final String message = evt['message'] ?? 'Unknown error';
|
||||
terminal.write('\r\nTerminal error: $message\r\n');
|
||||
_writeToTerminal('\r\nTerminal error: $message\r\n');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
// Clear buffers to free memory
|
||||
_inputBuffer.clear();
|
||||
_pendingOutputChunks.clear();
|
||||
_pendingOutputSize = 0;
|
||||
// Terminal cleanup is handled server-side when service closes
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -16,9 +16,23 @@ bool refreshingUser = false;
|
||||
|
||||
class UserModel {
|
||||
final RxString userName = ''.obs;
|
||||
final RxString displayName = ''.obs;
|
||||
final RxBool isAdmin = false.obs;
|
||||
final RxString networkError = ''.obs;
|
||||
bool get isLogin => userName.isNotEmpty;
|
||||
String get displayNameOrUserName =>
|
||||
displayName.value.trim().isEmpty ? userName.value : displayName.value;
|
||||
String get accountLabelWithHandle {
|
||||
final username = userName.value.trim();
|
||||
if (username.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
final preferred = displayName.value.trim();
|
||||
if (preferred.isEmpty || preferred == username) {
|
||||
return username;
|
||||
}
|
||||
return '$preferred (@$username)';
|
||||
}
|
||||
WeakReference<FFI> parent;
|
||||
|
||||
UserModel(this.parent) {
|
||||
@@ -98,7 +112,8 @@ class UserModel {
|
||||
_updateLocalUserInfo() {
|
||||
final userInfo = getLocalUserInfo();
|
||||
if (userInfo != null) {
|
||||
userName.value = userInfo['name'];
|
||||
userName.value = (userInfo['name'] ?? '').toString();
|
||||
displayName.value = (userInfo['display_name'] ?? '').toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,10 +125,12 @@ class UserModel {
|
||||
await gFFI.groupModel.reset();
|
||||
}
|
||||
userName.value = '';
|
||||
displayName.value = '';
|
||||
}
|
||||
|
||||
_parseAndUpdateUser(UserPayload user) {
|
||||
userName.value = user.name;
|
||||
displayName.value = user.displayName;
|
||||
isAdmin.value = user.isAdmin;
|
||||
bind.mainSetLocalOption(key: 'user_info', value: jsonEncode(user));
|
||||
if (isWeb) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(runner LANGUAGES CXX)
|
||||
project(runner LANGUAGES C CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
@@ -54,6 +54,55 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
# Wayland protocol for keyboard shortcuts inhibit
|
||||
pkg_check_modules(WAYLAND_CLIENT IMPORTED_TARGET wayland-client)
|
||||
pkg_check_modules(WAYLAND_PROTOCOLS_PKG QUIET wayland-protocols)
|
||||
pkg_check_modules(WAYLAND_SCANNER_PKG QUIET wayland-scanner)
|
||||
|
||||
if(WAYLAND_PROTOCOLS_PKG_FOUND)
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||
endif()
|
||||
if(WAYLAND_SCANNER_PKG_FOUND)
|
||||
pkg_get_variable(WAYLAND_SCANNER wayland-scanner wayland_scanner)
|
||||
endif()
|
||||
|
||||
if(WAYLAND_CLIENT_FOUND AND WAYLAND_PROTOCOLS_DIR AND WAYLAND_SCANNER)
|
||||
set(KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL
|
||||
"${WAYLAND_PROTOCOLS_DIR}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml")
|
||||
|
||||
if(EXISTS ${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL})
|
||||
set(WAYLAND_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/wayland-protocols")
|
||||
file(MAKE_DIRECTORY ${WAYLAND_GENERATED_DIR})
|
||||
|
||||
# Generate client header
|
||||
add_custom_command(
|
||||
OUTPUT "${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
COMMAND ${WAYLAND_SCANNER} client-header
|
||||
${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
DEPENDS ${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Generate protocol code
|
||||
add_custom_command(
|
||||
OUTPUT "${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-protocol.c"
|
||||
COMMAND ${WAYLAND_SCANNER} private-code
|
||||
${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-protocol.c"
|
||||
DEPENDS ${KEYBOARD_SHORTCUTS_INHIBIT_PROTOCOL}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set(WAYLAND_PROTOCOL_SOURCES
|
||||
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
"${WAYLAND_GENERATED_DIR}/keyboard-shortcuts-inhibit-unstable-v1-protocol.c"
|
||||
)
|
||||
|
||||
set(HAS_KEYBOARD_SHORTCUTS_INHIBIT TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME above,
|
||||
@@ -63,9 +112,11 @@ add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"wayland_shortcuts_inhibit.cc"
|
||||
"bump_mouse.cc"
|
||||
"bump_mouse_x11.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
${WAYLAND_PROTOCOL_SOURCES}
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
@@ -78,6 +129,13 @@ target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${CMAKE_DL_LIBS})
|
||||
# target_link_libraries(${BINARY_NAME} PRIVATE librustdesk)
|
||||
|
||||
# Wayland support for keyboard shortcuts inhibit
|
||||
if(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
target_include_directories(${BINARY_NAME} PRIVATE ${WAYLAND_GENERATED_DIR})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::WAYLAND_CLIENT)
|
||||
endif()
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
#include "wayland_shortcuts_inhibit.h"
|
||||
#endif
|
||||
|
||||
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
@@ -91,6 +96,13 @@ static void my_application_activate(GApplication* application) {
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
|
||||
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
// Register callback for sub-windows created by desktop_multi_window plugin
|
||||
// Only sub-windows (remote windows) need keyboard shortcuts inhibition
|
||||
desktop_multi_window_plugin_set_window_created_callback(
|
||||
(WindowCreatedCallback)wayland_shortcuts_inhibit_init_for_subwindow);
|
||||
#endif
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
|
||||
|
||||
244
flutter/linux/wayland_shortcuts_inhibit.cc
Normal file
244
flutter/linux/wayland_shortcuts_inhibit.cc
Normal file
@@ -0,0 +1,244 @@
|
||||
// Wayland keyboard shortcuts inhibit implementation
|
||||
// Uses the zwp_keyboard_shortcuts_inhibit_manager_v1 protocol to request
|
||||
// the compositor to disable system shortcuts for specific windows.
|
||||
|
||||
#include "wayland_shortcuts_inhibit.h"
|
||||
|
||||
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
|
||||
#include <cstring>
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <wayland-client.h>
|
||||
#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
|
||||
// Data structure to hold inhibitor state for each window
|
||||
typedef struct {
|
||||
struct zwp_keyboard_shortcuts_inhibit_manager_v1* manager;
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1* inhibitor;
|
||||
} ShortcutsInhibitData;
|
||||
|
||||
// Cleanup function for ShortcutsInhibitData
|
||||
static void shortcuts_inhibit_data_free(gpointer data) {
|
||||
ShortcutsInhibitData* inhibit_data = static_cast<ShortcutsInhibitData*>(data);
|
||||
if (inhibit_data->inhibitor != NULL) {
|
||||
zwp_keyboard_shortcuts_inhibitor_v1_destroy(inhibit_data->inhibitor);
|
||||
}
|
||||
if (inhibit_data->manager != NULL) {
|
||||
zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(inhibit_data->manager);
|
||||
}
|
||||
g_free(inhibit_data);
|
||||
}
|
||||
|
||||
// Wayland registry handler to find the shortcuts inhibit manager
|
||||
static void registry_handle_global(void* data, struct wl_registry* registry,
|
||||
uint32_t name, const char* interface,
|
||||
uint32_t /*version*/) {
|
||||
ShortcutsInhibitData* inhibit_data = static_cast<ShortcutsInhibitData*>(data);
|
||||
if (strcmp(interface,
|
||||
zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name) == 0) {
|
||||
inhibit_data->manager =
|
||||
static_cast<zwp_keyboard_shortcuts_inhibit_manager_v1*>(wl_registry_bind(
|
||||
registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface,
|
||||
1));
|
||||
}
|
||||
}
|
||||
|
||||
static void registry_handle_global_remove(void* /*data*/, struct wl_registry* /*registry*/,
|
||||
uint32_t /*name*/) {
|
||||
// Not needed for this use case
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
registry_handle_global,
|
||||
registry_handle_global_remove,
|
||||
};
|
||||
|
||||
// Inhibitor event handlers
|
||||
static void inhibitor_active(void* /*data*/,
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1* /*inhibitor*/) {
|
||||
// Inhibitor is now active, shortcuts are being captured
|
||||
}
|
||||
|
||||
static void inhibitor_inactive(void* /*data*/,
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1* /*inhibitor*/) {
|
||||
// Inhibitor is now inactive, shortcuts restored to compositor
|
||||
}
|
||||
|
||||
static const struct zwp_keyboard_shortcuts_inhibitor_v1_listener inhibitor_listener = {
|
||||
inhibitor_active,
|
||||
inhibitor_inactive,
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
static void uninhibit_keyboard_shortcuts(GtkWindow* window);
|
||||
|
||||
// Inhibit keyboard shortcuts on Wayland for a specific window
|
||||
static void inhibit_keyboard_shortcuts(GtkWindow* window) {
|
||||
GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(window));
|
||||
if (!GDK_IS_WAYLAND_DISPLAY(display)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already inhibited for this window
|
||||
if (g_object_get_data(G_OBJECT(window), "shortcuts-inhibit-data") != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ShortcutsInhibitData* inhibit_data = g_new0(ShortcutsInhibitData, 1);
|
||||
|
||||
struct wl_display* wl_display = gdk_wayland_display_get_wl_display(display);
|
||||
if (wl_display == NULL) {
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
struct wl_registry* registry = wl_display_get_registry(wl_display);
|
||||
if (registry == NULL) {
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
wl_registry_add_listener(registry, ®istry_listener, inhibit_data);
|
||||
wl_display_roundtrip(wl_display);
|
||||
|
||||
if (inhibit_data->manager == NULL) {
|
||||
wl_registry_destroy(registry);
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
if (gdk_window == NULL) {
|
||||
wl_registry_destroy(registry);
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
struct wl_surface* surface = gdk_wayland_window_get_wl_surface(gdk_window);
|
||||
if (surface == NULL) {
|
||||
wl_registry_destroy(registry);
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
GdkSeat* gdk_seat = gdk_display_get_default_seat(display);
|
||||
if (gdk_seat == NULL) {
|
||||
wl_registry_destroy(registry);
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
struct wl_seat* seat = gdk_wayland_seat_get_wl_seat(gdk_seat);
|
||||
if (seat == NULL) {
|
||||
wl_registry_destroy(registry);
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
inhibit_data->inhibitor =
|
||||
zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
|
||||
inhibit_data->manager, surface, seat);
|
||||
|
||||
if (inhibit_data->inhibitor == NULL) {
|
||||
wl_registry_destroy(registry);
|
||||
shortcuts_inhibit_data_free(inhibit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add listener to monitor active/inactive state
|
||||
zwp_keyboard_shortcuts_inhibitor_v1_add_listener(
|
||||
inhibit_data->inhibitor, &inhibitor_listener, window);
|
||||
|
||||
wl_display_roundtrip(wl_display);
|
||||
wl_registry_destroy(registry);
|
||||
|
||||
// Associate the inhibit data with the window for cleanup on destroy
|
||||
g_object_set_data_full(G_OBJECT(window), "shortcuts-inhibit-data",
|
||||
inhibit_data, shortcuts_inhibit_data_free);
|
||||
}
|
||||
|
||||
// Remove keyboard shortcuts inhibitor from a window
|
||||
static void uninhibit_keyboard_shortcuts(GtkWindow* window) {
|
||||
ShortcutsInhibitData* inhibit_data = static_cast<ShortcutsInhibitData*>(
|
||||
g_object_get_data(G_OBJECT(window), "shortcuts-inhibit-data"));
|
||||
|
||||
if (inhibit_data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This will trigger shortcuts_inhibit_data_free via g_object_set_data
|
||||
g_object_set_data(G_OBJECT(window), "shortcuts-inhibit-data", NULL);
|
||||
}
|
||||
|
||||
// Focus event handlers for dynamic inhibitor management
|
||||
static gboolean on_window_focus_in(GtkWidget* widget, GdkEventFocus* /*event*/, gpointer /*user_data*/) {
|
||||
if (GTK_IS_WINDOW(widget)) {
|
||||
inhibit_keyboard_shortcuts(GTK_WINDOW(widget));
|
||||
}
|
||||
return FALSE; // Continue event propagation
|
||||
}
|
||||
|
||||
static gboolean on_window_focus_out(GtkWidget* widget, GdkEventFocus* /*event*/, gpointer /*user_data*/) {
|
||||
if (GTK_IS_WINDOW(widget)) {
|
||||
uninhibit_keyboard_shortcuts(GTK_WINDOW(widget));
|
||||
}
|
||||
return FALSE; // Continue event propagation
|
||||
}
|
||||
|
||||
// Key for marking window as having focus handlers connected
|
||||
static const char* const kFocusHandlersConnectedKey = "shortcuts-inhibit-focus-handlers-connected";
|
||||
// Key for marking window as having a pending realize handler
|
||||
static const char* const kRealizeHandlerConnectedKey = "shortcuts-inhibit-realize-handler-connected";
|
||||
|
||||
// Callback when window is realized (mapped to screen)
|
||||
// Sets up focus-based inhibitor management
|
||||
static void on_window_realize(GtkWidget* widget, gpointer /*user_data*/) {
|
||||
if (GTK_IS_WINDOW(widget)) {
|
||||
// Check if focus handlers are already connected to avoid duplicates
|
||||
if (g_object_get_data(G_OBJECT(widget), kFocusHandlersConnectedKey) != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect focus events for dynamic inhibitor management
|
||||
g_signal_connect(widget, "focus-in-event",
|
||||
G_CALLBACK(on_window_focus_in), NULL);
|
||||
g_signal_connect(widget, "focus-out-event",
|
||||
G_CALLBACK(on_window_focus_out), NULL);
|
||||
|
||||
// Mark as connected to prevent duplicate connections
|
||||
g_object_set_data(G_OBJECT(widget), kFocusHandlersConnectedKey, GINT_TO_POINTER(1));
|
||||
|
||||
// If window already has focus, create inhibitor now
|
||||
if (gtk_window_has_toplevel_focus(GTK_WINDOW(widget))) {
|
||||
inhibit_keyboard_shortcuts(GTK_WINDOW(widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public API: Initialize shortcuts inhibit for a sub-window
|
||||
void wayland_shortcuts_inhibit_init_for_subwindow(void* view) {
|
||||
GtkWidget* widget = GTK_WIDGET(view);
|
||||
GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
|
||||
|
||||
if (toplevel != NULL && GTK_IS_WINDOW(toplevel)) {
|
||||
// Check if already initialized to avoid duplicate realize handlers
|
||||
if (g_object_get_data(G_OBJECT(toplevel), kFocusHandlersConnectedKey) != NULL ||
|
||||
g_object_get_data(G_OBJECT(toplevel), kRealizeHandlerConnectedKey) != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gtk_widget_get_realized(toplevel)) {
|
||||
// Window is already realized, set up focus handlers now
|
||||
on_window_realize(toplevel, NULL);
|
||||
} else {
|
||||
// Mark realize handler as connected to prevent duplicate connections
|
||||
// if called again before window is realized
|
||||
g_object_set_data(G_OBJECT(toplevel), kRealizeHandlerConnectedKey, GINT_TO_POINTER(1));
|
||||
// Wait for window to be realized
|
||||
g_signal_connect(toplevel, "realize",
|
||||
G_CALLBACK(on_window_realize), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
22
flutter/linux/wayland_shortcuts_inhibit.h
Normal file
22
flutter/linux/wayland_shortcuts_inhibit.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Wayland keyboard shortcuts inhibit support
|
||||
// This module provides functionality to inhibit system keyboard shortcuts
|
||||
// on Wayland compositors, allowing remote desktop windows to capture all
|
||||
// key events including Super, Alt+Tab, etc.
|
||||
|
||||
#ifndef WAYLAND_SHORTCUTS_INHIBIT_H_
|
||||
#define WAYLAND_SHORTCUTS_INHIBIT_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#if defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
|
||||
// Initialize shortcuts inhibit for a sub-window created by desktop_multi_window plugin.
|
||||
// This sets up focus-based inhibitor management: inhibitor is created when
|
||||
// the window gains focus and destroyed when it loses focus.
|
||||
//
|
||||
// @param view The FlView of the sub-window
|
||||
void wayland_shortcuts_inhibit_init_for_subwindow(void* view);
|
||||
|
||||
#endif // defined(GDK_WINDOWING_WAYLAND) && defined(HAS_KEYBOARD_SHORTCUTS_INHIBIT)
|
||||
|
||||
#endif // WAYLAND_SHORTCUTS_INHIBIT_H_
|
||||
@@ -10,7 +10,7 @@ TODO: Move this lib to a separate project.
|
||||
|
||||
## How it works
|
||||
|
||||
Terminalogies:
|
||||
Terminologies:
|
||||
|
||||
- cliprdr: this module
|
||||
- local: the endpoint which initiates a file copy events
|
||||
@@ -50,7 +50,7 @@ sequenceDiagram
|
||||
r ->> l: Format List Response (notified)
|
||||
r ->> l: Format Data Request (requests file list)
|
||||
activate l
|
||||
note left of l: Retrive file list from system clipboard
|
||||
note left of l: Retrieve file list from system clipboard
|
||||
l ->> r: Format Data Response (containing file list)
|
||||
deactivate l
|
||||
note over r: Update system clipboard with received file list
|
||||
@@ -84,10 +84,10 @@ and copy files to remote.
|
||||
The protocol was originally designed as an extension of the Windows RDP,
|
||||
so the specific message packages fits windows well.
|
||||
|
||||
When starting cliprdr, a thread is spawn to create a invisible window
|
||||
When starting cliprdr, a thread is spawned to create an invisible window
|
||||
and to subscribe to OLE clipboard events.
|
||||
The window's callback (see `cliprdr_proc` in `src/windows/wf_cliprdr.c`) was
|
||||
set to handle a variaty of events.
|
||||
set to handle a variety of events.
|
||||
|
||||
Detailed implementation is shown in pictures above.
|
||||
|
||||
@@ -108,18 +108,18 @@ after filtering out those pointing to our FUSE directory or duplicated,
|
||||
send format list directly to remote.
|
||||
|
||||
The cliprdr server also uses clipboard client for setting clipboard,
|
||||
or retrive paths from system.
|
||||
or retrieve paths from system.
|
||||
|
||||
#### Local File List
|
||||
|
||||
The local file list is a temperary list of file metadata.
|
||||
The local file list is a temporary list of file metadata.
|
||||
When receiving file contents PDU from peer, the server picks
|
||||
out the file requested and open it for reading if necessary.
|
||||
|
||||
Also when receiving Format Data Request PDU from remote asking for file list,
|
||||
the local file list should be rebuilt from file list retrieved from Clipboard Client.
|
||||
|
||||
Some caching and preloading could done on it since applications are likely to read
|
||||
Some caching and preloading could be done on it since applications are likely to read
|
||||
on the list sequentially.
|
||||
|
||||
#### FUSE server
|
||||
|
||||
@@ -261,6 +261,8 @@ impl KeyboardControllable for Enigo {
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.custom_keyboard {
|
||||
keyboard.key_sequence(sequence)
|
||||
} else {
|
||||
log::warn!("Enigo::key_sequence: no custom_keyboard set for Wayland!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,6 +279,7 @@ impl KeyboardControllable for Enigo {
|
||||
if let Some(keyboard) = &mut self.custom_keyboard {
|
||||
keyboard.key_down(key)
|
||||
} else {
|
||||
log::warn!("Enigo::key_down: no custom_keyboard set for Wayland!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -290,13 +293,24 @@ impl KeyboardControllable for Enigo {
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.custom_keyboard {
|
||||
keyboard.key_up(key)
|
||||
} else {
|
||||
log::warn!("Enigo::key_up: no custom_keyboard set for Wayland!");
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_click(&mut self, key: Key) {
|
||||
if self.tfc_key_click(key).is_err() {
|
||||
self.key_down(key).ok();
|
||||
self.key_up(key);
|
||||
if self.is_x11 {
|
||||
// X11: try tfc first, then fallback to key_down/key_up
|
||||
if self.tfc_key_click(key).is_err() {
|
||||
self.key_down(key).ok();
|
||||
self.key_up(key);
|
||||
}
|
||||
} else {
|
||||
if let Some(keyboard) = &mut self.custom_keyboard {
|
||||
keyboard.key_click(key);
|
||||
} else {
|
||||
log::warn!("Enigo::key_click: no custom_keyboard set for Wayland!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Submodule libs/hbb_common updated: 900077a2c2...0b60b9ffa0
@@ -98,7 +98,7 @@ unsafe fn check_x11_shm_available(c: *mut xcb_connection_t) -> Result<(), Error>
|
||||
let mut e: *mut xcb_generic_error_t = std::ptr::null_mut();
|
||||
let reply = xcb_shm_query_version_reply(c, cookie, &mut e as _);
|
||||
if reply.is_null() {
|
||||
// TODO: Should seperate SHM disabled from SHM not supported?
|
||||
// TODO: Should separate SHM disabled from SHM not supported?
|
||||
return Err(Error::UnsupportedExtension);
|
||||
} else {
|
||||
// https://github.com/FFmpeg/FFmpeg/blob/6229e4ac425b4566446edefb67d5c225eb397b58/libavdevice/xcbgrab.c#L229
|
||||
|
||||
@@ -336,7 +336,9 @@ def gen_custom_ARPSYSTEMCOMPONENT_True(args, dist_dir):
|
||||
f'{indent}<RegistryValue Type="integer" Name="Language" Value="[ProductLanguage]" />\n'
|
||||
)
|
||||
|
||||
estimated_size = get_folder_size(dist_dir)
|
||||
# EstimatedSize in uninstall registry must be in KB.
|
||||
estimated_size_bytes = get_folder_size(dist_dir)
|
||||
estimated_size = max(1, (estimated_size_bytes + 1023) // 1024)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="EstimatedSize" Value="{estimated_size}" />\n'
|
||||
)
|
||||
|
||||
@@ -2630,10 +2630,13 @@ impl LoginConfigHandler {
|
||||
display_name =
|
||||
serde_json::from_str::<serde_json::Value>(&LocalConfig::get_option("user_info"))
|
||||
.map(|x| {
|
||||
x.get("name")
|
||||
.map(|x| x.as_str().unwrap_or_default())
|
||||
x.get("display_name")
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|x| x.trim())
|
||||
.filter(|x| !x.is_empty())
|
||||
.or_else(|| x.get("name").and_then(|x| x.as_str()))
|
||||
.map(|x| x.to_owned())
|
||||
.unwrap_or_default()
|
||||
.to_owned()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ pub fn check_clipboard_cm() -> ResultType<MultiClipboards> {
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn update_clipboard_(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
|
||||
let to_update_data = proto::from_multi_clipbards(multi_clipboards);
|
||||
let to_update_data = proto::from_multi_clipboards(multi_clipboards);
|
||||
if to_update_data.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -432,7 +432,7 @@ impl ClipboardContext {
|
||||
#[cfg(target_os = "macos")]
|
||||
let is_kde_x11 = false;
|
||||
let clear_holder_text = if is_kde_x11 {
|
||||
"RustDesk placeholder to clear the file clipbard"
|
||||
"RustDesk placeholder to clear the file clipboard"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -672,7 +672,7 @@ mod proto {
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub fn from_multi_clipbards(multi_clipboards: Vec<Clipboard>) -> Vec<ClipboardData> {
|
||||
pub fn from_multi_clipboards(multi_clipboards: Vec<Clipboard>) -> Vec<ClipboardData> {
|
||||
multi_clipboards
|
||||
.into_iter()
|
||||
.filter_map(from_clipboard)
|
||||
@@ -814,7 +814,7 @@ pub mod clipboard_listener {
|
||||
subscribers: listener_lock.subscribers.clone(),
|
||||
};
|
||||
let (tx_start_res, rx_start_res) = channel();
|
||||
let h = start_clipbard_master_thread(handler, tx_start_res);
|
||||
let h = start_clipboard_master_thread(handler, tx_start_res);
|
||||
let shutdown = match rx_start_res.recv() {
|
||||
Ok((Some(s), _)) => s,
|
||||
Ok((None, err)) => {
|
||||
@@ -854,7 +854,7 @@ pub mod clipboard_listener {
|
||||
log::info!("Clipboard listener unsubscribed: {}", name);
|
||||
}
|
||||
|
||||
fn start_clipbard_master_thread(
|
||||
fn start_clipboard_master_thread(
|
||||
handler: impl ClipboardHandler + Send + 'static,
|
||||
tx_start_res: Sender<(Option<Shutdown>, String)>,
|
||||
) -> JoinHandle<()> {
|
||||
|
||||
@@ -1072,10 +1072,6 @@ fn get_api_server_(api: String, custom: String) -> String {
|
||||
if !api.is_empty() {
|
||||
return api.to_owned();
|
||||
}
|
||||
let api = option_env!("API_SERVER").unwrap_or_default();
|
||||
if !api.is_empty() {
|
||||
return api.into();
|
||||
}
|
||||
let s0 = get_custom_rendezvous_server(custom);
|
||||
if !s0.is_empty() {
|
||||
let s = crate::increase_port(&s0, -2);
|
||||
@@ -1737,8 +1733,7 @@ pub fn create_symmetric_key_msg(their_pk_b: [u8; 32]) -> (Bytes, Bytes, secretbo
|
||||
|
||||
#[inline]
|
||||
pub fn using_public_server() -> bool {
|
||||
option_env!("RENDEZVOUS_SERVER").unwrap_or("").is_empty()
|
||||
&& crate::get_custom_rendezvous_server(get_option("custom-rendezvous-server")).is_empty()
|
||||
crate::get_custom_rendezvous_server(get_option("custom-rendezvous-server")).is_empty()
|
||||
}
|
||||
|
||||
pub struct ThrottledInterval {
|
||||
|
||||
@@ -2759,6 +2759,11 @@ pub fn main_get_common(key: String) -> String {
|
||||
None => "",
|
||||
}
|
||||
.to_string();
|
||||
} else if key == "has-gnome-shortcuts-inhibitor-permission" {
|
||||
#[cfg(target_os = "linux")]
|
||||
return crate::platform::linux::has_gnome_shortcuts_inhibitor_permission().to_string();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false.to_string();
|
||||
} else {
|
||||
if key.starts_with("download-data-") {
|
||||
let id = key.replace("download-data-", "");
|
||||
@@ -2920,6 +2925,29 @@ pub fn main_set_common(_key: String, _value: String) {
|
||||
} else if _key == "cancel-downloader" {
|
||||
crate::hbbs_http::downloader::cancel(&_value);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
if _key == "clear-gnome-shortcuts-inhibitor-permission" {
|
||||
std::thread::spawn(move || {
|
||||
let (success, msg) =
|
||||
match crate::platform::linux::clear_gnome_shortcuts_inhibitor_permission() {
|
||||
Ok(_) => (true, "".to_owned()),
|
||||
Err(e) => (false, e.to_string()),
|
||||
};
|
||||
let data = HashMap::from([
|
||||
(
|
||||
"name",
|
||||
serde_json::json!("clear-gnome-shortcuts-inhibitor-permission-res"),
|
||||
),
|
||||
("success", serde_json::json!(success)),
|
||||
("msg", serde_json::json!(msg)),
|
||||
]);
|
||||
let _res = flutter::push_global_event(
|
||||
flutter::APP_TYPE_MAIN,
|
||||
serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_get_common_sync(
|
||||
|
||||
@@ -80,6 +80,8 @@ pub enum UserStatus {
|
||||
pub struct UserPayload {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub display_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub email: Option<String>,
|
||||
#[serde(default)]
|
||||
pub note: Option<String>,
|
||||
@@ -268,7 +270,12 @@ impl OidcSession {
|
||||
);
|
||||
LocalConfig::set_option(
|
||||
"user_info".to_owned(),
|
||||
serde_json::json!({ "name": auth_body.user.name, "status": auth_body.user.status }).to_string(),
|
||||
serde_json::json!({
|
||||
"name": auth_body.user.name,
|
||||
"display_name": auth_body.user.display_name,
|
||||
"status": auth_body.user.status
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "عرض RustDesk"),
|
||||
("This PC", "هذا الحاسب"),
|
||||
("or", "او"),
|
||||
("Continue with", "متابعة مع"),
|
||||
("Elevate", "ارتقاء"),
|
||||
("Zoom cursor", "تكبير المؤشر"),
|
||||
("Accept sessions via password", "قبول الجلسات عبر كلمة المرور"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "متابعة مع {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Паказаць RustDesk"),
|
||||
("This PC", "Гэты кампутар"),
|
||||
("or", "або"),
|
||||
("Continue with", "Працягнуць з"),
|
||||
("Elevate", "Павысіць"),
|
||||
("Zoom cursor", "Павялічэнне курсора"),
|
||||
("Accept sessions via password", "Прымаць сеансы па паролю"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Працягнуць з {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Покажи RustDesk"),
|
||||
("This PC", "Този компютър"),
|
||||
("or", "или"),
|
||||
("Continue with", "Продължи с"),
|
||||
("Elevate", "Повишаване"),
|
||||
("Zoom cursor", "Уголемяване курсор"),
|
||||
("Accept sessions via password", "Приемане сесии чрез парола"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Продължи с {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Mostra el RustDesk"),
|
||||
("This PC", "Aquest equip"),
|
||||
("or", "o"),
|
||||
("Continue with", "Continua amb"),
|
||||
("Elevate", "Permisos ampliats"),
|
||||
("Zoom cursor", "Escala del ratolí"),
|
||||
("Accept sessions via password", "Accepta les sessions mitjançant una contrasenya"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Continua amb {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "显示 RustDesk"),
|
||||
("This PC", "此电脑"),
|
||||
("or", "或"),
|
||||
("Continue with", "使用"),
|
||||
("Elevate", "提权"),
|
||||
("Zoom cursor", "缩放光标"),
|
||||
("Accept sessions via password", "只允许密码访问"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "更新日志"),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "使用 {} 登录"),
|
||||
("Display Name", "显示名称"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Zobrazit RustDesk"),
|
||||
("This PC", "Tento počítač"),
|
||||
("or", "nebo"),
|
||||
("Continue with", "Pokračovat s"),
|
||||
("Elevate", "Zvýšit"),
|
||||
("Zoom cursor", "Kurzor přiblížení"),
|
||||
("Accept sessions via password", "Přijímat relace pomocí hesla"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Pokračovat s {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Vis RustDesk"),
|
||||
("This PC", "Denne PC"),
|
||||
("or", "eller"),
|
||||
("Continue with", "Fortsæt med"),
|
||||
("Elevate", "Elevér"),
|
||||
("Zoom cursor", "Zoom markør"),
|
||||
("Accept sessions via password", "Acceptér sessioner via adgangskode"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Fortsæt med {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk anzeigen"),
|
||||
("This PC", "Dieser PC"),
|
||||
("or", "oder"),
|
||||
("Continue with", "Fortfahren mit"),
|
||||
("Elevate", "Zugriff gewähren"),
|
||||
("Zoom cursor", "Cursor vergrößern"),
|
||||
("Accept sessions via password", "Sitzung mit Passwort bestätigen"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Änderungsprotokoll"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Bildschirm während ausgehender Sitzungen aktiv halten"),
|
||||
("keep-awake-during-incoming-sessions-label", "Bildschirm während eingehender Sitzungen aktiv halten"),
|
||||
("Continue with {}", "Fortfahren mit {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
441
src/lang/el.rs
441
src/lang/el.rs
@@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Κατάσταση"),
|
||||
("Your Desktop", "Ο σταθμός εργασίας σας"),
|
||||
("desk_tip", "Η πρόσβαση στον σταθμό εργασίας σας είναι δυνατή με αυτό το αναγνωριστικό και τον κωδικό πρόσβασης."),
|
||||
("desk_tip", "Η πρόσβαση στον σταθμό εργασίας σας είναι δυνατή με αυτό το ID και τον κωδικό πρόσβασης."),
|
||||
("Password", "Κωδικός πρόσβασης"),
|
||||
("Ready", "Έτοιμο"),
|
||||
("Established", "Συνδέθηκε"),
|
||||
@@ -19,16 +19,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Recent sessions", "Πρόσφατες συνεδρίες"),
|
||||
("Address book", "Βιβλίο διευθύνσεων"),
|
||||
("Confirmation", "Επιβεβαίωση"),
|
||||
("TCP tunneling", "TCP tunneling"),
|
||||
("TCP tunneling", "Σήραγγα TCP"),
|
||||
("Remove", "Κατάργηση"),
|
||||
("Refresh random password", "Νέος τυχαίος κωδικός πρόσβασης"),
|
||||
("Refresh random password", "Ανανέωση τυχαίου κωδικού πρόσβασης"),
|
||||
("Set your own password", "Ορίστε τον δικό σας κωδικό πρόσβασης"),
|
||||
("Enable keyboard/mouse", "Ενεργοποίηση πληκτρολογίου/ποντικιού"),
|
||||
("Enable clipboard", "Ενεργοποίηση προχείρου"),
|
||||
("Enable file transfer", "Ενεργοποίηση μεταφοράς αρχείων"),
|
||||
("Enable TCP tunneling", "Ενεργοποίηση TCP tunneling"),
|
||||
("Enable TCP tunneling", "Ενεργοποίηση σήραγγας TCP"),
|
||||
("IP Whitelisting", "Λίστα επιτρεπόμενων IP"),
|
||||
("ID/Relay Server", "Διακομιστής ID/Αναμετάδοσης"),
|
||||
("ID/Relay Server", "ID/Διακομιστής Αναμετάδοσης"),
|
||||
("Import server config", "Εισαγωγή διαμόρφωσης διακομιστή"),
|
||||
("Export Server Config", "Εξαγωγή διαμόρφωσης διακομιστή"),
|
||||
("Import server configuration successfully", "Επιτυχής εισαγωγή διαμόρφωσης διακομιστή"),
|
||||
@@ -36,14 +36,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Invalid server configuration", "Μη έγκυρη διαμόρφωση διακομιστή"),
|
||||
("Clipboard is empty", "Το πρόχειρο είναι κενό"),
|
||||
("Stop service", "Διακοπή υπηρεσίας"),
|
||||
("Change ID", "Αλλαγή αναγνωριστικού ID"),
|
||||
("Change ID", "Αλλαγή του ID σας"),
|
||||
("Your new ID", "Το νέο σας ID"),
|
||||
("length %min% to %max%", "μέγεθος από %min% έως %max%"),
|
||||
("starts with a letter", "ξεκινά με γράμμα"),
|
||||
("allowed characters", "επιτρεπόμενοι χαρακτήρες"),
|
||||
("id_change_tip", "Επιτρέπονται μόνο οι χαρακτήρες a-z, A-Z, 0-9, - (παύλα) και _ (κάτω παύλα). Το πρώτο γράμμα πρέπει να είναι a-z, A-Z και το μήκος πρέπει να είναι μεταξύ 6 και 16 χαρακτήρων."),
|
||||
("Website", "Ιστότοπος"),
|
||||
("About", "Πληροφορίες"),
|
||||
("About", "Σχετικά"),
|
||||
("Slogan_tip", "Φτιαγμένο με πάθος - σε έναν κόσμο που βυθίζεται στο χάος!"),
|
||||
("Privacy Statement", "Πολιτική απορρήτου"),
|
||||
("Mute", "Σίγαση"),
|
||||
@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input", "Είσοδος ήχου"),
|
||||
("Enhancements", "Βελτιώσεις"),
|
||||
("Hardware Codec", "Κωδικοποιητής υλικού"),
|
||||
("Adaptive bitrate", "Adaptive bitrate"),
|
||||
("Adaptive bitrate", "Προσαρμοστικός ρυθμός μετάδοσης bit"),
|
||||
("ID Server", "Διακομιστής ID"),
|
||||
("Relay Server", "Διακομιστής αναμετάδοσης"),
|
||||
("API Server", "Διακομιστής API"),
|
||||
@@ -67,18 +67,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Skip", "Παράλειψη"),
|
||||
("Close", "Κλείσιμο"),
|
||||
("Retry", "Δοκίμασε ξανά"),
|
||||
("OK", "ΟΚ"),
|
||||
("OK", "Εντάξει"),
|
||||
("Password Required", "Απαιτείται κωδικός πρόσβασης"),
|
||||
("Please enter your password", "Παρακαλώ εισάγετε τον κωδικό πρόσβασης"),
|
||||
("Remember password", "Απομνημόνευση κωδικού πρόσβασης"),
|
||||
("Wrong Password", "Λάθος κωδικός πρόσβασης"),
|
||||
("Do you want to enter again?", "Επανασύνδεση;"),
|
||||
("Do you want to enter again?", "Θέλετε να γίνει επανασύνδεση;"),
|
||||
("Connection Error", "Σφάλμα σύνδεσης"),
|
||||
("Error", "Σφάλμα"),
|
||||
("Reset by the peer", "Η σύνδεση επαναφέρθηκε από τον απομακρυσμένο σταθμό"),
|
||||
("Connecting...", "Σύνδεση..."),
|
||||
("Connection in progress. Please wait.", "Σύνδεση σε εξέλιξη. Παρακαλώ περιμένετε."),
|
||||
("Please try 1 minute later", "Παρακαλώ ξαναδοκιμάστε σε 1 λεπτό"),
|
||||
("Please try 1 minute later", "Παρακαλώ δοκιμάστε ξανά σε 1 λεπτό"),
|
||||
("Login Error", "Σφάλμα εισόδου"),
|
||||
("Successful", "Επιτυχής"),
|
||||
("Connected, waiting for image...", "Συνδέθηκε, αναμονή για εικόνα..."),
|
||||
@@ -101,10 +101,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Select All", "Επιλογή όλων"),
|
||||
("Unselect All", "Κατάργηση επιλογής όλων"),
|
||||
("Empty Directory", "Κενός φάκελος"),
|
||||
("Not an empty directory", "Ο φάκελος δεν είναι κενός"),
|
||||
("Not an empty directory", "Η διαδρομή δεν είναι κενή"),
|
||||
("Are you sure you want to delete this file?", "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο;"),
|
||||
("Are you sure you want to delete this empty directory?", "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον κενό φάκελο;"),
|
||||
("Are you sure you want to delete the file of this directory?", "Είστε βέβαιοι ότι θέλετε να διαγράψετε το αρχείο αυτού του φακέλου;"),
|
||||
("Are you sure you want to delete this empty directory?", "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την κενή διαδρομή;"),
|
||||
("Are you sure you want to delete the file of this directory?", "Είστε βέβαιοι ότι θέλετε να διαγράψετε το αρχείο αυτής της διαδρομής;"),
|
||||
("Do this for all conflicts", "Κάνε αυτό για όλες τις διενέξεις"),
|
||||
("This is irreversible!", "Αυτό είναι μη αναστρέψιμο!"),
|
||||
("Deleting", "Διαγραφή"),
|
||||
@@ -133,8 +133,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Insert Ctrl + Alt + Del", "Εισαγωγή Ctrl + Alt + Del"),
|
||||
("Insert Lock", "Κλείδωμα απομακρυσμένου σταθμού"),
|
||||
("Refresh", "Ανανέωση"),
|
||||
("ID does not exist", "Το αναγνωριστικό ID δεν υπάρχει"),
|
||||
("Failed to connect to rendezvous server", "Αποτυχία σύνδεσης με διακομιστή"),
|
||||
("ID does not exist", "Το ID αυτό δεν υπάρχει"),
|
||||
("Failed to connect to rendezvous server", "Αποτυχία σύνδεσης με τον διακομιστή"),
|
||||
("Please try later", "Παρακαλώ δοκιμάστε αργότερα"),
|
||||
("Remote desktop is offline", "Ο απομακρυσμένος σταθμός εργασίας είναι εκτός σύνδεσης"),
|
||||
("Key mismatch", "Μη έγκυρο κλειδί"),
|
||||
@@ -146,17 +146,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Set Password", "Ορίστε κωδικό πρόσβασης"),
|
||||
("OS Password", "Κωδικός πρόσβασης λειτουργικού συστήματος"),
|
||||
("install_tip", "Λόγω UAC, το RustDesk ενδέχεται να μην λειτουργεί σωστά σε ορισμένες περιπτώσεις. Για να αποφύγετε το UAC, κάντε κλικ στο κουμπί παρακάτω για να εγκαταστήσετε το RustDesk στο σύστημα"),
|
||||
("Click to upgrade", "Αναβάθμιση τώρα"),
|
||||
("Click to upgrade", "Κάντε κλίκ για αναβάθμιση τώρα"),
|
||||
("Configure", "Διαμόρφωση"),
|
||||
("config_acc", "Για τον απομακρυσμένο έλεγχο του υπολογιστή σας, πρέπει να εκχωρήσετε δικαιώματα πρόσβασης στο RustDesk."),
|
||||
("config_screen", "Για να αποκτήσετε απομακρυσμένη πρόσβαση στον υπολογιστή σας, πρέπει να εκχωρήσετε το δικαίωμα RustDesk \"Screen Capture\"."),
|
||||
("config_acc", "Για να ελέγξετε την επιφάνεια εργασίας σας από απόσταση, πρέπει να παραχωρήσετε στο RustDesk το δικαίωμα της \"Προσβασιμότητας\"."),
|
||||
("config_screen", "Για να αποκτήσετε απομακρυσμένη πρόσβαση στην επιφάνεια εργασίας σας, πρέπει να παραχωρήσετε στο RustDesk το δικαίωμα της \"Εγγραφή οθόνης\"."),
|
||||
("Installing ...", "Γίνεται εγκατάσταση ..."),
|
||||
("Install", "Εγκατάσταση"),
|
||||
("Installation", "Η εγκατάσταση"),
|
||||
("Installation Path", "Διαδρομή εγκατάστασης"),
|
||||
("Create start menu shortcuts", "Δημιουργία συντομεύσεων μενού έναρξης"),
|
||||
("Create desktop icon", "Δημιουργία εικονιδίου επιφάνειας εργασίας"),
|
||||
("agreement_tip", "Με την εγκατάσταση αποδέχεστε την άδεια χρήσης"),
|
||||
("agreement_tip", "Με την εγκατάσταση, αποδέχεστε την άδεια χρήσης"),
|
||||
("Accept and Install", "Αποδοχή και εγκατάσταση"),
|
||||
("End-user license agreement", "Σύμβαση άδειας χρήσης τελικού χρήστη"),
|
||||
("Generating ...", "Δημιουργία ..."),
|
||||
@@ -170,8 +170,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Local Port", "Τοπική θύρα"),
|
||||
("Local Address", "Τοπική διεύθυνση"),
|
||||
("Change Local Port", "Αλλαγή τοπικής θύρας"),
|
||||
("setup_server_tip", "Για πιο γρήγορη σύνδεση, ρυθμίστε τον δικό σας διακομιστή σύνδεσης"),
|
||||
("Too short, at least 6 characters.", "Πολύ μικρό, τουλάχιστον 6 χαρακτήρες."),
|
||||
("setup_server_tip", "Για πιο γρήγορη σύνδεση, παρακαλούμε να ρυθμίστε τον δικό σας διακομιστή σύνδεσης"),
|
||||
("Too short, at least 6 characters.", "Πολύ μικρό, χρειάζεται τουλάχιστον 6 χαρακτήρες."),
|
||||
("The confirmation is not identical.", "Η επιβεβαίωση δεν είναι πανομοιότυπη."),
|
||||
("Permissions", "Άδειες"),
|
||||
("Accept", "Αποδοχή"),
|
||||
@@ -183,7 +183,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Relayed and encrypted connection", "Κρυπτογραφημένη σύνδεση με αναμετάδοση"),
|
||||
("Direct and unencrypted connection", "Άμεση και μη κρυπτογραφημένη σύνδεση"),
|
||||
("Relayed and unencrypted connection", "Μη κρυπτογραφημένη σύνδεση με αναμετάδοση"),
|
||||
("Enter Remote ID", "Εισαγωγή απομακρυσμένου ID"),
|
||||
("Enter Remote ID", "Εισαγωγή του απομακρυσμένου ID"),
|
||||
("Enter your password", "Εισάγετε τον κωδικό σας"),
|
||||
("Logging in...", "Γίνεται σύνδεση..."),
|
||||
("Enable RDP session sharing", "Ενεργοποίηση κοινής χρήσης RDP"),
|
||||
@@ -200,35 +200,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Login screen using Wayland is not supported", "Η οθόνη εισόδου με χρήση του Wayland δεν υποστηρίζεται"),
|
||||
("Reboot required", "Απαιτείται επανεκκίνηση"),
|
||||
("Unsupported display server", "Μη υποστηριζόμενος διακομιστής εμφάνισης "),
|
||||
("x11 expected", "απαιτείται X11"),
|
||||
("x11 expected", "αναμένεται X11"),
|
||||
("Port", "Θύρα"),
|
||||
("Settings", "Ρυθμίσεις"),
|
||||
("Username", "Όνομα χρήστη"),
|
||||
("Invalid port", "Μη έγκυρη θύρα"),
|
||||
("Closed manually by the peer", "Έκλεισε από τον απομακρυσμένο σταθμό"),
|
||||
("Enable remote configuration modification", "Ενεργοποίηση απομακρυσμένης τροποποίησης ρυθμίσεων"),
|
||||
("Closed manually by the peer", "Τερματίστηκε από τον απομακρυσμένο σταθμό"),
|
||||
("Enable remote configuration modification", "Ενεργοποίηση απομακρυσμένης τροποποίησης διαμόρφωσης"),
|
||||
("Run without install", "Εκτέλεση χωρίς εγκατάσταση"),
|
||||
("Connect via relay", "Πραγματοποίηση σύνδεση μέσω αναμεταδότη"),
|
||||
("Always connect via relay", "Σύνδεση πάντα μέσω αναμεταδότη"),
|
||||
("whitelist_tip", "Μόνο οι IP της λίστας επιτρεπόμενων έχουν πρόσβαση"),
|
||||
("Connect via relay", "Σύνδεση μέσω αναμεταδότη"),
|
||||
("Always connect via relay", "Να γίνεται σύνδεση πάντα μέσω αναμεταδότη"),
|
||||
("whitelist_tip", "Μόνο οι IP της λίστας επιτρεπόμενων να έχουν πρόσβαση σε εμένα"),
|
||||
("Login", "Σύνδεση"),
|
||||
("Verify", "Επαλήθευση"),
|
||||
("Remember me", "Να με θυμάσαι"),
|
||||
("Trust this device", "Εμπιστεύομαι αυτή την συσκευή"),
|
||||
("Trust this device", "Να εμπιστεύομαι αυτή την συσκευή"),
|
||||
("Verification code", "Κωδικός επαλήθευσης"),
|
||||
("verification_tip", "Εντοπίστηκε νέα συσκευή και εστάλη ένας κωδικός επαλήθευσης στην καταχωρισμένη διεύθυνση email. Εισαγάγετε τον κωδικό επαλήθευσης για να συνδεθείτε ξανά."),
|
||||
("verification_tip", "Ένας κωδικός επαλήθευσης έχει σταλεί στην καταχωρημένη διεύθυνση email. Εισαγάγετε τον κωδικό επαλήθευσης για να συνεχίσετε τη σύνδεση."),
|
||||
("Logout", "Αποσύνδεση"),
|
||||
("Tags", "Ετικέτες"),
|
||||
("Search ID", "Αναζήτηση ID"),
|
||||
("whitelist_sep", "Διαχωρίζονται με κόμμα, ερωτηματικό, διάστημα ή νέα γραμμή"),
|
||||
("Add ID", "Προσθήκη αναγνωριστικού ID"),
|
||||
("whitelist_sep", "Διαχωρίζονται με κόμμα, ερωτηματικό, κενό ή νέα γραμμή"),
|
||||
("Add ID", "Προσθήκη ID"),
|
||||
("Add Tag", "Προσθήκη ετικέτας"),
|
||||
("Unselect all tags", "Κατάργηση επιλογής όλων των ετικετών"),
|
||||
("Unselect all tags", "Αποεπιλογή όλων των ετικετών"),
|
||||
("Network error", "Σφάλμα δικτύου"),
|
||||
("Username missed", "Δεν συμπληρώσατε το όνομα χρήστη"),
|
||||
("Password missed", "Δεν συμπληρώσατε τον κωδικό πρόσβασης"),
|
||||
("Wrong credentials", "Λάθος διαπιστευτήρια"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("The verification code is incorrect or has expired", "Ο κωδικός επαλήθευσης είναι λανθασμένος ή έχει λήξει"),
|
||||
("Edit Tag", "Επεξεργασία ετικέτας"),
|
||||
("Forget Password", "Διαγραφή απομνημονευμένου κωδικού"),
|
||||
("Favorites", "Αγαπημένα"),
|
||||
@@ -239,7 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Socks5 Proxy", "Διαμεσολαβητής Socks5"),
|
||||
("Socks5/Http(s) Proxy", "Διαμεσολαβητής Socks5/Http(s)"),
|
||||
("Discovered", "Ανακαλύφθηκαν"),
|
||||
("install_daemon_tip", "Για να ξεκινά με την εκκίνηση του υπολογιστή, πρέπει να εγκαταστήσετε την υπηρεσία συστήματος"),
|
||||
("install_daemon_tip", "Για να ξεκινά με την εκκίνηση του υπολογιστή, πρέπει να εγκαταστήσετε την υπηρεσία συστήματος."),
|
||||
("Remote ID", "Απομακρυσμένο ID"),
|
||||
("Paste", "Επικόλληση"),
|
||||
("Paste here?", "Επικόλληση εδώ;"),
|
||||
@@ -262,28 +262,28 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Pinch to Zoom", "Τσίμπημα για ζουμ"),
|
||||
("Canvas Zoom", "Ζουμ σε καμβά"),
|
||||
("Reset canvas", "Επαναφορά καμβά"),
|
||||
("No permission of file transfer", "Δεν υπάρχει άδεια για μεταφορά αρχείων"),
|
||||
("No permission of file transfer", "Δεν υπάρχει άδεια για την μεταφορά αρχείων"),
|
||||
("Note", "Σημείωση"),
|
||||
("Connection", "Σύνδεση"),
|
||||
("Share screen", "Κοινή χρήση οθόνης"),
|
||||
("Chat", "Κουβέντα"),
|
||||
("Total", "Σύνολο"),
|
||||
("items", "στοιχεία"),
|
||||
("Selected", "Επιλεγμένο"),
|
||||
("Screen Capture", "Αποτύπωση οθόνης"),
|
||||
("Selected", "Επιλεγμένα"),
|
||||
("Screen Capture", "Καταγραφή οθόνης"),
|
||||
("Input Control", "Έλεγχος εισόδου"),
|
||||
("Audio Capture", "Εγγραφή ήχου"),
|
||||
("Do you accept?", "Δέχεσαι;"),
|
||||
("Open System Setting", "Άνοιγμα ρυθμίσεων συστήματος"),
|
||||
("How to get Android input permission?", "Πώς να αποκτήσω άδεια εισαγωγής Android;"),
|
||||
("How to get Android input permission?", "Πώς να αποκτήσω άδεια εισόδου για Android;"),
|
||||
("android_input_permission_tip1", "Για να μπορεί μία απομακρυσμένη συσκευή να ελέγχει τη συσκευή σας Android, πρέπει να επιτρέψετε στο RustDesk να χρησιμοποιεί την υπηρεσία \"Προσβασιμότητα\"."),
|
||||
("android_input_permission_tip2", "Παρακαλώ μεταβείτε στην επόμενη σελίδα ρυθμίσεων συστήματος, βρείτε και πληκτρολογήστε [Εγκατεστημένες υπηρεσίες], ενεργοποιήστε την υπηρεσία [Είσοδος RustDesk]."),
|
||||
("android_new_connection_tip", "θέλω να ελέγξω τη συσκευή σου."),
|
||||
("android_service_will_start_tip", "Η ενεργοποίηση της κοινής χρήσης οθόνης θα ξεκινήσει αυτόματα την υπηρεσία, ώστε άλλες συσκευές να μπορούν να ελέγχουν αυτήν τη συσκευή Android."),
|
||||
("android_stop_service_tip", "Η απενεργοποίηση της υπηρεσίας θα αποσυνδέσει αυτόματα όλες τις εγκατεστημένες συνδέσεις."),
|
||||
("android_version_audio_tip", "Η έκδοση Android που διαθέτετε δεν υποστηρίζει εγγραφή ήχου, ενημερώστε το σε Android 10 ή νεότερη έκδοση, εάν είναι δυνατόν."),
|
||||
("android_start_service_tip", ""),
|
||||
("android_permission_may_not_change_tip", ""),
|
||||
("android_input_permission_tip2", "Παρακαλούμε να μεταβείτε στην επόμενη σελίδα ρυθμίσεων συστήματος, βρείτε και πληκτρολογήστε [Εγκατεστημένες υπηρεσίες], ενεργοποιήστε την υπηρεσία [Είσοδος RustDesk]."),
|
||||
("android_new_connection_tip", "Έχει ληφθεί νέο αίτημα ελέγχου, το οποίο θέλει να ελέγξει την τρέχουσα συσκευή σας."),
|
||||
("android_service_will_start_tip", "Η ενεργοποίηση της \"Καταγραφής οθόνης\" θα ξεκινήσει αυτόματα την υπηρεσία, επιτρέποντας σε άλλες συσκευές να ζητήσουν σύνδεση με τη συσκευή σας."),
|
||||
("android_stop_service_tip", "Το κλείσιμο της υπηρεσίας αυτής θα κλείσει αυτόματα όλες τις υπάρχουσες συνδέσεις."),
|
||||
("android_version_audio_tip", "Η τρέχουσα έκδοση Android δεν υποστηρίζει εγγραφή ήχου, αναβαθμίστε σε Android 10 ή νεότερη έκδοση."),
|
||||
("android_start_service_tip", "Πατήστε [Έναρξη υπηρεσίας] ή ενεργοποιήστε την άδεια [Καταγραφή οθόνης] για να ξεκινήσετε την υπηρεσία κοινής χρήσης οθόνης."),
|
||||
("android_permission_may_not_change_tip", "Τα δικαιώματα για τις καθιερωμένες συνδέσεις δεν μπορούν να αλλάξουν άμεσα μέχρι να επανασυνδεθούν."),
|
||||
("Account", "Λογαριασμός"),
|
||||
("Overwrite", "Αντικατάσταση"),
|
||||
("This file exists, skip or overwrite this file?", "Αυτό το αρχείο υπάρχει, παράβλεψη ή αντικατάσταση αυτού του αρχείου;"),
|
||||
@@ -293,14 +293,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Succeeded", "Επιτυχής"),
|
||||
("Someone turns on privacy mode, exit", "Κάποιος ενεργοποιεί τη λειτουργία απορρήτου, έξοδος"),
|
||||
("Unsupported", "Δεν υποστηρίζεται"),
|
||||
("Peer denied", "Ο απομακρυσμένος σταθμός απέρριψε τη σύνδεση"),
|
||||
("Peer denied", "Ο απομακρυσμένος σταθμός έχει απορριφθεί"),
|
||||
("Please install plugins", "Παρακαλώ εγκαταστήστε τα πρόσθετα"),
|
||||
("Peer exit", "Ο απομακρυσμένος σταθμός έχει αποσυνδεθεί"),
|
||||
("Failed to turn off", "Αποτυχία απενεργοποίησης"),
|
||||
("Turned off", "Απενεργοποιημένο"),
|
||||
("Language", "Γλώσσα"),
|
||||
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
|
||||
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
|
||||
("Keep RustDesk background service", "Διατήρηση της υπηρεσίας παρασκηνίου του RustDesk"),
|
||||
("Ignore Battery Optimizations", "Αγνόηση βελτιστοποιήσεων μπαταρίας"),
|
||||
("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"),
|
||||
("Start on boot", "Έναρξη κατά την εκκίνηση"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Η έναρξη της υπηρεσίας κοινής χρήσης οθόνης κατά την εκκίνηση, απαιτεί ειδικά δικαιώματα"),
|
||||
@@ -315,11 +315,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Restart remote device", "Επανεκκίνηση απομακρυσμένης συσκευής"),
|
||||
("Are you sure you want to restart", "Είστε βέβαιοι ότι θέλετε να κάνετε επανεκκίνηση"),
|
||||
("Restarting remote device", "Γίνεται επανεκκίνηση της απομακρυσμένης συσκευής"),
|
||||
("remote_restarting_tip", "Η απομακρυσμένη συσκευή επανεκκινείται, κλείστε αυτό το μήνυμα και επανασυνδεθείτε χρησιμοποιώντας τον μόνιμο κωδικό πρόσβασης."),
|
||||
("remote_restarting_tip", "Γίνεται επανεκκίνηση της απομακρυσμένης συσκευής. Κλείστε αυτό το πλαίσιο μηνύματος και επανασυνδεθείτε με τον μόνιμο κωδικό πρόσβασης μετά από λίγο."),
|
||||
("Copied", "Αντιγράφηκε"),
|
||||
("Exit Fullscreen", "Έξοδος από πλήρη οθόνη"),
|
||||
("Fullscreen", "Πλήρης οθόνη"),
|
||||
("Mobile Actions", "Mobile Actions"),
|
||||
("Mobile Actions", "Ενέργειες για κινητά"),
|
||||
("Select Monitor", "Επιλογή οθόνης"),
|
||||
("Control Actions", "Ενέργειες ελέγχου"),
|
||||
("Display Settings", "Ρυθμίσεις οθόνης"),
|
||||
@@ -347,7 +347,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable audio", "Ενεργοποίηση ήχου"),
|
||||
("Unlock Network Settings", "Ξεκλείδωμα ρυθμίσεων δικτύου"),
|
||||
("Server", "Διακομιστής"),
|
||||
("Direct IP Access", "Πρόσβαση με χρήση IP"),
|
||||
("Direct IP Access", "Άμεση πρόσβαση IP"),
|
||||
("Proxy", "Διαμεσολαβητής"),
|
||||
("Apply", "Εφαρμογή"),
|
||||
("Disconnect all devices?", "Αποσύνδεση όλων των συσκευών;"),
|
||||
@@ -358,7 +358,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Pin Toolbar", "Καρφίτσωμα γραμμής εργαλείων"),
|
||||
("Unpin Toolbar", "Ξεκαρφίτσωμα γραμμής εργαλείων"),
|
||||
("Recording", "Εγγραφή"),
|
||||
("Directory", "Φάκελος εγγραφών"),
|
||||
("Directory", "Διαδρομή"),
|
||||
("Automatically record incoming sessions", "Αυτόματη εγγραφή εισερχόμενων συνεδριών"),
|
||||
("Automatically record outgoing sessions", "Αυτόματη εγγραφή εξερχόμενων συνεδριών"),
|
||||
("Change", "Αλλαγή"),
|
||||
@@ -373,24 +373,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevated_foreground_window_tip", "Το τρέχον παράθυρο της απομακρυσμένης επιφάνειας εργασίας απαιτεί υψηλότερα δικαιώματα για να λειτουργήσει, επομένως δεν μπορεί να χρησιμοποιήσει προσωρινά το ποντίκι και το πληκτρολόγιο. Μπορείτε να ζητήσετε από τον απομακρυσμένο χρήστη να ελαχιστοποιήσει το τρέχον παράθυρο ή να κάνετε κλικ στο κουμπί ανύψωσης στο παράθυρο διαχείρισης σύνδεσης. Για να αποφύγετε αυτό το πρόβλημα, συνιστάται η εγκατάσταση του λογισμικού στην απομακρυσμένη συσκευή."),
|
||||
("Disconnected", "Αποσυνδέθηκε"),
|
||||
("Other", "Άλλα"),
|
||||
("Confirm before closing multiple tabs", "Επιβεβαίωση πριν κλείσετε πολλές καρτέλες"),
|
||||
("Confirm before closing multiple tabs", "Επιβεβαίωση πριν κλείσουν πολλαπλές καρτέλες"),
|
||||
("Keyboard Settings", "Ρυθμίσεις πληκτρολογίου"),
|
||||
("Full Access", "Πλήρης πρόσβαση"),
|
||||
("Screen Share", "Κοινή χρήση οθόνης"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Το Wayland απαιτεί Ubuntu 21.04 ή νεότερη έκδοση."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Το Wayland απαιτεί υψηλότερη έκδοση του linux distro. Δοκιμάστε την επιφάνεια εργασίας X11 ή αλλάξτε το λειτουργικό σας σύστημα."),
|
||||
("JumpLink", "Προβολή"),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Το Wayland απαιτεί υψηλότερη έκδοση διανομής του linux. Δοκιμάστε την επιφάνεια εργασίας X11 ή αλλάξτε το λειτουργικό σας σύστημα."),
|
||||
("JumpLink", "Σύνδεσμος μετάβασης"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Επιλέξτε την οθόνη που θέλετε να μοιραστείτε (Λειτουργία στην πλευρά του απομακρυσμένου σταθμού)."),
|
||||
("Show RustDesk", "Εμφάνιση RustDesk"),
|
||||
("Show RustDesk", "Εμφάνιση του RustDesk"),
|
||||
("This PC", "Αυτός ο υπολογιστής"),
|
||||
("or", "ή"),
|
||||
("Continue with", "Συνέχεια με"),
|
||||
("Elevate", "Ανύψωση"),
|
||||
("Zoom cursor", "Kέρσορας μεγέθυνσης"),
|
||||
("Zoom cursor", "Δρομέας ζουμ"),
|
||||
("Accept sessions via password", "Αποδοχή συνεδριών με κωδικό πρόσβασης"),
|
||||
("Accept sessions via click", "Αποδοχή συνεδριών με κλικ"),
|
||||
("Accept sessions via both", "Αποδοχή συνεδριών και με τα δύο"),
|
||||
("Please wait for the remote side to accept your session request...", "Παρακαλώ περιμένετε μέχρι η απομακρυσμένη πλευρά να αποδεχτεί το αίτημα συνεδρίας σας..."),
|
||||
("Please wait for the remote side to accept your session request...", "Παρακαλώ περιμένετε μέχρι η απομακρυσμένη πλευρά να αποδεχτεί το αίτημα της συνεδρίας σας..."),
|
||||
("One-time Password", "Κωδικός μίας χρήσης"),
|
||||
("Use one-time password", "Χρήση κωδικού πρόσβασης μίας χρήσης"),
|
||||
("One-time password length", "Μήκος κωδικού πρόσβασης μίας χρήσης"),
|
||||
@@ -399,27 +398,27 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("hide_cm_tip", "Να επιτρέπεται η απόκρυψη, μόνο εάν αποδέχεστε συνδέσεις μέσω κωδικού πρόσβασης και χρησιμοποιείτε μόνιμο κωδικό πρόσβασης"),
|
||||
("wayland_experiment_tip", "Η υποστήριξη Wayland βρίσκεται σε πειραματικό στάδιο, χρησιμοποιήστε το X11 εάν χρειάζεστε πρόσβαση χωρίς επίβλεψη."),
|
||||
("Right click to select tabs", "Κάντε δεξί κλικ για να επιλέξετε καρτέλες"),
|
||||
("Skipped", "Παράλειψη"),
|
||||
("Add to address book", "Προσθήκη στο Βιβλίο Διευθύνσεων"),
|
||||
("Skipped", "Παραλήφθηκε"),
|
||||
("Add to address book", "Προσθήκη στο βιβλίο διευθύνσεων"),
|
||||
("Group", "Ομάδα"),
|
||||
("Search", "Αναζήτηση"),
|
||||
("Closed manually by web console", "Κλειστό χειροκίνητα από την κονσόλα web"),
|
||||
("Closed manually by web console", "Κλείσιμο χειροκίνητα από την κονσόλα ιστού"),
|
||||
("Local keyboard type", "Τύπος τοπικού πληκτρολογίου"),
|
||||
("Select local keyboard type", "Επιλογή τύπου τοπικού πληκτρολογίου"),
|
||||
("software_render_tip", "Εάν έχετε κάρτα γραφικών Nvidia και το παράθυρο σύνδεσης κλείνει αμέσως μετά τη σύνδεση, η εγκατάσταση του προγράμματος οδήγησης nouveau και η επιλογή χρήσης της επιτάχυνσης γραφικών μέσω λογισμικού μπορεί να βοηθήσει. Απαιτείται επανεκκίνηση."),
|
||||
("Always use software rendering", "Επιτάχυνση γραφικών μέσω λογισμικού"),
|
||||
("config_input", "Για να ελέγξετε την απομακρυσμένη επιφάνεια εργασίας με πληκτρολόγιο, πρέπει να εκχωρήσετε δικαιώματα στο RustDesk"),
|
||||
("config_microphone", "Ρύθμιση μικροφώνου"),
|
||||
("request_elevation_tip", "αίτημα ανύψωσης δικαιωμάτων χρήστη"),
|
||||
("software_render_tip", "Εάν χρησιμοποιείτε κάρτα γραφικών της Nvidia σε Linux και το παράθυρο απομακρυσμένης πρόσβασης κλείνει αμέσως μετά τη σύνδεση, η μετάβαση στο πρόγραμμα οδήγησης της Nouveau ανοιχτού κώδικα και η επιλογή χρήσης απόδοσης λογισμικού μπορεί να βοηθήσει. Απαιτείται επανεκκίνηση του λογισμικού."),
|
||||
("Always use software rendering", "Να χρησιμοποιείτε πάντα η απόδοση λογισμικού"),
|
||||
("config_input", "Για να ελέγξετε την απομακρυσμένη επιφάνεια εργασίας με το πληκτρολόγιο, πρέπει να παραχωρήσετε στο RustDesk το δικαίωμα της \"Παρακολούθηση εισόδου\"."),
|
||||
("config_microphone", "Για να μιλήσετε εξ αποστάσεως, πρέπει να παραχωρήσετε στο RustDesk το δικαίωμα της \"Εγγραφή ήχου\"."),
|
||||
("request_elevation_tip", "Μπορείτε επίσης να ζητήσετε ανύψωση εάν υπάρχει κάποιος στην απομακρυσμένη πλευρά."),
|
||||
("Wait", "Περιμένετε"),
|
||||
("Elevation Error", "Σφάλμα ανύψωσης δικαιωμάτων χρήστη"),
|
||||
("Elevation Error", "Σφάλμα ανύψωσης"),
|
||||
("Ask the remote user for authentication", "Ζητήστε από τον απομακρυσμένο χρήστη έλεγχο ταυτότητας"),
|
||||
("Choose this if the remote account is administrator", "Επιλέξτε αυτό εάν ο απομακρυσμένος λογαριασμός είναι διαχειριστής"),
|
||||
("Transmit the username and password of administrator", "Αποστολή του ονόματος χρήστη και του κωδικού πρόσβασης του διαχειριστή"),
|
||||
("still_click_uac_tip", "Εξακολουθεί να απαιτεί από τον απομακρυσμένο χρήστη να κάνει κλικ στο OK στο παράθυρο UAC όπου εκτελείται το RustDesk."),
|
||||
("Request Elevation", "Αίτημα ανύψωσης δικαιωμάτων χρήστη"),
|
||||
("wait_accept_uac_tip", "Περιμένετε να αποδεχτεί ο απομακρυσμένος χρήστης το παράθυρο διαλόγου UAC."),
|
||||
("Elevate successfully", "Επιτυχής ανύψωση δικαιωμάτων χρήστη"),
|
||||
("Transmit the username and password of administrator", "Μεταδώστε το όνομα χρήστη και τον κωδικό πρόσβασης του διαχειριστή"),
|
||||
("still_click_uac_tip", "Εξακολουθεί να απαιτεί από τον απομακρυσμένο χρήστη να κάνει κλικ στο πλήκτρο Εντάξει στο παράθυρο UAC όπου εκτελείται το RustDesk."),
|
||||
("Request Elevation", "Αίτημα ανύψωσης"),
|
||||
("wait_accept_uac_tip", "Περιμένετε μέχρι ο απομακρυσμένος χρήστης να αποδεχτεί το παράθυρο διαλόγου UAC."),
|
||||
("Elevate successfully", "Επιτυχής ανύψωση"),
|
||||
("uppercase", "κεφαλαία γράμματα"),
|
||||
("lowercase", "πεζά γράμματα"),
|
||||
("digit", "αριθμός"),
|
||||
@@ -428,7 +427,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Weak", "Αδύναμο"),
|
||||
("Medium", "Μέτριο"),
|
||||
("Strong", "Δυνατό"),
|
||||
("Switch Sides", "Εναλλαγή πλευράς"),
|
||||
("Switch Sides", "Αλλαγή πλευρών"),
|
||||
("Please confirm if you want to share your desktop?", "Παρακαλώ επιβεβαιώστε αν επιθυμείτε την κοινή χρήση της επιφάνειας εργασίας;"),
|
||||
("Display", "Εμφάνιση"),
|
||||
("Default View Style", "Προκαθορισμένος τρόπος εμφάνισης"),
|
||||
@@ -442,11 +441,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Φωνητική κλήση"),
|
||||
("Text chat", "Συνομιλία κειμένου"),
|
||||
("Stop voice call", "Διακοπή φωνητικής κλήσης"),
|
||||
("relay_hint_tip", "Εάν δεν είναι δυνατή η απευθείας σύνδεση, μπορείτε να δοκιμάσετε να συνδεθείτε μέσω διακομιστή αναμετάδοσης"),
|
||||
("relay_hint_tip", "Ενδέχεται να μην είναι δυνατή η απευθείας σύνδεση: μπορείτε να δοκιμάσετε να συνδεθείτε μέσω αναμετάδοσης. Επιπλέον, εάν θέλετε να χρησιμοποιήσετε την αναμετάδοση στην πρώτη σας προσπάθεια, μπορείτε να προσθέσετε την \"/r\" κατάληξη στο ID ή να επιλέξετε την επιλογή \"Πάντα σύνδεση μέσω αναμετάδοσης\" στην κάρτα πρόσφατων συνεδριών, εάν υπάρχει."),
|
||||
("Reconnect", "Επανασύνδεση"),
|
||||
("Codec", "Κωδικοποίηση"),
|
||||
("Resolution", "Ανάλυση"),
|
||||
("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"),
|
||||
("No transfers in progress", "Δεν υπάρχουν μεταφορές σε εξέλιξη"),
|
||||
("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"),
|
||||
("RDP Settings", "Ρυθμίσεις RDP"),
|
||||
("Sort by", "Ταξινόμηση κατά"),
|
||||
@@ -455,35 +454,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Minimize", "Ελαχιστοποίηση"),
|
||||
("Maximize", "Μεγιστοποίηση"),
|
||||
("Your Device", "Η συσκευή σας"),
|
||||
("empty_recent_tip", "Δεν υπάρχουν πρόσφατες συνεδρίες!\nΔοκιμάστε να ξεκινήσετε μια νέα."),
|
||||
("empty_favorite_tip", "Δεν υπάρχουν ακόμη αγαπημένες συνδέσεις;\nΑφού πραγματοποιήσετε σύνδεση με κάποιο απομακρυσμένο σταθμό, μπορείτε να τον προσθέσετε στα αγαπημένα σας!"),
|
||||
("empty_lan_tip", "Δεν έχουμε ανακαλυφθεί ακόμη απομακρυσμένοι σταθμοί."),
|
||||
("empty_address_book_tip", "Φαίνεται ότι αυτή τη στιγμή δεν υπάρχουν αγαπημένες συνδέσεις στο βιβλίο διευθύνσεών σας."),
|
||||
("empty_recent_tip", "Ωχ, δεν υπάρχουν πρόσφατες συνεδρίες!\nΏρα να προγραμματίσετε μια νέα."),
|
||||
("empty_favorite_tip", "Δεν έχετε ακόμα αγαπημένους απομακρυσμένους σταθμούς;\nΑς βρούμε κάποιον για να συνδεθούμε και ας τον προσθέσουμε στα αγαπημένα σας!"),
|
||||
("empty_lan_tip", "Ωχ όχι, φαίνεται ότι δεν έχουμε ανακαλύψει ακόμη κανέναν απομακρυσμένο σταθμό."),
|
||||
("empty_address_book_tip", "Ω, Αγαπητέ/ή μου, φαίνεται ότι αυτήν τη στιγμή δεν υπάρχουν απομακρυσμένοι σταθμοί στο βιβλίο διευθύνσεών σας."),
|
||||
("Empty Username", "Κενό όνομα χρήστη"),
|
||||
("Empty Password", "Κενός κωδικός πρόσβασης"),
|
||||
("Me", "Εγώ"),
|
||||
("identical_file_tip", "Το αρχείο είναι πανομοιότυπο με αυτό του άλλου υπολογιστή."),
|
||||
("identical_file_tip", "Αυτό το αρχείο είναι πανομοιότυπο με αυτό του απομακρυσμένου σταθμού."),
|
||||
("show_monitors_tip", "Εμφάνιση οθονών στη γραμμή εργαλείων"),
|
||||
("View Mode", "Λειτουργία προβολής"),
|
||||
("login_linux_tip", "Απαιτείται είσοδος σε απομακρυσμένο λογαριασμό Linux για την ενεργοποίηση του περιβάλλον εργασίας Χ."),
|
||||
("login_linux_tip", "Πρέπει να συνδεθείτε σε έναν απομακρυσμένο λογαριασμό Linux για να ενεργοποιήσετε μια συνεδρία επιφάνειας εργασίας X"),
|
||||
("verify_rustdesk_password_tip", "Επιβεβαιώστε τον κωδικό του RustDesk"),
|
||||
("remember_account_tip", "Απομνημόνευση αυτού του λογαριασμού"),
|
||||
("os_account_desk_tip", "Αυτός ο λογαριασμός θα χρησιμοποιηθεί για την είσοδο και διαχείριση του απομακρυσμένου λειτουργικού συστήματος"),
|
||||
("os_account_desk_tip", "Αυτός ο λογαριασμός χρησιμοποιείται για σύνδεση στο απομακρυσμένο λειτουργικό σύστημα και ενεργοποίηση της συνεδρίας επιφάνειας εργασίας σε headless"),
|
||||
("OS Account", "Λογαριασμός λειτουργικού συστήματος"),
|
||||
("another_user_login_title_tip", "Υπάρχει ήδη άλλος συνδεδεμένος χρήστης"),
|
||||
("another_user_login_text_tip", "Αποσύνδεση"),
|
||||
("xorg_not_found_title_tip", "Δεν βρέθηκε το Xorg"),
|
||||
("xorg_not_found_text_tip", "Παρακαλώ εγκαταστήστε το Xorg"),
|
||||
("no_desktop_title_tip", "Δεν υπάρχει διαθέσιμη επιφάνεια εργασίας"),
|
||||
("no_desktop_title_tip", "Δεν υπάρχει διαθέσιμο περιβάλλον επιφάνειας εργασίας"),
|
||||
("no_desktop_text_tip", "Παρακαλώ εγκαταστήστε το περιβάλλον GNOME"),
|
||||
("No need to elevate", "Δεν χρειάζονται αυξημένα δικαιώματα"),
|
||||
("No need to elevate", "Δεν χρειάζεται ανύψωση"),
|
||||
("System Sound", "Ήχος συστήματος"),
|
||||
("Default", "Προκαθορισμένο"),
|
||||
("New RDP", "Νέα απομακρυσμένη σύνδεση"),
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", "Επιλέξτε σταθμό"),
|
||||
("New RDP", "Νέα RDP"),
|
||||
("Fingerprint", "Δακτυλικό αποτύπωμα"),
|
||||
("Copy Fingerprint", "Αντιγραφή δακτυλικού αποτυπώματος"),
|
||||
("no fingerprints", "χωρίς δακτυλικά αποτυπώματα"),
|
||||
("Select a peer", "Επιλέξτε έναν σταθμό"),
|
||||
("Select peers", "Επιλέξτε σταθμούς"),
|
||||
("Plugins", "Επεκτάσεις"),
|
||||
("Uninstall", "Κατάργηση εγκατάστασης"),
|
||||
@@ -494,10 +493,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("resolution_original_tip", "Αρχική ανάλυση"),
|
||||
("resolution_fit_local_tip", "Προσαρμογή στην τοπική ανάλυση"),
|
||||
("resolution_custom_tip", "Προσαρμοσμένη ανάλυση"),
|
||||
("Collapse toolbar", "Εμφάνιση γραμμής εργαλείων"),
|
||||
("Accept and Elevate", "Αποδοχή με αυξημένα δικαιώματα"),
|
||||
("accept_and_elevate_btn_tooltip", "Αποδοχή της σύνδεσης με αυξημένα δικαιώματα χρήστη"),
|
||||
("clipboard_wait_response_timeout_tip", "Έληξε ο χρόνος αναμονής για την ανταπόκριση της αντιγραφής"),
|
||||
("Collapse toolbar", "Σύμπτυξη γραμμής εργαλείων"),
|
||||
("Accept and Elevate", "Αποδοχή και ανύψωση"),
|
||||
("accept_and_elevate_btn_tooltip", "Αποδεχτείτε τη σύνδεση και ανυψώστε τα δικαιώματα UAC."),
|
||||
("clipboard_wait_response_timeout_tip", "Λήξη χρονικού ορίου αναμονής για απάντηση αντιγραφής."),
|
||||
("Incoming connection", "Εισερχόμενη σύνδεση"),
|
||||
("Outgoing connection", "Εξερχόμενη σύνδεση"),
|
||||
("Exit", "Έξοδος"),
|
||||
@@ -506,7 +505,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Service", "Υπηρεσία"),
|
||||
("Start", "Έναρξη"),
|
||||
("Stop", "Διακοπή"),
|
||||
("exceed_max_devices", "Υπέρβαση μέγιστου ορίου αποθηκευμένων συνδέσεων"),
|
||||
("exceed_max_devices", "Έχετε φτάσει τον μέγιστο αριθμό διαχειριζόμενων συσκευών."),
|
||||
("Sync with recent sessions", "Συγχρονισμός των πρόσφατων συνεδριών"),
|
||||
("Sort tags", "Ταξινόμηση ετικετών"),
|
||||
("Open connection in new tab", "Άνοιγμα σύνδεσης σε νέα καρτέλα"),
|
||||
@@ -515,14 +514,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Already exists", "Υπάρχει ήδη"),
|
||||
("Change Password", "Αλλαγή κωδικού"),
|
||||
("Refresh Password", "Ανανέωση κωδικού"),
|
||||
("ID", ""),
|
||||
("ID", "ID"),
|
||||
("Grid View", "Προβολή σε πλακίδια"),
|
||||
("List View", "Προβολή σε λίστα"),
|
||||
("Select", "Επιλογή"),
|
||||
("Toggle Tags", "Εναλλαγή ετικετών"),
|
||||
("pull_ab_failed_tip", "Αποτυχία ανανέωσης βιβλίου διευθύνσεων"),
|
||||
("push_ab_failed_tip", "Αποτυχία συγχρονισμού βιβλίου διευθύνσεων"),
|
||||
("synced_peer_readded_tip", "Οι συσκευές των τρεχουσών συνεδριών θα συγχρονιστούν με το βιβλίο διευθύνσεων"),
|
||||
("pull_ab_failed_tip", "Η ανανέωση του βιβλίου διευθύνσεων απέτυχε"),
|
||||
("push_ab_failed_tip", "Αποτυχία συγχρονισμού του βιβλίου διευθύνσεων με τον διακομιστή"),
|
||||
("synced_peer_readded_tip", "Οι συσκευές που υπήρχαν στις πρόσφατες συνεδρίες θα συγχρονιστούν ξανά με το βιβλίο διευθύνσεων."),
|
||||
("Change Color", "Αλλαγή χρώματος"),
|
||||
("Primary Color", "Κυρίως χρώμα"),
|
||||
("HSV Color", "Χρώμα HSV"),
|
||||
@@ -537,31 +536,31 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("I Agree", "Συμφωνώ"),
|
||||
("Decline", "Διαφωνώ"),
|
||||
("Timeout in minutes", "Τέλος χρόνου σε λεπτά"),
|
||||
("auto_disconnect_option_tip", "Αυτόματη αποσύνδεση απομακρυσμένης συνεδρίας έπειτα από την πάροδο του χρονικού ορίου αδράνειας "),
|
||||
("auto_disconnect_option_tip", "Αυτόματο κλείσιμο εισερχόμενων συνεδριών σε περίπτωση αδράνειας χρήστη"),
|
||||
("Connection failed due to inactivity", "Η σύνδεση τερματίστηκε έπειτα από την πάροδο του χρόνου αδράνειας"),
|
||||
("Check for software update on startup", "Έλεγχος για ενημερώσεις κατα την εκκίνηση"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Παρακαλώ ενημερώστε τον RustDesk Server Pro στην έκδοση {} ή νεότερη!"),
|
||||
("Check for software update on startup", "Έλεγχος για ενημερώσεις κατά την εκκίνηση"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Παρακαλώ ενημερώστε το RustDesk Server Pro στην έκδοση {} ή νεότερη!"),
|
||||
("pull_group_failed_tip", "Αποτυχία ανανέωσης της ομάδας"),
|
||||
("Filter by intersection", ""),
|
||||
("Filter by intersection", "Φιλτράρισμα κατά διασταύρωση"),
|
||||
("Remove wallpaper during incoming sessions", "Αφαίρεση εικόνας φόντου στις εισερχόμενες συνδέσεις"),
|
||||
("Test", "Δοκιμή"),
|
||||
("display_is_plugged_out_msg", "Η οθόνη έχει αποσυνδεθεί, επιστρέψτε στην κύρια οθόνη προβολής"),
|
||||
("display_is_plugged_out_msg", "Η οθόνη είναι αποσυνδεδεμένη από την πρίζα, μεταβείτε στην πρώτη οθόνη."),
|
||||
("No displays", "Δεν υπάρχουν οθόνες"),
|
||||
("Open in new window", "Άνοιγμα σε νέο παράθυρο"),
|
||||
("Show displays as individual windows", "Εμφάνιση οθονών σε ξεχωριστά παράθυρα"),
|
||||
("Use all my displays for the remote session", "Χρήση όλων των οθονών της απομακρυσμένης σύνδεσης"),
|
||||
("selinux_tip", "Έχετε ενεργοποιημένο το SELinux, το οποίο πιθανόν εμποδίζει την ορθή λειτουργία του RustDesk."),
|
||||
("selinux_tip", "Το SELinux είναι ενεργοποιημένο στη συσκευή σας, κάτι που ενδέχεται να εμποδίσει την σωστή λειτουργία του RustDesk ως ελεγχόμενης πλευράς."),
|
||||
("Change view", "Αλλαγή απεικόνισης"),
|
||||
("Big tiles", "Μεγάλα εικονίδια"),
|
||||
("Small tiles", "Μικρά εικονίδια"),
|
||||
("List", "Λίστα"),
|
||||
("Virtual display", "Εινονική οθόνη"),
|
||||
("Plug out all", "Αποσύνδεση όλων"),
|
||||
("True color (4:4:4)", ""),
|
||||
("True color (4:4:4)", "Αληθινό χρώμα (4:4:4)"),
|
||||
("Enable blocking user input", "Ενεργοποίηση αποκλεισμού χειρισμού από τον χρήστη"),
|
||||
("id_input_tip", "Μπορείτε να εισάγετε ενα ID, μια διεύθυνση IP, ή ένα όνομα τομέα με την αντίστοιχη πόρτα (<domain>:<port>).\nΑν θέλετε να συνδεθείτε σε μια συσκευή σε άλλο διακομιστή, παρακαλώ να προσθέσετε και την διεύθυνση του διακομιστή (<id>@<server_address>?key=<key_value>), για παράδειγμα,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nΑν θέλετε να συνδεθείτε σε κάποιο δημόσιο διακομιστή, προσθέστε το όνομά του \"<id>@public\", η παράμετρος key δεν απαιτείται για τους δημόσιους διακομιστές."),
|
||||
("privacy_mode_impl_mag_tip", "Προφύλαξη Οθόνης"),
|
||||
("privacy_mode_impl_virtual_display_tip", "Εικονική Οθόνη"),
|
||||
("id_input_tip", "Μπορείτε να εισάγετε ένα ID, μια διεύθυνση IP, ή ένα όνομα τομέα με την αντίστοιχη πόρτα (<domain>:<port>).\nΑν θέλετε να συνδεθείτε σε μια συσκευή σε άλλο διακομιστή, παρακαλώ να προσθέσετε και την διεύθυνση του διακομιστή (<id>@<server_address>?key=<key_value>), για παράδειγμα,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nΑν θέλετε να συνδεθείτε σε κάποιο δημόσιο διακομιστή, προσθέστε το όνομά του \"<id>@public\", η παράμετρος key δεν απαιτείται για τους δημόσιους διακομιστές."),
|
||||
("privacy_mode_impl_mag_tip", "Λειτουργία 1"),
|
||||
("privacy_mode_impl_virtual_display_tip", "Λειτουργία 2"),
|
||||
("Enter privacy mode", "Ενεργοποίηση λειτουργίας απορρήτου"),
|
||||
("Exit privacy mode", "Διακοπή λειτουργίας απορρήτου"),
|
||||
("idd_not_support_under_win10_2004_tip", "Το πρόγραμμα οδήγησης έμμεσης οθόνης δεν υποστηρίζεται. Απαιτείτε λειτουργικό σύστημα Windows 10 έκδοση 2004 ή νεότερο."),
|
||||
@@ -571,26 +570,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("swap-left-right-mouse", "Εναλλαγή αριστερό-δεξί κουμπί του ποντικιού"),
|
||||
("2FA code", "κωδικός 2FA"),
|
||||
("More", "Περισσότερα"),
|
||||
("enable-2fa-title", "Ενεργοποίηση Πιστοποίησης Δύο Παραγόντων"),
|
||||
("enable-2fa-desc", "Ρυθμίστε τον έλεγχο ταυτότητας τώρα. Μπορείτε να χρησιμοποιήσετε μια εφαρμογή ελέγχου ταυτότητας όπως Authy, Microsoft ή Google Authenticator στο τηλέφωνο ή στην επιφάνεια εργασίας σας.Σαρώστε τον κωδικό QR με την εφαρμογή σας και εισαγάγετε τον κωδικό που εμφανίζει η εφαρμογή σας για να ενεργοποιήσετε τον έλεγχο ταυτότητας δύο παραγόντων."),
|
||||
("wrong-2fa-code", "Δεν είναι δυνατή η επαλήθευση του κωδικού. Ελέγξτε ότι ο κωδικός και οι ρυθμίσεις τοπικής ώρας είναι σωστές"),
|
||||
("enable-2fa-title", "Ενεργοποίηση πιστοποίησης δύο παραγόντων"),
|
||||
("enable-2fa-desc", "Παρακαλούμε να ρυθμίστε τώρα τον έλεγχο ταυτότητας. Μπορείτε να χρησιμοποιήσετε μια εφαρμογή ελέγχου ταυτότητας όπως το Authy, το Microsoft ή το Google Authenticator στο τηλέφωνο ή τον υπολογιστή σας.\n\nΣαρώστε τον κωδικό QR με την εφαρμογή σας και εισαγάγετε τον κωδικό που εμφανίζει η εφαρμογή σας για να ενεργοποιήσετε τον έλεγχο ταυτότητας δύο παραγόντων."),
|
||||
("wrong-2fa-code", "Δεν είναι δυνατή η επαλήθευση του κωδικού. Ελέγξτε ότι οι ρυθμίσεις κωδικού και τοπικής ώρας είναι σωστές."),
|
||||
("enter-2fa-title", "Έλεγχος ταυτότητας δύο παραγόντων"),
|
||||
("Email verification code must be 6 characters.", "Ο κωδικός επαλήθευσης email πρέπει να είναι εως 6 χαρακτήρες"),
|
||||
("Email verification code must be 6 characters.", "Ο κωδικός επαλήθευσης email πρέπει να είναι έως 6 χαρακτήρες"),
|
||||
("2FA code must be 6 digits.", "Ο κωδικός 2FA πρέπει να είναι 6ψήφιος."),
|
||||
("Multiple Windows sessions found", ""),
|
||||
("Multiple Windows sessions found", "Βρέθηκαν πολλές συνεδρίες των Windows"),
|
||||
("Please select the session you want to connect to", "Επιλέξτε τη συνεδρία στην οποία θέλετε να συνδεθείτε"),
|
||||
("powered_by_me", "Με την υποστήριξη της RustDesk"),
|
||||
("outgoing_only_desk_tip", ""),
|
||||
("preset_password_warning", "προειδοποίηση προκαθορισμένου κωδικού πρόσβασης"),
|
||||
("powered_by_me", "Με την υποστήριξη του RustDesk"),
|
||||
("outgoing_only_desk_tip", "Αυτή είναι μια προσαρμοσμένη έκδοση.\nΜπορείτε να συνδεθείτε με άλλες συσκευές, αλλά άλλες συσκευές δεν μπορούν να συνδεθούν με τη δική σας συσκευή."),
|
||||
("preset_password_warning", "Αυτή η προσαρμοσμένη έκδοση συνοδεύεται από έναν προκαθορισμένο κωδικό πρόσβασης. Όποιος γνωρίζει αυτόν τον κωδικό πρόσβασης θα μπορούσε να αποκτήσει τον πλήρη έλεγχο της συσκευής σας. Εάν δεν το περιμένατε αυτό, απεγκαταστήστε αμέσως το λογισμικό."),
|
||||
("Security Alert", "Ειδοποίηση ασφαλείας"),
|
||||
("My address book", "Το βιβλίο διευθύνσεών μου"),
|
||||
("Personal", "Προσωπικό"),
|
||||
("Owner", "Ιδιοκτήτης"),
|
||||
("Set shared password", "Ορίστε κοινόχρηστο κωδικό πρόσβασης"),
|
||||
("Set shared password", "Ορίστε έναν κοινόχρηστο κωδικό πρόσβασης"),
|
||||
("Exist in", "Υπάρχει στο"),
|
||||
("Read-only", "Μόνο για ανάγνωση"),
|
||||
("Read/Write", "Ανάγνωση/Εγγραφή"),
|
||||
("Full Control", "Πλήρης Έλεγχος"),
|
||||
("Full Control", "Πλήρης έλεγχος"),
|
||||
("share_warning_tip", "Τα παραπάνω πεδία είναι κοινόχρηστα και ορατά σε άλλους."),
|
||||
("Everyone", "Όλοι"),
|
||||
("ab_web_console_tip", "Περισσότερα στην κονσόλα web"),
|
||||
@@ -598,18 +597,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Δεν υπάρχουν φυσικές οθόνες, δεν χρειάζεται να χρησιμοποιήσετε τη λειτουργία απορρήτου."),
|
||||
("Follow remote cursor", "Παρακολούθηση απομακρυσμένου κέρσορα"),
|
||||
("Follow remote window focus", "Παρακολούθηση απομακρυσμένου ενεργού παραθύρου"),
|
||||
("default_proxy_tip", "Προκαθορισμένο πρωτόκολλο Socks5 στην πόρτα 1080"),
|
||||
("default_proxy_tip", "Το προεπιλεγμένο πρωτόκολλο και η θύρα είναι Socks5 και 1080"),
|
||||
("no_audio_input_device_tip", "Δεν βρέθηκε συσκευή εισόδου ήχου."),
|
||||
("Incoming", "Εισερχόμενη"),
|
||||
("Outgoing", "Εξερχόμενη"),
|
||||
("Clear Wayland screen selection", ""),
|
||||
("clear_Wayland_screen_selection_tip", ""),
|
||||
("confirm_clear_Wayland_screen_selection_tip", ""),
|
||||
("android_new_voice_call_tip", ""),
|
||||
("texture_render_tip", ""),
|
||||
("Use texture rendering", ""),
|
||||
("Floating window", ""),
|
||||
("floating_window_tip", ""),
|
||||
("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", "Χρήση απόδοσης υφής"),
|
||||
("Floating window", "Πλωτό παράθυρο"),
|
||||
("floating_window_tip", "Βοηθά στη διατήρηση της υπηρεσίας παρασκηνίου RustDesk"),
|
||||
("Keep screen on", "Διατήρηση οθόνης Ανοιχτή"),
|
||||
("Never", "Ποτέ"),
|
||||
("During controlled", "Κατα την διάρκεια απομακρυσμένου ελέγχου"),
|
||||
@@ -619,8 +618,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Apps", "Εφαρμογές"),
|
||||
("Volume up", "Αύξηση έντασης"),
|
||||
("Volume down", "Μείωση έντασης"),
|
||||
("Power", ""),
|
||||
("Telegram bot", ""),
|
||||
("Power", "Ενέργεια"),
|
||||
("Telegram bot", "Telegram bot"),
|
||||
("enable-bot-tip", "Εάν ενεργοποιήσετε αυτήν τη δυνατότητα, μπορείτε να λάβετε τον κωδικό 2FA από το bot σας. Μπορεί επίσης να λειτουργήσει ως ειδοποίηση σύνδεσης."),
|
||||
("enable-bot-desc", "1, Ανοίξτε μια συνομιλία με τον @BotFather., Στείλτε την εντολή \"/newbot\". Θα λάβετε ένα διακριτικό αφού ολοκληρώσετε αυτό το βήμα.3, Ξεκινήστε μια συνομιλία με το bot που μόλις δημιουργήσατε. Στείλτε ένα μήνυμα που αρχίζει με κάθετο (\"/\") όπως \"/hello\" για να το ενεργοποιήσετε."),
|
||||
("cancel-2fa-confirm-tip", "Είστε βέβαιοι ότι θέλετε να ακυρώσετε το 2FA;"),
|
||||
@@ -640,11 +639,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Parent directory", "Γονικός φάκελος"),
|
||||
("Resume", "Συνέχεια"),
|
||||
("Invalid file name", "Μη έγκυρο όνομα αρχείου"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("one-way-file-transfer-tip", "Η μονόδρομη μεταφορά αρχείων είναι ενεργοποιημένη στην ελεγχόμενη πλευρά."),
|
||||
("Authentication Required", "Απαιτείται έλεγχος ταυτότητας"),
|
||||
("Authenticate", "Πιστοποίηση"),
|
||||
("web_id_input_tip", ""),
|
||||
("Download", ""),
|
||||
("web_id_input_tip", "Μπορείτε να εισαγάγετε ένα ID στον ίδιο διακομιστή, η άμεση πρόσβαση IP δεν υποστηρίζεται στον web client.\nΕάν θέλετε να αποκτήσετε πρόσβαση σε μια συσκευή σε άλλον διακομιστή, παρακαλούμε να προσθέστε τη διεύθυνση διακομιστή (<id>@<server_address>?key=<key_value>), για παράδειγμα,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nΕάν θέλετε να αποκτήσετε πρόσβαση σε μια συσκευή σε δημόσιο διακομιστή, παρακαλούμε να εισαγάγετε \"<id>@public\". Το κλειδί δεν είναι απαραίτητο για δημόσιο διακομιστή."),
|
||||
("Download", "Λήψη"),
|
||||
("Upload folder", "Μεταφόρτωση φακέλου"),
|
||||
("Upload files", "Μεταφόρτωση αρχείων"),
|
||||
("Clipboard is synchronized", "Το πρόχειρο έχει συγχρονιστεί"),
|
||||
@@ -653,91 +652,93 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("new-version-of-{}-tip", "Υπάρχει διαθέσιμη νέα έκδοση του {}"),
|
||||
("Accessible devices", "Προσβάσιμες συσκευές"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "Αναβαθμίστε τον πελάτη RustDesk στην έκδοση {} ή νεότερη στην απομακρυσμένη πλευρά!"),
|
||||
("d3d_render_tip", ""),
|
||||
("Use D3D rendering", ""),
|
||||
("Printer", ""),
|
||||
("printer-os-requirement-tip", ""),
|
||||
("printer-requires-installed-{}-client-tip", ""),
|
||||
("printer-{}-not-installed-tip", ""),
|
||||
("printer-{}-ready-tip", ""),
|
||||
("Install {} Printer", ""),
|
||||
("Outgoing Print Jobs", ""),
|
||||
("Incoming Print Jobs", ""),
|
||||
("Incoming Print Job", ""),
|
||||
("use-the-default-printer-tip", ""),
|
||||
("use-the-selected-printer-tip", ""),
|
||||
("auto-print-tip", ""),
|
||||
("print-incoming-job-confirm-tip", ""),
|
||||
("remote-printing-disallowed-tile-tip", ""),
|
||||
("remote-printing-disallowed-text-tip", ""),
|
||||
("save-settings-tip", ""),
|
||||
("dont-show-again-tip", ""),
|
||||
("Take screenshot", ""),
|
||||
("Taking screenshot", ""),
|
||||
("screenshot-merged-screen-not-supported-tip", ""),
|
||||
("screenshot-action-tip", ""),
|
||||
("Save as", ""),
|
||||
("Copy to clipboard", ""),
|
||||
("Enable remote printer", ""),
|
||||
("Downloading {}", ""),
|
||||
("{} Update", ""),
|
||||
("{}-to-update-tip", ""),
|
||||
("download-new-version-failed-tip", ""),
|
||||
("Auto update", ""),
|
||||
("update-failed-check-msi-tip", ""),
|
||||
("websocket_tip", ""),
|
||||
("Use WebSocket", ""),
|
||||
("Trackpad speed", ""),
|
||||
("Default trackpad speed", ""),
|
||||
("Numeric one-time password", ""),
|
||||
("Enable IPv6 P2P connection", ""),
|
||||
("Enable UDP hole punching", ""),
|
||||
("d3d_render_tip", "Όταν είναι ενεργοποιημένη η απόδοση D3D, η οθόνη του τηλεχειριστηρίου ενδέχεται να είναι μαύρη σε ορισμένα μηχανήματα."),
|
||||
("Use D3D rendering", "Χρήση απόδοσης D3D"),
|
||||
("Printer", "Εκτυπωτής"),
|
||||
("printer-os-requirement-tip", "Η λειτουργία εξερχόμενης εκτύπωσης του εκτυπωτή απαιτεί Windows 10 ή νεότερη έκδοση."),
|
||||
("printer-requires-installed-{}-client-tip", "Για να χρησιμοποιήσετε την απομακρυσμένη εκτύπωση, πρέπει να εγκατασταθεί το {} σε αυτήν τη συσκευή."),
|
||||
("printer-{}-not-installed-tip", "Ο εκτυπωτής {} δεν είναι εγκατεστημένος."),
|
||||
("printer-{}-ready-tip", "Ο εκτυπωτής {} είναι εγκατεστημένος και έτοιμος για χρήση."),
|
||||
("Install {} Printer", "Εγκατάσταση εκτυπωτή {}"),
|
||||
("Outgoing Print Jobs", "Εξερχόμενες εργασίες εκτύπωσης"),
|
||||
("Incoming Print Jobs", "Εισερχόμενες εργασίες εκτύπωσης"),
|
||||
("Incoming Print Job", "Εισερχόμενη εργασία εκτύπωσης"),
|
||||
("use-the-default-printer-tip", "Χρήση του προεπιλεγμένου εκτυπωτή"),
|
||||
("use-the-selected-printer-tip", "Χρήση του επιλεγμένου εκτυπωτή"),
|
||||
("auto-print-tip", "Εκτυπώστε αυτόματα χρησιμοποιώντας τον επιλεγμένο εκτυπωτή."),
|
||||
("print-incoming-job-confirm-tip", "Λάβατε μια εργασία εκτύπωσης από απόσταση. Θέλετε να την εκτελέσετε από την πλευρά σας;"),
|
||||
("remote-printing-disallowed-tile-tip", "Η απομακρυσμένη εκτύπωση δεν επιτρέπεται"),
|
||||
("remote-printing-disallowed-text-tip", "Οι ρυθμίσεις δικαιωμάτων της ελεγχόμενης πλευράς απαγορεύουν την Απομακρυσμένη Εκτύπωση."),
|
||||
("save-settings-tip", "Αποθήκευση ρυθμίσεων"),
|
||||
("dont-show-again-tip", "Να μην εμφανιστεί ξανά αυτό"),
|
||||
("Take screenshot", "Λήψη στιγμιότυπου οθόνης"),
|
||||
("Taking screenshot", "Γίνεται λήψη στιγμιότυπου οθόνης"),
|
||||
("screenshot-merged-screen-not-supported-tip", "Η συγχώνευση στιγμιότυπων οθόνης από πολλές οθόνες δεν υποστηρίζεται προς το παρόν. Αλλάξτε σε μία μόνο οθόνη και δοκιμάστε ξανά."),
|
||||
("screenshot-action-tip", "Επιλέξτε πώς θα συνεχίσετε με το στιγμιότυπο οθόνης."),
|
||||
("Save as", "Αποθήκευση ως"),
|
||||
("Copy to clipboard", "Αντιγραφή στο πρόχειρο"),
|
||||
("Enable remote printer", "Ενεργοποίηση απομακρυσμένου εκτυπωτή"),
|
||||
("Downloading {}", "Γίνεται Λήψη {}"),
|
||||
("{} Update", "{} Ενημέρωση"),
|
||||
("{}-to-update-tip", "Το {} θα κλείσει τώρα και θα εγκαταστήσει τη νέα έκδοση."),
|
||||
("download-new-version-failed-tip", "Η λήψη απέτυχε. Μπορείτε να δοκιμάσετε ξανά ή να κάνετε κλικ στο κουμπί \"Λήψη\" για να κάνετε λήψη από τη σελίδα έκδοσης και να κάνετε αναβάθμιση χειροκίνητα."),
|
||||
("Auto update", "Αυτόματη ενημέρωση"),
|
||||
("update-failed-check-msi-tip", "Η μέθοδος εγκατάστασης απέτυχε. Κάντε κλικ στο κουμπί \"Λήψη\" για λήψη από τη σελίδα έκδοσης και κάντε χειροκίνητα την αναβάθμιση."),
|
||||
("websocket_tip", "Όταν χρησιμοποιείτε το WebSocket, υποστηρίζονται μόνο συνδέσεις αναμετάδοσης."),
|
||||
("Use WebSocket", "Χρήση WebSocket"),
|
||||
("Trackpad speed", "Ταχύτητα trackpad"),
|
||||
("Default trackpad speed", "Προεπιλεγμένη ταχύτητα trackpad"),
|
||||
("Numeric one-time password", "Αριθμητικός κωδικός πρόσβασης μίας χρήσης"),
|
||||
("Enable IPv6 P2P connection", "Ενεργοποίηση σύνδεσης IPv6 P2P"),
|
||||
("Enable UDP hole punching", "Ενεργοποίηση διάτρησης οπών UDP"),
|
||||
("View camera", "Προβολή κάμερας"),
|
||||
("Enable camera", ""),
|
||||
("No cameras", ""),
|
||||
("view_camera_unsupported_tip", ""),
|
||||
("Terminal", ""),
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("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.", ""),
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("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", ""),
|
||||
("Enable camera", "Ενεργοποίηση κάμερας"),
|
||||
("No cameras", "Δεν υπάρχουν κάμερες"),
|
||||
("view_camera_unsupported_tip", "Η τηλεχειριστήριο δεν υποστηρίζει την προβολή της κάμερας."),
|
||||
("Terminal", "Τερματικό"),
|
||||
("Enable terminal", "Ενεργοποίηση τερματικού"),
|
||||
("New tab", "Νέα καρτέλα"),
|
||||
("Keep terminal sessions on disconnect", "Διατήρηση περιόδων λειτουργίας τερματικού κατά την αποσύνδεση"),
|
||||
("Terminal (Run as administrator)", "Τερματικό (Εκτέλεση ως διαχειριστής)"),
|
||||
("terminal-admin-login-tip", "Παρακαλώ εισάγετε το όνομα χρήστη και τον κωδικό πρόσβασης διαχειριστή της ελεγχόμενης πλευράς."),
|
||||
("Failed to get user token.", "Αποτυχία λήψης διακριτικού χρήστη."),
|
||||
("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.", "Υποστηρίζεται μόνο στην εγκατεστημένη έκδοση."),
|
||||
("elevation_username_tip", "Εισαγάγετε όνομα χρήστη ή τομέα\\όνομα χρήστη"),
|
||||
("Preparing for installation ...", "Προετοιμασία για εγκατάσταση..."),
|
||||
("Show my cursor", "Εμφάνιση του κέρσορα μου"),
|
||||
("Scale custom", "Προσαρμοσμένη κλίμακα"),
|
||||
("Custom scale slider", "Ρυθμιστικό προσαρμοσμένης κλίμακας"),
|
||||
("Decrease", "Μείωση"),
|
||||
("Increase", "Αύξηση"),
|
||||
("Show virtual mouse", "Εμφάνιση εικονικού ποντικιού"),
|
||||
("Virtual mouse size", "Μέγεθος εικονικού ποντικιού"),
|
||||
("Small", "Μικρό"),
|
||||
("Large", "Μεγάλο"),
|
||||
("Show virtual joystick", "Εμφάνιση εικονικού joystick"),
|
||||
("Edit note", "Επεξεργασία σημείωσης"),
|
||||
("Alias", "Ψευδώνυμο"),
|
||||
("ScrollEdge", "Άκρη κύλισης"),
|
||||
("Allow insecure TLS fallback", "Να επιτρέπεται η μη ασφαλής εφεδρική λειτουργία TLS"),
|
||||
("allow-insecure-tls-fallback-tip", "Από προεπιλογή, το RustDesk επαληθεύει το πιστοποιητικό διακομιστή για πρωτόκολλα που χρησιμοποιούν TLS.\nΜε ενεργοποιημένη αυτήν την επιλογή, το RustDesk θα παρακάμψει το βήμα επαλήθευσης και θα προχωρήσει σε περίπτωση αποτυχίας επαλήθευσης."),
|
||||
("Disable UDP", "Απενεργοποίηση UDP"),
|
||||
("disable-udp-tip", "Ελέγχει εάν θα χρησιμοποιείται μόνο TCP.\nΌταν είναι ενεργοποιημένη αυτή η επιλογή, το RustDesk δεν θα χρησιμοποιεί πλέον το UDP 21116, αλλά θα χρησιμοποιείται το TCP 21116."),
|
||||
("server-oss-not-support-tip", "ΣΗΜΕΙΩΣΗ: Το OSS του διακομιστή RustDesk δεν περιλαμβάνει αυτήν τη λειτουργία."),
|
||||
("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", "Διατήρηση ενεργής οθόνης κατά τη διάρκεια των εισερχόμενων συνεδριών"),
|
||||
("Continue with {}", "Συνέχεια με {}"),
|
||||
("Display Name", "Εμφανιζόμενο όνομα"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("default_proxy_tip", "Default protocol and port are Socks5 and 1080"),
|
||||
("no_audio_input_device_tip", "No audio input device found."),
|
||||
("clear_Wayland_screen_selection_tip", "After clearing the screen selection, you can reselect the screen to share."),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "Are you sure to clear the Wayland screen selection?"),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "Are you sure you want to clear the Wayland screen selection?"),
|
||||
("android_new_voice_call_tip", "A new voice call request was received. If you accept, the audio will switch to voice communication."),
|
||||
("texture_render_tip", "Use texture rendering to make the pictures smoother. You could try disabling this option if you encounter rendering issues."),
|
||||
("floating_window_tip", "It helps to keep RustDesk background service"),
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
("Elevate", ""),
|
||||
("Zoom cursor", ""),
|
||||
("Accept sessions via password", ""),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", ""),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Mostrar RustDesk"),
|
||||
("This PC", "Este PC"),
|
||||
("or", "o"),
|
||||
("Continue with", "Continuar con"),
|
||||
("Elevate", "Elevar privilegios"),
|
||||
("Zoom cursor", "Ampliar cursor"),
|
||||
("Accept sessions via password", "Aceptar sesiones a través de contraseña"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Continuar con {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Kuva RustDesk"),
|
||||
("This PC", "See arvuti"),
|
||||
("or", "või"),
|
||||
("Continue with", "Jätka koos"),
|
||||
("Elevate", "Tõsta"),
|
||||
("Zoom cursor", "Suumi kursorit"),
|
||||
("Accept sessions via password", "Aktsepteeri seansid parooli kaudu"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Jätka koos {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Erakutsi RustDesk"),
|
||||
("This PC", "PC hau"),
|
||||
("or", "edo"),
|
||||
("Continue with", "Jarraitu honekin"),
|
||||
("Elevate", "Igo maila"),
|
||||
("Zoom cursor", "Handitu kurtsorea"),
|
||||
("Accept sessions via password", "Onartu saioak pasahitzaren bidez"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "{} honekin jarraitu"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk نمایش"),
|
||||
("This PC", "This PC"),
|
||||
("or", "یا"),
|
||||
("Continue with", "ادامه با"),
|
||||
("Elevate", "ارتقاء"),
|
||||
("Zoom cursor", " بزرگنمایی نشانگر ماوس"),
|
||||
("Accept sessions via password", "قبول درخواست با رمز عبور"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "ادامه با {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Näytä RustDesk"),
|
||||
("This PC", "Tämä tietokone"),
|
||||
("or", "tai"),
|
||||
("Continue with", "Jatka käyttäen"),
|
||||
("Elevate", "Korota oikeudet"),
|
||||
("Zoom cursor", "Suurennusosoitin"),
|
||||
("Accept sessions via password", "Hyväksy istunnot salasanalla"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Jatka käyttäen {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Set permanent password", "Définir le mot de passe permanent"),
|
||||
("Enable remote restart", "Activer le redémarrage à distance"),
|
||||
("Restart remote device", "Redémarrer l’appareil distant"),
|
||||
("Are you sure you want to restart", "Voulez-vous vraiment redémarrer l’appareil ?"),
|
||||
("Are you sure you want to restart", "Voulez-vous vraiment redémarrer"),
|
||||
("Restarting remote device", "Redémarrage de l’appareil distant"),
|
||||
("remote_restarting_tip", "L'appareil distant redémarre ; veuillez fermer cette boîte de dialogue et vous reconnecter en utilisant le mot de passe permanent dans quelques instants"),
|
||||
("Copied", "Copié"),
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Afficher RustDesk"),
|
||||
("This PC", "Ce PC"),
|
||||
("or", "ou"),
|
||||
("Continue with", "Continuer avec"),
|
||||
("Elevate", "Élever les privilèges"),
|
||||
("Zoom cursor", "Augmenter la taille du curseur"),
|
||||
("Accept sessions via password", "Accepter les sessions via mot de passe"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Journal des modifications"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Maintenir l’écran allumé lors des sessions sortantes"),
|
||||
("keep-awake-during-incoming-sessions-label", "Maintenir l’écran allumé lors des sessions entrantes"),
|
||||
("Continue with {}", "Continuer avec {}"),
|
||||
("Display Name", "Nom d’affichage"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk-ის ჩვენება"),
|
||||
("This PC", "ეს კომპიუტერი"),
|
||||
("or", "ან"),
|
||||
("Continue with", "გაგრძელება"),
|
||||
("Elevate", "უფლებების აწევა"),
|
||||
("Zoom cursor", "კურსორის მასშტაბირება"),
|
||||
("Accept sessions via password", "სესიების მიღება პაროლით"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "{}-ით გაგრძელება"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "הצג את RustDesk"),
|
||||
("This PC", "מחשב זה"),
|
||||
("or", "או"),
|
||||
("Continue with", "המשך עם"),
|
||||
("Elevate", "הפעל הרשאות מורחבות"),
|
||||
("Zoom cursor", "הגדל סמן"),
|
||||
("Accept sessions via password", "קבל הפעלות באמצעות סיסמה"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "המשך עם {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Prikaži RustDesk"),
|
||||
("This PC", "Ovo računalo"),
|
||||
("or", "ili"),
|
||||
("Continue with", "Nastavi sa"),
|
||||
("Elevate", "Izdigni"),
|
||||
("Zoom cursor", "Zumiraj kursor"),
|
||||
("Accept sessions via password", "Prihvati sesije preko lozinke"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Nastavi sa {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Password", "Jelszó"),
|
||||
("Ready", "Kész"),
|
||||
("Established", "Létrejött"),
|
||||
("connecting_status", "Kapcsolódás folyamatban…"),
|
||||
("connecting_status", "Kapcsolódás folyamatban ..."),
|
||||
("Enable service", "Szolgáltatás engedélyezése"),
|
||||
("Start service", "Szolgáltatás indítása"),
|
||||
("Service is running", "Szolgáltatás aktív"),
|
||||
@@ -28,7 +28,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable file transfer", "Fájlátvitel engedélyezése"),
|
||||
("Enable TCP tunneling", "TCP-alagút engedélyezése"),
|
||||
("IP Whitelisting", "IP engedélyezési lista"),
|
||||
("ID/Relay Server", "Azonosító-/Továbbító-kiszolgáló"),
|
||||
("ID/Relay Server", "ID/Továbbító-kiszolgáló"),
|
||||
("Import server config", "Kiszolgáló-konfiguráció importálása"),
|
||||
("Export Server Config", "Kiszolgáló-konfiguráció exportálása"),
|
||||
("Import server configuration successfully", "Kiszolgáló-konfiguráció sikeresen importálva"),
|
||||
@@ -54,7 +54,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enhancements", "Fejlesztések"),
|
||||
("Hardware Codec", "Hardveres kodek"),
|
||||
("Adaptive bitrate", "Adaptív bitráta"),
|
||||
("ID Server", "Azonosító-kiszolgáló"),
|
||||
("ID Server", "ID-kiszolgáló"),
|
||||
("Relay Server", "Továbbító-kiszolgáló"),
|
||||
("API Server", "API-kiszolgáló"),
|
||||
("invalid_http", "A címnek mindenképpen http(s)://-el kell kezdődnie."),
|
||||
@@ -76,12 +76,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection Error", "Kapcsolódási hiba"),
|
||||
("Error", "Hiba"),
|
||||
("Reset by the peer", "A kapcsolatot a másik fél lezárta."),
|
||||
("Connecting...", "Kapcsolódás…"),
|
||||
("Connection in progress. Please wait.", "A kapcsolódás folyamatban van. Kis türelmet…"),
|
||||
("Connecting...", "Kapcsolódás..."),
|
||||
("Connection in progress. Please wait.", "A kapcsolódás folyamatban van. Kis türelmet ..."),
|
||||
("Please try 1 minute later", "Próbálja meg 1 perc múlva"),
|
||||
("Login Error", "Bejelentkezési hiba"),
|
||||
("Successful", "Sikeres"),
|
||||
("Connected, waiting for image...", "Kapcsolódva, várakozás a képadatokra…"),
|
||||
("Connected, waiting for image...", "Kapcsolódva, várakozás a képadatokra..."),
|
||||
("Name", "Név"),
|
||||
("Type", "Típus"),
|
||||
("Modified", "Módosított"),
|
||||
@@ -127,7 +127,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Optimize reaction time", "Gyorsan reagáló"),
|
||||
("Custom", "Egyéni"),
|
||||
("Show remote cursor", "Távoli kurzor megjelenítése"),
|
||||
("Show quality monitor", "Kapcsolat minőségének megjelenítése"),
|
||||
("Show quality monitor", "Kijelző minőségének ellenőrzése"),
|
||||
("Disable clipboard", "Közös vágólap kikapcsolása"),
|
||||
("Lock after session end", "Távoli fiók zárolása a munkamenet végén"),
|
||||
("Insert Ctrl + Alt + Del", "Illessze be a Ctrl + Alt + Del billentyűzetkombinációt"),
|
||||
@@ -150,8 +150,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Configure", "Beállítás"),
|
||||
("config_acc", "A számítógép távoli vezérléséhez a RustDesknek hozzáférési jogokat kell adnia."),
|
||||
("config_screen", "Ahhoz, hogy távolról hozzáférhessen a számítógépéhez, meg kell adnia a RustDesknek a \"Képernyőfelvétel\" jogosultságot."),
|
||||
("Installing ...", "Telepítés…"),
|
||||
("Install", "Telepítés"),
|
||||
("Installing ...", "Telepítés ..."),
|
||||
("Install", "Telepítse"),
|
||||
("Installation", "Telepítés"),
|
||||
("Installation Path", "Telepítési útvonal"),
|
||||
("Create start menu shortcuts", "Start menü parancsikonok létrehozása"),
|
||||
@@ -159,10 +159,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("agreement_tip", "A telepítés folytatásával automatikusan elfogadásra kerül a licenc szerződés."),
|
||||
("Accept and Install", "Elfogadás és telepítés"),
|
||||
("End-user license agreement", "Végfelhasználói licenc szerződés"),
|
||||
("Generating ...", "Előállítás…"),
|
||||
("Generating ...", "Létrehozás ..."),
|
||||
("Your installation is lower version.", "A telepített verzió alacsonyabb."),
|
||||
("not_close_tcp_tip", "Ne zárja be ezt az ablakot, amíg TCP-alagutat használ"),
|
||||
("Listening ...", "Figyelés…"),
|
||||
("Listening ...", "Figyelés ..."),
|
||||
("Remote Host", "Távoli kiszolgáló"),
|
||||
("Remote Port", "Távoli port"),
|
||||
("Action", "Indítás"),
|
||||
@@ -177,7 +177,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept", "Elfogadás"),
|
||||
("Dismiss", "Elutasítás"),
|
||||
("Disconnect", "Kapcsolat bontása"),
|
||||
("Enable file copy and paste", "Fájlmásolás és -beillesztés engedélyezése"),
|
||||
("Enable file copy and paste", "Fájlmásolás és beillesztés engedélyezése"),
|
||||
("Connected", "Kapcsolódva"),
|
||||
("Direct and encrypted connection", "Közvetlen, és titkosított kapcsolat"),
|
||||
("Relayed and encrypted connection", "Továbbított, és titkosított kapcsolat"),
|
||||
@@ -185,7 +185,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Relayed and unencrypted connection", "Továbbított, és nem titkosított kapcsolat"),
|
||||
("Enter Remote ID", "Távoli számítógép azonosítója"),
|
||||
("Enter your password", "Adja meg a jelszavát"),
|
||||
("Logging in...", "Belépés folyamatban…"),
|
||||
("Logging in...", "Belépés folyamatban..."),
|
||||
("Enable RDP session sharing", "RDP-munkamenet-megosztás engedélyezése"),
|
||||
("Auto Login", "Automatikus bejelentkezés"),
|
||||
("Enable direct IP access", "Közvetlen IP-elérés engedélyezése"),
|
||||
@@ -219,7 +219,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("verification_tip", "A regisztrált e-mail-címre egy ellenőrző kód lesz elküldve. Adja meg az ellenőrző kódot az újbóli bejelentkezéshez."),
|
||||
("Logout", "Kilépés"),
|
||||
("Tags", "Címkék"),
|
||||
("Search ID", "Azonosító keresése…"),
|
||||
("Search ID", "Azonosító keresése..."),
|
||||
("whitelist_sep", "A címeket vesszővel, pontosvesszővel, szóközzel vagy új sorral kell elválasztani"),
|
||||
("Add ID", "Azonosító hozzáadása"),
|
||||
("Add Tag", "Címke hozzáadása"),
|
||||
@@ -258,10 +258,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Three-Finger vertically", "Három ujj függőlegesen"),
|
||||
("Mouse Wheel", "Egérgörgő"),
|
||||
("Two-Finger Move", "Kétujjas mozgatás"),
|
||||
("Canvas Move", "Vászon mozgatása"),
|
||||
("Canvas Move", "Nézet módosítása"),
|
||||
("Pinch to Zoom", "Kétujjas nagyítás"),
|
||||
("Canvas Zoom", "Vászon nagyítása"),
|
||||
("Reset canvas", "Vászon visszaállítása"),
|
||||
("Canvas Zoom", "Nézet nagyítása"),
|
||||
("Reset canvas", "Nézet visszaállítása"),
|
||||
("No permission of file transfer", "Nincs engedély a fájlátvitelre"),
|
||||
("Note", "Megjegyzés"),
|
||||
("Connection", "Kapcsolat"),
|
||||
@@ -314,7 +314,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable remote restart", "Távoli újraindítás engedélyezése"),
|
||||
("Restart remote device", "Távoli eszköz újraindítása"),
|
||||
("Are you sure you want to restart", "Biztosan újra szeretné indítani?"),
|
||||
("Restarting remote device", "Távoli eszköz újraindítása…"),
|
||||
("Restarting remote device", "Távoli eszköz újraindítása..."),
|
||||
("remote_restarting_tip", "A távoli eszköz újraindul, zárja be ezt az üzenetet, kapcsolódjon újra az állandó jelszavával"),
|
||||
("Copied", "Másolva"),
|
||||
("Exit Fullscreen", "Kilépés teljes képernyős módból"),
|
||||
@@ -369,12 +369,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Deny LAN discovery", "Felfedezés tiltása"),
|
||||
("Write a message", "Üzenet írása"),
|
||||
("Prompt", "Kérés"),
|
||||
("Please wait for confirmation of UAC...", "Várjon az UAC megerősítésére…"),
|
||||
("Please wait for confirmation of UAC...", "Várjon az UAC megerősítésére..."),
|
||||
("elevated_foreground_window_tip", "A távvezérelt számítógép jelenleg nyitott ablakához magasabb szintű jogok szükségesek. Ezért jelenleg nem lehetséges az egér és a billentyűzet használata. Kérje meg azt a felhasználót, akinek a számítógépét távolról vezérli, hogy minimalizálja az ablakot, vagy növelje a jogokat. A jövőbeni probléma elkerülése érdekében ajánlott a szoftvert a távvezérelt számítógépre telepíteni."),
|
||||
("Disconnected", "Kapcsolat bontva"),
|
||||
("Other", "Egyéb"),
|
||||
("Confirm before closing multiple tabs", "Biztosan bezárja az összes lapot?"),
|
||||
("Keyboard Settings", "Billentyűzet-beállítások"),
|
||||
("Keyboard Settings", "Billentyűzetbeállítások"),
|
||||
("Full Access", "Teljes hozzáférés"),
|
||||
("Screen Share", "Képernyőmegosztás"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "A Waylandhez Ubuntu 21.04 vagy újabb verzió szükséges."),
|
||||
@@ -384,13 +384,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "A RustDesk megjelenítése"),
|
||||
("This PC", "Ez a számítógép"),
|
||||
("or", "vagy"),
|
||||
("Continue with", "Folytatás a következővel"),
|
||||
("Elevate", "Hozzáférés engedélyezése"),
|
||||
("Zoom cursor", "Kurzor nagyítása"),
|
||||
("Accept sessions via password", "Munkamenetek elfogadása jelszóval"),
|
||||
("Accept sessions via click", "Munkamenetek elfogadása kattintással"),
|
||||
("Accept sessions via both", "Munkamenetek fogadása mindkettőn keresztül"),
|
||||
("Please wait for the remote side to accept your session request...", "Várjon, amíg a távoli oldal elfogadja a munkamenet-kérelmét…"),
|
||||
("Please wait for the remote side to accept your session request...", "Várjon, amíg a távoli oldal elfogadja a munkamenet-kérelmét..."),
|
||||
("One-time Password", "Egyszer használatos jelszó"),
|
||||
("Use one-time password", "Használjon ideiglenes jelszót"),
|
||||
("One-time password length", "Egyszer használatos jelszó hossza"),
|
||||
@@ -448,13 +447,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resolution", "Felbontás"),
|
||||
("No transfers in progress", "Nincs folyamatban átvitel"),
|
||||
("Set one-time password length", "Állítsa be az egyszeri jelszó hosszát"),
|
||||
("RDP Settings", "RDP-beállítások"),
|
||||
("RDP Settings", "RDP beállítások"),
|
||||
("Sort by", "Rendezés"),
|
||||
("New Connection", "Új kapcsolat"),
|
||||
("Restore", "Visszaállítás"),
|
||||
("Minimize", "Minimalizálás"),
|
||||
("Maximize", "Maximalizálás"),
|
||||
("Your Device", "Saját eszköz"),
|
||||
("Your Device", "Az én eszközöm"),
|
||||
("empty_recent_tip", "Nincsenek aktuális munkamenetek!\nIdeje ütemezni egy újat."),
|
||||
("empty_favorite_tip", "Még nincs kedvenc távoli állomása?\nHagyja, hogy találjunk valakit, akivel kapcsolatba tud lépni, és adja hozzá a kedvencekhez!"),
|
||||
("empty_lan_tip", "Úgy tűnik, még nem adott hozzá egyetlen távoli helyszínt sem."),
|
||||
@@ -469,7 +468,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("verify_rustdesk_password_tip", "RustDesk jelszó megerősítése"),
|
||||
("remember_account_tip", "Emlékezzen erre a fiókra"),
|
||||
("os_account_desk_tip", "Ezzel a fiókkal bejelentkezhet a távoli operációs rendszerbe, és aktiválhatja az asztali munkamenetet fej nélküli módban."),
|
||||
("OS Account", "OS-fiók"),
|
||||
("OS Account", "OS fiók"),
|
||||
("another_user_login_title_tip", "Egy másik felhasználó már bejelentkezett."),
|
||||
("another_user_login_text_tip", "Különálló"),
|
||||
("xorg_not_found_title_tip", "Xorg nem található."),
|
||||
@@ -569,7 +568,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input_source_2_tip", "2. bemeneti forrás"),
|
||||
("Swap control-command key", "Vezérlő- és parancsgombok cseréje"),
|
||||
("swap-left-right-mouse", "Bal és jobb egérgomb felcserélése"),
|
||||
("2FA code", "2FA-kód"),
|
||||
("2FA code", "2FA kód"),
|
||||
("More", "Továbbiak"),
|
||||
("enable-2fa-title", "Kétfaktoros hitelesítés aktiválása"),
|
||||
("enable-2fa-desc", "Állítsa be a hitelesítőt. Használhat egy hitelesítő alkalmazást, például az Aegis, Authy, a Microsoft- vagy a Google Authenticator alkalmazást a telefonján vagy az asztali számítógépén.\n\nOlvassa be a QR-kódot az alkalmazással, és adja meg az alkalmazás által megjelenített kódot a kétfaktoros hitelesítés aktiválásához."),
|
||||
@@ -648,13 +647,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Mappa feltöltése"),
|
||||
("Upload files", "Fájlok feltöltése"),
|
||||
("Clipboard is synchronized", "A vágólap szinkronizálva van"),
|
||||
("Update client clipboard", "Kliens vágólapjának frissítése"),
|
||||
("Update client clipboard", "Az ügyfél vágólapjának frissítése"),
|
||||
("Untagged", "Címkézetlen"),
|
||||
("new-version-of-{}-tip", "A(z) {} új verziója"),
|
||||
("Accessible devices", "Hozzáférhető eszközök"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "Frissítse a RustDesk klienst {} vagy újabb verziójára a távoli oldalon!"),
|
||||
("d3d_render_tip", "D3D-leképezés"),
|
||||
("Use D3D rendering", "D3D-leképezés használata"),
|
||||
("d3d_render_tip", "D3D leképezés"),
|
||||
("Use D3D rendering", "D3D leképezés használata"),
|
||||
("Printer", "Nyomtató"),
|
||||
("printer-os-requirement-tip", "Nyomtató operációs rendszerének minimális rendszerkövetelménye"),
|
||||
("printer-requires-installed-{}-client-tip", "A nyomtatóhoz szükséges a(z) {} kliens telepítése"),
|
||||
@@ -673,7 +672,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("save-settings-tip", "Beállítások mentése"),
|
||||
("dont-show-again-tip", "Ne jelenítse meg újra"),
|
||||
("Take screenshot", "Képernyőkép készítése"),
|
||||
("Taking screenshot", "Képernyőkép készítése…"),
|
||||
("Taking screenshot", "Képernyőkép készítése..."),
|
||||
("screenshot-merged-screen-not-supported-tip", "Egyesített képernyőről nem támogatott a képernyőkép készítése"),
|
||||
("screenshot-action-tip", "Képernyőkép-művelet"),
|
||||
("Save as", "Mentés másként"),
|
||||
@@ -681,7 +680,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable remote printer", "Távoli nyomtatók engedélyezése"),
|
||||
("Downloading {}", "{} letöltése"),
|
||||
("{} Update", "{} frissítés"),
|
||||
("{}-to-update-tip", "A(z) {} bezárása és az új verzió telepítése."),
|
||||
("{}-to-update-tip", "{} bezárása és az új verzió telepítése."),
|
||||
("download-new-version-failed-tip", "Ha a letöltés sikertelen, akkor vagy újrapróbálkozhat, vagy a \"Letöltés\" gombra kattintva letöltheti a kiadási oldalról, és manuálisan frissíthet."),
|
||||
("Auto update", "Automatikus frissítés"),
|
||||
("update-failed-check-msi-tip", "A telepítési módszer felismerése nem sikerült. Kattintson a \"Letöltés\" gombra, hogy letöltse a kiadási oldalról, és manuálisan frissítse."),
|
||||
@@ -708,7 +707,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to check if the user is an administrator.", "Hiba merült fel annak ellenőrzése során, hogy a felhasználó rendszergazda-e."),
|
||||
("Supported only in the installed version.", "Csak a telepített változatban támogatott."),
|
||||
("elevation_username_tip", "Felhasználónév vagy tartománynév megadása"),
|
||||
("Preparing for installation ...", "Felkészülés a telepítésre…"),
|
||||
("Preparing for installation ...", "Felkészülés a telepítésre ..."),
|
||||
("Show my cursor", "Kurzor megjelenítése"),
|
||||
("Scale custom", "Egyéni méretarány"),
|
||||
("Custom scale slider", "Egyéni méretarány-csúszka"),
|
||||
@@ -734,7 +733,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("rel-mouse-not-supported-peer-tip", "A kapcsolódott partner nem támogatja a relatív egér módot."),
|
||||
("rel-mouse-not-ready-tip", "A relatív egér mód még nem elérhető. Próbálja meg újra."),
|
||||
("rel-mouse-lock-failed-tip", "Nem sikerült zárolni a kurzort. A relatív egér mód le lett tiltva."),
|
||||
("rel-mouse-exit-{}-tip", "A kilépéshez nyomja meg a(z) {} gombot."),
|
||||
("rel-mouse-exit-{}-tip", "A kilépéshez nyomja meg a következő gombot: {}"),
|
||||
("rel-mouse-permission-lost-tip", "A billentyűzet-hozzáférés vissza lett vonva. A relatív egér mód le lett tilva."),
|
||||
("Changelog", "Változáslista"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Képernyő aktív állapotban tartása a kimenő munkamenetek során"),
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Tampilkan RustDesk"),
|
||||
("This PC", "PC ini"),
|
||||
("or", "atau"),
|
||||
("Continue with", "Lanjutkan dengan"),
|
||||
("Elevate", "Elevasi"),
|
||||
("Zoom cursor", "Perbersar Kursor"),
|
||||
("Accept sessions via password", "Izinkan sesi dengan kata sandi"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Lanjutkan dengan {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Visualizza RustDesk"),
|
||||
("This PC", "Questo PC"),
|
||||
("or", "O"),
|
||||
("Continue with", "Continua con"),
|
||||
("Elevate", "Eleva"),
|
||||
("Zoom cursor", "Cursore zoom"),
|
||||
("Accept sessions via password", "Accetta sessioni via password"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Novità programma"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Mantieni lo schermo attivo durante le sessioni in uscita"),
|
||||
("keep-awake-during-incoming-sessions-label", "Mantieni lo schermo attivo durante le sessioni in ingresso"),
|
||||
("Continue with {}", "Continua con {}"),
|
||||
("Display Name", "Visualizza nome"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk を表示"),
|
||||
("This PC", "この PC"),
|
||||
("or", "または"),
|
||||
("Continue with", "で続行"),
|
||||
("Elevate", "昇格"),
|
||||
("Zoom cursor", "カーソルを拡大する"),
|
||||
("Accept sessions via password", "パスワードでセッションを承認"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "{} で続行"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk 표시"),
|
||||
("This PC", "이 PC"),
|
||||
("or", "또는"),
|
||||
("Continue with", "계속"),
|
||||
("Elevate", "권한 상승"),
|
||||
("Zoom cursor", "커서 확대/축소"),
|
||||
("Accept sessions via password", "비밀번호를 통해 세션 수락"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "변경 기록"),
|
||||
("keep-awake-during-outgoing-sessions-label", "발신 세션 중 화면 켜짐 유지"),
|
||||
("keep-awake-during-incoming-sessions-label", "수신 세션 중 화면 켜짐 유지"),
|
||||
("Continue with {}", "{}(으)로 계속"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
("Elevate", ""),
|
||||
("Zoom cursor", ""),
|
||||
("Accept sessions via password", ""),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", ""),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Rodyti RustDesk"),
|
||||
("This PC", "Šis kompiuteris"),
|
||||
("or", "arba"),
|
||||
("Continue with", "Tęsti su"),
|
||||
("Elevate", "Pakelti"),
|
||||
("Zoom cursor", "Mastelio keitimo žymeklis"),
|
||||
("Accept sessions via password", "Priimti seansus naudojant slaptažodį"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Tęsti su {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Rādīt RustDesk"),
|
||||
("This PC", "Šis dators"),
|
||||
("or", "vai"),
|
||||
("Continue with", "Turpināt ar"),
|
||||
("Elevate", "Pacelt"),
|
||||
("Zoom cursor", "Tālummaiņas kursors"),
|
||||
("Accept sessions via password", "Pieņemt sesijas, izmantojot paroli"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Turpināt ar {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Vis RustDesk"),
|
||||
("This PC", "Denne PC"),
|
||||
("or", "eller"),
|
||||
("Continue with", "Fortsett med"),
|
||||
("Elevate", "Elever"),
|
||||
("Zoom cursor", "Zoom markør"),
|
||||
("Accept sessions via password", "Aksepter sesjoner via passord"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Fortsett med {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Toon RustDesk"),
|
||||
("This PC", "Deze PC"),
|
||||
("or", "of"),
|
||||
("Continue with", "Ga verder met"),
|
||||
("Elevate", "Verhoog"),
|
||||
("Zoom cursor", "Zoom cursor"),
|
||||
("Accept sessions via password", "Sessies accepteren via wachtwoord"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Wijzigingenlogboek"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Houd het scherm open tijdens de uitgaande sessies."),
|
||||
("keep-awake-during-incoming-sessions-label", "Houd het scherm open tijdens de inkomende sessies."),
|
||||
("Continue with {}", "Ga verder met {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Pokaż RustDesk"),
|
||||
("This PC", "Ten komputer"),
|
||||
("or", "lub"),
|
||||
("Continue with", "Kontynuuj z"),
|
||||
("Elevate", "Uzyskaj uprawnienia"),
|
||||
("Zoom cursor", "Powiększenie kursora"),
|
||||
("Accept sessions via password", "Uwierzytelnij sesję używając hasła"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Dziennik zmian"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Utrzymuj urządzenie w stanie aktywnym podczas sesji wychodzących"),
|
||||
("keep-awake-during-incoming-sessions-label", "Utrzymuj urządzenie w stanie aktywnym podczas sesji przychodzących"),
|
||||
("Continue with {}", "Kontynuuj z {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
("Elevate", ""),
|
||||
("Zoom cursor", ""),
|
||||
("Accept sessions via password", ""),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", ""),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Exibir RustDesk"),
|
||||
("This PC", "Este Computador"),
|
||||
("or", "ou"),
|
||||
("Continue with", "Continuar com"),
|
||||
("Elevate", "Elevar"),
|
||||
("Zoom cursor", "Aumentar cursor"),
|
||||
("Accept sessions via password", "Aceitar sessões via senha"),
|
||||
@@ -674,21 +673,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("dont-show-again-tip", "Não mostrar novamente"),
|
||||
("Take screenshot", "Capturar de tela"),
|
||||
("Taking screenshot", "Capturando tela"),
|
||||
("screenshot-merged-screen-not-supported-tip", ""),
|
||||
("screenshot-action-tip", ""),
|
||||
("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-action-tip", "Por favor, selecione como seguir com a captura de tela."),
|
||||
("Save as", "Salvar como"),
|
||||
("Copy to clipboard", "Copiar para área de transferência"),
|
||||
("Enable remote printer", "Habilitar impressora remota"),
|
||||
("Downloading {}", ""),
|
||||
("{} Update", ""),
|
||||
("{}-to-update-tip", ""),
|
||||
("Downloading {}", "Baixando {}"),
|
||||
("{} Update", "Atualização do {}"),
|
||||
("{}-to-update-tip", "{} será fechado agora para instalar a nova versão."),
|
||||
("download-new-version-failed-tip", "Falha no download. Você pode tentar novamente ou clicar no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
||||
("Auto update", "Atualização automática"),
|
||||
("update-failed-check-msi-tip", "Falha na verificação do método de instalação. Clique no botão \"Download\" para baixar da página releases e atualizar manualmente."),
|
||||
("websocket_tip", "Usando WebSocket, apenas conexões via relay são suportadas."),
|
||||
("Use WebSocket", "Usar WebSocket"),
|
||||
("Trackpad speed", "Velocidade do trackpad"),
|
||||
("Default trackpad speed", ""),
|
||||
("Default trackpad speed", "Velocidade padrão do trackpad"),
|
||||
("Numeric one-time password", "Senha numérica de uso único"),
|
||||
("Enable IPv6 P2P connection", "Habilitar conexão IPv6 P2P"),
|
||||
("Enable UDP hole punching", "Habilitar UDP hole punching"),
|
||||
@@ -718,11 +717,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Virtual mouse size", "Tamanho do mouse virtual"),
|
||||
("Small", "Pequeno"),
|
||||
("Large", "Grande"),
|
||||
("Show virtual joystick", ""),
|
||||
("Show virtual joystick", "Mostrar joystick virtual"),
|
||||
("Edit note", "Editar nota"),
|
||||
("Alias", "Apelido"),
|
||||
("ScrollEdge", "Rolagem nas bordas"),
|
||||
("Allow insecure TLS fallback", ""),
|
||||
("Allow insecure TLS fallback", "Permitir fallback TLS inseguro"),
|
||||
("allow-insecure-tls-fallback-tip", "Por padrão, o RustDesk verifica o certificado do servidor para protocolos que usam TLS.\nCom esta opção habilitada, o RustDesk ignorará a verificação e prosseguirá em caso de falha."),
|
||||
("Disable UDP", "Desabilitar UDP"),
|
||||
("disable-udp-tip", "Controla se deve usar somente TCP.\nCom esta opção habilitada, o RustDesk não usará mais UDP 21116, TCP 21116 será usado no lugar."),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Registro de alterações"),
|
||||
("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", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Afișează RustDesk"),
|
||||
("This PC", "Acest PC"),
|
||||
("or", "sau"),
|
||||
("Continue with", "Continuă cu"),
|
||||
("Elevate", "Sporește privilegii"),
|
||||
("Zoom cursor", "Cursor lupă"),
|
||||
("Accept sessions via password", "Acceptă începerea sesiunii folosind parola"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Continuă cu {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Показать RustDesk"),
|
||||
("This PC", "Этот компьютер"),
|
||||
("or", "или"),
|
||||
("Continue with", "Продолжить с"),
|
||||
("Elevate", "Повысить"),
|
||||
("Zoom cursor", "Масштабировать курсор"),
|
||||
("Accept sessions via password", "Принимать сеансы по паролю"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Журнал изменений"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Не отключать экран во время исходящих сеансов"),
|
||||
("keep-awake-during-incoming-sessions-label", "Не отключать экран во время входящих сеансов"),
|
||||
("Continue with {}", "Продолжить с {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Mustra RustDesk"),
|
||||
("This PC", "Custu PC"),
|
||||
("or", "O"),
|
||||
("Continue with", "Sighi cun"),
|
||||
("Elevate", "Cresche"),
|
||||
("Zoom cursor", "Cursore de ismanniamentu"),
|
||||
("Accept sessions via password", "Atzeta sessiones cun sa crae"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Sighi cun {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Zobraziť RustDesk"),
|
||||
("This PC", "Tento počítač"),
|
||||
("or", "alebo"),
|
||||
("Continue with", "Pokračovať s"),
|
||||
("Elevate", "Zvýšiť"),
|
||||
("Zoom cursor", "Kurzor priblíženia"),
|
||||
("Accept sessions via password", "Prijímanie relácií pomocou hesla"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Pokračovať s {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Prikaži RustDesk"),
|
||||
("This PC", "Ta računalnik"),
|
||||
("or", "ali"),
|
||||
("Continue with", "Nadaljuj z"),
|
||||
("Elevate", "Povzdig pravic"),
|
||||
("Zoom cursor", "Prilagodi velikost miškinega kazalca"),
|
||||
("Accept sessions via password", "Sprejmi seje z geslom"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Nadaljuj z {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Shfaq RustDesk"),
|
||||
("This PC", "Ky PC"),
|
||||
("or", "ose"),
|
||||
("Continue with", "Vazhdo me"),
|
||||
("Elevate", "Ngritja"),
|
||||
("Zoom cursor", "Zmadho kursorin"),
|
||||
("Accept sessions via password", "Prano sesionin nëpërmjet fjalëkalimit"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Vazhdo me {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Prikazi RustDesk"),
|
||||
("This PC", "Ovaj PC"),
|
||||
("or", "ili"),
|
||||
("Continue with", "Nastavi sa"),
|
||||
("Elevate", "Izdigni"),
|
||||
("Zoom cursor", "Zumiraj kursor"),
|
||||
("Accept sessions via password", "Prihvati sesije preko lozinke"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Nastavi sa {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Visa RustDesk"),
|
||||
("This PC", "Denna dator"),
|
||||
("or", "eller"),
|
||||
("Continue with", "Fortsätt med"),
|
||||
("Elevate", "Höj upp"),
|
||||
("Zoom cursor", "Zoom"),
|
||||
("Accept sessions via password", "Acceptera sessioner via lösenord"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Fortsätt med {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk ஐ காட்டு"),
|
||||
("This PC", "இந்த PC"),
|
||||
("or", "அல்லது"),
|
||||
("Continue with", "உடன் தொடர்"),
|
||||
("Elevate", "உயர்த்து"),
|
||||
("Zoom cursor", "கர்சரை பெரிதாக்கு"),
|
||||
("Accept sessions via password", "கடவுச்சொல் வழியாக அமர்வுகளை ஏற்று"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "{} உடன் தொடர்"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
("Elevate", ""),
|
||||
("Zoom cursor", ""),
|
||||
("Accept sessions via password", ""),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", ""),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "แสดง RustDesk"),
|
||||
("This PC", "พีซีเครื่องนี้"),
|
||||
("or", "หรือ"),
|
||||
("Continue with", "ทำต่อด้วย"),
|
||||
("Elevate", "ยกระดับ"),
|
||||
("Zoom cursor", "ขยายเคอร์เซอร์"),
|
||||
("Accept sessions via password", "ยอมรับการเชื่อมต่อด้วยรหัสผ่าน"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "ทำต่อด้วย {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
133
src/lang/tr.rs
133
src/lang/tr.rs
@@ -3,8 +3,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Durum"),
|
||||
("Your Desktop", "Sizin Masaüstünüz"),
|
||||
("desk_tip", "Masaüstünüze bu ID ve şifre ile erişilebilir"),
|
||||
("Password", "Şifre"),
|
||||
("desk_tip", "Masaüstünüze bu ID ve parola ile erişilebilir"),
|
||||
("Password", "Parola"),
|
||||
("Ready", "Hazır"),
|
||||
("Established", "Bağlantı sağlandı"),
|
||||
("connecting_status", "Bağlanılıyor "),
|
||||
@@ -13,16 +13,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Service is running", "Servis çalışıyor"),
|
||||
("Service is not running", "Servis çalışmıyor"),
|
||||
("not_ready_status", "Hazır değil. Bağlantınızı kontrol edin"),
|
||||
("Control Remote Desktop", "Bağlanılacak Uzak Bağlantı ID"),
|
||||
("Control Remote Desktop", "Uzak Masaüstünü Denetle"),
|
||||
("Transfer file", "Dosya transferi"),
|
||||
("Connect", "Bağlan"),
|
||||
("Recent sessions", "Son Bağlanılanlar"),
|
||||
("Recent sessions", "Son oturumlar"),
|
||||
("Address book", "Adres Defteri"),
|
||||
("Confirmation", "Onayla"),
|
||||
("TCP tunneling", "TCP Tünelleri"),
|
||||
("TCP tunneling", "TCP tünelleri"),
|
||||
("Remove", "Kaldır"),
|
||||
("Refresh random password", "Yeni rastgele şifre oluştur"),
|
||||
("Set your own password", "Kendi şifreni oluştur"),
|
||||
("Refresh random password", "Yeni rastgele parola oluştur"),
|
||||
("Set your own password", "Kendi parolanı oluştur"),
|
||||
("Enable keyboard/mouse", "Klavye ve Fareye izin ver"),
|
||||
("Enable clipboard", "Kopyalanan geçici veriye izin ver"),
|
||||
("Enable file transfer", "Dosya Transferine izin ver"),
|
||||
@@ -47,9 +47,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Bu kaotik dünyada gönülden yapıldı!"),
|
||||
("Privacy Statement", "Gizlilik Beyanı"),
|
||||
("Mute", "Sustur"),
|
||||
("Build Date", "Yapım Tarihi"),
|
||||
("Build Date", "Derleme Tarihi"),
|
||||
("Version", "Sürüm"),
|
||||
("Home", "Anasayfa"),
|
||||
("Home", "Ana Sayfa"),
|
||||
("Audio Input", "Ses Girişi"),
|
||||
("Enhancements", "Geliştirmeler"),
|
||||
("Hardware Codec", "Donanımsal Codec"),
|
||||
@@ -64,18 +64,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Not available", "Erişilebilir değil"),
|
||||
("Too frequent", "Çok sık"),
|
||||
("Cancel", "İptal"),
|
||||
("Skip", "Geç"),
|
||||
("Skip", "Atla"),
|
||||
("Close", "Kapat"),
|
||||
("Retry", "Tekrar Dene"),
|
||||
("OK", "Tamam"),
|
||||
("Password Required", "Şifre Gerekli"),
|
||||
("Please enter your password", "Lütfen şifrenizi giriniz"),
|
||||
("Remember password", "Şifreyi hatırla"),
|
||||
("Wrong Password", "Hatalı şifre"),
|
||||
("Password Required", "Parola Gerekli"),
|
||||
("Please enter your password", "Lütfen parolanızı giriniz"),
|
||||
("Remember password", "Parolayı hatırla"),
|
||||
("Wrong Password", "Hatalı parola"),
|
||||
("Do you want to enter again?", "Tekrar giriş yapmak ister misiniz?"),
|
||||
("Connection Error", "Bağlantı Hatası"),
|
||||
("Error", "Hata"),
|
||||
("Reset by the peer", "Eş tarafında sıfırla"),
|
||||
("Reset by the peer", "Eş tarafından sıfırlandı"),
|
||||
("Connecting...", "Bağlanılıyor..."),
|
||||
("Connection in progress. Please wait.", "Bağlantı sağlanıyor. Lütfen bekleyiniz."),
|
||||
("Please try 1 minute later", "Lütfen 1 dakika sonra tekrar deneyiniz"),
|
||||
@@ -141,10 +141,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Timeout", "Zaman aşımı"),
|
||||
("Failed to connect to relay server", "Relay sunucusuna bağlanılamadı"),
|
||||
("Failed to connect via rendezvous server", "ID oluşturma sunucusuna bağlanılamadı"),
|
||||
("Failed to connect via relay server", "Relay oluşturma sunucusuna bağlanılamadı"),
|
||||
("Failed to connect via relay server", "Aktarma sunucusuna bağlanılamadı"),
|
||||
("Failed to make direct connection to remote desktop", "Uzak masaüstüne doğrudan bağlantı kurulamadı"),
|
||||
("Set Password", "Şifre ayarla"),
|
||||
("OS Password", "İşletim Sistemi Şifresi"),
|
||||
("Set Password", "Parola ayarla"),
|
||||
("OS Password", "İşletim Sistemi Parolası"),
|
||||
("install_tip", "Kullanıcı Hesabı Denetimi nedeniyle, RustDesk bir uzak masaüstü olarak düzgün çalışmayabilir. Bu sorunu önlemek için, RustDesk'i sistem seviyesinde kurmak için aşağıdaki butona tıklayın."),
|
||||
("Click to upgrade", "Yükseltmek için tıklayınız"),
|
||||
("Configure", "Ayarla"),
|
||||
@@ -184,7 +184,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Direct and unencrypted connection", "Doğrudan ve şifrelenmemiş bağlantı"),
|
||||
("Relayed and unencrypted connection", "Aktarmalı ve şifrelenmemiş bağlantı"),
|
||||
("Enter Remote ID", "Uzak ID'yi Girin"),
|
||||
("Enter your password", "Şifrenizi girin"),
|
||||
("Enter your password", "Parolanızı girin"),
|
||||
("Logging in...", "Giriş yapılıyor..."),
|
||||
("Enable RDP session sharing", "RDP oturum paylaşımını etkinleştir"),
|
||||
("Auto Login", "Otomatik giriş"),
|
||||
@@ -208,8 +208,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by the peer", "Eş tarafından manuel olarak kapatıldı"),
|
||||
("Enable remote configuration modification", "Uzaktan yapılandırma değişikliğini etkinleştir"),
|
||||
("Run without install", "Yüklemeden çalıştır"),
|
||||
("Connect via relay", ""),
|
||||
("Always connect via relay", "Always connect via relay"),
|
||||
("Connect via relay", "Aktarmalı üzerinden bağlan"),
|
||||
("Always connect via relay", "Her zaman aktarmalı üzerinden bağlan"),
|
||||
("whitelist_tip", "Bu masaüstüne yalnızca yetkili IP adresleri bağlanabilir"),
|
||||
("Login", "Giriş yap"),
|
||||
("Verify", "Doğrula"),
|
||||
@@ -226,11 +226,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unselect all tags", "Tüm etiketlerin seçimini kaldır"),
|
||||
("Network error", "Bağlantı hatası"),
|
||||
("Username missed", "Kullanıcı adı boş"),
|
||||
("Password missed", "Şifre boş"),
|
||||
("Password missed", "Parola boş"),
|
||||
("Wrong credentials", "Yanlış kimlik bilgileri"),
|
||||
("The verification code is incorrect or has expired", "Doğrulama kodu hatalı veya süresi dolmuş"),
|
||||
("Edit Tag", "Etiketi düzenle"),
|
||||
("Forget Password", "Şifreyi Unut"),
|
||||
("Forget Password", "Parolayı Unut"),
|
||||
("Favorites", "Favoriler"),
|
||||
("Add to Favorites", "Favorilere ekle"),
|
||||
("Remove from Favorites", "Favorilerden çıkar"),
|
||||
@@ -268,9 +268,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Share screen", "Ekranı Paylaş"),
|
||||
("Chat", "Mesajlaş"),
|
||||
("Total", "Toplam"),
|
||||
("items", "öğeler"),
|
||||
("items", "ögeler"),
|
||||
("Selected", "Seçildi"),
|
||||
("Screen Capture", "Ekran görüntüsü"),
|
||||
("Screen Capture", "Ekran Görüntüsü"),
|
||||
("Input Control", "Giriş Kontrolü"),
|
||||
("Audio Capture", "Ses Yakalama"),
|
||||
("Do you accept?", "Kabul ediyor musun?"),
|
||||
@@ -285,7 +285,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti başlat] ögesine dokunun veya [Ekran Görüntüsü] iznini etkinleştirin."),
|
||||
("android_permission_may_not_change_tip", "Kurulan bağlantılara ait izinler, yeniden bağlantı kurulana kadar anında değiştirilemez."),
|
||||
("Account", "Hesap"),
|
||||
("Overwrite", "üzerine yaz"),
|
||||
("Overwrite", "Üzerine yaz"),
|
||||
("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"),
|
||||
("Quit", "Çıkış"),
|
||||
("Help", "Yardım"),
|
||||
@@ -295,8 +295,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unsupported", "desteklenmiyor"),
|
||||
("Peer denied", "eş reddedildi"),
|
||||
("Please install plugins", "Lütfen eklentileri yükleyin"),
|
||||
("Peer exit", "eş çıkışı"),
|
||||
("Failed to turn off", "kapatılamadı"),
|
||||
("Peer exit", "Eş çıkışı"),
|
||||
("Failed to turn off", "Kapatılamadı"),
|
||||
("Turned off", "Kapatıldı"),
|
||||
("Language", "Dil"),
|
||||
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
|
||||
@@ -308,32 +308,32 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Legacy mode", "Eski mod"),
|
||||
("Map mode", "Haritalama modu"),
|
||||
("Translate mode", "Çeviri modu"),
|
||||
("Use permanent password", "Kalıcı şifre kullan"),
|
||||
("Use both passwords", "İki şifreyi de kullan"),
|
||||
("Set permanent password", "Kalıcı şifre oluştur"),
|
||||
("Use permanent password", "Kalıcı parola kullan"),
|
||||
("Use both passwords", "İki parolayı da kullan"),
|
||||
("Set permanent password", "Kalıcı parola oluştur"),
|
||||
("Enable remote restart", "Uzaktan yeniden başlatmayı aktif et"),
|
||||
("Restart remote device", "Uzaktaki cihazı yeniden başlat"),
|
||||
("Are you sure you want to restart", "Yeniden başlatmak istediğinize emin misin?"),
|
||||
("Are you sure you want to restart", "Yeniden başlatmak istediğine emin misin?"),
|
||||
("Restarting remote device", "Uzaktan yeniden başlatılıyor"),
|
||||
("remote_restarting_tip", "Uzak cihaz yeniden başlatılıyor, lütfen bu mesaj kutusunu kapatın ve bir süre sonra kalıcı şifre ile yeniden bağlanın"),
|
||||
("remote_restarting_tip", "Uzak cihaz yeniden başlatılıyor, lütfen bu mesaj kutusunu kapatın ve bir süre sonra kalıcı parola ile yeniden bağlanın"),
|
||||
("Copied", "Kopyalandı"),
|
||||
("Exit Fullscreen", "Tam ekrandan çık"),
|
||||
("Fullscreen", "Tam ekran"),
|
||||
("Exit Fullscreen", "Tam Ekrandan Çık"),
|
||||
("Fullscreen", "Tam Ekran"),
|
||||
("Mobile Actions", "Mobil İşlemler"),
|
||||
("Select Monitor", "Monitörü Seç"),
|
||||
("Control Actions", "Kontrol Eylemleri"),
|
||||
("Display Settings", "Görüntü ayarları"),
|
||||
("Display Settings", "Görüntü Ayarları"),
|
||||
("Ratio", "Oran"),
|
||||
("Image Quality", "Görüntü kalitesi"),
|
||||
("Image Quality", "Görüntü Kalitesi"),
|
||||
("Scroll Style", "Kaydırma Stili"),
|
||||
("Show Toolbar", "Araç Çubuğunu Göster"),
|
||||
("Hide Toolbar", "Araç Çubuğunu Gizle"),
|
||||
("Direct Connection", "Doğrudan Bağlantı"),
|
||||
("Relay Connection", "Röle Bağlantısı"),
|
||||
("Relay Connection", "Aktarmalı Bağlantı"),
|
||||
("Secure Connection", "Güvenli Bağlantı"),
|
||||
("Insecure Connection", "Güvenli Olmayan Bağlantı"),
|
||||
("Scale original", "Orijinali ölçeklendir"),
|
||||
("Scale adaptive", "Ölçek uyarlanabilir"),
|
||||
("Scale original", "Orijinal ölçekte"),
|
||||
("Scale adaptive", "Uyarlanabilir ölçekte"),
|
||||
("General", "Genel"),
|
||||
("Security", "Güvenlik"),
|
||||
("Theme", "Tema"),
|
||||
@@ -347,18 +347,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable audio", "Sesi Aktif Et"),
|
||||
("Unlock Network Settings", "Ağ Ayarlarını Aç"),
|
||||
("Server", "Sunucu"),
|
||||
("Direct IP Access", "Direk IP Erişimi"),
|
||||
("Direct IP Access", "Doğrudan IP Erişimi"),
|
||||
("Proxy", "Vekil"),
|
||||
("Apply", "Uygula"),
|
||||
("Disconnect all devices?", "Tüm cihazların bağlantısını kes?"),
|
||||
("Disconnect all devices?", "Tüm cihazların bağlantısı kesilsin mi?"),
|
||||
("Clear", "Temizle"),
|
||||
("Audio Input Device", "Ses Giriş Aygıtı"),
|
||||
("Use IP Whitelisting", "IP Beyaz Listeyi Kullan"),
|
||||
("Network", "Ağ"),
|
||||
("Pin Toolbar", "Araç Çubuğunu Sabitle"),
|
||||
("Unpin Toolbar", "Araç Çubuğunu Sabitlemeyi Kaldır"),
|
||||
("Recording", "Kayıt Ediliyor"),
|
||||
("Directory", "Klasör"),
|
||||
("Recording", "Kaydediliyor"),
|
||||
("Directory", "Dizin"),
|
||||
("Automatically record incoming sessions", "Gelen oturumları otomatik olarak kaydet"),
|
||||
("Automatically record outgoing sessions", "Giden oturumları otomatik olarak kaydet"),
|
||||
("Change", "Değiştir"),
|
||||
@@ -384,16 +384,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk'i Göster"),
|
||||
("This PC", "Bu PC"),
|
||||
("or", "veya"),
|
||||
("Continue with", "bununla devam et"),
|
||||
("Elevate", "Yükseltme"),
|
||||
("Zoom cursor", "Yakınlaştırma imleci"),
|
||||
("Accept sessions via password", "Oturumları parola ile kabul etme"),
|
||||
("Accept sessions via click", "Tıklama yoluyla oturumları kabul edin"),
|
||||
("Accept sessions via both", "Her ikisi aracılığıyla oturumları kabul edin"),
|
||||
("Please wait for the remote side to accept your session request...", "Lütfen uzak tarafın oturum isteğinizi kabul etmesini bekleyin..."),
|
||||
("One-time Password", "Tek Kullanımlık Şifre"),
|
||||
("One-time Password", "Tek Kullanımlık Parola"),
|
||||
("Use one-time password", "Tek seferlik parola kullanın"),
|
||||
("One-time password length", "Tek seferlik şifre uzunluğu"),
|
||||
("One-time password length", "Tek seferlik parola uzunluğu"),
|
||||
("Request access to your device", "Cihazınıza erişim talep edin"),
|
||||
("Hide connection management window", "Bağlantı yönetimi penceresini gizle"),
|
||||
("hide_cm_tip", "Oturumları yalnızca parola ile kabul edebilir ve kalıcı parola kullanıyorsanız gizlemeye izin verin"),
|
||||
@@ -442,7 +441,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Sesli görüşme"),
|
||||
("Text chat", "Metin sohbeti"),
|
||||
("Stop voice call", "Sesli görüşmeyi durdur"),
|
||||
("relay_hint_tip", "Doğrudan bağlanmak mümkün olmayabilir; röle aracılığıyla bağlanmayı deneyebilirsiniz. Ayrıca, ilk denemenizde bir röle kullanmak istiyorsanız, ID'nin sonuna \"/r\" ekleyebilir veya son oturum kartındaki \"Her Zaman Röle Üzerinden Bağlan\" seçeneğini seçebilirsiniz."),
|
||||
("relay_hint_tip", "Doğrudan bağlanmak mümkün olmayabilir; aktarmalı bağlanmayı deneyebilirsiniz. Ayrıca, ilk denemenizde aktarma sunucusu kullanmak istiyorsanız ID'nin sonuna \"/r\" ekleyebilir veya son oturum kartındaki \"Her Zaman Aktarmalı Üzerinden Bağlan\" seçeneğini seçebilirsiniz."),
|
||||
("Reconnect", "Yeniden Bağlan"),
|
||||
("Codec", "Kodlayıcı"),
|
||||
("Resolution", "Çözünürlük"),
|
||||
@@ -477,7 +476,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no_desktop_title_tip", "Masaüstü mevcut değil"),
|
||||
("no_desktop_text_tip", "Lütfen GNOME masaüstünü yükleyin"),
|
||||
("No need to elevate", "Yükseltmeye gerek yok"),
|
||||
("System Sound", "Sistem Ses"),
|
||||
("System Sound", "Sistem Sesi"),
|
||||
("Default", "Varsayılan"),
|
||||
("New RDP", "Yeni RDP"),
|
||||
("Fingerprint", "Parmak İzi"),
|
||||
@@ -495,7 +494,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("resolution_fit_local_tip", "Yerel çözünürlüğe sığdır"),
|
||||
("resolution_custom_tip", "Özel çözünürlük"),
|
||||
("Collapse toolbar", "Araç çubuğunu daralt"),
|
||||
("Accept and Elevate", "Kabul et ve yükselt"),
|
||||
("Accept and Elevate", "Kabul Et ve Yükselt"),
|
||||
("accept_and_elevate_btn_tooltip", "Bağlantıyı kabul et ve UAC izinlerini yükselt."),
|
||||
("clipboard_wait_response_timeout_tip", "Kopyalama yanıtı için zaman aşımına uğradı."),
|
||||
("Incoming connection", "Gelen bağlantı"),
|
||||
@@ -534,7 +533,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("scam_text1", "Eğer tanımadığınız ve güvenmediğiniz birisiyle telefonda konuşuyorsanız ve sizden RustDesk'i kullanmanızı ve hizmeti başlatmanızı istiyorsa devam etmeyin ve hemen telefonu kapatın."),
|
||||
("scam_text2", "Muhtemelen paranızı veya diğer özel bilgilerinizi çalmaya çalışan dolandırıcılardır."),
|
||||
("Don't show again", "Bir daha gösterme"),
|
||||
("I Agree", "Kabul ediyorum"),
|
||||
("I Agree", "Kabul Ediyorum"),
|
||||
("Decline", "Reddet"),
|
||||
("Timeout in minutes", "Zaman aşımı (dakika)"),
|
||||
("auto_disconnect_option_tip", "Kullanıcı etkin olmadığında gelen oturumları otomatik olarak kapat"),
|
||||
@@ -559,7 +558,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Plug out all", "Tümünü çıkar"),
|
||||
("True color (4:4:4)", "Gerçek renk (4:4:4)"),
|
||||
("Enable blocking user input", "Kullanıcı girişini engellemeyi etkinleştir"),
|
||||
("id_input_tip", "Bir ID, doğrudan IP veya portlu bir etki alanı (<domain>:<port>) girebilirsiniz.\nBaşka bir sunucudaki bir cihaza erişmek istiyorsanız lütfen sunucu adresini (<id>@<server_address>?key=<key_value>) ekleyin, örneğin,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nGenel bir sunucudaki bir cihaza erişmek istiyorsanız lütfen \"<id>@public\" girin, genel sunucu için anahtara gerek yoktur.\n\nİlk bağlantıda bir röle bağlantısının kullanılmasını zorlamak istiyorsanız ID'nin sonuna \"/r\" ekleyin, örneğin, \"9123456234/r\"."),
|
||||
("id_input_tip", "Bir ID, doğrudan IP veya portlu bir etki alanı (<domain>:<port>) girebilirsiniz.\nBaşka bir sunucudaki bir cihaza erişmek istiyorsanız lütfen sunucu adresini (<id>@<server_address>?key=<key_value>) ekleyin, örneğin,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nGenel bir sunucudaki bir cihaza erişmek istiyorsanız lütfen \"<id>@public\" girin, genel sunucu için anahtara gerek yoktur.\n\nİlk bağlantıda bir aktarma bağlantısının kullanılmasını zorlamak istiyorsanız ID'nin sonuna \"/r\" ekleyin, örneğin, \"9123456234/r\"."),
|
||||
("privacy_mode_impl_mag_tip", "Mod 1"),
|
||||
("privacy_mode_impl_virtual_display_tip", "Mod 2"),
|
||||
("Enter privacy mode", "Gizlilik moduna gir"),
|
||||
@@ -581,12 +580,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please select the session you want to connect to", "Lütfen bağlanmak istediğiniz oturumu seçin"),
|
||||
("powered_by_me", "RustDesk tarafından desteklenmektedir"),
|
||||
("outgoing_only_desk_tip", "Bu özelleştirilmiş bir sürümdür.\nDiğer cihazlara bağlanabilirsiniz, ancak diğer cihazlar cihazınıza bağlanamaz."),
|
||||
("preset_password_warning", "Bu özelleştirilmiş sürüm, önceden ayarlanmış bir şifre ile birlikte gelir. Bu parolayı bilen herkes cihazınızın tam kontrolünü ele geçirebilir. Bunu beklemiyorsanız yazılımı hemen kaldırın."),
|
||||
("preset_password_warning", "Bu özelleştirilmiş sürüm, önceden ayarlanmış bir parola ile birlikte gelir. Bu parolayı bilen herkes cihazınızın tam kontrolünü ele geçirebilir. Bunu beklemiyorsanız yazılımı hemen kaldırın."),
|
||||
("Security Alert", "Güvenlik Uyarısı"),
|
||||
("My address book", "Adres defterim"),
|
||||
("Personal", "Kişisel"),
|
||||
("Owner", "Sahip"),
|
||||
("Set shared password", "Paylaşılan şifreyi ayarla"),
|
||||
("Set shared password", "Paylaşılan parolayı ayarla"),
|
||||
("Exist in", "İçinde varolan"),
|
||||
("Read-only", "Salt okunur"),
|
||||
("Read/Write", "Okuma/Yazma"),
|
||||
@@ -599,7 +598,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Follow remote cursor", "Uzak imleci takip et"),
|
||||
("Follow remote window focus", "Uzak pencere odağını takip et"),
|
||||
("default_proxy_tip", "Varsayılan protokol ve port Socks5 ve 1080'dir."),
|
||||
("no_audio_input_device_tip", "Varsayılan protokol ve port, Socks5 ve 1080'dir"),
|
||||
("no_audio_input_device_tip", "Ses girişi aygıtı bulunamadı."),
|
||||
("Incoming", "Gelen"),
|
||||
("Outgoing", "Giden"),
|
||||
("Clear Wayland screen selection", "Wayland ekran seçimini temizle"),
|
||||
@@ -612,7 +611,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("floating_window_tip", "RustDesk arka plan hizmetini açık tutmaya yardımcı olur"),
|
||||
("Keep screen on", "Ekranı açık tut"),
|
||||
("Never", "Asla"),
|
||||
("During controlled", "Kontrol sırasınd"),
|
||||
("During controlled", "Kontrol sırasında"),
|
||||
("During service is on", "Servis açıkken"),
|
||||
("Capture screen using DirectX", "DirectX kullanarak ekran görüntüsü al"),
|
||||
("Back", "Geri"),
|
||||
@@ -620,7 +619,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Volume up", "Sesi yükselt"),
|
||||
("Volume down", "Sesi azalt"),
|
||||
("Power", "Güç"),
|
||||
("Telegram bot", "Telegram bot"),
|
||||
("Telegram bot", "Telegram botu"),
|
||||
("enable-bot-tip", "Bu özelliği etkinleştirirseniz botunuzdan 2FA kodunu alabilirsiniz. Aynı zamanda bağlantı bildirimi işlevi de görebilir."),
|
||||
("enable-bot-desc", "1. @BotFather ile bir sohbet açın.\n2. \"/newbot\" komutunu gönderin. Bu adımı tamamladıktan sonra bir jeton alacaksınız.\n3. Yeni oluşturduğunuz botla bir sohbet başlatın. Etkinleştirmek için eğik çizgiyle (\"/\") başlayan \"/merhaba\" gibi bir mesaj gönderin.\n"),
|
||||
("cancel-2fa-confirm-tip", "2FA'yı iptal etmek istediğinizden emin misiniz?"),
|
||||
@@ -642,7 +641,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Invalid file name", "Geçersiz dosya adı"),
|
||||
("one-way-file-transfer-tip", "Kontrol edilen tarafta tek yönlü dosya transferi aktiftir."),
|
||||
("Authentication Required", "Kimlik Doğrulama Gerekli"),
|
||||
("Authenticate", "Kimlik doğrulaması"),
|
||||
("Authenticate", "Kimlik Doğrula"),
|
||||
("web_id_input_tip", "Aynı sunucuda bir kimlik girebilirsiniz, web istemcisinde doğrudan IP erişimi desteklenmez.\nBaşka bir sunucudaki bir cihaza erişmek istiyorsanız lütfen sunucu adresini (<id>@<server_address>?key=<key_value>) ekleyin, örneğin,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nGenel bir sunucudaki bir cihaza erişmek istiyorsanız, lütfen \"<id>@public\" girin, genel sunucu için anahtara gerek yoktur."),
|
||||
("Download", "İndir"),
|
||||
("Upload folder", "Klasör yükle"),
|
||||
@@ -661,9 +660,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("printer-{}-not-installed-tip", "{} Yazıcısı yüklü değil."),
|
||||
("printer-{}-ready-tip", "{} Yazıcısı kuruldu ve kullanıma hazır."),
|
||||
("Install {} Printer", "{} Yazıcısını Yükle"),
|
||||
("Outgoing Print Jobs", "Giden Baskı İşleri"),
|
||||
("Incoming Print Jobs", "Gelen Baskı İşleri"),
|
||||
("Incoming Print Job", "Gelen Baskı İşi"),
|
||||
("Outgoing Print Jobs", "Giden Yazdırma İşleri"),
|
||||
("Incoming Print Jobs", "Gelen Yazdırma İşleri"),
|
||||
("Incoming Print Job", "Gelen Yazdırma İşi"),
|
||||
("use-the-default-printer-tip", "Varsayılan yazıcıyı kullan"),
|
||||
("use-the-selected-printer-tip", "Seçili yazıcıyı kullan"),
|
||||
("auto-print-tip", "Seçili yazıcıyı kullanarak otomatik olarak yazdır."),
|
||||
@@ -685,11 +684,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("download-new-version-failed-tip", "İndirme başarısız oldu. Tekrar deneyebilir veya 'İndir' düğmesine tıklayarak sürüm sayfasından manuel olarak indirip güncelleyebilirsiniz."),
|
||||
("Auto update", "Otomatik güncelleme"),
|
||||
("update-failed-check-msi-tip", "Kurulum yöntemi denetimi başarısız oldu. Sürüm sayfasından indirmek ve manuel olarak yükseltmek için lütfen \"İndir\" düğmesine tıklayın."),
|
||||
("websocket_tip", "WebSocket kullanıldığında yalnızca röle bağlantıları desteklenir."),
|
||||
("websocket_tip", "WebSocket kullanıldığında yalnızca aktarma bağlantıları desteklenir."),
|
||||
("Use WebSocket", "WebSocket'ı kullan"),
|
||||
("Trackpad speed", "İzleme paneli hızı"),
|
||||
("Default trackpad speed", "Varsayılan izleme paneli hızı"),
|
||||
("Numeric one-time password", "Sayısal tek seferlik şifre"),
|
||||
("Numeric one-time password", "Sayısal tek seferlik parola"),
|
||||
("Enable IPv6 P2P connection", "IPv6 P2P bağlantısını etkinleştir"),
|
||||
("Enable UDP hole punching", "UDP delik açmayı etkinleştir"),
|
||||
("View camera", "Kamerayı görüntüle"),
|
||||
@@ -701,16 +700,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("New tab", "Yeni sekme"),
|
||||
("Keep terminal sessions on disconnect", "Bağlantı kesildiğinde terminal oturumlarını açık tut"),
|
||||
("Terminal (Run as administrator)", "Terminal (Yönetici olarak çalıştır)"),
|
||||
("terminal-admin-login-tip", "Lütfen kontrol edilen tarafın yönetici kullanıcı adı ve şifresini giriniz."),
|
||||
("terminal-admin-login-tip", "Lütfen kontrol edilen tarafın yönetici kullanıcı adı ve parolasını giriniz."),
|
||||
("Failed to get user token.", "Kullanıcı belirteci alınamadı."),
|
||||
("Incorrect username or password.", "Hatalı kullanıcı adı veya şifre."),
|
||||
("Incorrect username or password.", "Hatalı kullanıcı adı veya parola."),
|
||||
("The user is not an administrator.", "Kullanıcı bir yönetici değil."),
|
||||
("Failed to check if the user is an administrator.", "Kullanıcının yönetici olup olmadığı kontrol edilemedi."),
|
||||
("Supported only in the installed version.", "Sadece yüklü sürümde desteklenir."),
|
||||
("elevation_username_tip", "Kullanıcı adı veya etki alanı\\kullanıcı adı girin"),
|
||||
("Preparing for installation ...", "Kuruluma hazırlanıyor..."),
|
||||
("Show my cursor", "İmlecimi göster"),
|
||||
("Scale custom", "Özel boyutlandır"),
|
||||
("Scale custom", "Özel ölçekte"),
|
||||
("Custom scale slider", "Özel ölçek kaydırıcısı"),
|
||||
("Decrease", "Azalt"),
|
||||
("Increase", "Arttır"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Değişiklik Günlüğü"),
|
||||
("keep-awake-during-outgoing-sessions-label", "Giden oturumlar süresince ekranı açık tutun"),
|
||||
("keep-awake-during-incoming-sessions-label", "Gelen oturumlar süresince ekranı açık tutun"),
|
||||
("Continue with {}", "{} ile devam et"),
|
||||
("Display Name", "Görünen Ad"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "顯示 RustDesk"),
|
||||
("This PC", "此電腦"),
|
||||
("or", "或"),
|
||||
("Continue with", "繼續"),
|
||||
("Elevate", "提升權限"),
|
||||
("Zoom cursor", "縮放游標"),
|
||||
("Accept sessions via password", "只允許透過輸入密碼進行連線"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "更新日誌"),
|
||||
("keep-awake-during-outgoing-sessions-label", "在連出工作階段期間保持螢幕喚醒"),
|
||||
("keep-awake-during-incoming-sessions-label", "在連入工作階段期間保持螢幕喚醒"),
|
||||
("Continue with {}", "使用 {} 登入"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Показати RustDesk"),
|
||||
("This PC", "Цей ПК"),
|
||||
("or", "чи"),
|
||||
("Continue with", "Продовжити з"),
|
||||
("Elevate", "Розширення прав"),
|
||||
("Zoom cursor", "Збільшити вказівник"),
|
||||
("Accept sessions via password", "Підтверджувати сеанси паролем"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Продовжити з {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -384,7 +384,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "Hiện RustDesk"),
|
||||
("This PC", "Máy tính này"),
|
||||
("or", "hoặc"),
|
||||
("Continue with", "Tiếp tục với"),
|
||||
("Elevate", "Nâng quyền"),
|
||||
("Zoom cursor", "Phóng to con trỏ"),
|
||||
("Accept sessions via password", "Chấp nhận phiên qua mật khẩu"),
|
||||
@@ -739,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Changelog", "Nhật ký thay đổi"),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Continue with {}", "Tiếp tục với {}"),
|
||||
("Display Name", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -2088,3 +2088,122 @@ pub fn is_selinux_enforcing() -> bool {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the app ID for shortcuts inhibitor permission.
|
||||
/// Returns different ID based on whether running in Flatpak or native.
|
||||
/// The ID must match the installed .desktop filename, as GNOME Shell's
|
||||
/// inhibitShortcutsDialog uses `Shell.WindowTracker.get_window_app(window).get_id()`.
|
||||
fn get_shortcuts_inhibitor_app_id() -> String {
|
||||
if is_flatpak() {
|
||||
// In Flatpak, FLATPAK_ID is set automatically by the runtime to the app ID
|
||||
// (e.g., "com.rustdesk.RustDesk"). This is the most reliable source.
|
||||
// Fall back to constructing from app name if not available.
|
||||
match std::env::var("FLATPAK_ID") {
|
||||
Ok(id) if !id.is_empty() => format!("{}.desktop", id),
|
||||
_ => {
|
||||
let app_name = crate::get_app_name();
|
||||
format!("com.{}.{}.desktop", app_name.to_lowercase(), app_name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!("{}.desktop", crate::get_app_name().to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
const PERMISSION_STORE_DEST: &str = "org.freedesktop.impl.portal.PermissionStore";
|
||||
const PERMISSION_STORE_PATH: &str = "/org/freedesktop/impl/portal/PermissionStore";
|
||||
const PERMISSION_STORE_IFACE: &str = "org.freedesktop.impl.portal.PermissionStore";
|
||||
|
||||
/// Clear GNOME shortcuts inhibitor permission via D-Bus.
|
||||
/// This allows the permission dialog to be shown again.
|
||||
pub fn clear_gnome_shortcuts_inhibitor_permission() -> ResultType<()> {
|
||||
let app_id = get_shortcuts_inhibitor_app_id();
|
||||
log::info!(
|
||||
"Clearing shortcuts inhibitor permission for app_id: {}, is_flatpak: {}",
|
||||
app_id,
|
||||
is_flatpak()
|
||||
);
|
||||
|
||||
let conn = dbus::blocking::Connection::new_session()?;
|
||||
let proxy = conn.with_proxy(
|
||||
PERMISSION_STORE_DEST,
|
||||
PERMISSION_STORE_PATH,
|
||||
std::time::Duration::from_secs(3),
|
||||
);
|
||||
|
||||
// DeletePermission(s table, s id, s app) -> ()
|
||||
let result: Result<(), dbus::Error> = proxy.method_call(
|
||||
PERMISSION_STORE_IFACE,
|
||||
"DeletePermission",
|
||||
("gnome", "shortcuts-inhibitor", app_id.as_str()),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(()) => {
|
||||
log::info!("Successfully cleared GNOME shortcuts inhibitor permission");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
let err_name = e.name().unwrap_or("");
|
||||
// If the permission doesn't exist, that's also fine
|
||||
if err_name == "org.freedesktop.portal.Error.NotFound"
|
||||
|| err_name == "org.freedesktop.DBus.Error.UnknownObject"
|
||||
|| err_name == "org.freedesktop.DBus.Error.ServiceUnknown"
|
||||
{
|
||||
log::info!("GNOME shortcuts inhibitor permission was not set ({})", err_name);
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Failed to clear permission: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if GNOME shortcuts inhibitor permission exists.
|
||||
pub fn has_gnome_shortcuts_inhibitor_permission() -> bool {
|
||||
let app_id = get_shortcuts_inhibitor_app_id();
|
||||
|
||||
let conn = match dbus::blocking::Connection::new_session() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::debug!("Failed to connect to session bus: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let proxy = conn.with_proxy(
|
||||
PERMISSION_STORE_DEST,
|
||||
PERMISSION_STORE_PATH,
|
||||
std::time::Duration::from_secs(3),
|
||||
);
|
||||
|
||||
// Lookup(s table, s id) -> (a{sas} permissions, v data)
|
||||
// We only need the permissions dict; check if app_id is a key.
|
||||
let result: Result<
|
||||
(
|
||||
std::collections::HashMap<String, Vec<String>>,
|
||||
dbus::arg::Variant<Box<dyn dbus::arg::RefArg>>,
|
||||
),
|
||||
dbus::Error,
|
||||
> = proxy.method_call(
|
||||
PERMISSION_STORE_IFACE,
|
||||
"Lookup",
|
||||
("gnome", "shortcuts-inhibitor"),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok((permissions, _)) => {
|
||||
let found = permissions.contains_key(&app_id);
|
||||
log::debug!(
|
||||
"Shortcuts inhibitor permission lookup: app_id={}, found={}, keys={:?}",
|
||||
app_id,
|
||||
found,
|
||||
permissions.keys().collect::<Vec<_>>()
|
||||
);
|
||||
found
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("Failed to query shortcuts inhibitor permission: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +107,9 @@ pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
let center_x = rect.left + (rect.right - rect.left) / 2;
|
||||
let center_y = rect.top + (rect.bottom - rect.top) / 2;
|
||||
center_x >= display.x
|
||||
&& center_x <= display.x + display.width
|
||||
&& center_x < display.x + display.width
|
||||
&& center_y >= display.y
|
||||
&& center_y <= display.y + display.height
|
||||
&& center_y < display.y + display.height
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
|
||||
static MANUAL_RESTARTED: AtomicBool = AtomicBool::new(false);
|
||||
static SENT_REGISTER_PK: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RendezvousMediator {
|
||||
@@ -689,6 +690,7 @@ impl RendezvousMediator {
|
||||
..Default::default()
|
||||
});
|
||||
socket.send(&msg_out).await?;
|
||||
SENT_REGISTER_PK.store(true, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -904,3 +906,28 @@ async fn udp_nat_listen(
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// When config is not yet synced from root, register_pk may have already been sent with a new generated pk.
|
||||
// After config sync completes, the pk may change. This struct detects pk changes and triggers
|
||||
// a re-registration by setting key_confirmed to false.
|
||||
// NOTE:
|
||||
// This only corrects PK registration for the current ID. If root uses a non-default mac-generated ID,
|
||||
// this does not resolve the multi-ID issue by itself.
|
||||
pub struct CheckIfResendPk {
|
||||
pk: Option<Vec<u8>>,
|
||||
}
|
||||
impl CheckIfResendPk {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pk: Config::get_cached_pk(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for CheckIfResendPk {
|
||||
fn drop(&mut self) {
|
||||
if SENT_REGISTER_PK.load(Ordering::SeqCst) && Config::get_cached_pk() != self.pk {
|
||||
Config::set_key_confirmed(false);
|
||||
log::info!("Set key_confirmed to false due to pk changed, will resend register_pk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ type ConnMap = HashMap<i32, ConnInner>;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
const CONFIG_SYNC_INTERVAL_SECS: f32 = 0.3;
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
// 3s is enough for at least one initial sync attempt:
|
||||
// 0.3s backoff + up to 1s connect timeout + up to 1s response timeout.
|
||||
const CONFIG_SYNC_INITIAL_WAIT_SECS: u64 = 3;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CHILD_PROCESS: Childs = Default::default();
|
||||
@@ -600,7 +604,7 @@ pub async fn start_server(is_server: bool, no_server: bool) {
|
||||
allow_err!(input_service::setup_uinput(0, 1920, 0, 1080).await);
|
||||
}
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
tokio::spawn(async { sync_and_watch_config_dir().await });
|
||||
wait_initial_config_sync().await;
|
||||
#[cfg(target_os = "windows")]
|
||||
crate::platform::try_kill_broker();
|
||||
#[cfg(feature = "hwcodec")]
|
||||
@@ -685,13 +689,43 @@ pub async fn start_ipc_url_server() {
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
async fn sync_and_watch_config_dir() {
|
||||
async fn wait_initial_config_sync() {
|
||||
if crate::platform::is_root() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-server process should not block startup, but still keeps background sync/watch alive.
|
||||
if !crate::is_server() {
|
||||
tokio::spawn(async move {
|
||||
sync_and_watch_config_dir(None).await;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let (sync_done_tx, mut sync_done_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
tokio::spawn(async move {
|
||||
sync_and_watch_config_dir(Some(sync_done_tx)).await;
|
||||
});
|
||||
|
||||
// Server process waits up to N seconds for initial root->local sync to reduce stale-start window.
|
||||
tokio::select! {
|
||||
_ = &mut sync_done_rx => {
|
||||
}
|
||||
_ = tokio::time::sleep(Duration::from_secs(CONFIG_SYNC_INITIAL_WAIT_SECS)) => {
|
||||
log::warn!(
|
||||
"timed out waiting {}s for initial config sync, continue startup and keep syncing in background",
|
||||
CONFIG_SYNC_INITIAL_WAIT_SECS
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
async fn sync_and_watch_config_dir(sync_done_tx: Option<tokio::sync::oneshot::Sender<()>>) {
|
||||
let mut cfg0 = (Config::get(), Config2::get());
|
||||
let mut synced = false;
|
||||
let mut is_root_config_empty = false;
|
||||
let mut sync_done_tx = sync_done_tx;
|
||||
let tries = if crate::is_server() { 30 } else { 3 };
|
||||
log::debug!("#tries of ipc service connection: {}", tries);
|
||||
use hbb_common::sleep;
|
||||
@@ -706,6 +740,8 @@ async fn sync_and_watch_config_dir() {
|
||||
Data::SyncConfig(Some(configs)) => {
|
||||
let (config, config2) = *configs;
|
||||
let _chk = crate::ipc::CheckIfRestart::new();
|
||||
#[cfg(target_os = "macos")]
|
||||
let _chk_pk = crate::CheckIfResendPk::new();
|
||||
if !config.is_empty() {
|
||||
if cfg0.0 != config {
|
||||
cfg0.0 = config.clone();
|
||||
@@ -717,8 +753,20 @@ async fn sync_and_watch_config_dir() {
|
||||
Config2::set(config2);
|
||||
log::info!("sync config2 from root");
|
||||
}
|
||||
} else {
|
||||
// only on macos, because this issue was only reproduced on macos
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// root config is empty, mark for sync in watch loop
|
||||
// to prevent root from generating a new config on login screen
|
||||
is_root_config_empty = true;
|
||||
}
|
||||
}
|
||||
synced = true;
|
||||
// Notify startup waiter once initial sync phase finishes successfully.
|
||||
if let Some(tx) = sync_done_tx.take() {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -729,8 +777,14 @@ async fn sync_and_watch_config_dir() {
|
||||
loop {
|
||||
sleep(CONFIG_SYNC_INTERVAL_SECS).await;
|
||||
let cfg = (Config::get(), Config2::get());
|
||||
if cfg != cfg0 {
|
||||
log::info!("config updated, sync to root");
|
||||
let should_sync =
|
||||
cfg != cfg0 || (is_root_config_empty && !cfg.0.is_empty());
|
||||
if should_sync {
|
||||
if is_root_config_empty {
|
||||
log::info!("root config is empty, sync our config to root");
|
||||
} else {
|
||||
log::info!("config updated, sync to root");
|
||||
}
|
||||
match conn.send(&Data::SyncConfig(Some(cfg.clone().into()))).await {
|
||||
Err(e) => {
|
||||
log::error!("sync config to root failed: {}", e);
|
||||
@@ -745,6 +799,7 @@ async fn sync_and_watch_config_dir() {
|
||||
_ => {
|
||||
cfg0 = cfg;
|
||||
conn.next_timeout(1000).await.ok();
|
||||
is_root_config_empty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,6 +810,10 @@ async fn sync_and_watch_config_dir() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Notify startup waiter even when initial sync is skipped/failed, to avoid unnecessary waiting.
|
||||
if let Some(tx) = sync_done_tx.take() {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
log::warn!("skipped config sync");
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,10 @@ struct Input {
|
||||
|
||||
const KEY_CHAR_START: u64 = 9999;
|
||||
|
||||
// XKB keycode for Insert key (evdev KEY_INSERT code 110 + 8 for XKB offset)
|
||||
#[cfg(target_os = "linux")]
|
||||
const XKB_KEY_INSERT: u16 = evdev::Key::KEY_INSERT.code() + 8;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MouseCursorSub {
|
||||
inner: ConnInner,
|
||||
@@ -1105,8 +1109,12 @@ pub fn handle_mouse_simulation_(evt: &MouseEvent, conn: i32) {
|
||||
// Clamp delta to prevent extreme/malicious values from reaching OS APIs.
|
||||
// This matches the Flutter client's kMaxRelativeMouseDelta constant.
|
||||
const MAX_RELATIVE_MOUSE_DELTA: i32 = 10000;
|
||||
let dx = evt.x.clamp(-MAX_RELATIVE_MOUSE_DELTA, MAX_RELATIVE_MOUSE_DELTA);
|
||||
let dy = evt.y.clamp(-MAX_RELATIVE_MOUSE_DELTA, MAX_RELATIVE_MOUSE_DELTA);
|
||||
let dx = evt
|
||||
.x
|
||||
.clamp(-MAX_RELATIVE_MOUSE_DELTA, MAX_RELATIVE_MOUSE_DELTA);
|
||||
let dy = evt
|
||||
.y
|
||||
.clamp(-MAX_RELATIVE_MOUSE_DELTA, MAX_RELATIVE_MOUSE_DELTA);
|
||||
en.mouse_move_relative(dx, dy);
|
||||
// Get actual cursor position after relative movement for tracking
|
||||
if let Some((x, y)) = crate::get_cursor_pos() {
|
||||
@@ -1465,20 +1473,26 @@ fn map_keyboard_mode(evt: &KeyEvent) {
|
||||
// Wayland
|
||||
#[cfg(target_os = "linux")]
|
||||
if !crate::platform::linux::is_x11() {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
let code = evt.chr() as u16;
|
||||
|
||||
if evt.down {
|
||||
en.key_down(enigo::Key::Raw(code)).ok();
|
||||
} else {
|
||||
en.key_up(enigo::Key::Raw(code));
|
||||
}
|
||||
wayland_send_raw_key(evt.chr() as u16, evt.down);
|
||||
return;
|
||||
}
|
||||
|
||||
sim_rdev_rawkey_position(evt.chr() as _, evt.down);
|
||||
}
|
||||
|
||||
/// Send raw keycode on Wayland via the active backend (uinput or RemoteDesktop portal).
|
||||
/// The keycode is expected to be a Linux keycode (evdev code + 8 for X11 compatibility).
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
fn wayland_send_raw_key(code: u16, down: bool) {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
if down {
|
||||
en.key_down(enigo::Key::Raw(code)).ok();
|
||||
} else {
|
||||
en.key_up(enigo::Key::Raw(code));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn add_flags_to_enigo(en: &mut Enigo, key_event: &KeyEvent) {
|
||||
// When long-pressed the command key, then press and release
|
||||
@@ -1559,6 +1573,20 @@ fn need_to_uppercase(en: &mut Enigo) -> bool {
|
||||
}
|
||||
|
||||
fn process_chr(en: &mut Enigo, chr: u32, down: bool) {
|
||||
// On Wayland with uinput mode, use clipboard for character input
|
||||
#[cfg(target_os = "linux")]
|
||||
if !crate::platform::linux::is_x11() && wayland_use_uinput() {
|
||||
// Skip clipboard for hotkeys (Ctrl/Alt/Meta pressed)
|
||||
if !is_hotkey_modifier_pressed(en) {
|
||||
if down {
|
||||
if let Ok(c) = char::try_from(chr) {
|
||||
input_char_via_clipboard_server(en, c);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let key = char_value_to_key(chr);
|
||||
|
||||
if down {
|
||||
@@ -1578,15 +1606,136 @@ fn process_chr(en: &mut Enigo, chr: u32, down: bool) {
|
||||
}
|
||||
|
||||
fn process_unicode(en: &mut Enigo, chr: u32) {
|
||||
// On Wayland with uinput mode, use clipboard for character input
|
||||
#[cfg(target_os = "linux")]
|
||||
if !crate::platform::linux::is_x11() && wayland_use_uinput() {
|
||||
if let Ok(c) = char::try_from(chr) {
|
||||
input_char_via_clipboard_server(en, c);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(chr) = char::try_from(chr) {
|
||||
en.key_sequence(&chr.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
fn process_seq(en: &mut Enigo, sequence: &str) {
|
||||
// On Wayland with uinput mode, use clipboard for text input
|
||||
#[cfg(target_os = "linux")]
|
||||
if !crate::platform::linux::is_x11() && wayland_use_uinput() {
|
||||
input_text_via_clipboard_server(en, sequence);
|
||||
return;
|
||||
}
|
||||
|
||||
en.key_sequence(&sequence);
|
||||
}
|
||||
|
||||
/// Delay in milliseconds to wait for clipboard to sync on Wayland.
|
||||
/// This is an empirical value — Wayland provides no callback or event to confirm
|
||||
/// clipboard content has been received by the compositor. Under heavy system load,
|
||||
/// this delay may be insufficient, but there is no reliable alternative mechanism.
|
||||
#[cfg(target_os = "linux")]
|
||||
const CLIPBOARD_SYNC_DELAY_MS: u64 = 50;
|
||||
|
||||
/// Internal: Set clipboard content without delay.
|
||||
/// Returns true if clipboard was set successfully.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn set_clipboard_content(text: &str) -> bool {
|
||||
use arboard::{Clipboard, LinuxClipboardKind, SetExtLinux};
|
||||
|
||||
let mut clipboard = match Clipboard::new() {
|
||||
Ok(cb) => cb,
|
||||
Err(e) => {
|
||||
log::error!("set_clipboard_content: failed to create clipboard: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Set both CLIPBOARD and PRIMARY selections
|
||||
// Terminal uses PRIMARY for Shift+Insert, GUI apps use CLIPBOARD
|
||||
if let Err(e) = clipboard
|
||||
.set()
|
||||
.clipboard(LinuxClipboardKind::Clipboard)
|
||||
.text(text.to_owned())
|
||||
{
|
||||
log::error!("set_clipboard_content: failed to set CLIPBOARD: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = clipboard
|
||||
.set()
|
||||
.clipboard(LinuxClipboardKind::Primary)
|
||||
.text(text.to_owned())
|
||||
{
|
||||
log::warn!("set_clipboard_content: failed to set PRIMARY: {:?}", e);
|
||||
// Continue anyway, CLIPBOARD might work
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Set clipboard content for paste operation (sync version for use in blocking contexts).
|
||||
///
|
||||
/// Note: The original clipboard content is intentionally NOT restored after paste.
|
||||
/// Restoring clipboard could cause race conditions where subsequent keystrokes
|
||||
/// might accidentally paste the old clipboard content instead of the intended input.
|
||||
/// This trade-off prioritizes input reliability over preserving clipboard state.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
pub(super) fn set_clipboard_for_paste_sync(text: &str) -> bool {
|
||||
if !set_clipboard_content(text) {
|
||||
return false;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(CLIPBOARD_SYNC_DELAY_MS));
|
||||
true
|
||||
}
|
||||
|
||||
/// Check if a character is ASCII printable (0x20-0x7E).
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
pub(super) fn is_ascii_printable(c: char) -> bool {
|
||||
c as u32 >= 0x20 && c as u32 <= 0x7E
|
||||
}
|
||||
|
||||
/// Input a single character via clipboard + Shift+Insert in server process.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
fn input_char_via_clipboard_server(en: &mut Enigo, chr: char) {
|
||||
input_text_via_clipboard_server(en, &chr.to_string());
|
||||
}
|
||||
|
||||
/// Input text via clipboard + Shift+Insert in server process.
|
||||
/// Shift+Insert is more universal than Ctrl+V, works in both GUI apps and terminals.
|
||||
///
|
||||
/// Note: Clipboard content is NOT restored after paste - see `set_clipboard_for_paste_sync` for rationale.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn input_text_via_clipboard_server(en: &mut Enigo, text: &str) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
if !set_clipboard_for_paste_sync(text) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use ENIGO's custom_keyboard directly to avoid creating new IPC connections
|
||||
// which would cause excessive logging and keyboard device creation/destruction
|
||||
if en.key_down(Key::Shift).is_err() {
|
||||
log::error!("input_text_via_clipboard_server: failed to press Shift, skipping paste");
|
||||
return;
|
||||
}
|
||||
if en.key_down(Key::Raw(XKB_KEY_INSERT)).is_err() {
|
||||
log::error!("input_text_via_clipboard_server: failed to press Insert, releasing Shift");
|
||||
en.key_up(Key::Shift);
|
||||
return;
|
||||
}
|
||||
en.key_up(Key::Raw(XKB_KEY_INSERT));
|
||||
en.key_up(Key::Shift);
|
||||
|
||||
// Brief delay to allow the target application to process the paste event.
|
||||
// Empirical value — no reliable synchronization mechanism exists on Wayland.
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn release_keys(en: &mut Enigo, to_release: &Vec<Key>) {
|
||||
for key in to_release {
|
||||
@@ -1621,6 +1770,64 @@ fn is_function_key(ck: &EnumOrUnknown<ControlKey>) -> bool {
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Check if any hotkey modifier (Ctrl/Alt/Meta) is currently pressed.
|
||||
/// Used to detect hotkey combinations like Ctrl+C, Alt+Tab, etc.
|
||||
///
|
||||
/// Note: Shift is intentionally NOT checked here. Shift+character produces a different
|
||||
/// character (e.g., Shift+a → 'A'), which is normal text input, not a hotkey.
|
||||
/// Shift is only relevant as a hotkey modifier when combined with Ctrl/Alt/Meta
|
||||
/// (e.g., Ctrl+Shift+Z), in which case this function already returns true via Ctrl.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
fn is_hotkey_modifier_pressed(en: &mut Enigo) -> bool {
|
||||
get_modifier_state(Key::Control, en)
|
||||
|| get_modifier_state(Key::RightControl, en)
|
||||
|| get_modifier_state(Key::Alt, en)
|
||||
|| get_modifier_state(Key::RightAlt, en)
|
||||
|| get_modifier_state(Key::Meta, en)
|
||||
|| get_modifier_state(Key::RWin, en)
|
||||
}
|
||||
|
||||
/// Release Shift keys before character input in Legacy/Translate mode.
|
||||
/// In these modes, the character has already been converted by the client,
|
||||
/// so we should input it directly without Shift modifier affecting the result.
|
||||
///
|
||||
/// Note: Does NOT release Shift if hotkey modifiers (Ctrl/Alt/Meta) are pressed,
|
||||
/// to preserve combinations like Ctrl+Shift+Z.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn release_shift_for_char_input(en: &mut Enigo) {
|
||||
// Don't release Shift if hotkey modifiers (Ctrl/Alt/Meta) are pressed.
|
||||
// This preserves combinations like Ctrl+Shift+Z.
|
||||
if is_hotkey_modifier_pressed(en) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In translate mode, the client has already converted the keystroke to a character
|
||||
// (e.g., Shift+a → 'A'). We release Shift here so the server inputs the character
|
||||
// directly without Shift affecting the result.
|
||||
//
|
||||
// Shift is intentionally NOT restored after input — the client will send an explicit
|
||||
// Shift key_up event when the user physically releases Shift. Restoring it here would
|
||||
// cause a brief Shift re-press that could interfere with the next input event.
|
||||
|
||||
let is_x11 = crate::platform::linux::is_x11();
|
||||
|
||||
if get_modifier_state(Key::Shift, en) {
|
||||
if !is_x11 {
|
||||
en.key_up(Key::Shift);
|
||||
} else {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftLeft));
|
||||
}
|
||||
}
|
||||
if get_modifier_state(Key::RightShift, en) {
|
||||
if !is_x11 {
|
||||
en.key_up(Key::RightShift);
|
||||
} else {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftRight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn legacy_keyboard_mode(evt: &KeyEvent) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::try_change_desktop();
|
||||
@@ -1640,11 +1847,24 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
|
||||
process_control_key(&mut en, &ck, down)
|
||||
}
|
||||
Some(key_event::Union::Chr(chr)) => {
|
||||
// For character input in Legacy mode, we need to release Shift first.
|
||||
// The character has already been converted by the client, so we should
|
||||
// input it directly without Shift modifier affecting the result.
|
||||
// Only Ctrl/Alt/Meta should be kept for hotkeys like Ctrl+C.
|
||||
#[cfg(target_os = "linux")]
|
||||
release_shift_for_char_input(&mut en);
|
||||
|
||||
let record_key = chr as u64 + KEY_CHAR_START;
|
||||
record_pressed_key(KeysDown::EnigoKey(record_key), down);
|
||||
process_chr(&mut en, chr, down)
|
||||
}
|
||||
Some(key_event::Union::Unicode(chr)) => process_unicode(&mut en, chr),
|
||||
Some(key_event::Union::Unicode(chr)) => {
|
||||
// Same as Chr: release Shift for Unicode input
|
||||
#[cfg(target_os = "linux")]
|
||||
release_shift_for_char_input(&mut en);
|
||||
|
||||
process_unicode(&mut en, chr)
|
||||
}
|
||||
Some(key_event::Union::Seq(ref seq)) => process_seq(&mut en, seq),
|
||||
_ => {}
|
||||
}
|
||||
@@ -1665,6 +1885,51 @@ fn translate_process_code(code: u32, down: bool) {
|
||||
fn translate_keyboard_mode(evt: &KeyEvent) {
|
||||
match &evt.union {
|
||||
Some(key_event::Union::Seq(seq)) => {
|
||||
// On Wayland, handle character input directly in this (--server) process using clipboard.
|
||||
// This function runs in the --server process (logged-in user session), which has
|
||||
// WAYLAND_DISPLAY and XDG_RUNTIME_DIR — so clipboard operations work here.
|
||||
//
|
||||
// Why not let it go through uinput IPC:
|
||||
// 1. For uinput mode: the uinput service thread runs in the --service (root) process,
|
||||
// which typically lacks user session environment. Clipboard operations there are
|
||||
// unreliable. Handling clipboard here avoids that issue.
|
||||
// 2. For RDP input mode: Portal's notify_keyboard_keysym API interprets keysyms
|
||||
// based on its internal modifier state, which may not match our released state.
|
||||
// Using clipboard bypasses this issue entirely.
|
||||
#[cfg(target_os = "linux")]
|
||||
if !crate::platform::linux::is_x11() {
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
|
||||
// Check if this is a hotkey (Ctrl/Alt/Meta pressed)
|
||||
// For hotkeys, we send character-based key events via Enigo instead of
|
||||
// using the clipboard. This relies on the local keyboard layout for
|
||||
// mapping characters to physical keys.
|
||||
// This assumes client and server use the same keyboard layout (common case).
|
||||
// Note: For non-Latin keyboards (e.g., Arabic), hotkeys may not work
|
||||
// correctly if the character cannot be mapped to a key via KEY_MAP_LAYOUT.
|
||||
// This is a known limitation - most common hotkeys (Ctrl+A/C/V/Z) use Latin
|
||||
// characters which are mappable on most keyboard layouts.
|
||||
if is_hotkey_modifier_pressed(&mut en) {
|
||||
// For hotkeys, send character-based key events via Enigo.
|
||||
// This relies on the local keyboard layout mapping (KEY_MAP_LAYOUT).
|
||||
for chr in seq.chars() {
|
||||
if !is_ascii_printable(chr) {
|
||||
log::warn!(
|
||||
"Hotkey with non-ASCII character may not work correctly on non-Latin keyboard layouts"
|
||||
);
|
||||
}
|
||||
en.key_click(Key::Layout(chr));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal text input: release Shift and use clipboard
|
||||
release_shift_for_char_input(&mut en);
|
||||
|
||||
input_text_via_clipboard_server(&mut en, seq);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fr -> US
|
||||
// client: Shift + & => 1(send to remote)
|
||||
// remote: Shift + 1 => !
|
||||
@@ -1682,11 +1947,16 @@ fn translate_keyboard_mode(evt: &KeyEvent) {
|
||||
#[cfg(target_os = "linux")]
|
||||
let simulate_win_hot_key = false;
|
||||
if !simulate_win_hot_key {
|
||||
if get_modifier_state(Key::Shift, &mut en) {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftLeft));
|
||||
}
|
||||
if get_modifier_state(Key::RightShift, &mut en) {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftRight));
|
||||
#[cfg(target_os = "linux")]
|
||||
release_shift_for_char_input(&mut en);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if get_modifier_state(Key::Shift, &mut en) {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftLeft));
|
||||
}
|
||||
if get_modifier_state(Key::RightShift, &mut en) {
|
||||
simulate_(&EventType::KeyRelease(RdevKey::ShiftRight));
|
||||
}
|
||||
}
|
||||
}
|
||||
for chr in seq.chars() {
|
||||
@@ -1706,7 +1976,16 @@ fn translate_keyboard_mode(evt: &KeyEvent) {
|
||||
Some(key_event::Union::Chr(..)) => {
|
||||
#[cfg(target_os = "windows")]
|
||||
translate_process_code(evt.chr(), evt.down);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !crate::platform::linux::is_x11() {
|
||||
// Wayland: use uinput to send raw keycode
|
||||
wayland_send_raw_key(evt.chr() as u16, evt.down);
|
||||
} else {
|
||||
sim_rdev_rawkey_position(evt.chr() as _, evt.down);
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
sim_rdev_rawkey_position(evt.chr() as _, evt.down);
|
||||
}
|
||||
Some(key_event::Union::Unicode(..)) => {
|
||||
@@ -1717,7 +1996,11 @@ fn translate_keyboard_mode(evt: &KeyEvent) {
|
||||
simulate_win2win_hotkey(*code, evt.down);
|
||||
}
|
||||
_ => {
|
||||
log::debug!("Unreachable. Unexpected key event {:?}", &evt);
|
||||
log::debug!(
|
||||
"Unreachable. Unexpected key event (mode={:?}, down={:?})",
|
||||
&evt.mode,
|
||||
&evt.down
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::uinput::service::map_key;
|
||||
use super::input_service::set_clipboard_for_paste_sync;
|
||||
use crate::uinput::service::{can_input_via_keysym, char_to_keysym, map_key};
|
||||
use dbus::{blocking::SyncConnection, Path};
|
||||
use enigo::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use hbb_common::ResultType;
|
||||
use hbb_common::{log, ResultType};
|
||||
use scrap::wayland::pipewire::{get_portal, PwStreamInfo};
|
||||
use scrap::wayland::remote_desktop_portal::OrgFreedesktopPortalRemoteDesktop as remote_desktop_portal;
|
||||
use std::collections::HashMap;
|
||||
@@ -19,14 +20,74 @@ pub mod client {
|
||||
const PRESSED_DOWN_STATE: u32 = 1;
|
||||
const PRESSED_UP_STATE: u32 = 0;
|
||||
|
||||
/// Modifier key state tracking for RDP input.
|
||||
/// Portal API doesn't provide a way to query key state, so we track it ourselves.
|
||||
#[derive(Default)]
|
||||
struct ModifierState {
|
||||
shift_left: bool,
|
||||
shift_right: bool,
|
||||
ctrl_left: bool,
|
||||
ctrl_right: bool,
|
||||
alt_left: bool,
|
||||
alt_right: bool,
|
||||
meta_left: bool,
|
||||
meta_right: bool,
|
||||
}
|
||||
|
||||
impl ModifierState {
|
||||
fn update(&mut self, key: &Key, down: bool) {
|
||||
match key {
|
||||
Key::Shift => self.shift_left = down,
|
||||
Key::RightShift => self.shift_right = down,
|
||||
Key::Control => self.ctrl_left = down,
|
||||
Key::RightControl => self.ctrl_right = down,
|
||||
Key::Alt => self.alt_left = down,
|
||||
Key::RightAlt => self.alt_right = down,
|
||||
Key::Meta | Key::Super | Key::Windows | Key::Command => self.meta_left = down,
|
||||
Key::RWin => self.meta_right = down,
|
||||
// Handle raw keycodes for modifier keys (Linux evdev codes + 8)
|
||||
// In translate mode, modifier keys may be sent as Chr events with raw keycodes.
|
||||
// The +8 offset converts evdev codes to X11/XKB keycodes.
|
||||
Key::Raw(code) => {
|
||||
const EVDEV_OFFSET: u16 = 8;
|
||||
const KEY_LEFTSHIFT: u16 = evdev::Key::KEY_LEFTSHIFT.code() + EVDEV_OFFSET;
|
||||
const KEY_RIGHTSHIFT: u16 = evdev::Key::KEY_RIGHTSHIFT.code() + EVDEV_OFFSET;
|
||||
const KEY_LEFTCTRL: u16 = evdev::Key::KEY_LEFTCTRL.code() + EVDEV_OFFSET;
|
||||
const KEY_RIGHTCTRL: u16 = evdev::Key::KEY_RIGHTCTRL.code() + EVDEV_OFFSET;
|
||||
const KEY_LEFTALT: u16 = evdev::Key::KEY_LEFTALT.code() + EVDEV_OFFSET;
|
||||
const KEY_RIGHTALT: u16 = evdev::Key::KEY_RIGHTALT.code() + EVDEV_OFFSET;
|
||||
const KEY_LEFTMETA: u16 = evdev::Key::KEY_LEFTMETA.code() + EVDEV_OFFSET;
|
||||
const KEY_RIGHTMETA: u16 = evdev::Key::KEY_RIGHTMETA.code() + EVDEV_OFFSET;
|
||||
match *code {
|
||||
KEY_LEFTSHIFT => self.shift_left = down,
|
||||
KEY_RIGHTSHIFT => self.shift_right = down,
|
||||
KEY_LEFTCTRL => self.ctrl_left = down,
|
||||
KEY_RIGHTCTRL => self.ctrl_right = down,
|
||||
KEY_LEFTALT => self.alt_left = down,
|
||||
KEY_RIGHTALT => self.alt_right = down,
|
||||
KEY_LEFTMETA => self.meta_left = down,
|
||||
KEY_RIGHTMETA => self.meta_right = down,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RdpInputKeyboard {
|
||||
conn: Arc<SyncConnection>,
|
||||
session: Path<'static>,
|
||||
modifier_state: ModifierState,
|
||||
}
|
||||
|
||||
impl RdpInputKeyboard {
|
||||
pub fn new(conn: Arc<SyncConnection>, session: Path<'static>) -> ResultType<Self> {
|
||||
Ok(Self { conn, session })
|
||||
Ok(Self {
|
||||
conn,
|
||||
session,
|
||||
modifier_state: ModifierState::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,29 +100,192 @@ pub mod client {
|
||||
self
|
||||
}
|
||||
|
||||
fn get_key_state(&mut self, _: Key) -> bool {
|
||||
// no api for this
|
||||
false
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
// Use tracked modifier state for supported keys
|
||||
match key {
|
||||
Key::Shift => self.modifier_state.shift_left,
|
||||
Key::RightShift => self.modifier_state.shift_right,
|
||||
Key::Control => self.modifier_state.ctrl_left,
|
||||
Key::RightControl => self.modifier_state.ctrl_right,
|
||||
Key::Alt => self.modifier_state.alt_left,
|
||||
Key::RightAlt => self.modifier_state.alt_right,
|
||||
Key::Meta | Key::Super | Key::Windows | Key::Command => {
|
||||
self.modifier_state.meta_left
|
||||
}
|
||||
Key::RWin => self.modifier_state.meta_right,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, s: &str) {
|
||||
for c in s.chars() {
|
||||
let key = Key::Layout(c);
|
||||
let _ = handle_key(true, key, self.conn.clone(), &self.session);
|
||||
let _ = handle_key(false, key, self.conn.clone(), &self.session);
|
||||
let keysym = char_to_keysym(c);
|
||||
// ASCII characters: use keysym
|
||||
if can_input_via_keysym(c, keysym) {
|
||||
if let Err(e) = send_keysym(keysym, true, self.conn.clone(), &self.session) {
|
||||
log::error!("Failed to send keysym down: {:?}", e);
|
||||
}
|
||||
if let Err(e) = send_keysym(keysym, false, self.conn.clone(), &self.session) {
|
||||
log::error!("Failed to send keysym up: {:?}", e);
|
||||
}
|
||||
} else {
|
||||
// Non-ASCII: use clipboard
|
||||
input_text_via_clipboard(&c.to_string(), self.conn.clone(), &self.session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_down(&mut self, key: Key) -> enigo::ResultType {
|
||||
handle_key(true, key, self.conn.clone(), &self.session)?;
|
||||
if let Key::Layout(chr) = key {
|
||||
let keysym = char_to_keysym(chr);
|
||||
// ASCII characters: use keysym
|
||||
if can_input_via_keysym(chr, keysym) {
|
||||
send_keysym(keysym, true, self.conn.clone(), &self.session)?;
|
||||
} else {
|
||||
// Non-ASCII: use clipboard (complete key press in key_down)
|
||||
input_text_via_clipboard(&chr.to_string(), self.conn.clone(), &self.session);
|
||||
}
|
||||
} else {
|
||||
handle_key(true, key.clone(), self.conn.clone(), &self.session)?;
|
||||
// Update modifier state only after successful send —
|
||||
// if handle_key fails, we don't want stale "pressed" state
|
||||
// affecting subsequent key event decisions.
|
||||
self.modifier_state.update(&key, true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_up(&mut self, key: Key) {
|
||||
let _ = handle_key(false, key, self.conn.clone(), &self.session);
|
||||
// Intentionally asymmetric with key_down: update state BEFORE sending.
|
||||
// On release, we always mark as released even if the send fails below,
|
||||
// to avoid permanently stuck-modifier state in our tracker. The trade-off
|
||||
// (tracker says "released" while OS may still have it pressed) is acceptable
|
||||
// because such failures are rare and subsequent events will resynchronize.
|
||||
self.modifier_state.update(&key, false);
|
||||
|
||||
if let Key::Layout(chr) = key {
|
||||
// ASCII characters: send keysym up if we also sent it on key_down
|
||||
let keysym = char_to_keysym(chr);
|
||||
if can_input_via_keysym(chr, keysym) {
|
||||
if let Err(e) = send_keysym(keysym, false, self.conn.clone(), &self.session)
|
||||
{
|
||||
log::error!("Failed to send keysym up: {:?}", e);
|
||||
}
|
||||
}
|
||||
// Non-ASCII: already handled completely in key_down via clipboard paste,
|
||||
// no corresponding release needed (clipboard paste is an atomic operation)
|
||||
} else {
|
||||
if let Err(e) = handle_key(false, key, self.conn.clone(), &self.session) {
|
||||
log::error!("Failed to handle key up: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_click(&mut self, key: Key) {
|
||||
let _ = handle_key(true, key, self.conn.clone(), &self.session);
|
||||
let _ = handle_key(false, key, self.conn.clone(), &self.session);
|
||||
if let Key::Layout(chr) = key {
|
||||
let keysym = char_to_keysym(chr);
|
||||
// ASCII characters: use keysym
|
||||
if can_input_via_keysym(chr, keysym) {
|
||||
if let Err(e) = send_keysym(keysym, true, self.conn.clone(), &self.session) {
|
||||
log::error!("Failed to send keysym down: {:?}", e);
|
||||
}
|
||||
if let Err(e) = send_keysym(keysym, false, self.conn.clone(), &self.session) {
|
||||
log::error!("Failed to send keysym up: {:?}", e);
|
||||
}
|
||||
} else {
|
||||
// Non-ASCII: use clipboard
|
||||
input_text_via_clipboard(&chr.to_string(), self.conn.clone(), &self.session);
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = handle_key(true, key.clone(), self.conn.clone(), &self.session) {
|
||||
log::error!("Failed to handle key down: {:?}", e);
|
||||
} else {
|
||||
// Only mark modifier as pressed if key-down was actually delivered
|
||||
self.modifier_state.update(&key, true);
|
||||
}
|
||||
// Always mark as released to avoid stuck-modifier state
|
||||
self.modifier_state.update(&key, false);
|
||||
if let Err(e) = handle_key(false, key, self.conn.clone(), &self.session) {
|
||||
log::error!("Failed to handle key up: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Input text via clipboard + Shift+Insert.
|
||||
/// Shift+Insert is more universal than Ctrl+V, works in both GUI apps and terminals.
|
||||
///
|
||||
/// Note: Clipboard content is NOT restored after paste - see `set_clipboard_for_paste_sync` for rationale.
|
||||
fn input_text_via_clipboard(text: &str, conn: Arc<SyncConnection>, session: &Path<'static>) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
if !set_clipboard_for_paste_sync(text) {
|
||||
return;
|
||||
}
|
||||
|
||||
let portal = get_portal(&conn);
|
||||
let shift_keycode = evdev::Key::KEY_LEFTSHIFT.code() as i32;
|
||||
let insert_keycode = evdev::Key::KEY_INSERT.code() as i32;
|
||||
|
||||
// Send Shift+Insert (universal paste shortcut)
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
session,
|
||||
HashMap::new(),
|
||||
shift_keycode,
|
||||
PRESSED_DOWN_STATE,
|
||||
) {
|
||||
log::error!("input_text_via_clipboard: failed to press Shift: {:?}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Press Insert
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
session,
|
||||
HashMap::new(),
|
||||
insert_keycode,
|
||||
PRESSED_DOWN_STATE,
|
||||
) {
|
||||
log::error!("input_text_via_clipboard: failed to press Insert: {:?}", e);
|
||||
// Still try to release Shift.
|
||||
// Note: clipboard has already been set by set_clipboard_for_paste_sync but paste
|
||||
// never happened. We don't attempt to restore the previous clipboard contents
|
||||
// because reading the clipboard on Wayland requires focus/permission.
|
||||
let _ = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
session,
|
||||
HashMap::new(),
|
||||
shift_keycode,
|
||||
PRESSED_UP_STATE,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Release Insert
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
session,
|
||||
HashMap::new(),
|
||||
insert_keycode,
|
||||
PRESSED_UP_STATE,
|
||||
) {
|
||||
log::error!(
|
||||
"input_text_via_clipboard: failed to release Insert: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Release Shift
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
session,
|
||||
HashMap::new(),
|
||||
shift_keycode,
|
||||
PRESSED_UP_STATE,
|
||||
) {
|
||||
log::error!("input_text_via_clipboard: failed to release Shift: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +420,39 @@ pub mod client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a keysym via RemoteDesktop portal.
|
||||
fn send_keysym(
|
||||
keysym: i32,
|
||||
down: bool,
|
||||
conn: Arc<SyncConnection>,
|
||||
session: &Path<'static>,
|
||||
) -> ResultType<()> {
|
||||
let state: u32 = if down {
|
||||
PRESSED_DOWN_STATE
|
||||
} else {
|
||||
PRESSED_UP_STATE
|
||||
};
|
||||
let portal = get_portal(&conn);
|
||||
log::trace!(
|
||||
"send_keysym: calling notify_keyboard_keysym, keysym={:#x}, state={}",
|
||||
keysym,
|
||||
state
|
||||
);
|
||||
match remote_desktop_portal::notify_keyboard_keysym(
|
||||
&portal,
|
||||
session,
|
||||
HashMap::new(),
|
||||
keysym,
|
||||
state,
|
||||
) {
|
||||
Ok(_) => {
|
||||
log::trace!("send_keysym: notify_keyboard_keysym succeeded");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_raw_evdev_keycode(key: u16) -> i32 {
|
||||
// 8 is the offset between xkb and evdev
|
||||
let mut key = key as i32 - 8;
|
||||
@@ -231,22 +488,86 @@ pub mod client {
|
||||
}
|
||||
_ => {
|
||||
if let Ok((key, is_shift)) = map_key(&key) {
|
||||
if is_shift {
|
||||
remote_desktop_portal::notify_keyboard_keycode(
|
||||
let shift_keycode = evdev::Key::KEY_LEFTSHIFT.code() as i32;
|
||||
if down {
|
||||
// Press: Shift down first, then key down
|
||||
if is_shift {
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
&session,
|
||||
HashMap::new(),
|
||||
shift_keycode,
|
||||
state,
|
||||
) {
|
||||
log::error!("handle_key: failed to press Shift: {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
&session,
|
||||
HashMap::new(),
|
||||
evdev::Key::KEY_LEFTSHIFT.code() as i32,
|
||||
key.code() as i32,
|
||||
state,
|
||||
)?;
|
||||
) {
|
||||
log::error!("handle_key: failed to press key: {:?}", e);
|
||||
// Best-effort: release Shift if it was pressed
|
||||
if is_shift {
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
&session,
|
||||
HashMap::new(),
|
||||
shift_keycode,
|
||||
PRESSED_UP_STATE,
|
||||
) {
|
||||
log::warn!(
|
||||
"handle_key: best-effort Shift release also failed: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
return Err(e.into());
|
||||
}
|
||||
} else {
|
||||
// Release: key up first, then Shift up
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
&session,
|
||||
HashMap::new(),
|
||||
key.code() as i32,
|
||||
PRESSED_UP_STATE,
|
||||
) {
|
||||
log::error!("handle_key: failed to release key: {:?}", e);
|
||||
// Best-effort: still try to release Shift
|
||||
if is_shift {
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
&session,
|
||||
HashMap::new(),
|
||||
shift_keycode,
|
||||
PRESSED_UP_STATE,
|
||||
) {
|
||||
log::warn!(
|
||||
"handle_key: best-effort Shift release also failed: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
return Err(e.into());
|
||||
}
|
||||
if is_shift {
|
||||
if let Err(e) = remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
&session,
|
||||
HashMap::new(),
|
||||
shift_keycode,
|
||||
PRESSED_UP_STATE,
|
||||
) {
|
||||
log::error!("handle_key: failed to release Shift: {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
remote_desktop_portal::notify_keyboard_keycode(
|
||||
&portal,
|
||||
&session,
|
||||
HashMap::new(),
|
||||
key.code() as i32,
|
||||
state,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,6 +777,32 @@ impl TerminalServiceProxy {
|
||||
) -> Result<Option<TerminalResponse>> {
|
||||
let mut response = TerminalResponse::new();
|
||||
|
||||
// When the client requests a terminal_id that doesn't exist but there are
|
||||
// surviving persistent sessions, remap the lowest-ID session to the requested
|
||||
// terminal_id. This handles the case where _nextTerminalId resets to 1 on
|
||||
// reconnect but the server-side sessions have non-contiguous IDs (e.g. {2: htop}).
|
||||
//
|
||||
// The client's requested terminal_id may not match any surviving session ID
|
||||
// (e.g. _nextTerminalId incremented beyond the surviving IDs). This remap is a
|
||||
// one-time handle reassignment — only the first reconnect triggers it because
|
||||
// needs_session_sync is cleared afterward. Remaining sessions are communicated
|
||||
// back via `persistent_sessions` with their original server-side IDs.
|
||||
if !service.sessions.contains_key(&open.terminal_id)
|
||||
&& service.needs_session_sync
|
||||
&& !service.sessions.is_empty()
|
||||
{
|
||||
if let Some(&lowest_id) = service.sessions.keys().min() {
|
||||
log::info!(
|
||||
"Remapping persistent session {} -> {} for reconnection",
|
||||
lowest_id,
|
||||
open.terminal_id
|
||||
);
|
||||
if let Some(session_arc) = service.sessions.remove(&lowest_id) {
|
||||
service.sessions.insert(open.terminal_id, session_arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if terminal already exists
|
||||
if let Some(session_arc) = service.sessions.get(&open.terminal_id) {
|
||||
// Reconnect to existing terminal
|
||||
@@ -824,7 +850,7 @@ impl TerminalServiceProxy {
|
||||
|
||||
// Create new terminal session
|
||||
log::info!(
|
||||
"Creating new terminal {} for service: {}",
|
||||
"Creating new terminal {} for service {}",
|
||||
open.terminal_id,
|
||||
service.service_id
|
||||
);
|
||||
|
||||
@@ -90,6 +90,13 @@ pub mod client {
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
// Sequence events are normally handled in the --server process before reaching here.
|
||||
// Forward via IPC as a fallback — input_text_wayland can still handle ASCII chars
|
||||
// via keysym/uinput, though non-ASCII will be skipped (no clipboard in --service).
|
||||
log::debug!(
|
||||
"UInputKeyboard::key_sequence called (len={})",
|
||||
sequence.len()
|
||||
);
|
||||
allow_err!(self.send(Data::Keyboard(DataKeyboard::Sequence(sequence.to_string()))));
|
||||
}
|
||||
|
||||
@@ -178,6 +185,9 @@ pub mod client {
|
||||
pub mod service {
|
||||
use super::*;
|
||||
use hbb_common::lazy_static;
|
||||
use scrap::wayland::{
|
||||
pipewire::RDP_SESSION_INFO, remote_desktop_portal::OrgFreedesktopPortalRemoteDesktop,
|
||||
};
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -309,6 +319,9 @@ pub mod service {
|
||||
('/', (evdev::Key::KEY_SLASH, false)),
|
||||
(';', (evdev::Key::KEY_SEMICOLON, false)),
|
||||
('\'', (evdev::Key::KEY_APOSTROPHE, false)),
|
||||
// Space is intentionally in both KEY_MAP_LAYOUT (char-to-evdev for text input)
|
||||
// and KEY_MAP (Key::Space for key events). Both maps serve different lookup paths.
|
||||
(' ', (evdev::Key::KEY_SPACE, false)),
|
||||
|
||||
// Shift + key
|
||||
('A', (evdev::Key::KEY_A, true)),
|
||||
@@ -364,6 +377,155 @@ pub mod service {
|
||||
static ref RESOLUTION: Mutex<((i32, i32), (i32, i32))> = Mutex::new(((0, 0), (0, 0)));
|
||||
}
|
||||
|
||||
/// Input text on Wayland using layout-independent methods.
|
||||
/// ASCII chars (0x20-0x7E): Portal keysym or uinput fallback
|
||||
/// Non-ASCII chars: skipped — this runs in the --service (root) process where clipboard
|
||||
/// operations are unreliable (typically no user session environment).
|
||||
/// Non-ASCII input is normally handled by the --server process via input_text_via_clipboard_server.
|
||||
fn input_text_wayland(text: &str, keyboard: &mut VirtualDevice) {
|
||||
let portal_info = {
|
||||
let session_info = RDP_SESSION_INFO.lock().unwrap();
|
||||
session_info
|
||||
.as_ref()
|
||||
.map(|info| (info.conn.clone(), info.session.clone()))
|
||||
};
|
||||
|
||||
for c in text.chars() {
|
||||
let keysym = char_to_keysym(c);
|
||||
if can_input_via_keysym(c, keysym) {
|
||||
// Try Portal first — down+up on the same channel
|
||||
if let Some((ref conn, ref session)) = portal_info {
|
||||
let portal = scrap::wayland::pipewire::get_portal(conn);
|
||||
if portal
|
||||
.notify_keyboard_keysym(session, HashMap::new(), keysym, 1)
|
||||
.is_ok()
|
||||
{
|
||||
if let Err(e) =
|
||||
portal.notify_keyboard_keysym(session, HashMap::new(), keysym, 0)
|
||||
{
|
||||
log::warn!(
|
||||
"input_text_wayland: portal key-up failed for keysym {:#x}: {:?}",
|
||||
keysym,
|
||||
e
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Portal unavailable or failed, fallback to uinput (down+up together)
|
||||
let key = enigo::Key::Layout(c);
|
||||
if let Ok((evdev_key, is_shift)) = map_key(&key) {
|
||||
let mut shift_pressed = false;
|
||||
if is_shift {
|
||||
let shift_down =
|
||||
InputEvent::new(EventType::KEY, evdev::Key::KEY_LEFTSHIFT.code(), 1);
|
||||
if keyboard.emit(&[shift_down]).is_ok() {
|
||||
shift_pressed = true;
|
||||
} else {
|
||||
log::warn!("input_text_wayland: failed to press Shift for '{}'", c);
|
||||
}
|
||||
}
|
||||
let key_down = InputEvent::new(EventType::KEY, evdev_key.code(), 1);
|
||||
let key_up = InputEvent::new(EventType::KEY, evdev_key.code(), 0);
|
||||
allow_err!(keyboard.emit(&[key_down, key_up]));
|
||||
if shift_pressed {
|
||||
let shift_up =
|
||||
InputEvent::new(EventType::KEY, evdev::Key::KEY_LEFTSHIFT.code(), 0);
|
||||
allow_err!(keyboard.emit(&[shift_up]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("Skipping non-ASCII character in uinput service (no clipboard access)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a single key down or up event for a Layout character.
|
||||
/// Used by KeyDown/KeyUp to maintain correct press/release semantics.
|
||||
/// `down`: true for key press, false for key release.
|
||||
fn input_char_wayland_key_event(chr: char, down: bool, keyboard: &mut VirtualDevice) {
|
||||
let keysym = char_to_keysym(chr);
|
||||
let portal_state: u32 = if down { 1 } else { 0 };
|
||||
|
||||
if can_input_via_keysym(chr, keysym) {
|
||||
let portal_info = {
|
||||
let session_info = RDP_SESSION_INFO.lock().unwrap();
|
||||
session_info
|
||||
.as_ref()
|
||||
.map(|info| (info.conn.clone(), info.session.clone()))
|
||||
};
|
||||
if let Some((ref conn, ref session)) = portal_info {
|
||||
let portal = scrap::wayland::pipewire::get_portal(conn);
|
||||
if portal
|
||||
.notify_keyboard_keysym(session, HashMap::new(), keysym, portal_state)
|
||||
.is_ok()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Portal unavailable or failed, fallback to uinput
|
||||
let key = enigo::Key::Layout(chr);
|
||||
if let Ok((evdev_key, is_shift)) = map_key(&key) {
|
||||
if down {
|
||||
// Press: Shift↓ (if needed) → Key↓
|
||||
if is_shift {
|
||||
let shift_down =
|
||||
InputEvent::new(EventType::KEY, evdev::Key::KEY_LEFTSHIFT.code(), 1);
|
||||
if let Err(e) = keyboard.emit(&[shift_down]) {
|
||||
log::warn!("input_char_wayland_key_event: failed to press Shift for '{}': {:?}", chr, e);
|
||||
}
|
||||
}
|
||||
let key_down = InputEvent::new(EventType::KEY, evdev_key.code(), 1);
|
||||
allow_err!(keyboard.emit(&[key_down]));
|
||||
} else {
|
||||
// Release: Key↑ → Shift↑ (if needed)
|
||||
let key_up = InputEvent::new(EventType::KEY, evdev_key.code(), 0);
|
||||
allow_err!(keyboard.emit(&[key_up]));
|
||||
if is_shift {
|
||||
let shift_up =
|
||||
InputEvent::new(EventType::KEY, evdev::Key::KEY_LEFTSHIFT.code(), 0);
|
||||
if let Err(e) = keyboard.emit(&[shift_up]) {
|
||||
log::warn!("input_char_wayland_key_event: failed to release Shift for '{}': {:?}", chr, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-ASCII: no reliable down/up semantics available.
|
||||
// Clipboard paste is atomic and handled elsewhere.
|
||||
log::debug!(
|
||||
"Skipping non-ASCII character key {} in uinput service",
|
||||
if down { "down" } else { "up" }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if character can be input via keysym (ASCII printable with valid keysym).
|
||||
#[inline]
|
||||
pub(crate) fn can_input_via_keysym(c: char, keysym: i32) -> bool {
|
||||
// ASCII printable: 0x20 (space) to 0x7E (tilde)
|
||||
(c as u32 >= 0x20 && c as u32 <= 0x7E) && keysym != 0
|
||||
}
|
||||
|
||||
/// Convert a Unicode character to X11 keysym.
|
||||
pub(crate) fn char_to_keysym(c: char) -> i32 {
|
||||
let codepoint = c as u32;
|
||||
if codepoint == 0 {
|
||||
// Null character has no keysym
|
||||
0
|
||||
} else if (0x20..=0x7E).contains(&codepoint) {
|
||||
// ASCII printable (0x20-0x7E): keysym == Unicode codepoint
|
||||
codepoint as i32
|
||||
} else if (0xA0..=0xFF).contains(&codepoint) {
|
||||
// Latin-1 supplement (0xA0-0xFF): keysym == Unicode codepoint (per X11 keysym spec)
|
||||
codepoint as i32
|
||||
} else {
|
||||
// Everything else (control chars 0x01-0x1F, DEL 0x7F, and all other non-ASCII Unicode):
|
||||
// keysym = 0x01000000 | codepoint (X11 Unicode keysym encoding)
|
||||
(0x0100_0000 | codepoint) as i32
|
||||
}
|
||||
}
|
||||
|
||||
fn create_uinput_keyboard() -> ResultType<VirtualDevice> {
|
||||
// TODO: ensure keys here
|
||||
let mut keys = AttributeSet::<evdev::Key>::new();
|
||||
@@ -390,13 +552,13 @@ pub mod service {
|
||||
|
||||
pub fn map_key(key: &enigo::Key) -> ResultType<(evdev::Key, bool)> {
|
||||
if let Some(k) = KEY_MAP.get(&key) {
|
||||
log::trace!("mapkey {:?}, get {:?}", &key, &k);
|
||||
log::trace!("mapkey matched in KEY_MAP, evdev={:?}", &k);
|
||||
return Ok((k.clone(), false));
|
||||
} else {
|
||||
match key {
|
||||
enigo::Key::Layout(c) => {
|
||||
if let Some((k, is_shift)) = KEY_MAP_LAYOUT.get(&c) {
|
||||
log::trace!("mapkey {:?}, get {:?}", &key, k);
|
||||
log::trace!("mapkey Layout matched, evdev={:?}", k);
|
||||
return Ok((k.clone(), is_shift.clone()));
|
||||
}
|
||||
}
|
||||
@@ -421,41 +583,68 @@ pub mod service {
|
||||
keyboard: &mut VirtualDevice,
|
||||
data: &DataKeyboard,
|
||||
) {
|
||||
log::trace!("handle_keyboard {:?}", &data);
|
||||
let data_desc = match data {
|
||||
DataKeyboard::Sequence(seq) => format!("Sequence(len={})", seq.len()),
|
||||
DataKeyboard::KeyDown(Key::Layout(_))
|
||||
| DataKeyboard::KeyUp(Key::Layout(_))
|
||||
| DataKeyboard::KeyClick(Key::Layout(_)) => "Layout(<redacted>)".to_string(),
|
||||
_ => format!("{:?}", data),
|
||||
};
|
||||
log::trace!("handle_keyboard received: {}", data_desc);
|
||||
match data {
|
||||
DataKeyboard::Sequence(_seq) => {
|
||||
// ignore
|
||||
DataKeyboard::Sequence(seq) => {
|
||||
// Normally handled by --server process (input_text_via_clipboard_server).
|
||||
// Fallback: input_text_wayland handles ASCII via keysym/uinput;
|
||||
// non-ASCII will be skipped (no clipboard access in --service process).
|
||||
if !seq.is_empty() {
|
||||
input_text_wayland(seq, keyboard);
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyDown(enigo::Key::Raw(code)) => {
|
||||
let down_event = InputEvent::new(EventType::KEY, *code - 8, 1);
|
||||
allow_err!(keyboard.emit(&[down_event]));
|
||||
}
|
||||
DataKeyboard::KeyUp(enigo::Key::Raw(code)) => {
|
||||
let up_event = InputEvent::new(EventType::KEY, *code - 8, 0);
|
||||
allow_err!(keyboard.emit(&[up_event]));
|
||||
}
|
||||
DataKeyboard::KeyDown(key) => {
|
||||
if let Ok((k, is_shift)) = map_key(key) {
|
||||
if is_shift {
|
||||
let down_event =
|
||||
InputEvent::new(EventType::KEY, evdev::Key::KEY_LEFTSHIFT.code(), 1);
|
||||
allow_err!(keyboard.emit(&[down_event]));
|
||||
}
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
if *code < 8 {
|
||||
log::error!("Invalid Raw keycode {} (must be >= 8 due to XKB offset), skipping", code);
|
||||
} else {
|
||||
let down_event = InputEvent::new(EventType::KEY, *code - 8, 1);
|
||||
allow_err!(keyboard.emit(&[down_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyUp(key) => {
|
||||
if let Ok((k, _)) = map_key(key) {
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
DataKeyboard::KeyUp(enigo::Key::Raw(code)) => {
|
||||
if *code < 8 {
|
||||
log::error!("Invalid Raw keycode {} (must be >= 8 due to XKB offset), skipping", code);
|
||||
} else {
|
||||
let up_event = InputEvent::new(EventType::KEY, *code - 8, 0);
|
||||
allow_err!(keyboard.emit(&[up_event]));
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyDown(key) => {
|
||||
if let Key::Layout(chr) = key {
|
||||
input_char_wayland_key_event(*chr, true, keyboard);
|
||||
} else {
|
||||
if let Ok((k, _is_shift)) = map_key(key) {
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
allow_err!(keyboard.emit(&[down_event]));
|
||||
}
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyUp(key) => {
|
||||
if let Key::Layout(chr) = key {
|
||||
input_char_wayland_key_event(*chr, false, keyboard);
|
||||
} else {
|
||||
if let Ok((k, _)) = map_key(key) {
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
allow_err!(keyboard.emit(&[up_event]));
|
||||
}
|
||||
}
|
||||
}
|
||||
DataKeyboard::KeyClick(key) => {
|
||||
if let Ok((k, _)) = map_key(key) {
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
allow_err!(keyboard.emit(&[down_event, up_event]));
|
||||
if let Key::Layout(chr) = key {
|
||||
input_text_wayland(&chr.to_string(), keyboard);
|
||||
} else {
|
||||
if let Ok((k, _is_shift)) = map_key(key) {
|
||||
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
|
||||
let up_event = InputEvent::new(EventType::KEY, k.code(), 0);
|
||||
allow_err!(keyboard.emit(&[down_event, up_event]));
|
||||
}
|
||||
}
|
||||
}
|
||||
DataKeyboard::GetKeyState(key) => {
|
||||
@@ -580,9 +769,13 @@ pub mod service {
|
||||
}
|
||||
|
||||
fn spawn_keyboard_handler(mut stream: Connection) {
|
||||
log::debug!("spawn_keyboard_handler: new keyboard handler connection");
|
||||
tokio::spawn(async move {
|
||||
let mut keyboard = match create_uinput_keyboard() {
|
||||
Ok(keyboard) => keyboard,
|
||||
Ok(keyboard) => {
|
||||
log::debug!("UInput keyboard device created successfully");
|
||||
keyboard
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to create keyboard {}", e);
|
||||
return;
|
||||
@@ -602,6 +795,7 @@ pub mod service {
|
||||
handle_keyboard(&mut stream, &mut keyboard, &data).await;
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Unexpected data type in keyboard handler");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +358,22 @@ function getUserName() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function getAccountLabelWithHandle() {
|
||||
try {
|
||||
var user = JSON.parse(handler.get_local_option("user_info"));
|
||||
var username = (user.name || '').trim();
|
||||
if (!username) {
|
||||
return '';
|
||||
}
|
||||
var displayName = (user.display_name || '').trim();
|
||||
if (!displayName || displayName == username) {
|
||||
return username;
|
||||
}
|
||||
return displayName + " (@" + username + ")";
|
||||
} catch(e) {}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Shared dialog functions
|
||||
function open_custom_server_dialog() {
|
||||
var configOptions = handler.get_options();
|
||||
@@ -493,7 +509,7 @@ class MyIdMenu: Reactor.Component {
|
||||
}
|
||||
|
||||
function renderPop() {
|
||||
var username = handler.get_local_option("access_token") ? getUserName() : '';
|
||||
var accountLabel = handler.get_local_option("access_token") ? getAccountLabelWithHandle() : '';
|
||||
return <popup>
|
||||
<menu.context #config-options>
|
||||
{!disable_settings && <li #enable-keyboard><span>{svg_checkmark}</span>{translate('Enable keyboard/mouse')}</li>}
|
||||
@@ -521,8 +537,8 @@ class MyIdMenu: Reactor.Component {
|
||||
{!disable_settings && <DirectServer />}
|
||||
{!disable_settings && false && handler.using_public_server() && <li #allow-always-relay><span>{svg_checkmark}</span>{translate('Always connect via relay')}</li>}
|
||||
{!disable_change_id && handler.is_ok_change_id() ? <div .separator /> : ""}
|
||||
{!disable_account && (username ?
|
||||
<li #logout>{translate('Logout')} ({username})</li> :
|
||||
{!disable_account && (accountLabel ?
|
||||
<li #logout>{translate('Logout')} ({accountLabel})</li> :
|
||||
<li #login>{translate('Login')}</li>)}
|
||||
{!disable_change_id && !disable_settings && handler.is_ok_change_id() && key_confirmed && connect_status > 0 ? <li #change-id>{translate('Change ID')}</li> : ""}
|
||||
<div .separator />
|
||||
@@ -1430,6 +1446,9 @@ checkConnectStatus();
|
||||
|
||||
function set_local_user_info(user) {
|
||||
var user_info = {name: user.name};
|
||||
if (user.display_name) {
|
||||
user_info.display_name = user.display_name;
|
||||
}
|
||||
if (user.status) {
|
||||
user_info.status = user.status;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ fn check_update(manually: bool) -> ResultType<()> {
|
||||
if !(manually || config::Config::get_bool_option(config::keys::OPTION_ALLOW_AUTO_UPDATE)) {
|
||||
return Ok(());
|
||||
}
|
||||
if !do_check_software_update().is_ok() {
|
||||
if do_check_software_update().is_err() {
|
||||
// ignore
|
||||
return Ok(());
|
||||
}
|
||||
@@ -185,7 +185,7 @@ fn check_update(manually: bool) -> ResultType<()> {
|
||||
let mut file = std::fs::File::create(&file_path)?;
|
||||
file.write_all(&file_data)?;
|
||||
}
|
||||
// We have checked if the `conns`` is empty before, but we need to check again.
|
||||
// We have checked if the `conns` is empty before, but we need to check again.
|
||||
// No need to care about the downloaded file here, because it's rare case that the `conns` are empty
|
||||
// before the download, but not empty after the download.
|
||||
if has_no_active_conns() {
|
||||
|
||||
Reference in New Issue
Block a user