mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-14 16:31:03 +03:00
Compare commits
13 Commits
ae3267e52b
...
9343affe0b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9343affe0b | ||
|
|
7c830e76c6 | ||
|
|
1fe451c2e8 | ||
|
|
0b08a83d4b | ||
|
|
e9b81e3475 | ||
|
|
7220f00410 | ||
|
|
fd471fcf02 | ||
|
|
7c6e661fcc | ||
|
|
3f207e91f6 | ||
|
|
cec4085238 | ||
|
|
0d917c6fa1 | ||
|
|
893dc27798 | ||
|
|
7cc82c1575 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -5,7 +5,7 @@ env:
|
||||
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
VCPKG_COMMIT_ID: "9e593bb18ea69cc5095e012465dcd675a822ed0d"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
4
.github/workflows/flutter-build.yml
vendored
4
.github/workflows/flutter-build.yml
vendored
@@ -36,13 +36,13 @@ env:
|
||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# vcpkg version: 2025.08.27
|
||||
# vcpkg version: 2026.07.29
|
||||
# If we change the `VCPKG COMMIT_ID`, please remember:
|
||||
# 1. Call `$VCPKG_ROOT/vcpkg x-update-baseline` to update the baseline in `vcpkg.json`.
|
||||
# Or we may face build issue like
|
||||
# https://github.com/rustdesk/rustdesk/actions/runs/14414119794/job/40427970174
|
||||
# 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`.
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
VCPKG_COMMIT_ID: "9e593bb18ea69cc5095e012465dcd675a822ed0d"
|
||||
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836" # 2025.01.13, got "/opt/artifacts/vcpkg/vcpkg: No such file or directory" with latest version
|
||||
VERSION: "1.4.9"
|
||||
NDK_VERSION: "r28c"
|
||||
|
||||
2
.github/workflows/playground.yml
vendored
2
.github/workflows/playground.yml
vendored
@@ -16,7 +16,7 @@ env:
|
||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||
TAG_NAME: "nightly"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
VCPKG_COMMIT_ID: "9e593bb18ea69cc5095e012465dcd675a822ed0d"
|
||||
VERSION: "1.4.9"
|
||||
NDK_VERSION: "r26d"
|
||||
#signing keys env variable checks
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<a href="#raw-steps-to-build">Build</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
<a href="#screenshots">Screenshots</a><br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>] | [<a href="docs/README-RO.md">Română</a>]<br>
|
||||
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b>
|
||||
</p>
|
||||
|
||||
1
build.rs
1
build.rs
@@ -72,7 +72,6 @@ fn install_android_deps() {
|
||||
path.join("lib").to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=ndk_compat");
|
||||
println!("cargo:rustc-link-lib=oboe");
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
println!("cargo:rustc-link-lib=OpenSLES");
|
||||
}
|
||||
|
||||
@@ -278,7 +278,39 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
item.state != JobState.inProgress,
|
||||
child: LinearPercentIndicator(
|
||||
animateFromLastPercent: true,
|
||||
center: Text(item.percentText),
|
||||
center: SizedBox.expand(
|
||||
child: ShaderMask(
|
||||
blendMode: BlendMode.srcATop,
|
||||
shaderCallback: (bounds) =>
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Colors.white,
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: [item.percent, item.percent],
|
||||
).createShader(bounds),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: item.percentText,
|
||||
children: [
|
||||
if (item.recvJobRes)
|
||||
TextSpan(
|
||||
text:
|
||||
' ${readableFileSize(item.speed)}/s',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: MyTheme.darkGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
barRadius: Radius.circular(15),
|
||||
percent: item.percent,
|
||||
progressColor: MyTheme.accent,
|
||||
@@ -1094,6 +1126,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
return element.name.contains(_searchText.value);
|
||||
}).toList(growable: false)
|
||||
: entries;
|
||||
// Keep rows lazy so large directories only build visible list items.
|
||||
final rows = filteredEntries.map((entry) {
|
||||
final sizeStr =
|
||||
entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
|
||||
@@ -1276,7 +1309,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
],
|
||||
))),
|
||||
);
|
||||
}).toList(growable: false);
|
||||
});
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@@ -1292,7 +1325,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
controller: scrollController,
|
||||
itemExtent: kDesktopFileTransferRowHeight,
|
||||
itemBuilder: (context, index) {
|
||||
return rows[index];
|
||||
return rows.elementAt(index);
|
||||
},
|
||||
itemCount: rows.length,
|
||||
),
|
||||
|
||||
@@ -22,6 +22,14 @@ import '../../models/file_model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../../models/server_model.dart';
|
||||
|
||||
/// Set only by this window's own close control, and only once the user has confirmed. Any other
|
||||
/// way the window can go - a session logout closing every window, the window manager, a native
|
||||
/// title-bar button this app does not draw - leaves it false, which is the honest answer:
|
||||
/// nothing in that close says who asked for it. It lives at file scope because the control that
|
||||
/// sets it (`ConnectionManagerState`) and the handler that reads it (`_DesktopServerPageState`)
|
||||
/// are different widgets.
|
||||
bool _cmClosedByOperator = false;
|
||||
|
||||
class DesktopServerPage extends StatefulWidget {
|
||||
const DesktopServerPage({Key? key}) : super(key: key);
|
||||
|
||||
@@ -55,7 +63,10 @@ class _DesktopServerPageState extends State<DesktopServerPage>
|
||||
|
||||
@override
|
||||
void onWindowClose() {
|
||||
Future.wait([gFFI.serverModel.closeAll(), gFFI.close()]).then((_) {
|
||||
// Other platforms keep the old behaviour exactly: the ambiguity this guards against is a
|
||||
// Linux session logout, which closes every window in the session.
|
||||
final byOperator = _cmClosedByOperator || !isLinux;
|
||||
Future.wait([gFFI.serverModel.closeAll(byOperator: byOperator), gFFI.close()]).then((_) {
|
||||
if (isMacOS) {
|
||||
RdPlatformChannel.instance.terminate();
|
||||
} else {
|
||||
@@ -327,6 +338,7 @@ class ConnectionManagerState extends State<ConnectionManager>
|
||||
var tabController = gFFI.serverModel.tabController;
|
||||
final connLength = tabController.length;
|
||||
if (connLength <= 1) {
|
||||
_cmClosedByOperator = true;
|
||||
windowManager.close();
|
||||
return true;
|
||||
} else {
|
||||
@@ -338,6 +350,9 @@ class ConnectionManagerState extends State<ConnectionManager>
|
||||
res = await closeConfirmDialog();
|
||||
}
|
||||
if (res) {
|
||||
// After the dialog, never before it: an external close while it is open must not
|
||||
// inherit an intent the user had not expressed yet.
|
||||
_cmClosedByOperator = true;
|
||||
windowManager.close();
|
||||
}
|
||||
return res;
|
||||
|
||||
@@ -366,8 +366,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
return BottomSheetBody(
|
||||
leading: CircularProgressIndicator(),
|
||||
title: translate("Waiting"),
|
||||
text:
|
||||
"${translate("Speed")}: ${readableFileSize(activeJob.speed)}/s",
|
||||
text: "${readableFileSize(activeJob.speed)}/s",
|
||||
onCanceled: () {
|
||||
model.jobController.cancelJob(activeJob.id);
|
||||
jobTable.clear();
|
||||
|
||||
@@ -46,6 +46,12 @@ class JobID {
|
||||
|
||||
typedef GetSessionID = SessionID Function();
|
||||
typedef GetDialogManager = OverlayDialogManager? Function();
|
||||
typedef ReadRemoteDirectory = Future<void> Function(
|
||||
SessionID sessionId, String path, bool includeHidden);
|
||||
|
||||
const _kRemoteReadDirTimeout = Duration(seconds: 30);
|
||||
const _kRemoteSessionChangedError =
|
||||
'Remote directory read cancelled because the session changed';
|
||||
|
||||
class FileModel {
|
||||
final WeakReference<FFI> parent;
|
||||
@@ -84,6 +90,7 @@ class FileModel {
|
||||
}
|
||||
|
||||
Future<void> onReady() async {
|
||||
fileFetcher.beginRemoteSession();
|
||||
await evtLoop.onReady();
|
||||
if (!isWeb) await localController.onReady();
|
||||
await remoteController.onReady();
|
||||
@@ -133,7 +140,11 @@ class FileModel {
|
||||
final id = int.tryParse(evt['id']?.toString() ?? '');
|
||||
if (id != null) {
|
||||
final err = evt['err']?.toString() ?? 'Unknown error';
|
||||
fileFetcher.tryCompleteRecursiveTaskWithError(id, err);
|
||||
if (id == 0) {
|
||||
fileFetcher.tryCompleteRemoteTaskWithError(err);
|
||||
} else {
|
||||
fileFetcher.tryCompleteRecursiveTaskWithError(id, err);
|
||||
}
|
||||
}
|
||||
// Always call jobController.jobError(evt) to ensure all error events are processed,
|
||||
// even if the event does not have a valid job ID. This allows for generic error handling
|
||||
@@ -350,6 +361,8 @@ class FileController {
|
||||
final history = RxList<String>.empty(growable: true);
|
||||
final sortBy = SortBy.name.obs;
|
||||
var sortAscending = true;
|
||||
// Incremented for each navigation; only the latest generation applies results.
|
||||
int _directoryRequestGeneration = 0;
|
||||
final JobController jobController;
|
||||
final WeakReference<FFI> rootState;
|
||||
|
||||
@@ -484,12 +497,19 @@ class FileController {
|
||||
path = "$path\\";
|
||||
}
|
||||
}
|
||||
final requestGeneration = ++_directoryRequestGeneration;
|
||||
try {
|
||||
final fd = await fileFetcher.fetchDirectory(path, isLocal, showHidden);
|
||||
if (requestGeneration != _directoryRequestGeneration) {
|
||||
return true;
|
||||
}
|
||||
fd.format(isWindows, sort: sortBy.value);
|
||||
directory.value = fd;
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (requestGeneration != _directoryRequestGeneration) {
|
||||
return true;
|
||||
}
|
||||
debugPrint("Failed to openDirectory $path: $e");
|
||||
return false;
|
||||
}
|
||||
@@ -541,6 +561,7 @@ class FileController {
|
||||
void initDirAndHome(Map<String, dynamic> evt) {
|
||||
try {
|
||||
final fd = FileDirectory.fromJson(jsonDecode(evt['value']));
|
||||
final isHomeResponse = fileFetcher.isLikelyRemoteHomeResponse(fd.path);
|
||||
fd.format(options.value.isWindows, sort: sortBy.value);
|
||||
if (fd.id > 0) {
|
||||
final jobIndex = jobController.getJob(fd.id);
|
||||
@@ -556,10 +577,12 @@ class FileController {
|
||||
debugPrint("update receive details: ${fd.path}");
|
||||
jobController.jobTable.refresh();
|
||||
}
|
||||
} else if (options.value.home.isEmpty) {
|
||||
} else if (options.value.home.isEmpty && isHomeResponse) {
|
||||
options.value.home = fd.path;
|
||||
debugPrint("init remote home: ${fd.path}");
|
||||
directory.value = fd;
|
||||
if (_directoryRequestGeneration == 0) {
|
||||
directory.value = fd;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("initDirAndHome err=$e");
|
||||
@@ -1362,16 +1385,78 @@ class JobResultListener<T> {
|
||||
}
|
||||
}
|
||||
|
||||
class _RemoteReadTask {
|
||||
final bool includeHidden;
|
||||
final Completer<FileDirectory> completer = Completer<FileDirectory>();
|
||||
final Completer<void> released = Completer<void>();
|
||||
late final Timer timer;
|
||||
|
||||
_RemoteReadTask(this.includeHidden);
|
||||
}
|
||||
|
||||
class FileFetcher {
|
||||
// Map<String,Completer<FileDirectory>> localTasks = {}; // now we only use read local dir sync
|
||||
Map<String, Completer<FileDirectory>> remoteTasks = {};
|
||||
final Map<String, _RemoteReadTask> _remoteReadTasks = {};
|
||||
Map<String, Completer<List<FileDirectory>>> remoteEmptyDirsTasks = {};
|
||||
Map<int, Completer<FileDirectory>> readRecursiveTasks = {};
|
||||
int _remoteSessionGeneration = 0;
|
||||
|
||||
final GetSessionID getSessionID;
|
||||
final ReadRemoteDirectory _readRemoteDirectory;
|
||||
SessionID get sessionId => getSessionID();
|
||||
|
||||
FileFetcher(this.getSessionID);
|
||||
FileFetcher(this.getSessionID, {ReadRemoteDirectory? readRemoteDirectory})
|
||||
: _readRemoteDirectory = readRemoteDirectory ??
|
||||
((sessionId, path, includeHidden) => bind.sessionReadRemoteDir(
|
||||
sessionId: sessionId,
|
||||
path: path,
|
||||
includeHidden: includeHidden));
|
||||
|
||||
bool hasPendingRemoteRead(String path) => _remoteReadTasks.containsKey(path);
|
||||
|
||||
bool isLikelyRemoteHomeResponse(String path) =>
|
||||
_remoteReadTasks.isEmpty ||
|
||||
(_remoteReadTasks.length == 1 &&
|
||||
hasPendingRemoteRead("") &&
|
||||
!hasPendingRemoteRead(path));
|
||||
|
||||
void beginRemoteSession() {
|
||||
_remoteSessionGeneration++;
|
||||
final pendingTasks = _remoteReadTasks.entries.toList(growable: false);
|
||||
for (final entry in pendingTasks) {
|
||||
final task = entry.value;
|
||||
if (!_removeRemoteReadTask(entry.key, task)) continue;
|
||||
task.completer.completeError(StateError(_kRemoteSessionChangedError));
|
||||
}
|
||||
}
|
||||
|
||||
_RemoteReadTask _registerRemoteReadTask(String path, bool includeHidden) {
|
||||
if (hasPendingRemoteRead(path)) {
|
||||
throw "Failed to registerReadTask, already have same read job";
|
||||
}
|
||||
final task = _RemoteReadTask(includeHidden);
|
||||
_remoteReadTasks[path] = task;
|
||||
task.timer = Timer(_kRemoteReadDirTimeout, () {
|
||||
if (!_removeRemoteReadTask(path, task)) return;
|
||||
task.completer.completeError("Failed to read dir, timeout");
|
||||
});
|
||||
return task;
|
||||
}
|
||||
|
||||
bool _removeRemoteReadTask(String path, _RemoteReadTask task) {
|
||||
if (!identical(_remoteReadTasks[path], task)) return false;
|
||||
_remoteReadTasks.remove(path);
|
||||
task.timer.cancel();
|
||||
task.released.complete();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _completeRemoteReadTask(String path, FileDirectory directory) {
|
||||
final task = _remoteReadTasks[path];
|
||||
if (task == null || !_removeRemoteReadTask(path, task)) return false;
|
||||
task.completer.complete(directory);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<List<FileDirectory>> registerReadEmptyDirsTask(
|
||||
bool isLocal, String path) {
|
||||
@@ -1391,23 +1476,6 @@ class FileFetcher {
|
||||
return c.future;
|
||||
}
|
||||
|
||||
Future<FileDirectory> registerReadTask(bool isLocal, String path) {
|
||||
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
||||
final tasks = remoteTasks; // bypass now
|
||||
if (tasks.containsKey(path)) {
|
||||
throw "Failed to registerReadTask, already have same read job";
|
||||
}
|
||||
final c = Completer<FileDirectory>();
|
||||
tasks[path] = c;
|
||||
|
||||
Timer(Duration(seconds: 2), () {
|
||||
tasks.remove(path);
|
||||
if (c.isCompleted) return;
|
||||
c.completeError("Failed to read dir, timeout");
|
||||
});
|
||||
return c.future;
|
||||
}
|
||||
|
||||
Future<FileDirectory> registerReadRecursiveTask(int actID) {
|
||||
final tasks = readRecursiveTasks;
|
||||
if (tasks.containsKey(actID)) {
|
||||
@@ -1445,27 +1513,37 @@ class FileFetcher {
|
||||
|
||||
tryCompleteTask(String? msg, String? isLocalStr) {
|
||||
if (msg == null || isLocalStr == null) return;
|
||||
late final Map<Object, Completer<FileDirectory>> tasks;
|
||||
try {
|
||||
final fd = FileDirectory.fromJson(jsonDecode(msg));
|
||||
if (fd.id > 0) {
|
||||
// fd.id > 0 is result for read recursive
|
||||
// to-do later,will be better if every fetch use ID,so that there will only one task map for read and recursive read
|
||||
tasks = readRecursiveTasks;
|
||||
final completer = tasks.remove(fd.id);
|
||||
completer?.complete(fd);
|
||||
} else if (fd.path.isNotEmpty) {
|
||||
// result for normal read dir
|
||||
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
||||
tasks = remoteTasks; // bypass now
|
||||
final completer = tasks.remove(fd.path);
|
||||
final completer = readRecursiveTasks.remove(fd.id);
|
||||
completer?.complete(fd);
|
||||
return;
|
||||
}
|
||||
if (isLocalStr == "false" && fd.path.isNotEmpty) {
|
||||
if (_completeRemoteReadTask(fd.path, fd)) {
|
||||
return;
|
||||
}
|
||||
// A Home request uses an empty path but returns its resolved path.
|
||||
if (isLikelyRemoteHomeResponse(fd.path)) {
|
||||
_completeRemoteReadTask("", fd);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("tryCompleteJob err: $e");
|
||||
}
|
||||
}
|
||||
|
||||
bool tryCompleteRemoteTaskWithError(String error) {
|
||||
if (_remoteReadTasks.length != 1) return false;
|
||||
final entry = _remoteReadTasks.entries.single;
|
||||
final task = entry.value;
|
||||
if (!_removeRemoteReadTask(entry.key, task)) return false;
|
||||
task.completer.completeError(error);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Complete a pending recursive read task with an error.
|
||||
// See FileModel.handleJobError() for why this is necessary.
|
||||
void tryCompleteRecursiveTaskWithError(int id, String error) {
|
||||
@@ -1506,9 +1584,26 @@ class FileFetcher {
|
||||
final fd = FileDirectory.fromJson(jsonDecode(res));
|
||||
return fd;
|
||||
} else {
|
||||
await bind.sessionReadRemoteDir(
|
||||
sessionId: sessionId, path: path, includeHidden: showHidden);
|
||||
return registerReadTask(isLocal, path);
|
||||
final remoteSessionGeneration = _remoteSessionGeneration;
|
||||
final pendingTask = _remoteReadTasks[path];
|
||||
if (pendingTask != null) {
|
||||
if (pendingTask.includeHidden == showHidden) {
|
||||
return pendingTask.completer.future;
|
||||
}
|
||||
await pendingTask.released.future;
|
||||
if (remoteSessionGeneration != _remoteSessionGeneration) {
|
||||
throw StateError(_kRemoteSessionChangedError);
|
||||
}
|
||||
return fetchDirectory(path, isLocal, showHidden);
|
||||
}
|
||||
final task = _registerRemoteReadTask(path, showHidden);
|
||||
unawaited(Future<void>.sync(
|
||||
() => _readRemoteDirectory(sessionId, path, showHidden))
|
||||
.catchError((Object error, StackTrace stackTrace) {
|
||||
if (!_removeRemoteReadTask(path, task)) return;
|
||||
task.completer.completeError(error, stackTrace);
|
||||
}));
|
||||
return task.completer.future;
|
||||
}
|
||||
} catch (e) {
|
||||
return Future.error(e);
|
||||
|
||||
@@ -738,9 +738,13 @@ class ServerModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> closeAll() async {
|
||||
await Future.wait(
|
||||
_clients.map((client) => bind.cmCloseConnection(connId: client.id)));
|
||||
/// `byOperator` false means the CM's window went away rather than a person asking for the
|
||||
/// peers to go. The sessions end either way; only the close reason differs, and with it
|
||||
/// whether the peer is allowed to reconnect. See `ipc::Data::CmWindowClosed`.
|
||||
Future<void> closeAll({bool byOperator = true}) async {
|
||||
await Future.wait(_clients.map((client) => byOperator
|
||||
? bind.cmCloseConnection(connId: client.id)
|
||||
: bind.cmCloseConnectionWindow(connId: client.id)));
|
||||
_clients.clear();
|
||||
tabController.state.value.tabs.clear();
|
||||
if (isAndroid) androidUpdatekeepScreenOn();
|
||||
|
||||
@@ -1373,6 +1373,10 @@ class RustdeskImpl {
|
||||
throw UnimplementedError("cmLoginRes");
|
||||
}
|
||||
|
||||
Future<void> cmCloseConnectionWindow({required int connId, dynamic hint}) {
|
||||
throw UnimplementedError("cmCloseConnectionWindow");
|
||||
}
|
||||
|
||||
Future<void> cmCloseConnection({required int connId, dynamic hint}) {
|
||||
throw UnimplementedError("cmCloseConnection");
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: 533883bcb0ffe91a9afdb13b8bac9b14b3e054ba
|
||||
resolved-ref: 8b774a66671cbb9bcb2631af6ac28f9bdd469ce3
|
||||
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
|
||||
281
flutter/test/file_model_test.dart
Normal file
281
flutter/test/file_model_test.dart
Normal file
@@ -0,0 +1,281 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_hbb/models/file_model.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
final _sessionId = UuidValue('00000000-0000-0000-0000-000000000000');
|
||||
|
||||
class _FakeFFI implements FFI {
|
||||
@override
|
||||
String id = 'test-peer';
|
||||
@override
|
||||
UuidValue get sessionId => _sessionId;
|
||||
@override
|
||||
late final FfiModel ffiModel = FfiModel(WeakReference(this));
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
FileController _createController(FileFetcher fileFetcher) {
|
||||
final ffi = _FakeFFI();
|
||||
return FileController(
|
||||
isLocal: false,
|
||||
getSessionID: () => _sessionId,
|
||||
rootState: WeakReference(ffi),
|
||||
jobController: JobController(() => _sessionId, () => null),
|
||||
fileFetcher: fileFetcher,
|
||||
getOtherSideDirectoryData: () =>
|
||||
DirectoryData(FileDirectory(), DirectoryOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
FileDirectory _directory(String path) => FileDirectory()..path = path;
|
||||
|
||||
String _directoryJson(String path) => jsonEncode({
|
||||
'id': 0,
|
||||
'path': path,
|
||||
'entries': <Object>[],
|
||||
});
|
||||
|
||||
class _SentRead {
|
||||
final String path;
|
||||
final bool includeHidden;
|
||||
|
||||
const _SentRead(this.path, this.includeHidden);
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('a fast remote response is matched after registration', () async {
|
||||
late final FileFetcher fileFetcher;
|
||||
fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, path, __) {
|
||||
fileFetcher.tryCompleteTask(_directoryJson(path), 'false');
|
||||
return Future<void>.value();
|
||||
},
|
||||
);
|
||||
final directory = await fileFetcher.fetchDirectory('/fast', false, false);
|
||||
expect(directory.path, '/fast');
|
||||
});
|
||||
|
||||
test('a send failure fails and removes its registered task', () async {
|
||||
final failure = StateError('send failed');
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, __, ___) => Future<void>.error(failure),
|
||||
);
|
||||
await expectLater(
|
||||
fileFetcher.fetchDirectory('/failed', false, false),
|
||||
throwsA(same(failure)),
|
||||
);
|
||||
expect(fileFetcher.hasPendingRemoteRead('/failed'), isFalse);
|
||||
});
|
||||
|
||||
test('a resolved Home path completes the sole empty-path request', () async {
|
||||
final sent = <_SentRead>[];
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, path, includeHidden) async {
|
||||
sent.add(_SentRead(path, includeHidden));
|
||||
},
|
||||
);
|
||||
final controller = _createController(fileFetcher);
|
||||
controller.directory.value = _directory('/initial');
|
||||
|
||||
final home = controller.openDirectory('');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
final response = _directoryJson('/home/user');
|
||||
controller.initDirAndHome({'value': response});
|
||||
expect(controller.homePath, '/home/user');
|
||||
expect(controller.directory.value.path, '/initial');
|
||||
fileFetcher.tryCompleteTask(response, 'false');
|
||||
expect(await home, isTrue);
|
||||
expect(controller.directory.value.path, '/home/user');
|
||||
expect(sent.single.path, isEmpty);
|
||||
});
|
||||
|
||||
test('an automatic response initializes Home without a pending request', () {
|
||||
final controller = _createController(FileFetcher(() => _sessionId));
|
||||
|
||||
controller.initDirAndHome({'value': _directoryJson('/home/user')});
|
||||
|
||||
expect(controller.homePath, '/home/user');
|
||||
expect(controller.directory.value.path, '/home/user');
|
||||
});
|
||||
|
||||
test('an exact path response is not taken by a pending Home request',
|
||||
() async {
|
||||
final sent = <_SentRead>[];
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, path, includeHidden) async {
|
||||
sent.add(_SentRead(path, includeHidden));
|
||||
},
|
||||
);
|
||||
final home = fileFetcher.fetchDirectory('', false, false);
|
||||
final regular = fileFetcher.fetchDirectory('/regular', false, false);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
var homeCompleted = false;
|
||||
home.then<void>((_) => homeCompleted = true);
|
||||
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/unmatched'), 'false');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(homeCompleted, isFalse);
|
||||
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/regular'), 'false');
|
||||
expect((await regular).path, '/regular');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(homeCompleted, isFalse);
|
||||
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/home/user'), 'false');
|
||||
expect((await home).path, '/home/user');
|
||||
expect(sent.map((request) => request.path), ['', '/regular']);
|
||||
});
|
||||
|
||||
test('a read error completes the sole pending request', () async {
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, __, ___) async {},
|
||||
);
|
||||
final request = fileFetcher.fetchDirectory('/denied', false, false);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
final expectation = expectLater(request, throwsA('permission denied'));
|
||||
|
||||
fileFetcher.tryCompleteRemoteTaskWithError('permission denied');
|
||||
|
||||
await expectation;
|
||||
expect(fileFetcher.hasPendingRemoteRead('/denied'), isFalse);
|
||||
});
|
||||
|
||||
test('same-path requests share the pending read', () async {
|
||||
final sent = <_SentRead>[];
|
||||
late final FileFetcher fileFetcher;
|
||||
fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, path, includeHidden) async {
|
||||
sent.add(_SentRead(path, includeHidden));
|
||||
},
|
||||
);
|
||||
final controller = _createController(fileFetcher);
|
||||
controller.directory.value = _directory('/initial');
|
||||
|
||||
final first = controller.openDirectory('/same');
|
||||
final waiting = controller.openDirectory('/same');
|
||||
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(sent.map((request) => request.path), ['/same']);
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
||||
expect(await first, isTrue);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(sent.map((request) => request.path), ['/same']);
|
||||
expect(await waiting, isTrue);
|
||||
expect(controller.directory.value.path, '/same');
|
||||
});
|
||||
|
||||
test('same-path requests with different hidden options are serialized',
|
||||
() async {
|
||||
final sent = <_SentRead>[];
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, path, includeHidden) async {
|
||||
sent.add(_SentRead(path, includeHidden));
|
||||
},
|
||||
);
|
||||
|
||||
final first = fileFetcher.fetchDirectory('/same', false, false);
|
||||
final second = fileFetcher.fetchDirectory('/same', false, true);
|
||||
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(sent.map((request) => request.includeHidden), [false]);
|
||||
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
||||
expect((await first).path, '/same');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(sent.map((request) => request.includeHidden), [false, true]);
|
||||
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
||||
expect((await second).path, '/same');
|
||||
});
|
||||
|
||||
test('session invalidation cancels active and waiting reads', () async {
|
||||
final sent = <_SentRead>[];
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, path, includeHidden) async {
|
||||
sent.add(_SentRead(path, includeHidden));
|
||||
},
|
||||
);
|
||||
final first = fileFetcher.fetchDirectory('/same', false, false);
|
||||
final waiting = fileFetcher.fetchDirectory('/same', false, true);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
final firstError = expectLater(first, throwsA(isA<StateError>()));
|
||||
final waitingError = expectLater(waiting, throwsA(isA<StateError>()));
|
||||
|
||||
fileFetcher.beginRemoteSession();
|
||||
|
||||
await firstError;
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(sent.map((request) => request.includeHidden), [false]);
|
||||
await waitingError;
|
||||
expect(fileFetcher.hasPendingRemoteRead('/same'), isFalse);
|
||||
final replacement = fileFetcher.fetchDirectory('/same', false, true);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(sent.map((request) => request.includeHidden), [false, true]);
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
||||
expect((await replacement).path, '/same');
|
||||
});
|
||||
|
||||
test('a late dispatch failure cannot remove a replacement task', () async {
|
||||
final dispatches = <Completer<void>>[];
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, __, ___) {
|
||||
final dispatch = Completer<void>();
|
||||
dispatches.add(dispatch);
|
||||
return dispatch.future;
|
||||
},
|
||||
);
|
||||
final first = fileFetcher.fetchDirectory('/same', false, false);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
final firstError = expectLater(first, throwsA(isA<StateError>()));
|
||||
fileFetcher.beginRemoteSession();
|
||||
await firstError;
|
||||
|
||||
final replacement = fileFetcher.fetchDirectory('/same', false, false);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
expect(dispatches, hasLength(2));
|
||||
dispatches.first.completeError(StateError('late dispatch failure'));
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(fileFetcher.hasPendingRemoteRead('/same'), isTrue);
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/same'), 'false');
|
||||
expect((await replacement).path, '/same');
|
||||
dispatches.last.complete();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
});
|
||||
|
||||
test('navigation ignores stale directory responses', () async {
|
||||
final fileFetcher = FileFetcher(
|
||||
() => _sessionId,
|
||||
readRemoteDirectory: (_, __, ___) async {},
|
||||
);
|
||||
final controller = _createController(fileFetcher);
|
||||
controller.directory.value = _directory('/initial');
|
||||
|
||||
final stale = controller.openDirectory('/stale');
|
||||
final latest = controller.openDirectory('/latest');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/latest'), 'false');
|
||||
expect(await latest, isTrue);
|
||||
fileFetcher.tryCompleteTask(_directoryJson('/stale'), 'false');
|
||||
expect(await stale, isTrue);
|
||||
|
||||
expect(controller.directory.value.path, '/latest');
|
||||
});
|
||||
}
|
||||
@@ -48,6 +48,7 @@ pub struct Capturer {
|
||||
duplication: ComPtr<IDXGIOutputDuplication>,
|
||||
fastlane: bool,
|
||||
surface: ComPtr<IDXGISurface>,
|
||||
readable: ComPtr<ID3D11Texture2D>,
|
||||
texture: ComPtr<ID3D11Texture2D>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
@@ -163,6 +164,7 @@ impl Capturer {
|
||||
duplication: ComPtr(duplication),
|
||||
fastlane: desc.DesktopImageInSystemMemory == TRUE,
|
||||
surface: ComPtr(ptr::null_mut()),
|
||||
readable: ComPtr(ptr::null_mut()),
|
||||
texture: ComPtr(ptr::null_mut()),
|
||||
width: display.width() as usize,
|
||||
height: display.height() as usize,
|
||||
@@ -346,19 +348,19 @@ impl Capturer {
|
||||
if self.fastlane {
|
||||
wrap_hresult((*self.duplication.0).MapDesktopSurface(&mut rect))?;
|
||||
} else {
|
||||
self.surface = ComPtr(self.ohgodwhat(frame.0)?);
|
||||
self.ohgodwhat(frame.0)?;
|
||||
wrap_hresult((*self.surface.0).Map(&mut rect, DXGI_MAP_READ))?;
|
||||
}
|
||||
Ok((rect.pBits, rect.Pitch))
|
||||
}
|
||||
|
||||
// copy from GPU memory to system memory
|
||||
unsafe fn ohgodwhat(&mut self, frame: *mut IDXGIResource) -> io::Result<*mut IDXGISurface> {
|
||||
unsafe fn ohgodwhat(&mut self, frame: *mut IDXGIResource) -> io::Result<()> {
|
||||
let mut texture: *mut ID3D11Texture2D = ptr::null_mut();
|
||||
(*frame).QueryInterface(
|
||||
wrap_hresult((*frame).QueryInterface(
|
||||
&IID_ID3D11Texture2D,
|
||||
&mut texture as *mut *mut _ as *mut *mut _,
|
||||
);
|
||||
))?;
|
||||
let texture = ComPtr(texture);
|
||||
|
||||
#[allow(invalid_value)]
|
||||
@@ -370,24 +372,37 @@ impl Capturer {
|
||||
texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
texture_desc.MiscFlags = 0;
|
||||
|
||||
let mut readable = ptr::null_mut();
|
||||
wrap_hresult((*self.device.0).CreateTexture2D(
|
||||
&mut texture_desc,
|
||||
ptr::null(),
|
||||
&mut readable,
|
||||
))?;
|
||||
(*readable).SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
|
||||
let readable = ComPtr(readable);
|
||||
// Avoid per-frame staging texture allocation and the kernel allocation churn it causes.
|
||||
let mut current: D3D11_TEXTURE2D_DESC = mem::zeroed();
|
||||
if !self.surface.is_null() {
|
||||
(*self.readable.0).GetDesc(&mut current);
|
||||
}
|
||||
if current.Width != texture_desc.Width
|
||||
|| current.Height != texture_desc.Height
|
||||
|| current.Format != texture_desc.Format
|
||||
{
|
||||
let mut readable = ptr::null_mut();
|
||||
wrap_hresult((*self.device.0).CreateTexture2D(
|
||||
&mut texture_desc,
|
||||
ptr::null(),
|
||||
&mut readable,
|
||||
))?;
|
||||
(*readable).SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
|
||||
let readable = ComPtr(readable);
|
||||
|
||||
let mut surface = ptr::null_mut();
|
||||
(*readable.0).QueryInterface(
|
||||
&IID_IDXGISurface,
|
||||
&mut surface as *mut *mut _ as *mut *mut _,
|
||||
);
|
||||
let mut surface = ptr::null_mut();
|
||||
wrap_hresult((*readable.0).QueryInterface(
|
||||
&IID_IDXGISurface,
|
||||
&mut surface as *mut *mut _ as *mut *mut _,
|
||||
))?;
|
||||
|
||||
(*self.context.0).CopyResource(readable.0 as *mut _, texture.0 as *mut _);
|
||||
self.readable = readable;
|
||||
self.surface = ComPtr(surface);
|
||||
}
|
||||
|
||||
Ok(surface)
|
||||
(*self.context.0).CopyResource(self.readable.0 as *mut _, texture.0 as *mut _);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout: UINT) -> io::Result<Frame<'a>> {
|
||||
@@ -485,10 +500,10 @@ impl Capturer {
|
||||
}
|
||||
|
||||
let mut texture: *mut ID3D11Texture2D = ptr::null_mut();
|
||||
(*frame.0).QueryInterface(
|
||||
wrap_hresult((*frame.0).QueryInterface(
|
||||
&IID_ID3D11Texture2D,
|
||||
&mut texture as *mut *mut _ as *mut *mut _,
|
||||
);
|
||||
))?;
|
||||
let texture = ComPtr(texture);
|
||||
self.texture = texture;
|
||||
|
||||
|
||||
13
res/vcpkg/aom/aom-uninitialized-pointer-3.9.1.diff
Normal file
13
res/vcpkg/aom/aom-uninitialized-pointer-3.9.1.diff
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/build/cmake/aom_configure.cmake b/build/cmake/aom_configure.cmake
|
||||
index aaef2c310..5500ad4a3 100644
|
||||
--- a/build/cmake/aom_configure.cmake
|
||||
+++ b/build/cmake/aom_configure.cmake
|
||||
@@ -309,6 +309,8 @@ if(MSVC)
|
||||
|
||||
# Disable MSVC warnings that suggest making code non-portable.
|
||||
add_compiler_flag_if_supported("/wd4996")
|
||||
+ # Disable MSVC warnings for potentially uninitialized local pointer variable.
|
||||
+ add_compiler_flag_if_supported("/wd4703")
|
||||
if(ENABLE_WERROR)
|
||||
add_compiler_flag_if_supported("/WX")
|
||||
endif()
|
||||
@@ -1,7 +1,7 @@
|
||||
diff --git a/build/cmake/aom_configure.cmake b/build/cmake/aom_configure.cmake
|
||||
diff --git a/cmake/aom_configure.cmake b/cmake/aom_configure.cmake
|
||||
index aaef2c310..5500ad4a3 100644
|
||||
--- a/build/cmake/aom_configure.cmake
|
||||
+++ b/build/cmake/aom_configure.cmake
|
||||
--- a/cmake/aom_configure.cmake
|
||||
+++ b/cmake/aom_configure.cmake
|
||||
@@ -309,6 +309,8 @@ if(MSVC)
|
||||
|
||||
# Disable MSVC warnings that suggest making code non-portable.
|
||||
|
||||
@@ -9,25 +9,24 @@ get_filename_component(PERL_PATH ${PERL} DIRECTORY)
|
||||
vcpkg_add_to_path(${PERL_PATH})
|
||||
|
||||
if(DEFINED ENV{USE_AOM_391})
|
||||
set(AOM_CONFIG_PATH "lib/cmake/aom")
|
||||
vcpkg_from_git(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
URL "https://aomedia.googlesource.com/aom"
|
||||
REF 8ad484f8a18ed1853c094e7d3a4e023b2a92df28 # 3.9.1
|
||||
PATCHES
|
||||
aom-uninitialized-pointer.diff
|
||||
aom-uninitialized-pointer-3.9.1.diff
|
||||
aom-avx2.diff
|
||||
aom-install.diff
|
||||
)
|
||||
else()
|
||||
set(AOM_CONFIG_PATH "lib/cmake/AOM")
|
||||
vcpkg_from_git(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
URL "https://aomedia.googlesource.com/aom"
|
||||
REF 10aece4157eb79315da205f39e19bf6ab3ee30d0 # 3.12.1
|
||||
REF 03087864cf4bea6abb0d28f95cf7843511413d8f # 3.14.1
|
||||
PATCHES
|
||||
aom-uninitialized-pointer.diff
|
||||
# aom-avx2.diff
|
||||
# Can be dropped when https://bugs.chromium.org/p/aomedia/issues/detail?id=3029 is merged into the upstream
|
||||
aom-install.diff
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -67,7 +66,7 @@ if(VCPKG_TARGET_IS_WINDOWS)
|
||||
endif()
|
||||
|
||||
# Move cmake configs
|
||||
vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/${PORT})
|
||||
vcpkg_cmake_config_fixup(CONFIG_PATH ${AOM_CONFIG_PATH})
|
||||
|
||||
# Remove duplicate files
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aom",
|
||||
"version-semver": "3.12.1",
|
||||
"version-semver": "3.14.1",
|
||||
"port-version": 0,
|
||||
"description": "AV1 codec library",
|
||||
"homepage": "https://aomedia.googlesource.com/aom",
|
||||
|
||||
@@ -2197,6 +2197,15 @@ pub fn cm_close_connection(conn_id: i32) {
|
||||
crate::ui_cm_interface::close(conn_id);
|
||||
}
|
||||
|
||||
/// The CM window closed. On Linux that is ambiguous - a logout closes it the same way a person
|
||||
/// does - so it ends the session without the no-retry reason; elsewhere it is a plain close.
|
||||
pub fn cm_close_connection_window(conn_id: i32) {
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::ui_cm_interface::close_window(conn_id);
|
||||
#[cfg(all(not(target_os = "linux"), not(target_os = "ios")))]
|
||||
crate::ui_cm_interface::close(conn_id);
|
||||
}
|
||||
|
||||
pub fn cm_remove_disconnected_connection(conn_id: i32) {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
crate::ui_cm_interface::remove(conn_id);
|
||||
|
||||
@@ -501,6 +501,15 @@ pub enum Data {
|
||||
ControlPermissionsRemoteModify(Option<bool>),
|
||||
#[cfg(target_os = "windows")]
|
||||
FileTransferEnabledState(Option<bool>),
|
||||
/// CM -> server: the connection manager's WINDOW went away, which is not the same event
|
||||
/// as the operator disconnecting a peer. Linux only, and deliberately: there a session
|
||||
/// logout closes every window, and the close arrives at the CM indistinguishable from a
|
||||
/// person clicking it - measured on KDE, the CM gets no signal and logind still reports the
|
||||
/// session active. So the ambiguous case ends the session WITHOUT the no-retry reason and
|
||||
/// the peer is allowed to reconnect (landing on the greeter after a logout), while the
|
||||
/// explicit Disconnect button keeps sending `Close` and kicking for good.
|
||||
#[cfg(target_os = "linux")]
|
||||
CmWindowClosed,
|
||||
// --- DRM/KMS capture (opt-in `drm` feature) over the `_drm` service-scoped channel ---
|
||||
// All of the following are `cfg(all(linux, drm))`, so the drm-off IPC wire is byte-identical
|
||||
// to upstream. Protocol on `_drm`: on connect the root service sends `DrmDisplayList`, the
|
||||
|
||||
@@ -45,6 +45,7 @@ mod th;
|
||||
mod tr;
|
||||
mod tw;
|
||||
mod uk;
|
||||
mod ur;
|
||||
mod vi;
|
||||
mod ta;
|
||||
mod ge;
|
||||
@@ -80,6 +81,7 @@ pub const LANGS: &[(&str, &str)] = &[
|
||||
("ko", "한국어"),
|
||||
("kz", "Қазақ"),
|
||||
("uk", "Українська"),
|
||||
("ur", "اردو"),
|
||||
("fa", "فارسی"),
|
||||
("ca", "Català"),
|
||||
("el", "Ελληνικά"),
|
||||
@@ -208,6 +210,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
|
||||
"be" => be::T.deref(),
|
||||
"he" => he::T.deref(),
|
||||
"hr" => hr::T.deref(),
|
||||
"ur" => ur::T.deref(),
|
||||
"sc" => sc::T.deref(),
|
||||
"ta" => ta::T.deref(),
|
||||
"ge" => ge::T.deref(),
|
||||
|
||||
750
src/lang/ur.rs
Normal file
750
src/lang/ur.rs
Normal file
@@ -0,0 +1,750 @@
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "حالت"),
|
||||
("Your Desktop", "آپ کا ڈیسک ٹاپ"),
|
||||
("desk_tip", ""),
|
||||
("Password", "پاس ورڈ"),
|
||||
("Ready", "تیار"),
|
||||
("Established", "قائم کیا گیا"),
|
||||
("connecting_status", "کنیکٹنگ_سٹیٹس"),
|
||||
("Enable service", "سروس کو فعال کریں"),
|
||||
("Start service", "سروس شروع کریں"),
|
||||
("Service is running", "سروس چل رہی ہے"),
|
||||
("Service is not running", "سروس نہیں چل رہی ہے"),
|
||||
("not_ready_status", ""),
|
||||
("Control Remote Desktop", "ریموٹ ڈیسک ٹاپ کو کنٹرول کریں"),
|
||||
("Transfer file", "فائل منتقل کریں"),
|
||||
("Connect", "کنیکٹ کریں"),
|
||||
("Recent sessions", "حالیہ سیشنز"),
|
||||
("Address book", "پتہ کتاب"),
|
||||
("Confirmation", "تصدیق"),
|
||||
("TCP tunneling", "TCP ٹنلینگ"),
|
||||
("Remove", "ہٹائیں"),
|
||||
("Refresh random password", "بے ترتیب پاس ورڈ ریفریش کریں"),
|
||||
("Set your own password", "اپنا پاس ورڈ خود سیٹ کریں"),
|
||||
("Enable keyboard/mouse", "ماوس/کی بورڈ کو فعال کریں"),
|
||||
("Enable clipboard", "کلپ بورڈ کو فعال کریں"),
|
||||
("Enable file transfer", "فائل ٹرانسفر کو فعال کریں"),
|
||||
("Enable TCP tunneling", "TCP ٹنلینگ کو فعال کریں"),
|
||||
("IP Whitelisting", "IP وائٹ لسٹنگ"),
|
||||
("ID/Relay Server", "ID/ریلے سرور"),
|
||||
("Import server config", "سرور کی تشکیل درآمد کریں"),
|
||||
("Export Server Config", "سرور کی تشکیل برآمد کریں"),
|
||||
("Import server configuration successfully", "سرور کی تشکیل کامیابی سے درآمد ہو گئی"),
|
||||
("Export server configuration successfully", "سرور کی تشکیل کامیابی سے برآمد ہو گئی"),
|
||||
("Invalid server configuration", "سرور کی تشکیل غلط ہے"),
|
||||
("Clipboard is empty", "کلپ بورڈ خالی ہے"),
|
||||
("Stop service", "سروس بند کریں"),
|
||||
("Change ID", "ٰID تبدیل کریں"),
|
||||
("Your new ID", "آپ کی نئی ID"),
|
||||
("length %min% to %max%", "لمبائی %min% سے %max%"),
|
||||
("starts with a letter", "حرف سے شروع ہوتا ہے"),
|
||||
("allowed characters", "اجازت یافتہ حروف"),
|
||||
("id_change_tip", ""),
|
||||
("Website", "ویب سائٹ"),
|
||||
("About", "کے بارے میں"),
|
||||
("Slogan_tip", "سلوگن_ٹپ"),
|
||||
("Privacy Statement", "رازداری کا بیان"),
|
||||
("License", "لائسنس"),
|
||||
("Mute", "خاموش"),
|
||||
("Build Date", "بنیاد کی تاریخ"),
|
||||
("Version", "ورژن"),
|
||||
("Home", "گھر"),
|
||||
("Audio Input", "آڈیو ان پٹ"),
|
||||
("Enhancements", "اضافہ"),
|
||||
("Hardware Codec", "ہارڈ ویئر کوڈیک"),
|
||||
("Adaptive bitrate", "ایڈاپٹیو بٹ ریٹ"),
|
||||
("ID Server", "ID سرور"),
|
||||
("Relay Server", "ریلے سرور"),
|
||||
("API Server", "اے پی آئی سرور"),
|
||||
("invalid_http", "غلط HTTP"),
|
||||
("Invalid IP", "غلط IP"),
|
||||
("Invalid format", "غلط فارمیٹ"),
|
||||
("server_not_support", "سرور کی حمایت نہیں ہے"),
|
||||
("Not available", "دستیاب نہیں"),
|
||||
("Too frequent", "بہت اکثر"),
|
||||
("Cancel", "منسوخ کریں"),
|
||||
("Skip", "چھوڑ دیں"),
|
||||
("Close", "بند کریں"),
|
||||
("Retry", "دوبارہ کوشش کریں"),
|
||||
("OK", "ٹھیک ہے"),
|
||||
("Password Required", "پاس ورڈ درکار ہے"),
|
||||
("Please enter your password", "اپنا پاس ورڈ درج کریں"),
|
||||
("Remember password", "پاس ورڈ یاد رکھیں"),
|
||||
("Wrong Password", "غلط پاس ورڈ"),
|
||||
("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 منٹ بعد کوشش کریں"),
|
||||
("Login Error", "لاگ ان کی خرابی"),
|
||||
("Successful", "کامیاب"),
|
||||
("Connected, waiting for image...", "کنیکٹ ہو گیا، تصویر کے لیے انتظار کر رہا ہے..."),
|
||||
("Name", "نام"),
|
||||
("Type", "ٹائپ"),
|
||||
("Modified", "تبدیل"),
|
||||
("Size", "حجم"),
|
||||
("Show Hidden Files", "خفیہ فائلیں دکھائیں"),
|
||||
("Receive", "وصول کریں"),
|
||||
("Send", "بھیجیں"),
|
||||
("Refresh File", "فائل ریفریش کریں"),
|
||||
("Local", "مقامی"),
|
||||
("Remote", "ریموٹ"),
|
||||
("Remote Computer", "ریموٹ کمپیوٹر"),
|
||||
("Local Computer", "مقامی کمپیوٹر"),
|
||||
("Confirm Delete", "حذف کی تصدیق کریں"),
|
||||
("Delete", "حذف کریں"),
|
||||
("Properties", "خصوصیات"),
|
||||
("Multi Select", "ملٹی سلیکٹ"),
|
||||
("Select All", "سب کو منتخب کریں"),
|
||||
("Unselect All", "سب کو غیر منتخب کریں"),
|
||||
("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?", "کیا آپ واقعی اس ڈائرکٹری کی فائل کو حذف کرنا چاہتے ہیں؟"),
|
||||
("Do this for all conflicts", "تمام تضادوں کے لئے یہ کرو"),
|
||||
("This is irreversible!", "ینہ واپس نہ لایا جا سکتا!"),
|
||||
("Deleting", "حذف ہو رہا ہے..."),
|
||||
("files", "فائلیں"),
|
||||
("Waiting", "انتظار کر رہا ہے"),
|
||||
("Finished", "ختم ہو گیا"),
|
||||
("Speed", "رفتار"),
|
||||
("Custom Image Quality", "کسٹم تصویر کی معیار"),
|
||||
("Privacy mode", "موڈ رازداری "),
|
||||
("Block user input", "یوزر ان پٹ کو بلاک کریں"),
|
||||
("Unblock user input", "یوزر ان پٹ کو غیر بلاک کریں"),
|
||||
("Adjust Window", "ونڈو کو سیدھا کریں"),
|
||||
("Original", "اصل"),
|
||||
("Shrink", "کم کریں"),
|
||||
("Stretch", "وسیع کریں"),
|
||||
("Scrollbar", "اسکرول بار"),
|
||||
("ScrollAuto", "آٹو اسکرول"),
|
||||
("Good image quality", "اچھی تصویر کی معیار"),
|
||||
("Balanced", "متوازن"),
|
||||
("Optimize reaction time", "ریکشن کے وقت کو بہتر بنائیں"),
|
||||
("Custom", "کسٹم"),
|
||||
("Show remote cursor", "ریموٹ کرسر دکھائیں"),
|
||||
("Show quality monitor", "معیار کا مانیٹر دکھائیں"),
|
||||
("Disable clipboard", "کلپ بورڈ کو غیر فعال کریں"),
|
||||
("Lock after session end", "سیشن ختم ہونے کے بعد لاک کریں"),
|
||||
("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del داخل کریں"),
|
||||
("Insert Lock", "لاک داخل کریں"),
|
||||
("Refresh", "ریفریش کریں"),
|
||||
("ID does not exist", "ID موجود نہیں ہے"),
|
||||
("Failed to connect to rendezvous server", "رینڈوز سرور سے کنکشن کرنے میں ناکام"),
|
||||
("Please try later", "براہِ مہربانی بعد میں کوشش کریں"),
|
||||
("Remote desktop is offline", "ریموٹ ڈیسکٹاپ آف لائن ہے"),
|
||||
("Key mismatch", "کلید ممچ نہیں"),
|
||||
("Timeout", "وقت کی ختم"),
|
||||
("Failed to connect to relay server", "ریلے سرور سے کنکشن کرنے میں ناکام"),
|
||||
("Failed to connect via rendezvous server", "رینڈوز سرور سے کنکشن کرنے میں ناکام"),
|
||||
("Failed to connect via relay server", "ریلے سرور سے کنکشن کرنے میں ناکام"),
|
||||
("Failed to make direct connection to remote desktop", "ریموٹ ڈیسکٹاپ سے مستقیم کنکشن قائم کرنے میں ناکام"),
|
||||
("Set Password", "پاس ورڈ مرتب کریں"),
|
||||
("OS Password", "OS پاس ورڈ"),
|
||||
("install_tip", "انسٹال کرنے کا مشورہ"),
|
||||
("Click to upgrade", "اپگریڈ کرنے کے لئے کلک کریں"),
|
||||
("Configure", "ترتیب دینا"),
|
||||
("config_acc", ""),
|
||||
("config_screen", ""),
|
||||
("Installing ...", "انسٹال ہو رہا ہے..."),
|
||||
("Install", "انسٹال کریں"),
|
||||
("Installation", "انسٹالیشن"),
|
||||
("Installation Path", "انسٹالیشن کا راستہ"),
|
||||
("Create start menu shortcuts", "اسٹارٹ مینو شارٹ کٹس بنائیں"),
|
||||
("Create desktop icon", "ڈیسکٹاپ آئیکن بنائیں"),
|
||||
("agreement_tip", ""),
|
||||
("Accept and Install", "قبول کریں اور انسٹال کریں"),
|
||||
("End-user license agreement", "اختتامی صارف کے لائسنس کا معاہدہ"),
|
||||
("Generating ...", "بنا رہے ہیں..."),
|
||||
("Your installation is lower version.", "آپ کی تنصیب کم ورژن ہے۔"),
|
||||
("Please install the latest version.", "براہِ مہربانی تازہ ترین ورژن انسٹال کریں۔"),
|
||||
("not_close_tcp_tip", ""),
|
||||
("Listening ...", "سن رہا ہے..."),
|
||||
("Remote Host", "ریموٹ میزبان"),
|
||||
("Remote Port", "ریموٹ پورٹ"),
|
||||
("Action", "عمل"),
|
||||
("Add", "شامل کریں"),
|
||||
("Local Port", "مقامی پورٹ"),
|
||||
("Local Address", "مقامی ایڈریس"),
|
||||
("Change Local Port", "مقامی پورٹ تبدیل کریں"),
|
||||
("setup_server_tip", "سرور کی ترتیب کا مشورہ"),
|
||||
("Too short, at least 6 characters.", "بہت چھوٹا، کم از کم 6 حروف۔"),
|
||||
("The confirmation is not identical.", "تصدیق ایک جیسی نہیں ہے۔"),
|
||||
("Permissions", "اجازتیں"),
|
||||
("Accept", "قبول کریں"),
|
||||
("Dismiss", "مسترد کریں"),
|
||||
("Disconnect", "منقطع کریں"),
|
||||
("Enable file copy and paste", "فائل کاپی اور پیسٹ فعال کریں"),
|
||||
("Connected", "منسلک ہے"),
|
||||
("Direct and encrypted connection", "براہِ راست اور خفیہ کنکشن"),
|
||||
("Relayed and encrypted connection", "آگے بڑھا ہوا اور خفیہ کنکشن"),
|
||||
("Direct and unencrypted connection", "براہِ راست اور غیر خفیہ کنکشن"),
|
||||
("Relayed and unencrypted connection", "آگے بڑھا ہوا اور غیر خفیہ کنکشن"),
|
||||
("Enter Remote ID", "ریموٹ آئی ڈی درج کریں"),
|
||||
("Enter your password", "اپنا پاس ورڈ درج کریں"),
|
||||
("Logging in...", "لاگ ان ہو رہا ہے..."),
|
||||
("Enable RDP session sharing", "RDP سیشن شیئرنگ کو فعال کریں"),
|
||||
("Auto Login", "خودکار لاگ ان"),
|
||||
("Enable direct IP access", "براہِ راست IP رسائی کو فعال کریں"),
|
||||
("Rename", "نام تبدیل کریں"),
|
||||
("Space", "جگہ"),
|
||||
("Create desktop shortcut", "ڈیسک ٹاپ شارٹ کٹ بنائیں"),
|
||||
("Change Path", "راستہ تبدیل کریں"),
|
||||
("Create Folder", "فولڈر بنائیں"),
|
||||
("Please enter the folder name", "فولڈر کا نام درج کریں"),
|
||||
("Fix it", "ٹھیک کریں"),
|
||||
("Warning", "انتباہ"),
|
||||
("Login screen using Wayland is not supported", "Wayland کا استعمال کرتے ہوئے لاگ ان اسکرین کی حمایت نہیں کی جاتی ہے"),
|
||||
("Reboot required", "دوبارہ شروع کرنے کی ضرورت ہے"),
|
||||
("Unsupported display server", "غیر معاون ڈسپلے سرور"),
|
||||
("x11 expected", "x11 کی توقع ہے"),
|
||||
("Port", "پورٹ"),
|
||||
("Settings", "ترتیبات"),
|
||||
("Username", "یوزر نیم"),
|
||||
("Invalid port", "غلط پورٹ"),
|
||||
("Closed manually by the peer", "پیر کی طرف سے دستی طور پر بند"),
|
||||
("Enable remote configuration modification", "ریموٹ کنفیگریشن ترمیم کو فعال کریں"),
|
||||
("Run without install", "انسٹال کے بغیر چلائیں"),
|
||||
("Connect via relay", "ریلے کے ذریعے کنیکٹ کریں"),
|
||||
("Always connect via relay", "ہمیشہ ریلے کے ذریعے کنیکٹ کریں"),
|
||||
("whitelist_tip", ""),
|
||||
("Login", "لاگ ان کریں"),
|
||||
("Verify", "تصدیق کریں"),
|
||||
("Remember me", "یاد رکھیں"),
|
||||
("Trust this device", "اس ڈیوائس پر اعتماد کریں"),
|
||||
("Verification code", "تصدیق کوڈ"),
|
||||
("verification_tip", "تصدیق کا مشورہ"),
|
||||
("Logout", "لاگ آؤٹ"),
|
||||
("Tags", "ٹیگز"),
|
||||
("Search ID", "ID تلاش کریں"),
|
||||
("whitelist_sep", ""),
|
||||
("Add ID", "ID شامل کریں"),
|
||||
("Add Tag", "ٹیگ شامل کریں"),
|
||||
("Unselect all tags", "تمام ٹیگز کو غیر منتخب کریں"),
|
||||
("Network error", "نیٹ ورک کی خرابی"),
|
||||
("Username missed", "یوزر نیم چھوٹ گیا"),
|
||||
("Password missed", "پاس ورڈ چھوٹ گیا"),
|
||||
("Wrong credentials", "غلط اسناد"),
|
||||
("The verification code is incorrect or has expired", "تصدیق کوڈ غلط ہے یا ختم ہو چکا ہے"),
|
||||
("Edit Tag", "ٹیگ ایڈٹ کریں"),
|
||||
("Forget Password", "پاس ورڈ بھول گئے"),
|
||||
("Favorites", "پسندیدہ"),
|
||||
("Add to Favorites", "پسندیدہ میں شامل کریں"),
|
||||
("Remove from Favorites", "پسندیدہ سے ہٹائیں"),
|
||||
("Empty", "خالی"),
|
||||
("Invalid folder name", "فولڈر کا نام غلط ہے"),
|
||||
("Socks5 Proxy", "پروکسی ساکس5"),
|
||||
("Socks5/Http(s) Proxy", "ساکس5/Http(s) پروکسی"),
|
||||
("Discovered", "دریافت شدہ"),
|
||||
("install_daemon_tip", ""),
|
||||
("Remote ID", "ریموٹ ID"),
|
||||
("Paste", "چسپاں کریں"),
|
||||
("Paste here?", "یہاں چسپاں کریں؟"),
|
||||
("Are you sure to close the connection?", "کیا آپ واقعی کنکشن بند کرنا چاہتے ہیں؟"),
|
||||
("Download new version", "نیا ورژن ڈاؤن لوڈ کریں"),
|
||||
("Touch mode", "تچ موڈ"),
|
||||
("Mouse mode", "ماؤس موڈ"),
|
||||
("One-Finger Tap", "ایک انگلی سے ٹیپ"),
|
||||
("Left Mouse", "بائیں ماؤس"),
|
||||
("One-Long Tap", "ایک لمبا ٹیپ"),
|
||||
("Two-Finger Tap", "دو انگلیوں سے ٹیپ"),
|
||||
("Right Mouse", "دائیں ماؤس"),
|
||||
("One-Finger Move", "ایک انگلی سے حرکت"),
|
||||
("Double Tap & Move", "دو بار ٹیپ اور حرکت"),
|
||||
("Mouse Drag", "ماؤس گھسیٹنا"),
|
||||
("Three-Finger vertically", "تین انگلیوں سے عمودی"),
|
||||
("Mouse Wheel", "ماؤس ویل"),
|
||||
("Two-Finger Move", "دو انگلیوں سے حرکت"),
|
||||
("Canvas Move", "کینوس حرکت"),
|
||||
("Pinch to Zoom", "زوم کرنے کے لیے چوٹکی"),
|
||||
("Canvas Zoom", "کینوس زوم"),
|
||||
("Reset canvas", "کینوس ری سیٹ کریں"),
|
||||
("No permission of file transfer", "فائل ٹرانسفر کی اجازت نہیں ہے"),
|
||||
("Note", "نوٹ"),
|
||||
("Connection", "رابطہ"),
|
||||
("Share screen", "سکرین شیئر کریں"),
|
||||
("Chat", "بات چیت"),
|
||||
("Total", "کل"),
|
||||
("items", "اشیاء"),
|
||||
("Selected", "منتخب شدہ"),
|
||||
("Screen Capture", "سکرین قابض"),
|
||||
("Input Control", "درآمد کنٹرول"),
|
||||
("Audio Capture", "آڈیو قابض"),
|
||||
("Do you accept?", "کیا آپ قبول کرتے ہیں؟"),
|
||||
("Open System Setting", "سسٹم کی ترتیبات کھولیں"),
|
||||
("How to get Android input permission?", "Android کی درآمد کی اجازت کیسے حاصل کریں؟"),
|
||||
("android_input_permission_tip1", ""),
|
||||
("android_input_permission_tip2", ""),
|
||||
("android_new_connection_tip", ""),
|
||||
("android_service_will_start_tip", ""),
|
||||
("android_stop_service_tip", ""),
|
||||
("android_version_audio_tip", ""),
|
||||
("android_start_service_tip", ""),
|
||||
("android_permission_may_not_change_tip", ""),
|
||||
("Account", "کھاتا"),
|
||||
("Overwrite", "اوور رائٹ کریں"),
|
||||
("This file exists, skip or overwrite this file?", "یہ فائل موجود ہے، اس فائل کو چھوڑیں یا اوور رائٹ کریں؟"),
|
||||
("Quit", "بند کریں"),
|
||||
("Help", "مدد"),
|
||||
("Failed", "ناکام"),
|
||||
("Succeeded", "کامیاب ہو گیا"),
|
||||
("Someone turns on privacy mode, exit", "کوئی پرائیویسی موڈ آن کرتا ہے، باہر نکلیں"),
|
||||
("Unsupported", "غیر معاون"),
|
||||
("Peer denied", "ہم منسب نے انکار کر دیا"),
|
||||
("Please install plugins", "براہِ مہربانی پلگ ان انسٹال کریں"),
|
||||
("Peer exit", "ہم منسب باہر نکل گیا"),
|
||||
("Failed to turn off", "بند کرنے میں ناکام"),
|
||||
("Turned off", "بند کر دیا"),
|
||||
("Language", "زبان"),
|
||||
("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", "بوٹ پر سکرین شیئرنگ سروس شروع کریں، خاص اجازتوں کی ضرورت ہے"),
|
||||
("Connection not allowed", "جڑنے کی اجازت نہیں ہے"),
|
||||
("Legacy mode", "میراث موڈ"),
|
||||
("Map mode", "میپ موڈ"),
|
||||
("Translate mode", "ترجمہ موڈ"),
|
||||
("Use permanent password", "مستقل پاس ورڈ استعمال کریں"),
|
||||
("Use both passwords", "دونوں پاس ورڈ استعمال کریں"),
|
||||
("Set permanent password", "مستقل پاس ورڈ مرتب کریں"),
|
||||
("Enable remote restart", "ریموٹ ری اسٹارٹ کو فعال کریں"),
|
||||
("Restart remote device", "ریموٹ ڈیوائس کو ری اسٹارٹ کریں"),
|
||||
("Are you sure you want to restart", "کیا آپ واقعی ری اسٹارٹ کرنا چاہتے ہیں؟"),
|
||||
("Restarting remote device", "ریموٹ ڈیوائس ری اسٹارٹ ہو رہی ہے"),
|
||||
("remote_restarting_tip", ""),
|
||||
("Copied", "نقل ہو گیا"),
|
||||
("Exit Fullscreen", "مکمل سکرین سے باہر نکلیں"),
|
||||
("Fullscreen", "مکمل سکرین"),
|
||||
("Mobile Actions", "موبائل کے عمل"),
|
||||
("Select Monitor", "مانیٹر منتخب کریں"),
|
||||
("Control Actions", "عمل کو قابو کریں"),
|
||||
("Display Settings", "ڈسپلے کی ترتیبات"),
|
||||
("Ratio", "تناسب"),
|
||||
("Image Quality", "تصویر کا معیار"),
|
||||
("Scroll Style", "سکرول اسٹائل"),
|
||||
("Show Toolbar", "ٹول بار دکھائیں"),
|
||||
("Hide Toolbar", "ٹول بار چھپائیں"),
|
||||
("Direct Connection", "مستقیم کنکشن"),
|
||||
("Relay Connection", "ریلے کنکشن"),
|
||||
("Secure Connection", "محفوظ کنکشن"),
|
||||
("Insecure Connection", "غیر محفوظ کنکشن"),
|
||||
("Scale original", "اصل پیمانہ"),
|
||||
("Scale adaptive", "اضافی پیمانہ"),
|
||||
("General", "جنرل"),
|
||||
("Security", "سیکورٹی"),
|
||||
("Theme", "تھیم"),
|
||||
("Dark Theme", "ڈارک تھیم"),
|
||||
("Light Theme", "لائٹ تھیم"),
|
||||
("Dark", "ڈارک"),
|
||||
("Light", "لائٹ"),
|
||||
("Follow System", "سسٹم کو اپناؤ"),
|
||||
("Enable hardware codec", "ہارڈ ویئر کوڈیک کو فعال کریں"),
|
||||
("Unlock Security Settings", "سیکورٹی ترتیبات کو اندراج کریں"),
|
||||
("Enable audio", "آڈیو کو فعال کریں"),
|
||||
("Unlock Network Settings", "نیٹ ورک ترتیبات کو اندراج کریں"),
|
||||
("Server", "سرور"),
|
||||
("Direct IP Access", "مستقیم IP رسائی"),
|
||||
("Proxy", "پراکسی"),
|
||||
("Apply", "لاگو کریں"),
|
||||
("Disconnect all devices?", "تمام ڈیوائسز سے رابطہ منقطع کریں؟"),
|
||||
("Clear", "صاف کریں"),
|
||||
("Audio Input Device", "آڈیو ان پٹ ڈیوائس"),
|
||||
("Use IP Whitelisting", "IP وہٹ لسٹنگ استعمال کریں"),
|
||||
("Network", "نیٹ ورک"),
|
||||
("Pin Toolbar", "ٹول بار پن کریں"),
|
||||
("Unpin Toolbar", "ٹول بار ان پن کریں"),
|
||||
("Recording", "ریکارڈنگ"),
|
||||
("Directory", "ڈائرکٹری"),
|
||||
("Automatically record incoming sessions", "آئندہ سیشنز کو خودکار طور پر ریکارڈ کریں"),
|
||||
("Automatically record outgoing sessions", "بہرحال سیشنز کو خودکار طور پر ریکارڈ کریں"),
|
||||
("Change", "تبدیل کریں"),
|
||||
("Start session recording", "سیشن ریکارڈنگ شروع کریں"),
|
||||
("Stop session recording", "سیشن ریکارڈنگ روک دیں"),
|
||||
("Enable recording session", "ریکارڈنگ سیشن کو فعال کریں"),
|
||||
("Enable LAN discovery", "LAN کی دریافت کو فعال کریں"),
|
||||
("Deny LAN discovery", "LAN کی دریافت کو رد کریں"),
|
||||
("Write a message", "ایک پیغام لکھیں"),
|
||||
("Prompt", "پرامپٹ"),
|
||||
("Please wait for confirmation of UAC...", "UAC کی تصدیق کے لئے انتظار کریں..."),
|
||||
("elevated_foreground_window_tip", "الیویٹڈ_فارگراؤنڈ_ونڈو_ٹپ"),
|
||||
("Disconnected", "منقطع ہو گیا"),
|
||||
("Other", "دوسرا"),
|
||||
("Confirm before closing multiple tabs", "زیادہ ٹیبز بند کرنے سے پہلے تصدیق کریں"),
|
||||
("Keyboard Settings", "کیبورڈ ترتیبات"),
|
||||
("Full Access", "مکمل رسائی"),
|
||||
("Screen Share", "سکرین شئیر"),
|
||||
("ubuntu-21-04-required", "ubuntu-21-04 کی ضرورت"),
|
||||
("wayland-requires-higher-linux-version", "wayland کو اعلی لینکس ورژن کی ضرورت ہے"),
|
||||
("xdp-portal-unavailable", "xdp پورٹل دستیاب نہیں ہے"),
|
||||
("JumpLink", "جمپ لنک"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "شیئر کرنے کے لیے سکرین منتخب کریں (ہم منسب کی طرف سے کام کریں)۔"),
|
||||
("Show RustDesk", "RustDesk دکھائیں"),
|
||||
("This PC", "یہ PC"),
|
||||
("or", "یا"),
|
||||
("Elevate", "علیٰ کریں"),
|
||||
("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...", "رضائی کے لئے انتظار کریں..."),
|
||||
("One-time Password", "ایک بارہ پاس ورڈ"),
|
||||
("Use one-time password", "ایک بارہ پاس ورڈ استعمال کریں"),
|
||||
("One-time password length", "ایک بارہ پاس ورڈ کی لمبائی"),
|
||||
("Request access to your device", "اپنے آلہ تک رسائی کا درخواست دیں"),
|
||||
("Hide connection management window", "رابطہ مینجمنٹ ونڈو چھپائیں"),
|
||||
("hide_cm_tip", "hide_cm_tip"),
|
||||
("wayland_experiment_tip", "wayland_experiment_tip"),
|
||||
("Right click to select tabs", "ٹیبز منتخب کرنے کے لیے دائیں کلک کریں"),
|
||||
("Skipped", "چھوڑا گیا"),
|
||||
("Add to address book", "پتہ کتاب میں شامل کریں"),
|
||||
("Group", "گروپ"),
|
||||
("Search", "تلاش"),
|
||||
("Closed manually by web console", "ویب کنسول کے ذریعے دستی طور پر بند کیا گیا"),
|
||||
("Local keyboard type", "مقامی کیبورڈ کا قسم"),
|
||||
("Select local keyboard type", "مقامی کیبورڈ کا قسم منتخب کریں"),
|
||||
("software_render_tip", ""),
|
||||
("Always use software rendering", "ہم sempre سافٹ ویر رینڈرنگ استعمال کریں"),
|
||||
("config_input", "config_input"),
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("Wait", "انتظار کریں"),
|
||||
("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", ""),
|
||||
("Request Elevation", "علیٰ کرنے کا درخواست دیں"),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("Elevate successfully", "علیٰ کامیابی سے ہو گئے"),
|
||||
("uppercase", "بڑے حروف"),
|
||||
("lowercase", "چھوٹے حروف"),
|
||||
("digit", "عدد"),
|
||||
("special character", "خاص حرف"),
|
||||
("length>=8", "لمبائی>=8"),
|
||||
("Weak", "ضعیف"),
|
||||
("Medium", "درمیان"),
|
||||
("Strong", "مضبوط"),
|
||||
("Switch Sides", "پلٹنے کے سائڈس"),
|
||||
("Please confirm if you want to share your desktop?", "براہ کرم تصدیق کریں اگر آپ اپنے ڈیسک ٹاپ کو شئیر کرنا چاہتے ہیں؟"),
|
||||
("Display", "ڈسپلے"),
|
||||
("Default View Style", "ڈیفالٹ دیکھنے کا طریقہ"),
|
||||
("Default Scroll Style", "ڈیفالٹ سکرول کا طریقہ"),
|
||||
("Default Image Quality", "ڈیفالٹ تصویر کی معیار"),
|
||||
("Default Codec", "ڈیفالٹ کوڈک"),
|
||||
("Bitrate", "بٹ ریٹ"),
|
||||
("FPS", ""),
|
||||
("Auto", "خودکار"),
|
||||
("Other Default Options", "دوسروں ڈیفالٹ اختیارات"),
|
||||
("Voice call", "صوتی کال"),
|
||||
("Text chat", "متن چیٹ"),
|
||||
("Stop voice call", "صوتی کال کو روکیں"),
|
||||
("relay_hint_tip", "relay_hint_tip"),
|
||||
("Reconnect", "دوبارہ کنکٹ کریں"),
|
||||
("Codec", "کوڈک"),
|
||||
("Resolution", "ریزولیشن"),
|
||||
("No transfers in progress", "کوئی منتقلی جاری نہیں"),
|
||||
("Set one-time password length", "ایک بار کے لیے پاس ورڈ کی لمبائی سیٹ کریں"),
|
||||
("RDP Settings", "RDP سیٹنگز"),
|
||||
("Sort by", "ترتیر کے لحاظ سے"),
|
||||
("New Connection", "نئی کنکشن"),
|
||||
("Restore", "بحال کریں"),
|
||||
("Minimize", "کم کریں"),
|
||||
("Maximize", "زیادہ کریں"),
|
||||
("Your Device", "آپ کا آلہ"),
|
||||
("empty_recent_tip", "خالی حالیہ ٹپ"),
|
||||
("empty_favorite_tip", "خالی پسندیدہ ٹپ"),
|
||||
("empty_lan_tip", "خالی LAN ٹپ"),
|
||||
("empty_address_book_tip", "خالی پتہ کتاب ٹپ"),
|
||||
("Empty Username", "خالی صارف نام"),
|
||||
("Empty Password", "خالی پاس ورڈ"),
|
||||
("Me", "میں"),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("View Mode", "دیکھنے کا طریقہ"),
|
||||
("login_linux_tip", "login_linux_tip"),
|
||||
("verify_rustdesk_password_tip", ""),
|
||||
("remember_account_tip", ""),
|
||||
("os_account_desk_tip", ""),
|
||||
("OS Account", "OS اکاؤنٹ"),
|
||||
("another_user_login_title_tip", ""),
|
||||
("another_user_login_text_tip", ""),
|
||||
("xorg_not_found_title_tip", ""),
|
||||
("xorg_not_found_text_tip", ""),
|
||||
("no_desktop_title_tip", ""),
|
||||
("no_desktop_text_tip", ""),
|
||||
("No need to elevate", "اپنے کو ہیں نہیں"),
|
||||
("System Sound", "سسٹم سائونڈ"),
|
||||
("Default", "ڈیفالٹ"),
|
||||
("New RDP", "نیا RDP"),
|
||||
("Fingerprint", "فنگر پرنٹ"),
|
||||
("Copy Fingerprint", "فنگر پرنٹ کاپی کریں"),
|
||||
("no fingerprints", "کوئی فنگر پرنٹ نہیں"),
|
||||
("Select a peer", "ایک پیر منتخب کریں"),
|
||||
("Select peers", "پیرز منتخب کریں"),
|
||||
("Plugins", "پلگ انز"),
|
||||
("Uninstall", "ان انسٹال کریں"),
|
||||
("Update", "اپڈیٹ کریں"),
|
||||
("Enable", "فعال کریں"),
|
||||
("Disable", "غیر فعال کریں"),
|
||||
("Options", "اختیارات"),
|
||||
("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", ""),
|
||||
("Incoming connection", "آنے والا کنکشن"),
|
||||
("Outgoing connection", "جانے والا کنکشن"),
|
||||
("Exit", "خارج ہوں"),
|
||||
("Open", "کھولیں"),
|
||||
("logout_tip", ""),
|
||||
("Service", "سروس"),
|
||||
("Start", "شروع کریں"),
|
||||
("Stop", "روک دیں"),
|
||||
("exceed_max_devices", ""),
|
||||
("Sync with recent sessions", "پچھلے سیشنز کے ساتھ ہم آہنگ کریں"),
|
||||
("Sort tags", "ٹیگز کو ترتیب دیں"),
|
||||
("Open connection in new tab", "کنکشن کو نئے ٹیب میں کھولیں"),
|
||||
("Move tab to new window", "ٹیب کو نئی ونڈو میں منتقل کریں"),
|
||||
("Can not be empty", "خالی نہیں ہو سکتا"),
|
||||
("Already exists", "پہلے سے موجود ہے"),
|
||||
("Change Password", "پاسورڈ تبدیل کریں"),
|
||||
("Refresh Password", "پاسورڈ ریفریش کریں"),
|
||||
("ID", ""),
|
||||
("Grid View", "گوڈ ویو"),
|
||||
("List View", "لسٹ ویو"),
|
||||
("Select", "منتخب کریں"),
|
||||
("Toggle Tags", "ٹیگز ٹوگل کریں"),
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("Change Color", "رنگ تبدیل کریں"),
|
||||
("Primary Color", "پرائمری رنگ"),
|
||||
("HSV Color", "HSV رنگ"),
|
||||
("Installation Successful!", "انسٹالیشن کامیاب ہو گئی"),
|
||||
("Installation failed!", "انسٹالیشن ناکام ہو گئی"),
|
||||
("Reverse mouse wheel", "ریورس ماؤس وھیل"),
|
||||
("{} sessions", "{} سیشنز"),
|
||||
("scam_title", "سکم ٹائٹل"),
|
||||
("scam_text1", "سکم ٹیکسٹ 1"),
|
||||
("scam_text2", "سکم ٹیکسٹ 2"),
|
||||
("Don't show again", "دوبارہ نہ دکھائیں"),
|
||||
("I Agree", "میں قبول کرتا ہوں"),
|
||||
("Decline", "ناکام کریں"),
|
||||
("Timeout in minutes", "منٹوں میں ٹائیم آؤٹ"),
|
||||
("auto_disconnect_option_tip", ""),
|
||||
("Connection failed due to inactivity", "انفعال کی وजہ سے کنکشن ناکام ہو گیا"),
|
||||
("Check for software update on startup", "سٹارٹ اپ پر سافٹ ویر اپڈیٹ کے لیے چیک کریں"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("Filter by intersection", "فلٹر بائی انسٹریکشن"),
|
||||
("Remove wallpaper during incoming sessions", "ان کلینگ سیشنز کے دوران والپیپر کو ہٹائیں"),
|
||||
("Test", "ٹیسٹ"),
|
||||
("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", ""),
|
||||
("Change view", "ویو تبدیل کریں"),
|
||||
("Big tiles", "بڑے ٹائل"),
|
||||
("Small tiles", "چھوٹے ٹائل"),
|
||||
("List", "لسٹ"),
|
||||
("Virtual display", "ویچول دسپلے"),
|
||||
("Plug out all", "تمام پلگ آؤٹ کریں"),
|
||||
("True color (4:4:4)", "اصل رنگ (4:4:4)"),
|
||||
("Enable blocking user input", "صارف ان پٹ کو روکنے کی اجازت دیں"),
|
||||
("id_input_tip", ""),
|
||||
("privacy_mode_impl_mag_tip", ""),
|
||||
("privacy_mode_impl_virtual_display_tip", ""),
|
||||
("Enter privacy mode", "خفیہ موڈ میں داخل ہوں"),
|
||||
("Exit privacy mode", "خفیہ موڈ سے باہر نکلیں"),
|
||||
("idd_not_support_under_win10_2004_tip", ""),
|
||||
("input_source_1_tip", ""),
|
||||
("input_source_2_tip", ""),
|
||||
("Swap control-command key", "control-command کلید کو سوپ کریں"),
|
||||
("swap-left-right-mouse", "بائی-دائی ماؤس کو سوپ کریں"),
|
||||
("2FA code", "2FA کوڈ"),
|
||||
("More", "مزید"),
|
||||
("enable-2fa-title", "2FA کو فعال کریں"),
|
||||
("enable-2fa-desc", "2FA کم سے زیادہ ترتیب دینے کے لیے فعال کریں"),
|
||||
("wrong-2fa-code", "2FA کوڈ غلط ہے"),
|
||||
("enter-2fa-title", "2FA کوڈ درج کریں"),
|
||||
("Email verification code must be 6 characters.", "ای میل توثیق کوڈ 6 حروف کا ہونا چاہیے."),
|
||||
("2FA code must be 6 digits.", "2FA کوڈ 6 اعداد کا ہونا چاہیے."),
|
||||
("Multiple Windows sessions found", "متعدد ونڈوز سیشن ملے"),
|
||||
("Please select the session you want to connect to", "براہ کرم وہ سیشن منتخب کریں جس سے آپ منسلک ہونا چاہتے ہیں"),
|
||||
("powered_by_me", "میں کی طرف سے طاقتور"),
|
||||
("outgoing_only_desk_tip", ""),
|
||||
("preset_password_warning", ""),
|
||||
("Security Alert", "سیکورٹی الرٹ"),
|
||||
("My address book", "میری ایڈریس بک"),
|
||||
("Personal", "شخصی"),
|
||||
("Owner", "مالک"),
|
||||
("Set shared password", "پھیلاو پاس ورڈ مرتب کریں"),
|
||||
("Exist in", "موجود ہے"),
|
||||
("Read-only", "صرف پڑھنے کے لیے"),
|
||||
("Read/Write", "پڑھنے/لکھنے"),
|
||||
("Full Control", "پورا کنٹرول"),
|
||||
("share_warning_tip", ""),
|
||||
("Everyone", "ہر کوئی"),
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", "ریموٹ کرسر کی پیروی کریں"),
|
||||
("Follow remote window focus", "ریموٹ ونڈو فوکس کی پیروی کریں"),
|
||||
("default_proxy_tip", ""),
|
||||
("no_audio_input_device_tip", ""),
|
||||
("Incoming", "آنے والے"),
|
||||
("Outgoing", "بھیجے جا رہے"),
|
||||
("Clear Wayland screen selection", "Wayland سکرین کی انتخاب صاف کریں"),
|
||||
("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", ""),
|
||||
("Keep screen on", "سکرین کو آن رکھیں"),
|
||||
("Never", "کبھی نہیں"),
|
||||
("During controlled", "کنٹرول کے دوران"),
|
||||
("During service is on", "سروس فعال ہو تو"),
|
||||
("Capture screen using DirectX", "DirectX کا استعمال کرکے سکرین کی تصویر لیں"),
|
||||
("Back", "واپس"),
|
||||
("Apps", "ایپس"),
|
||||
("Volume up", "آواز بڑھائیں"),
|
||||
("Volume down", "آواز کم کریں"),
|
||||
("Power", "پاور"),
|
||||
("Telegram bot", "ٹیلیگرام بات"),
|
||||
("enable-bot-tip", ""),
|
||||
("enable-bot-desc", ""),
|
||||
("cancel-2fa-confirm-tip", ""),
|
||||
("cancel-bot-confirm-tip", ""),
|
||||
("About RustDesk", "رستڈیسک کے بارے میں"),
|
||||
("Send clipboard keystrokes", "کلپ بورڈ کی چابیاں بھیجیں"),
|
||||
("network_error_tip", ""),
|
||||
("Unlock with PIN", "PIN کے ساتھ انلاک کریں"),
|
||||
("Requires at least {} characters", "کم از کم {} حروف کی ضرورت ہے"),
|
||||
("Wrong PIN", "غلط PIN"),
|
||||
("Set PIN", "PIN سیٹ کریں"),
|
||||
("Enable trusted devices", "معتبر آلے فعال کریں"),
|
||||
("Manage trusted devices", "معتبر آلے مینیج کریں"),
|
||||
("Platform", "پلیٹ فارم"),
|
||||
("Days remaining", "دن باقی"),
|
||||
("enable-trusted-devices-tip", ""),
|
||||
("Parent directory", "والد ڈائرکٹری"),
|
||||
("Resume", "جاری رکھیں"),
|
||||
("Invalid file name", "غلط فائل کا نام"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", "توثیق کی ضرورت ہے"),
|
||||
("Authenticate", "توثیق کریں"),
|
||||
("web_id_input_tip", ""),
|
||||
("Download", "ڈاؤن لوڈ کریں"),
|
||||
("Upload folder", "اپ لوڈ فولڈر"),
|
||||
("Upload files", "فائلیں اپ لوڈ کریں"),
|
||||
("Clipboard is synchronized", "کلپ بورڈ مطابق ہے"),
|
||||
("Update client clipboard", "کلپ بورڈ کو اپ ڈیٹ کریں"),
|
||||
("Untagged", "غیر تعلق یافتہ"),
|
||||
("new-version-of-{}-tip", ""),
|
||||
("Accessible devices", "قابلِ رسائی والے آلے"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
|
||||
("d3d_render_tip", ""),
|
||||
("Use D3D rendering", "D3D رینڈرنگ کا استعمال کریں"),
|
||||
("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", "WebSocket استعمال کریں"),
|
||||
("Trackpad speed", "ٹریک پیڈ کی رفتار"),
|
||||
("Default trackpad speed", "ڈیفالٹ ٹریک پیڈ کی رفتار"),
|
||||
("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", "غیر محفوظ TLS فالبیک کی اجازت دیں"),
|
||||
("allow-insecure-tls-fallback-tip", ""),
|
||||
("Disable UDP", "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", ""),
|
||||
("Continue with {}", "continue-with-{}"),
|
||||
("Display Name", "display-name"),
|
||||
("password-hidden-tip", ""),
|
||||
("preset-password-in-use-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -1957,6 +1957,14 @@ mod desktop {
|
||||
const ENV_KEY_WAYLAND_DISPLAY: &str = "WAYLAND_DISPLAY";
|
||||
const ENV_KEY_DBUS_SESSION_BUS_ADDRESS: &str = "DBUS_SESSION_BUS_ADDRESS";
|
||||
|
||||
/// A compositor that runs Xwayland without exporting `XAUTHORITY` (wlroots, e.g. Hyprland)
|
||||
/// still hands out a usable session through the Wayland side. Requiring xauth there never
|
||||
/// succeeded, so every refresh ran the retry loop to the end, 240 shell pipelines at a time.
|
||||
/// https://github.com/rustdesk/rustdesk/issues/15952
|
||||
fn is_session_env_complete(display: &str, xauth: &str, wl_display: &str, dbus: &str) -> bool {
|
||||
!display.is_empty() && (!xauth.is_empty() || (!wl_display.is_empty() && !dbus.is_empty()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Desktop {
|
||||
pub sid: String,
|
||||
@@ -2023,15 +2031,51 @@ mod desktop {
|
||||
PLASMA_KDED,
|
||||
tray.as_str(),
|
||||
];
|
||||
self.display.clear();
|
||||
self.xauth.clear();
|
||||
self.wl_display.clear();
|
||||
self.dbus.clear();
|
||||
let mut kept = 0u8;
|
||||
for proc in display_proc {
|
||||
self.display = get_env(ENV_KEY_DISPLAY, &self.uid, proc);
|
||||
self.xauth = get_env(ENV_KEY_XAUTHORITY, &self.uid, proc);
|
||||
self.wl_display = get_env(ENV_KEY_WAYLAND_DISPLAY, &self.uid, proc);
|
||||
self.dbus = get_env(ENV_KEY_DBUS_SESSION_BUS_ADDRESS, &self.uid, proc);
|
||||
if !self.display.is_empty() && !self.xauth.is_empty() {
|
||||
let display = get_env(ENV_KEY_DISPLAY, &self.uid, proc);
|
||||
let xauth = get_env(ENV_KEY_XAUTHORITY, &self.uid, proc);
|
||||
let wl_display = get_env(ENV_KEY_WAYLAND_DISPLAY, &self.uid, proc);
|
||||
let dbus = get_env(ENV_KEY_DBUS_SESSION_BUS_ADDRESS, &self.uid, proc);
|
||||
// Take a candidate whole and keep the best seen. Assigning each variable
|
||||
// unconditionally let a pattern that does not run on this desktop blank out
|
||||
// the values an earlier one had answered with, which is how a session with a
|
||||
// working portal ended up starting its `--server` with no compositor and no
|
||||
// bus at all. The Wayland-only rank is what a session whose Xwayland exports
|
||||
// no `XAUTHORITY` can still offer.
|
||||
let complete = is_session_env_complete(&display, &xauth, &wl_display, &dbus);
|
||||
let rank = if complete {
|
||||
3
|
||||
} else if !wl_display.is_empty() && !dbus.is_empty() {
|
||||
2
|
||||
} else if !display.is_empty() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if rank > kept {
|
||||
kept = rank;
|
||||
self.display = display;
|
||||
self.xauth = xauth;
|
||||
self.wl_display = wl_display;
|
||||
self.dbus = dbus;
|
||||
}
|
||||
if complete {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// The Wayland pair on its own is a session the child server can be started
|
||||
// against -- it is what `get_display_xauth_wayland` returns on. Retrying is for a
|
||||
// session that has not finished coming up, and a compositor whose Xwayland starts
|
||||
// on demand may never export a `DISPLAY` for this walk to find, so waiting ten
|
||||
// more rounds for one costs the whole probe again on every refresh.
|
||||
if kept >= 2 {
|
||||
break;
|
||||
}
|
||||
sleep_millis(300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,6 +642,18 @@ impl Connection {
|
||||
conn.on_close("connection manager", true).await;
|
||||
break;
|
||||
}
|
||||
// The connection manager's window went away rather than a person
|
||||
// disconnecting this peer. End the session exactly as above, but do not
|
||||
// send the manual close reason: it is the one thing that stops the peer
|
||||
// from retrying, and on a logout the retry is the whole point - it is
|
||||
// what puts the peer back on the login screen a moment later.
|
||||
#[cfg(target_os = "linux")]
|
||||
ipc::Data::CmWindowClosed => {
|
||||
conn.chat_unanswered = false; // seen
|
||||
conn.file_transferred = false; //seen
|
||||
conn.on_close("connection manager window closed", true).await;
|
||||
break;
|
||||
}
|
||||
ipc::Data::CmErr(e) => {
|
||||
if e != "expected" {
|
||||
// cm closed before connection
|
||||
@@ -1201,6 +1213,13 @@ impl Connection {
|
||||
ipc::Data::Close => {
|
||||
bail!("Close requested from connection manager");
|
||||
}
|
||||
// Same end as above: a tunnel must not outlive the window either.
|
||||
// Only the reason differs, and a port forward carries none - the
|
||||
// peer sees the tunnel drop and decides for itself.
|
||||
#[cfg(target_os = "linux")]
|
||||
ipc::Data::CmWindowClosed => {
|
||||
bail!("Connection manager window closed");
|
||||
}
|
||||
ipc::Data::CmErr(e) => {
|
||||
log::error!("Connection manager error: {e}");
|
||||
bail!("{e}");
|
||||
|
||||
@@ -153,6 +153,7 @@ fn make_tray() -> hbb_common::ResultType<()> {
|
||||
// We create the icon once the event loop is actually running
|
||||
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
|
||||
let mut builder = TrayIconBuilder::new()
|
||||
.with_id(crate::get_app_name().to_lowercase())
|
||||
.with_menu(Box::new(tray_menu.clone()))
|
||||
.with_tooltip(tooltip(0))
|
||||
.with_icon(icon.clone());
|
||||
|
||||
@@ -377,6 +377,15 @@ pub fn close(id: i32) {
|
||||
};
|
||||
}
|
||||
|
||||
/// Like `close`, but says the CM's WINDOW closed rather than a person disconnecting this peer.
|
||||
/// See `ipc::Data::CmWindowClosed`.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn close_window(id: i32) {
|
||||
if let Some(client) = CLIENTS.read().unwrap().get(&id) {
|
||||
allow_err!(client.tx.send(Data::CmWindowClosed));
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove(id: i32) {
|
||||
CLIENTS.write().unwrap().remove(&id);
|
||||
@@ -1537,13 +1546,19 @@ async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender<Data>) {
|
||||
fs::get_path(dir)
|
||||
}
|
||||
};
|
||||
if let Ok(Ok(fd)) = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await {
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_response = FileResponse::new();
|
||||
file_response.set_dir(fd);
|
||||
msg_out.set_file_response(file_response);
|
||||
send_raw(msg_out, tx);
|
||||
}
|
||||
let result = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await;
|
||||
let msg_out = match result {
|
||||
Ok(Ok(fd)) => {
|
||||
let mut msg_out = Message::new();
|
||||
let mut file_response = FileResponse::new();
|
||||
file_response.set_dir(fd);
|
||||
msg_out.set_file_response(file_response);
|
||||
msg_out
|
||||
}
|
||||
Ok(Err(err)) => fs::new_error(0, err, -1),
|
||||
Err(err) => fs::new_error(0, err, -1),
|
||||
};
|
||||
send_raw(msg_out, tx);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
@@ -1741,7 +1756,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
fn read_dir_success() {
|
||||
fn read_dir_reports_success_and_error() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let (tx, mut rx) = unbounded_channel();
|
||||
@@ -1764,6 +1779,18 @@ mod tests {
|
||||
_ => panic!("unexpected data"),
|
||||
}
|
||||
let _ = fs::remove_dir_all(&dir);
|
||||
|
||||
super::read_dir(&dir.to_string_lossy(), false, &tx).await;
|
||||
|
||||
match rx.recv().await.unwrap() {
|
||||
Data::RawMessage(bytes) => {
|
||||
let mut msg = Message::new();
|
||||
msg.merge_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(msg.file_response().error().id, 0);
|
||||
assert!(!msg.file_response().error().error.is_empty());
|
||||
}
|
||||
_ => panic!("unexpected data"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
"host": false,
|
||||
"platform": "windows & arm64"
|
||||
},
|
||||
{
|
||||
"name": "oboe",
|
||||
"platform": "android"
|
||||
},
|
||||
{
|
||||
"name": "opus",
|
||||
"host": true
|
||||
@@ -91,7 +87,7 @@
|
||||
"vcpkg-configuration": {
|
||||
"default-registry": {
|
||||
"kind": "builtin",
|
||||
"baseline": "120deac3062162151622ca4860575a33844ba10b"
|
||||
"baseline": "9e593bb18ea69cc5095e012465dcd675a822ed0d"
|
||||
},
|
||||
"overlay-ports": [
|
||||
"./res/vcpkg"
|
||||
|
||||
Reference in New Issue
Block a user