Compare commits

...

13 Commits

Author SHA1 Message Date
rustdesk
9343affe0b fix: check the frame QueryInterface result in dxgi capture
Both AcquireNextFrame paths cast the IDXGIResource to ID3D11Texture2D
without looking at the HRESULT. ohgodwhat() then dereferences the null
pointer in GetDesc(), and get_texture() hands a null texture to the vram
encoder. Return the error instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011sw75MSAz7PTqrSALdStXe
2026-08-27 15:02:31 +08:00
rustdesk
7c830e76c6 fix: reuse the dxgi staging texture instead of one per frame
ohgodwhat() created a full screen D3D11_USAGE_STAGING texture for every
captured frame and pinned each one with SetEvictionPriority(MAXIMUM).
Because D3D11 resource destruction may be deferred, that per-frame churn
can accumulate a large amount of graphics kernel paged pool on affected
drivers. Keep a single staging texture and rebuild it only when the
desktop image changes shape.

Also check the IDXGISurface QueryInterface result, so a failure can no
longer leave surface null while readable holds a valid texture.

Reported in #15945.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011sw75MSAz7PTqrSALdStXe
2026-08-27 15:02:31 +08:00
RustDesk
1fe451c2e8 chore(flutter): bump desktop_multi_window for show recovery (#15959)
Pick up rustdesk-org/rustdesk_desktop_multi_window#37, which re-arms the existing bounded redraw timer whenever a secondary window is shown, including when its first frame was generated while hidden but not presented.

This may perform one delayed child refresh on each show. It intentionally does not add a presentation-complete flag: Flutter reports frame generation rather than successful presentation, so recording success after a synthetic refresh could suppress later self-recovery without a reliable success signal.
2026-08-27 14:42:09 +08:00
fufesou
0b08a83d4b fix(file-transfer): improve large directory loading (#15830)
* fix(file-transfer): improve large directory loading

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file-transfer): avoid failing newer directory reads

Track each remote directory request by its registered completer and only remove
the task when it still matches, preventing stale failures from affecting newer
requests for the same path.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file-transfer): handle slow directory listings safely

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file transfer): correlate directory responses with requests

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file transfer): prevent automatic directory responses from matching requests

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file-transfer): handle large remote directory listings reliably

- build file rows lazily
- register remote reads before sending requests
- handle Home paths, stale responses, errors, and timeouts
- serialize same-path reads with different hidden-file options

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(file transfer): reduce diffs

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: build

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: invalidate pending dir reads on reconnect

Signed-off-by: fufesou <linlong1266@gmail.com>

* test(file-transfer): cover remote directory read lifecycle

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-08-27 13:01:11 +08:00
rustdesk
e9b81e3475 typo 2026-08-27 11:47:36 +08:00
RustDesk
7220f00410 fix(linux): a Wayland session without XAUTHORITY is not incomplete (#15978)
Fixes #15952.

Hyprland runs Xwayland without exporting `XAUTHORITY`, and
`get_display_xauth_xwayland` only returns once it has both `DISPLAY` and
`XAUTHORITY`. On such a session that condition is never met, so every refresh
runs the retry loop to the end: 10 rounds x 6 process patterns x 4 variables =
240 `get_env` calls, each a `sh -c` pipeline of ~12 processes starting with a
full `ps -u <uid> -f`. That is ~2900 fork/exec per refresh, and the service loop
repeats every 500 ms. The reporter measured a full core on a low-end laptop and
~60% of a core on a 13600KF.

The Wayland side answers for such a session, so accept `DISPLAY` together with
either `XAUTHORITY` or `WAYLAND_DISPLAY` + `DBUS_SESSION_BUS_ADDRESS`. The
portal answers on the first pattern, which ends the walk there, as it already
did on desktops that do export an xauth.

The loop also assigned all four variables unconditionally per pattern, so the
patterns that do not run on a given desktop blanked out what an earlier one had
answered with -- the portal's valid `DISPLAY=:1` included. That is why the
`--server` was then started with no `WAYLAND_DISPLAY` and no
`DBUS_SESSION_BUS_ADDRESS`. Candidates are now taken from one pattern as a whole
and ranked, so a later pattern replaces an earlier answer only by being better,
and a session that can only offer a compositor and a bus still keeps them.

A compositor that starts Xwayland on demand shows the same shape from the other
side: the portal came up before Xwayland did, so its environment carries a valid
`WAYLAND_DISPLAY` and `DBUS_SESSION_BUS_ADDRESS` but no `DISPLAY`, and no pattern
here may ever produce one. That pair alone is a session the child server can be
started against -- it is exactly what `get_display_xauth_wayland` returns on --
so it outranks a bare `DISPLAY` and ends the retrying, while the rest of the
round still looks for something that completes the session.

Not specific to the drm build: the function is not feature-gated, and the commit
the report points at does not touch it.


Claude-Session: https://claude.ai/code/session_01Q5egQpH4q4GoXJiuMoTJ5t

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 11:39:59 +08:00
fufesou
fd471fcf02 fix: show speed in desktop file transfer status (#15980)
* fix: show speed in desktop file transfer status

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: move file transfer speed beside progress bar

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: move file transfer speed into progress bar

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: refine file transfer speed display

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: adapt file transfer progress text colors

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: reduce file transfer speed text weight

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-08-27 11:08:58 +08:00
Jade
7c6e661fcc fix(linux): Set AppIndicator ID for tray-icon (#15981)
* set static AppIndicator ID in tray-icon init

allows DEs, eg. KDE to 'remember' the user's configuration of tray hidden/unhidden. see: https://github.com/rustdesk/rustdesk/discussions/15208

Signed-off-by: Jade <5164609+gnosticJade@users.noreply.github.com>

* Update tray.rs

---------

Signed-off-by: Jade <5164609+gnosticJade@users.noreply.github.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2026-08-27 09:41:24 +08:00
Mariano Abad
3f207e91f6 fix(linux): a session logout should hand the peer to the login screen (#15905)
* fix(linux): a session logout should hand the peer to the login screen

Logging out closes every window in the session, the connection manager's
included, and its close handler kicks every peer with the reason a person
gets when they disconnect one by hand. That reason is the one thing the
client never retries on, so the remote session dies on a frozen frame
instead of reconnecting to the greeter that is already there.

The close carries nothing to tell the two apart: measured on KDE, the CM
receives no signal and logind still reports the session active at that
instant, and the server is killed within a few hundred ms either way, so
neither a state check nor a grace period can decide it. What is
distinguishable is the ACTION: disconnecting a peer is not the same event
as this window going away. So the window-close path now says so, and the
server ends the session without poisoning the retry; the Disconnect
button and the app's own close control keep kicking exactly as before.
Linux only, since that is where a logout closes the window.

Verified on plasma/sddm with a client attached: a logout now reconnects
to the greeter with no dialog, while closing the manager window still
shows Closed manually by the peer.

* fix(linux): close the tunnel too, and keep the web build compiling

Three seams the first pass missed. The web bridge is hand written, not
generated, so the new call needs its stub there or flutter build web
stops compiling - and that job is disabled in CI, so it would have gone
green. try_port_forward_loop is a second consumer of the same channel
and only knew Close, so a forwarded tunnel outlived the window it was
supposed to die with. And the variant had landed inside the DRM section,
whose comment says everything below it is drm-gated.
2026-08-26 18:26:26 +08:00
Kino
cec4085238 Bump aom to v3.14.1 (#15883)
* Bump aom to v3.14.1

* Remove oboe dependency in vcpkg.json
2026-08-25 19:53:56 +08:00
fufesou
0d917c6fa1 fix: remove dup translations (#15967)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-08-25 18:25:49 +08:00
Rafli Surya Wijaya
893dc27798 docs(readme): fix broken Screenshots section anchor link (#15964) 2026-08-25 11:21:34 +08:00
Abdullah Kaleem
7cc82c1575 Add Urdu language support for UI strings (#15961)
* Add Urdu language support for UI strings till 329 line

Co-authored-by: Copilot <copilot@github.com>

* Add Urdu translations for additional UI strings

* Add Urdu language support in lang.rs

* Fix Urdu translations and remove unused keys in ur.rs

---------

Co-authored-by: Copilot <copilot@github.com>
2026-08-25 09:19:37 +08:00
27 changed files with 1415 additions and 100 deletions

View File

@@ -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:

View File

@@ -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"

View File

@@ -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

View File

@@ -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>

View File

@@ -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");
}

View File

@@ -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,
),

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();

View File

@@ -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");
}

View File

@@ -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"

View 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');
});
}

View File

@@ -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;

View 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()

View File

@@ -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.

View File

@@ -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

View File

@@ -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",

View File

@@ -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);

View File

@@ -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

View File

@@ -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
View 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();
}

View File

@@ -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);
}
}

View File

@@ -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}");

View File

@@ -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());

View File

@@ -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"),
}
});
}

View File

@@ -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"