mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 21:41:02 +03:00
port forward shared conn (#16062)
* hbb_common: bump to the port-forward-mux proto Also latches PortForward.multiplex into login_scope_digest, which destructures PortForward's fields exhaustively by design (a new field must be latched or deliberately ignored to compile). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: window accounting and channel frame builders Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: fix RecvWindow counter overflow on long transfers Replace cumulative accounting (granted/received) with remaining credit tracking to prevent u32 overflow after 4 GiB of data on a single channel. Wire behavior is identical, but the fix allows large file transfers without mid-stream channel closure. Add regression test for 8 GiB transfer to verify fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: credit-windowed relay halves and channel coordinator Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * server: PortForwardMux channel table and per-channel tasks Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * server: multiplexed port-forward connections stay in the protobuf loop Wire PortForwardMux into Connection: take the multiplexed path at login when the controller sets PortForward.multiplex, route PortForwardChannel frames to it from on_message, sweep the channel table's targets after open/close, and clean it up on connection close. Introduce is_port_forward() (socket-based or multiplexed) and use it at the four sites that classify the connection, so a multiplexed connection stays in the message loop, gets TestDelay keepalives, and reports features.port_forward_mux in PeerInfo. The three sites that break into the raw pipe loop or gate the keepalive still check port_forward_socket specifically, since a multiplexed connection must not take that path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * cm: update a port-forward row's targets as tunnel channels come and go Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: controller tunnel with a single-writer stream loop Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: publish Muxed before spawning the tunnel loop Publishing after spawn let a loop that dies immediately reset the state first, so the later publish pinned it at Muxed with a dead handle forever. Also adds a test pinning open-before-data ordering across many concurrently opened channels, and drops an unused Clone derive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: share one multiplexed tunnel across a window's listeners Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: fix round 1 review findings Drop the mux default-false assignment now that definite-assignment proves every path that reads it has set it; the enable-port-forward-mux config commit picks up the missing attribution trailers; the default-on test pins the enable- prefix itself rather than option2bool's weaker fallback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: end-to-end tests over a loopback tunnel Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: fix bulk test's premature half-close, pin the half-close limitation many_channels_echo_concurrently_and_a_bulk_one_does_not_starve_them dropped its bulk write half as soon as writing finished, which shuts down the write side of the socket and, by design (see the design doc's TCP half-close non-goal; today's run_forward does the same), ends the whole channel. Keep the write half alive until the reader is done so the test measures starvation, not half-close. Add a_local_half_close_ends_the_whole_channel to pin that limitation in code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: cap send credit and other final review fixes Fix 1 (critical): clamp SendCredit to MAX_SEND_CREDIT (= CHANNEL_WINDOW) in both new() and add(), so a peer with tunnel permission can no longer advertise an unbounded window and force the controlled side's unbounded FrameSink::Direct sink to buffer unlimited target data per channel. Fix 2: rename the "starve" test to many_channels_echo_concurrently and drop its (untrue) starvation claim, since it opens every channel before the bulk transfer starts. Add a_channel_opened_during_a_bulk_transfer_ is_served_promptly, which opens the small channel while the bulk one is demonstrably mid-flight. Fix 3: only look up the tunnel permission for `open` frames in the PortForwardChannel arm of on_message, instead of once per data frame. Fix 4: two rustfmt deviations in connection.rs (matches! wrapping and a tuple literal), fixed by hand without a blanket cargo fmt run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: report a refused channel's reason as an error dialog The controlled side already answers a refused port-forward channel with opened { success: false, message }; on the multiplexed path TunnelHandle:: on_frame only logged that message at debug and closed the channel, so the user saw a closed connection with no explanation, worst on the RDP path where only the RDP client's own error remained. on_frame now returns the message the window should show, deduplicated per distinct reason (capped at MAX_REPORTED_OPEN_ERRORS) so one page load's dozen refused connections surface one dialog per reason instead of a dozen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * Use on_error for refused-channel dialog in tunnel_loop Redirect the refused-channel error through the standard on_error path instead of calling msgbox directly, for consistency with other errors in the port-forward flow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: apply the whole-branch review Correctness: - listen(): the Legacy arm is merged with the Claimed arm. On its own it ignored outcome.local_eof, so a client that hung up during login still got a target connect, an audit record and a CM row on the controlled side, and ignored outcome.mux, so a peer upgraded while a legacy window stayed open answered as a tunnel while the controller went raw. - Refusal dialogs are deduplicated per quiet spell (10 s) rather than per tunnel lifetime; the lifetime set went silent for the rest of a long-lived window after the first burst. - Android's CM listener handles UpdatePortForward; it fell into `_ => {}`. - relay_socket_to_tunnel reads into one scratch buffer per channel and sends an exact-size copy. A frame owning its 64 KiB read allocation pinned it until sent, once per byte on interactive traffic. Consistency and cleanups: - The controlled side's refusal text is the raw pipe's wording, RDP substitution included. - connection.rs: the PortForwardChannel arm is a one-line hook, the CM label is pushed from the 1 s tick alone, and the unreachable inner.tx fall-through is gone. - The Ready enum is removed; wait_ready() returns Option<Claim>. - SendCredit::add wakes with notify_one alone. - on_ui_command() replaces the two ui_receiver handlers in listen(). - TunnelHandle is no longer re-exported (unused-import warning). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: a legacy window stays legacy until it is reopened Review: the merged `Claimed | Legacy` arm gave a legacy window a hot transition to a tunnel — every accept re-negotiated, and a peer upgraded while the window stayed open was promoted underneath live connections. The product does not need a mode switch inside a window's lifetime, and the transition was extra state-machine surface for nothing: reopening the window picks up an upgraded peer. The two arms are separate again. `Claimed` negotiates once and the peer's answer fixes the window's mode. `Legacy` logs in for every accept as before, asks for no tunnel — `LoginConfigHandler::port_forward_mux` carries the request per login, so the raw pipe never has to talk to a peer that thinks it agreed to multiplex — and ignores what the peer reports. Both arms keep skipping a local socket that hung up during login. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * hbb_common: bump to main with rustdesk/hbb_common#594 merged Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * server: admit only INITIAL_WINDOW on a channel before opened The demultiplexer accepted CHANNEL_WINDOW into a pending channel's unbounded queue, four times the bound the channel task enforces once it polls. The window now starts at INITIAL_WINDOW and is widened right before `opened` advertises the rest. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: a tunnel ends when its window drops the Tunnel The loop held its own handle and state sender, so once the window closed nothing was left to stop it: it kept answering TestDelay and the peer connection, CM row included, lived on until the peer went away. `Tunnel` now owns a watch sender nobody sends on; the loop's receiver errors when the last `Tunnel` drops, and the loop ends. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: one tunnel per mapping, bound to the authenticated target The login latches `PortForward.host`/`port` into the session scope and approval is shown that target, but a window-wide tunnel let any later `open` name another target with only `enable-tunnel` rechecked. A tunnel now belongs to one listener and serves the one target its login authenticated: the controlled side refuses an `open` for any other target, and a window with several targets uses one connection each, approved on its own. With one owner per tunnel the claim needs no waiters: `Establishing`, `Claim::Wait` and `wait_ready` go, and `try_claim` becomes a plain read. The CM label that followed a tunnel's targets goes with them; a row shows its mapping's target, as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: the legacy comment names the mapping, not the window Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: a window violation drops the channel on the spot Both demultiplexers only queued a `Violation` and left the entry until the channel task woke and exited, so a peer that kept sending past the window queued one more entry per frame in the meantime, bounded by nothing. The entry now goes the moment `accept` fails; later frames for that id are unknown-channel noise. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: the login's target travels with the accept, not the handler `listen()` wrote `lc.port_forward` (and, on this branch, `port_forward_mux`) into the window's shared `LoginConfigHandler` before connecting, and `create_login_msg` read them back only when the peer's `Hash` arrived. Two mappings logging in at the same time could therefore swap targets: on master that bridged a local socket to the wrong target, and with a tunnel bound to its login's target it also left the mapping refusing every later accept until it was recreated. The target is now a `PortForward` carried by the interface clone that handles one accept, passed explicitly down to `create_login_msg`; the handler no longer has a field to race on. No lock spans the login. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port_forward_mux: pin permission revocation and whole-tunnel failure in tests Both already hold; the review asked for them to be stated. `enable-tunnel` turned off mid-session refuses the next `open` while the live channel keeps relaying, and a dead tunnel ends every channel on it together, after which the next accept establishes again on the same `Tunnel`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: the legacy comment names re-adding the mapping only Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: the raw pipe runs the code it always ran The multiplexed login had replaced `connect_and_login`, so a mapping with the setting off, a peer without the feature, or a listener latched `Legacy` still went through the tunnel's state machine, the capped pre-read and the changed local-EOF rule. Feature off now means the old code: `listen()` keeps its accept arm and `connect_and_login` as they were, and the tunnel is a branch taken only when the setting is on, in `establish_tunnel` with its own `connect_and_login_mux`. The one line the raw path does differently is the target riding with the accept's interface clone instead of the shared handler. `get_port_forward_mux_enabled` had one caller and moves in here, so `common.rs` is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: a UI login answers the challenge its own connection was given `handle_login_from_ui` hashed the typed password against `lc.hash`, the window's shared handler field, and the window's password prompt is broadcast to every listener. With two mappings both waiting on that prompt, the `Hash` that arrived last had overwritten the other's, so one of the two answered the wrong challenge and failed to log in. Master shares the same state and broadcasts the same way. The `Hash` is now a parameter of the login; `Session` keeps it beside the connection it belongs to, and the per-accept clone that `with_port_forward` makes gets a slot of its own. `lc.hash` stays for `handle_peer_info`, which only needs the salt, and that is per peer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: a mapping without its hash waits for it before answering the prompt The window's password prompt is broadcast to every mapping, and can reach one whose own connection has not received its `Hash` yet. That mapping used to answer anyway, with a digest over an empty challenge: the peer refused it and counted a failed attempt, and the empty-salt result was written into the shared `lc.password`, where the mapping that prompted had just stored the right one and the next `handle_peer_info` would persist whatever was there. The connection's challenge is now `Option<Hash>`, `None` until `handle_hash` runs, and `handle_login_from_ui` sends nothing without it. The mapping that prompted stores the salted password in the shared handler, and the waiting one logs in with that against its own challenge when its `Hash` arrives, without prompting again. Test: A answers its prompt, the same broadcast reaches B before its hash, B sends nothing, B's hash arrives and its login carries B's challenge and B's target with no dialog. It runs the real `handle_hash` for B. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: the tunnel's login is the raw pipe's, asked for by a window flag Master's fix for the shared login slots (#16069) keeps the target and the challenge in the window's `LoginConfigHandler` and serializes the mappings' logins with a turn lock, all inside `port_forward.rs`. This branch had carried a broader shape of the same fix, a `with_port_forward` on `Interface` and the target and `Hash` as parameters through the login functions, which every caller had to follow. That is gone: `Interface`, `Session`, `create_login_msg`, `send_login`, `handle_hash` and `handle_login_from_ui` are as on master. What the tunnel needs on top is one bit in the login, `multiplex`. It is a window flag beside `port_forward` in the handler, set once in `io_loop` before the window's mappings start, so an accept's claim and its login read the same value; the setting takes effect for windows opened after it changes. `connect_and_login_mux` is now master's `connect_and_login` with the tunnel's three differences and the same `hash_arrived` and `login_from_ui` calls. The raw pipe is master's, line for line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * hbb_common: bump to main with rustdesk/hbb_common#595 merged 840c8ec..f94e3fe is that one merge: the five local settings custom clients could not preset. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: the off switch gets a checkbox in Settings → General `enable-port-forward-mux` was readable only by editing the config file. It is a local setting of the controlling side, so it sits with the other outgoing ones, after "Open connection in new tab", with a tooltip saying what it does. The two new keys are translated in every language. The three that the mobile file manager added, "Export", "Export Logs" and "Import Folder", were empty everywhere but five languages; they are filled in too, and Korean's "xdp-portal-unavailable" with them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * Urdu: fill the backlog of empty and missing translations ur.rs had fallen behind: 104 keys carried an empty value and 35 keys the other languages have were absent altogether. Both are filled in, the missing ones in the order template.rs lists them. Eight entries stay empty on purpose. They are keys that only ur.rs still carries, absent from template.rs and from every other language, so their English source cannot be recovered and nothing reads them: remember_account_tip, os_account_desk_tip, another_user_login_*_tip, xorg_not_found_*_tip and no_desktop_*_tip. Twelve more dead keys keep the values they have; removing either group is a separate decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * Urdu: drop the keys template.rs no longer lists The twenty keys removed here are absent from template.rs and from every other language file; ur.rs was the only one still carrying them, eight of them with no value at all. They are leftovers of features that are gone: the plugin menu, the OS-account login prompts, the Xorg and no-desktop errors. ur.rs now holds exactly the template's key set, all of it translated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: closing the tunnel reaches channels parked on their socket A channel whose far end neither reads nor writes has both relays parked on the socket, not on the inbound queue, so `close_all` dropping the queue's sender woke neither: the socket and both tasks lived on until the far end hung up. Both sides now hold a per-tunnel teardown signal that `run_channel` selects on beside its own cancel, and `close_all` sends it after clearing the map. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: a mapping latched to the raw pipe logs in without asking for the tunnel The login copied the window's `port_forward_mux` into `multiplex`, so a mapping that had latched to the raw pipe on an old peer kept asking for the tunnel. Once that peer was upgraded it answered with a tunnel while the controller switched to raw framing, and every later connection on the mapping was dead until it was re-added. The login now carries its own `port_forward_multiplex`, filled with the target under the turn lock: the probe asks, the raw pipe does not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: a channel opened as its tunnel closes still gets the teardown `open` can straddle `close_all`: the claim passed, the frame receiver was still alive, and the channel subscribed after the signal had gone out. `watch::subscribe` marks earlier sends as seen, and the entry sits in a map that was already cleared, so nothing would ever end it. The signal is now a level: `close_all` raises it with `send_replace`, which stores even with no channel live, and `run_channel` waits for the value rather than for a change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: the connect guard counts a live tunnel as connected `connect_port_forward_if_needed` returned early only for a raw-pipe socket; called again with a tunnel up it would have built a second `PortForwardMux` and dropped every channel of the first. Not reachable today, since the logon response is sent once, but the other checks in this change already read `is_port_forward()`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * Urdu: the two terminal clipboard keys master added Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: a tunnel's TCP stream refuses packets over twice MAX_FRAME The codec takes a header declaring up to 1 GiB and hands the packet up only once it has all arrived, so the channel window bounded what the peer may send, not what this side buffers. Both sides now cap the codec at 2 * MAX_FRAME as soon as multiplexing is agreed: a data frame with its envelope and MAC fits with room to spare, and a header over the cap ends the tunnel before a byte of payload is read. TCP only; the WebSocket and WebRTC codecs carry caps of their own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * port forward: a channel id still live when the counter comes round is skipped The controller handed out `next_id` unchecked. 2^32 opens later it lands on a channel still up: the entry here was replaced, while the peer, which ignores an `open` for a live id, kept routing that id to the old socket, so the new local connection's bytes went into the old target connection. The id is now taken under the map's lock and advanced past any id in use. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1753,6 +1753,14 @@ pub struct LoginConfigHandler {
|
||||
pub remember: bool,
|
||||
config: PeerConfig,
|
||||
pub port_forward: (String, i32),
|
||||
/// This login's `multiplex`, filled with `port_forward` under the turn
|
||||
/// lock. `port_forward_mux` says whether a mapping probes for the tunnel;
|
||||
/// one the probe latched to the raw pipe logs in without asking, so an
|
||||
/// upgraded peer keeps giving it the raw pipe.
|
||||
pub(crate) port_forward_multiplex: bool,
|
||||
/// Set once per window, before its mappings start: every accept's claim
|
||||
/// reads it.
|
||||
pub(crate) port_forward_mux: bool,
|
||||
/// Held by a port-forward mapping from filling `port_forward` and `hash`
|
||||
/// until its login is built from them; a window's mappings log in
|
||||
/// concurrently.
|
||||
@@ -2769,6 +2777,7 @@ impl LoginConfigHandler {
|
||||
ConnType::PORT_FORWARD | ConnType::RDP => lr.set_port_forward(PortForward {
|
||||
host: self.port_forward.0.clone(),
|
||||
port: self.port_forward.1,
|
||||
multiplex: self.port_forward_multiplex,
|
||||
..Default::default()
|
||||
}),
|
||||
ConnType::TERMINAL => {
|
||||
@@ -4066,6 +4075,26 @@ mod retry_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod port_forward_mux_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_login_asks_for_the_tunnel_when_its_mapping_probes() {
|
||||
let mut lc = LoginConfigHandler::default();
|
||||
lc.conn_type = ConnType::PORT_FORWARD;
|
||||
let asks = |lc: &LoginConfigHandler| {
|
||||
lc.create_login_msg(String::new(), String::new(), vec![])
|
||||
.login_request()
|
||||
.port_forward()
|
||||
.multiplex
|
||||
};
|
||||
assert!(!asks(&lc));
|
||||
lc.port_forward_multiplex = true;
|
||||
assert!(asks(&lc));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn hc_connection(
|
||||
feedback: i32,
|
||||
rendezvous_server: String,
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "لقطة الشاشة للشاشات المدمجة غير مدعومة"),
|
||||
("screenshot-action-tip", "إجراء لقطة الشاشة"),
|
||||
("Save as", "حفظ باسم"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "تصدير"),
|
||||
("Export Logs", "تصدير السجلات"),
|
||||
("Import Folder", "استيراد مجلد"),
|
||||
("Copy to clipboard", "نسخ إلى الحافظة"),
|
||||
("Enable remote printer", "تمكين الطابعة عن بُعد"),
|
||||
("Downloading {}", "جارٍ تنزيل {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "تفعيل"),
|
||||
("Reuse one connection for port forwarding", "إعادة استخدام اتصال واحد لإعادة توجيه المنافذ"),
|
||||
("port-forward-mux-tip", "تمرير جميع اتصالات إعادة توجيه المنافذ عبر اتصال واحد بالجهاز الآخر، بدلاً من الاتصال وتسجيل الدخول من جديد لكل اتصال."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Аб’яднанне здымкаў экранаў з некалькіх дысплэяў у дадзены момант не падтрымліваецца. Пераключыцеся на адзін з дысплэяў і паўтарыце дзеянне."),
|
||||
("screenshot-action-tip", "Выберыце, што рабіць з атрыманым здымкам экрана."),
|
||||
("Save as", "Захаваць у файл"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Экспартаваць"),
|
||||
("Export Logs", "Экспартаваць журналы"),
|
||||
("Import Folder", "Імпартаваць папку"),
|
||||
("Copy to clipboard", "Скапіяваць у буфер абмену"),
|
||||
("Enable remote printer", "Выкарыстоўваць аддалены прынтар"),
|
||||
("Downloading {}", "Ідзе спампоўванне {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Уключыць"),
|
||||
("Reuse one connection for port forwarding", "Выкарыстоўваць адно злучэнне для перанакіравання партоў"),
|
||||
("port-forward-mux-tip", "Перадаваць усе злучэнні аднаго перанакіравання партоў праз адно злучэнне з аддаленай прыладай замест паўторнага падлучэння і ўваходу для кожнага з іх."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Обединяването на снимки от няколко екрана в момента не се поддържа. Моля, превключете към един екран и опитайте отново."),
|
||||
("screenshot-action-tip", "Моля, изберете как да продължите със снимката на екрана."),
|
||||
("Save as", "Запазване като"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Изнасяне"),
|
||||
("Export Logs", "Изнасяне на дневниците"),
|
||||
("Import Folder", "Внасяне на папка"),
|
||||
("Copy to clipboard", "Копиране в клипборда"),
|
||||
("Enable remote printer", "Позволяване на отдалечен принтер"),
|
||||
("Downloading {}", "Изтегляне на {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Активирай"),
|
||||
("Reuse one connection for port forwarding", "Използване на една връзка за пренасочване на портове"),
|
||||
("port-forward-mux-tip", "Всички връзки на едно пренасочване на портове минават през една връзка към отсрещния компютър, вместо да се свързвате и влизате отново за всяка от тях."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Actualment no és possible combinar captures de pantalla de diverses pantalles. Canvieu a una sola pantalla i torneu a provar."),
|
||||
("screenshot-action-tip", "Seleccioneu com voleu continuar amb la captura de pantalla."),
|
||||
("Save as", "Anomena i desa"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exporta"),
|
||||
("Export Logs", "Exporta els registres"),
|
||||
("Import Folder", "Importa una carpeta"),
|
||||
("Copy to clipboard", "Copia al porta-retalls"),
|
||||
("Enable remote printer", "Habilita l'impressora remota"),
|
||||
("Downloading {}", "Descarregant {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Habilita"),
|
||||
("Reuse one connection for port forwarding", "Reutilitza una connexió per a la redirecció de ports"),
|
||||
("port-forward-mux-tip", "Fa passar totes les connexions d'una redirecció de ports per una única connexió amb l'altre equip, en lloc de connectar i iniciar la sessió de nou per a cadascuna."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", "允许终端应用复制到剪贴板"),
|
||||
("Enable", "启用"),
|
||||
("Reuse one connection for port forwarding", "端口转发复用同一条连接"),
|
||||
("port-forward-mux-tip", "同一条端口转发规则上的所有连接共用一条到对方的连接,而不是每条连接都重新连接并登录一次。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Sloučení snímků obrazovky z více displejů aktuálně není podporováno. Přepněte na jeden displej a zkuste to znovu."),
|
||||
("screenshot-action-tip", "Vyberte, jak pokračovat se snímkem obrazovky."),
|
||||
("Save as", "Uložit jako"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exportovat"),
|
||||
("Export Logs", "Exportovat protokoly"),
|
||||
("Import Folder", "Importovat složku"),
|
||||
("Copy to clipboard", "Kopírovat do schránky"),
|
||||
("Enable remote printer", "Povolit vzdálenou tiskárnu"),
|
||||
("Downloading {}", "Stahuje se {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Povolit"),
|
||||
("Reuse one connection for port forwarding", "Znovu použít jedno připojení pro přesměrování portů"),
|
||||
("port-forward-mux-tip", "Vede všechna připojení jednoho přesměrování portů přes jediné připojení k protějšku místo opakovaného připojování a přihlašování pro každé z nich."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Sammenfletning af skærmbilleder fra flere skærme understøttes ikke i øjeblikket. Skift venligst til en enkelt skærm og prøv igen."),
|
||||
("screenshot-action-tip", "Vælg venligst, hvordan du vil fortsætte med skærmbilledet."),
|
||||
("Save as", "Gem som"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Eksportér"),
|
||||
("Export Logs", "Eksportér logfiler"),
|
||||
("Import Folder", "Importér mappe"),
|
||||
("Copy to clipboard", "Kopiér til udklipsholder"),
|
||||
("Enable remote printer", "Aktivér fjernprinter"),
|
||||
("Downloading {}", "Downloader {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Aktivér"),
|
||||
("Reuse one connection for port forwarding", "Genbrug én forbindelse til portvideresendelse"),
|
||||
("port-forward-mux-tip", "Fører alle forbindelser i en portvideresendelse gennem én enkelt forbindelse til modparten i stedet for at forbinde og logge ind igen for hver enkelt."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Das Zusammenführen von Screenshots von mehreren Bildschirmen wird derzeit nicht unterstützt. Bitte wechseln Sie zu einem einzelnen Bildschirm und versuchen Sie es erneut."),
|
||||
("screenshot-action-tip", "Bitte wählen Sie aus, wie Sie mit dem Screenshot fortfahren möchten."),
|
||||
("Save as", "Speichern unter"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exportieren"),
|
||||
("Export Logs", "Protokolle exportieren"),
|
||||
("Import Folder", "Ordner importieren"),
|
||||
("Copy to clipboard", "In Zwischenablage kopieren"),
|
||||
("Enable remote printer", "Entfernten Drucker aktivieren"),
|
||||
("Downloading {}", "{} herunterladen"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Aktivieren"),
|
||||
("Reuse one connection for port forwarding", "Eine Verbindung für die Portweiterleitung wiederverwenden"),
|
||||
("port-forward-mux-tip", "Alle Verbindungen einer Portweiterleitung über eine einzige Verbindung zur Gegenstelle führen, statt sich für jede einzelne neu zu verbinden und anzumelden."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Η συγχώνευση στιγμιότυπων οθόνης από πολλές οθόνες δεν υποστηρίζεται προς το παρόν. Αλλάξτε σε μία μόνο οθόνη και δοκιμάστε ξανά."),
|
||||
("screenshot-action-tip", "Επιλέξτε πώς θα συνεχίσετε με το στιγμιότυπο οθόνης."),
|
||||
("Save as", "Αποθήκευση ως"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Εξαγωγή"),
|
||||
("Export Logs", "Εξαγωγή αρχείων καταγραφής"),
|
||||
("Import Folder", "Εισαγωγή φακέλου"),
|
||||
("Copy to clipboard", "Αντιγραφή στο πρόχειρο"),
|
||||
("Enable remote printer", "Ενεργοποίηση απομακρυσμένου εκτυπωτή"),
|
||||
("Downloading {}", "Γίνεται Λήψη {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Ενεργοποίηση"),
|
||||
("Reuse one connection for port forwarding", "Επαναχρησιμοποίηση μίας σύνδεσης για την προώθηση θυρών"),
|
||||
("port-forward-mux-tip", "Όλες οι συνδέσεις μιας προώθησης θυρών περνούν από μία μόνο σύνδεση προς τον απομακρυσμένο υπολογιστή, αντί να πραγματοποιείται νέα σύνδεση και ταυτοποίηση για κάθε μία."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -277,5 +277,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Your ip is blocked by the peer", "Your IP is blocked by the peer"),
|
||||
("sync-clipboard-between-sessions-tip", "Text or images copied in one remote session are also sent to the clipboard of your other connected sessions."),
|
||||
("terminal-clipboard-write-tip", "An app in the terminal wants to copy text to this device's clipboard. If granted, this permission applies to terminal apps in all connections until you turn it off in Settings. Manual copy and paste are unaffected."),
|
||||
("port-forward-mux-tip", "Carry every connection of a port-forward mapping over a single connection to the peer, instead of connecting and logging in again for each one."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Kunfandi ekrankopiojn de pluraj ekranoj aktuale ne estas subtenata. Bonvolu ŝanĝi al unu ekrano kaj reprovi."),
|
||||
("screenshot-action-tip", "Bonvolu elekti kiel daŭrigi kun la ekrankopio."),
|
||||
("Save as", "Konservi kiel"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Eksporti"),
|
||||
("Export Logs", "Eksporti protokolojn"),
|
||||
("Import Folder", "Importi dosierujon"),
|
||||
("Copy to clipboard", "Kopii al la poŝo"),
|
||||
("Enable remote printer", "Ebligi foran presilon"),
|
||||
("Downloading {}", "Elŝutas {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Ebligi"),
|
||||
("Reuse one connection for port forwarding", "Reuzi unu konekton por pordo-plusendado"),
|
||||
("port-forward-mux-tip", "Ĉiuj konektoj de unu pordo-plusendado iras tra unu sola konekto al la alia komputilo, anstataŭ konekti kaj ensaluti denove por ĉiu el ili."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "La fusión de capturas de pantalla de múltiples monitores no está soportada. Por favor, cambie a un monitor e inténtelo de nuevo."),
|
||||
("screenshot-action-tip", "Por favor, seleccione cómo continuar con la captura de pantalla."),
|
||||
("Save as", "Guardar como"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exportar"),
|
||||
("Export Logs", "Exportar registros"),
|
||||
("Import Folder", "Importar carpeta"),
|
||||
("Copy to clipboard", "Copiar al portapapeles"),
|
||||
("Enable remote printer", "Habilitar impresora remota"),
|
||||
("Downloading {}", "Descargando {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Habilitar"),
|
||||
("Reuse one connection for port forwarding", "Reutilizar una conexión para la redirección de puertos"),
|
||||
("port-forward-mux-tip", "Llevar todas las conexiones de una redirección de puertos por una única conexión con el otro equipo, en lugar de conectar e iniciar sesión de nuevo para cada una."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Mitme kuva kuvatõmmiste ühendamine pole praegu toetatud. Palun lülitu ühele kuvale ja proovi uuesti."),
|
||||
("screenshot-action-tip", "Palun vali, kuidas kuvatõmmisega jätkata."),
|
||||
("Save as", "Salvesta kui"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Ekspordi"),
|
||||
("Export Logs", "Ekspordi logid"),
|
||||
("Import Folder", "Impordi kaust"),
|
||||
("Copy to clipboard", "Kopeeri lõikelauale"),
|
||||
("Enable remote printer", "Luba kaugprinter"),
|
||||
("Downloading {}", "Allalaadimine: {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Luba"),
|
||||
("Reuse one connection for port forwarding", "Kasuta pordi suunamiseks üht ühendust"),
|
||||
("port-forward-mux-tip", "Juhib ühe pordisuunamise kõik ühendused ühe teise arvutiga loodud ühenduse kaudu, selle asemel et iga ühenduse jaoks uuesti ühenduda ja sisse logida."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Pantaila anitzen pantaila-argazkiak bateratzea ez da onartzen une honetan. Aldatu pantaila bakarrera eta saiatu berriro."),
|
||||
("screenshot-action-tip", "Hautatu pantaila-argazkiarekin nola jarraitu."),
|
||||
("Save as", "Gorde honela"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Esportatu"),
|
||||
("Export Logs", "Esportatu erregistroak"),
|
||||
("Import Folder", "Inportatu karpeta"),
|
||||
("Copy to clipboard", "Kopiatu arbelera"),
|
||||
("Enable remote printer", "Gaitu urruneko inprimagailua"),
|
||||
("Downloading {}", "{} deskargatzen"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Gaitu"),
|
||||
("Reuse one connection for port forwarding", "Berrerabili konexio bakarra portuen birbideratzerako"),
|
||||
("port-forward-mux-tip", "Portu-birbideratze baten konexio guztiak beste ordenagailurako konexio bakar batetik eramaten ditu, bakoitzerako berriro konektatu eta saioa hasi beharrean."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "ادغام تصاویر از نمایشگرهای متعدد در حال حاضر پشتیبانی نمی شود. لطفاً به یک صفحه نمایش واحد تغییر دهید و دوباره امتحان کنید."),
|
||||
("screenshot-action-tip", "لطفاً نحوه ادامه با تصویر را انتخاب کنید."),
|
||||
("Save as", "ذخیره به عنوان"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "خروجی گرفتن"),
|
||||
("Export Logs", "خروجی گرفتن از گزارشها"),
|
||||
("Import Folder", "درونریزی پوشه"),
|
||||
("Copy to clipboard", "در کلیپ بورد کپی کنید"),
|
||||
("Enable remote printer", "چاپگر از راه دور را فعال کنید"),
|
||||
("Downloading {}", "بارگیری {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "فعالسازی"),
|
||||
("Reuse one connection for port forwarding", "استفاده مجدد از یک اتصال برای هدایت پورت"),
|
||||
("port-forward-mux-tip", "همه اتصالهای یک هدایت پورت از یک اتصال واحد به دستگاه مقابل عبور میکنند، بهجای اتصال و ورود دوباره برای هر کدام."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Yhdistetyn näytön kuvakaappaus ei ole tuettu"),
|
||||
("screenshot-action-tip", "Valitse, mitä haluat tehdä kuvakaappaukselle"),
|
||||
("Save as", "Tallenna nimellä"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Vie"),
|
||||
("Export Logs", "Vie lokit"),
|
||||
("Import Folder", "Tuo kansio"),
|
||||
("Copy to clipboard", "Kopioi leikepöydälle"),
|
||||
("Enable remote printer", "Ota etätulostin käyttöön"),
|
||||
("Downloading {}", "Ladataan {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Ota käyttöön"),
|
||||
("Reuse one connection for port forwarding", "Käytä yhtä yhteyttä portin edelleenohjaukseen"),
|
||||
("port-forward-mux-tip", "Välittää kaikki yhden portin edelleenohjauksen yhteydet yhden vastapuoleen avatun yhteyden kautta sen sijaan, että jokaista varten muodostettaisiin yhteys ja kirjauduttaisiin uudelleen."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Actuellement, la prise de capture d’écran ne prend pas en charge les affichages multiples. Veuillez réessayer après avoir sélectionné un seul affichage."),
|
||||
("screenshot-action-tip", "Veuillez choisir l’action à effectuer avec la capture d’écran."),
|
||||
("Save as", "Enregistrer sous"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exporter"),
|
||||
("Export Logs", "Exporter les journaux"),
|
||||
("Import Folder", "Importer un dossier"),
|
||||
("Copy to clipboard", "Copier dans le presse-papier"),
|
||||
("Enable remote printer", "Activer l’impression à distance"),
|
||||
("Downloading {}", "Téléchargement de {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Activer"),
|
||||
("Reuse one connection for port forwarding", "Réutiliser une seule connexion pour la redirection de ports"),
|
||||
("port-forward-mux-tip", "Faire passer toutes les connexions d'une redirection de ports par une seule connexion vers le pair, au lieu de se connecter et de s'authentifier à nouveau pour chacune."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "რამდენიმე ეკრანის სურათის გაერთიანება ამჟამად მხარდაჭერილი არ არის. გადართეთ ერთ ეკრანზე და სცადეთ ხელახლა."),
|
||||
("screenshot-action-tip", "აირჩიეთ, როგორ გავაგრძელოთ ეკრანის სურათთან მუშაობა."),
|
||||
("Save as", "შენახვა როგორც"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "ექსპორტი"),
|
||||
("Export Logs", "ჟურნალების ექსპორტი"),
|
||||
("Import Folder", "საქაღალდის იმპორტი"),
|
||||
("Copy to clipboard", "ბუფერში კოპირება"),
|
||||
("Enable remote printer", "დისტანციური პრინტერის ჩართვა"),
|
||||
("Downloading {}", "მიმდინარეობს {}-ის ჩამოტვირთვა"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "ჩართვა"),
|
||||
("Reuse one connection for port forwarding", "პორტის გადამისამართებისთვის ერთი კავშირის ხელახლა გამოყენება"),
|
||||
("port-forward-mux-tip", "ერთი პორტის გადამისამართების ყველა კავშირი გადის მეორე კომპიუტერთან დამყარებული ერთი კავშირით, ნაცვლად იმისა, რომ თითოეულისთვის თავიდან დაუკავშირდეს და შევიდეს სისტემაში."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "મર્જ કરેલ સ્ક્રીનશોટ સપોર્ટેડ નથી."),
|
||||
("screenshot-action-tip", "સ્ક્રીનશોટ પછીની ક્રિયા"),
|
||||
("Save as", "તરીકે સાચવો"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "એક્સપોર્ટ કરો"),
|
||||
("Export Logs", "લોગ એક્સપોર્ટ કરો"),
|
||||
("Import Folder", "ફોલ્ડર ઇમ્પોર્ટ કરો"),
|
||||
("Copy to clipboard", "ક્લિપબોર્ડમાં કોપી કરો"),
|
||||
("Enable remote printer", "રિમોટ પ્રિન્ટર સક્ષમ કરો"),
|
||||
("Downloading {}", "{} ડાઉનલોડ થઈ રહ્યું છે"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "સક્ષમ કરો"),
|
||||
("Reuse one connection for port forwarding", "પોર્ટ ફોરવર્ડિંગ માટે એક જ કનેક્શન ફરી વાપરો"),
|
||||
("port-forward-mux-tip", "એક પોર્ટ ફોરવર્ડિંગનાં બધાં કનેક્શન સામેના કમ્પ્યુટર સાથેના એક જ કનેક્શન મારફતે જાય છે, દરેક માટે ફરીથી કનેક્ટ અને લોગિન કરવાને બદલે."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "צילום מסך משולב מכל המסכים אינו נתמך"),
|
||||
("screenshot-action-tip", "בחר פעולה לאחר צילום המסך"),
|
||||
("Save as", "שמור בשם"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "ייצוא"),
|
||||
("Export Logs", "ייצוא יומנים"),
|
||||
("Import Folder", "ייבוא תיקייה"),
|
||||
("Copy to clipboard", "העתק ללוח"),
|
||||
("Enable remote printer", "אפשר מדפסת מרוחקת"),
|
||||
("Downloading {}", "מוריד את {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "הפעל"),
|
||||
("Reuse one connection for port forwarding", "שימוש חוזר בחיבור אחד להעברת פורטים"),
|
||||
("port-forward-mux-tip", "כל החיבורים של העברת פורטים אחת עוברים דרך חיבור יחיד למחשב המרוחק, במקום ליצור חיבור חדש ולהיכנס מחדש עבור כל אחד מהם."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "मर्ज की गई स्क्रीन के स्क्रीनशॉट समर्थित नहीं हैं।"),
|
||||
("screenshot-action-tip", "स्क्रीनशॉट लेने के बाद की कार्रवाई"),
|
||||
("Save as", "इस रूप में सहेजें"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "एक्सपोर्ट करें"),
|
||||
("Export Logs", "लॉग एक्सपोर्ट करें"),
|
||||
("Import Folder", "फ़ोल्डर इंपोर्ट करें"),
|
||||
("Copy to clipboard", "क्लिपबोर्ड पर कॉपी करें"),
|
||||
("Enable remote printer", "रिमोट प्रिंटर सक्षम करें"),
|
||||
("Downloading {}", "{} डाउनलोड हो रहा है"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "सक्षम करें"),
|
||||
("Reuse one connection for port forwarding", "पोर्ट फ़ॉरवर्डिंग के लिए एक ही कनेक्शन दोबारा उपयोग करें"),
|
||||
("port-forward-mux-tip", "एक पोर्ट फ़ॉरवर्डिंग के सभी कनेक्शन दूसरे कंप्यूटर से बने एक ही कनेक्शन से होकर जाते हैं, हर एक के लिए दोबारा कनेक्ट और लॉगिन करने के बजाय।"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Spajanje snimaka zaslona s više zaslona trenutačno nije podržano. Prebacite se na jedan zaslon i pokušajte ponovno."),
|
||||
("screenshot-action-tip", "Odaberite kako nastaviti sa snimkom zaslona."),
|
||||
("Save as", "Spremi kao"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Izvoz"),
|
||||
("Export Logs", "Izvoz zapisnika"),
|
||||
("Import Folder", "Uvoz mape"),
|
||||
("Copy to clipboard", "Kopiraj u međuspremnik"),
|
||||
("Enable remote printer", "Omogući udaljeni pisač"),
|
||||
("Downloading {}", "Preuzimanje {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Omogući"),
|
||||
("Reuse one connection for port forwarding", "Ponovno koristi jednu vezu za prosljeđivanje portova"),
|
||||
("port-forward-mux-tip", "Sve veze jednog prosljeđivanja portova idu kroz jednu vezu prema drugoj strani, umjesto ponovnog povezivanja i prijave za svaku od njih."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Egyesített képernyőről nem támogatott a képernyőkép készítése"),
|
||||
("screenshot-action-tip", "Képernyőkép-művelet"),
|
||||
("Save as", "Mentés másként"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exportálás"),
|
||||
("Export Logs", "Naplók exportálása"),
|
||||
("Import Folder", "Mappa importálása"),
|
||||
("Copy to clipboard", "Másolás a vágólapra"),
|
||||
("Enable remote printer", "Távoli nyomtatók engedélyezése"),
|
||||
("Downloading {}", "{} letöltése"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Engedélyezés"),
|
||||
("Reuse one connection for port forwarding", "Egyetlen kapcsolat újrafelhasználása a portátirányításhoz"),
|
||||
("port-forward-mux-tip", "Egy portátirányítás összes kapcsolatát egyetlen, a másik géppel létesített kapcsolaton vezeti át, ahelyett hogy mindegyikhez újra csatlakozna és bejelentkezne."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Menggabungkan tangkapan layar dari beberapa tampilan saat ini tidak didukung. Silakan beralih ke satu tampilan dan coba lagi."),
|
||||
("screenshot-action-tip", "Silakan pilih cara melanjutkan dengan tangkapan layar."),
|
||||
("Save as", "Simpan sebagai"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Ekspor"),
|
||||
("Export Logs", "Ekspor Log"),
|
||||
("Import Folder", "Impor Folder"),
|
||||
("Copy to clipboard", "Salin ke papan klip"),
|
||||
("Enable remote printer", "Aktifkan printer jarak jauh"),
|
||||
("Downloading {}", "Mendownload {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Aktifkan"),
|
||||
("Reuse one connection for port forwarding", "Gunakan ulang satu koneksi untuk penerusan port"),
|
||||
("port-forward-mux-tip", "Menyalurkan semua koneksi dari satu penerusan port melalui satu koneksi ke perangkat lain, alih-alih menyambung dan masuk lagi untuk setiap koneksi."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "L'unione della cattura di schermate di più display non è attualmente supportata.\nPassa ad un singolo display e riprova."),
|
||||
("screenshot-action-tip", "Seleziona come continuare con la schermata."),
|
||||
("Save as", "Salva come"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Esporta"),
|
||||
("Export Logs", "Esporta i log"),
|
||||
("Import Folder", "Importa cartella"),
|
||||
("Copy to clipboard", "Copia negli appunti"),
|
||||
("Enable remote printer", "Abilita stampante remota"),
|
||||
("Downloading {}", "Download {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Abilita"),
|
||||
("Reuse one connection for port forwarding", "Riutilizza una sola connessione per l'inoltro delle porte"),
|
||||
("port-forward-mux-tip", "Fa passare tutte le connessioni di un inoltro di porte su un'unica connessione verso il dispositivo remoto, invece di connettersi e autenticarsi di nuovo per ognuna."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "複数のディスプレイのスクリーンショットの結合は、現在非対応です。単一のディスプレイに切り替えてもう一度お試しください。"),
|
||||
("screenshot-action-tip", "スクリーンショットを続行する方法を選択してください。"),
|
||||
("Save as", "保存先"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "エクスポート"),
|
||||
("Export Logs", "ログをエクスポート"),
|
||||
("Import Folder", "フォルダをインポート"),
|
||||
("Copy to clipboard", "クリップボードにコピー"),
|
||||
("Enable remote printer", "リモートプリンターを有効化する"),
|
||||
("Downloading {}", "{} をダウンロード中"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "有効にする"),
|
||||
("Reuse one connection for port forwarding", "ポート転送で 1 つの接続を再利用する"),
|
||||
("port-forward-mux-tip", "1 つのポート転送のすべての接続を、相手への 1 本の接続にまとめます。接続ごとに接続とログインをやり直しません。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Screen Share", "화면 공유"),
|
||||
("ubuntu-21-04-required", "Wayland는 Ubuntu 21.04 이상 버전이 필요합니다."),
|
||||
("wayland-requires-higher-linux-version", "Wayland는 상위 버전의 Linux 배포판이 필요합니다. X11 데스크탑을 사용하거나 OS를 변경하세요."),
|
||||
("xdp-portal-unavailable", ""),
|
||||
("xdp-portal-unavailable", "Wayland 화면 캡처에 실패했습니다. XDG Desktop Portal이 중단되었거나 사용할 수 없습니다. `systemctl --user restart xdg-desktop-portal` 명령으로 다시 시작해 보세요."),
|
||||
("JumpLink", "점프 링크"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하세요 (피어 측에서 작동)"),
|
||||
("Show RustDesk", "RustDesk 표시"),
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "현재 다중 디스플레이의 스크린샷 병합이 지원되지 않습니다. 단일 디스플레이로 전환한 후 다시 시도해 주세요."),
|
||||
("screenshot-action-tip", "스크린샷을 계속 진행할 방법을 선택해 주세요."),
|
||||
("Save as", "다른 이름으로 저장"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "내보내기"),
|
||||
("Export Logs", "로그 내보내기"),
|
||||
("Import Folder", "폴더 가져오기"),
|
||||
("Copy to clipboard", "클립보드에 복사"),
|
||||
("Enable remote printer", "원격 프린터 허용"),
|
||||
("Downloading {}", "{} 다운로드 중"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "활성화"),
|
||||
("Reuse one connection for port forwarding", "포트 포워딩에 연결 하나를 재사용"),
|
||||
("port-forward-mux-tip", "포트 포워딩 하나의 모든 연결을 상대방과의 단일 연결로 전달합니다. 연결마다 다시 접속하고 로그인하지 않습니다."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Бірнеше дисплейдің скриншоттарын біріктіруге қазір қолдау көрсетілмейді. Жеке дисплейге ауысып, қайталап көруді өтінеміз."),
|
||||
("screenshot-action-tip", "Скриншотпен қалай жалғастыру керектігін таңдауды өтінеміз."),
|
||||
("Save as", "Басқаша сақтау"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Экспорттау"),
|
||||
("Export Logs", "Журналдарды экспорттау"),
|
||||
("Import Folder", "Қалтаны импорттау"),
|
||||
("Copy to clipboard", "Көшіру-тақтаға көшіру"),
|
||||
("Enable remote printer", "Қашықтағы принтерді іске қосу"),
|
||||
("Downloading {}", "{} жүктелуде"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Қосу"),
|
||||
("Reuse one connection for port forwarding", "Порт бағыттау үшін бір қосылымды қайта пайдалану"),
|
||||
("port-forward-mux-tip", "Бір порт бағыттаудың барлық қосылымдары әрқайсысы үшін қайта қосылып кірудің орнына қарсы құрылғымен орнатылған бір қосылым арқылы өтеді."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Kelių ekranų nuotraukų sujungimas šiuo metu nepalaikomas. Perjunkite į vieną ekraną ir bandykite dar kartą."),
|
||||
("screenshot-action-tip", "Pasirinkite, ką daryti su ekrano nuotrauka."),
|
||||
("Save as", "Įrašyti kaip"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Eksportuoti"),
|
||||
("Export Logs", "Eksportuoti žurnalus"),
|
||||
("Import Folder", "Importuoti aplanką"),
|
||||
("Copy to clipboard", "Kopijuoti į iškarpinę"),
|
||||
("Enable remote printer", "Įgalinti nuotolinį spausdintuvą"),
|
||||
("Downloading {}", "Atsisiunčiama {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Įgalinti"),
|
||||
("Reuse one connection for port forwarding", "Prievadų peradresavimui naudoti vieną ryšį"),
|
||||
("port-forward-mux-tip", "Visi vieno prievadų peradresavimo ryšiai eina per vieną ryšį su kitu kompiuteriu, užuot kiekvienam iš jų jungiantis ir prisijungiant iš naujo."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Vairāku displeju ekrānuzņēmumu apvienošana pašlaik netiek atbalstīta. Lūdzu, pārslēdzieties uz vienu displeju un mēģiniet vēlreiz."),
|
||||
("screenshot-action-tip", "Lūdzu, atlasiet, kā turpināt darbu ar ekrānuzņēmumu."),
|
||||
("Save as", "Saglabāt kā"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Eksportēt"),
|
||||
("Export Logs", "Eksportēt žurnālus"),
|
||||
("Import Folder", "Importēt mapi"),
|
||||
("Copy to clipboard", "Kopēt starpliktuvē"),
|
||||
("Enable remote printer", "Iespējot attālo printeri"),
|
||||
("Downloading {}", "Notiek {} lejupielāde"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Iespējot"),
|
||||
("Reuse one connection for port forwarding", "Atkārtoti izmantot vienu savienojumu portu pārsūtīšanai"),
|
||||
("port-forward-mux-tip", "Visi viena portu pārsūtījuma savienojumi tiek novadīti pa vienu savienojumu ar otru datoru, nevis katram no tiem izveidojot jaunu savienojumu un pieteikšanos."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "മെർജ് ചെയ്ത സ്ക്രീൻഷോട്ട് പിന്തുണയ്ക്കുന്നില്ല."),
|
||||
("screenshot-action-tip", "സ്ക്രീൻഷോട്ടിന് ശേഷമുള്ള നടപടി"),
|
||||
("Save as", "പേരിൽ സേവ് ചെയ്യുക"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "എക്സ്പോർട്ട് ചെയ്യുക"),
|
||||
("Export Logs", "ലോഗുകൾ എക്സ്പോർട്ട് ചെയ്യുക"),
|
||||
("Import Folder", "ഫോൾഡർ ഇംപോർട്ട് ചെയ്യുക"),
|
||||
("Copy to clipboard", "ക്ലിപ്പ്ബോർഡിലേക്ക് കോപ്പി ചെയ്യുക"),
|
||||
("Enable remote printer", "റിമോട്ട് പ്രിന്റർ അനുവദിക്കുക"),
|
||||
("Downloading {}", "{} ഡൗൺലോഡ് ചെയ്യുന്നു"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "അനുവദിക്കുക"),
|
||||
("Reuse one connection for port forwarding", "പോർട്ട് ഫോർവേഡിംഗിന് ഒരേ കണക്ഷൻ വീണ്ടും ഉപയോഗിക്കുക"),
|
||||
("port-forward-mux-tip", "ഒരു പോർട്ട് ഫോർവേഡിംഗിന്റെ എല്ലാ കണക്ഷനുകളും മറ്റേ കമ്പ്യൂട്ടറിലേക്കുള്ള ഒരൊറ്റ കണക്ഷനിലൂടെ കടന്നുപോകുന്നു, ഓരോന്നിനും വീണ്ടും കണക്റ്റ് ചെയ്ത് ലോഗിൻ ചെയ്യുന്നതിനു പകരം."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Sammenslåing av skjermbilder fra flere skjermer støttes for øyeblikket ikke. Bytt til én enkelt skjerm og prøv igjen."),
|
||||
("screenshot-action-tip", "Velg hvordan du vil fortsette med skjermbildet."),
|
||||
("Save as", "Lagre som"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Eksporter"),
|
||||
("Export Logs", "Eksporter logger"),
|
||||
("Import Folder", "Importer mappe"),
|
||||
("Copy to clipboard", "Kopier til utklipstavlen"),
|
||||
("Enable remote printer", "Aktiver fjernskriver"),
|
||||
("Downloading {}", "Laster ned {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Aktiver"),
|
||||
("Reuse one connection for port forwarding", "Gjenbruk én tilkobling for portvideresending"),
|
||||
("port-forward-mux-tip", "Fører alle tilkoblinger i en portvideresending gjennom én enkelt tilkobling til motparten i stedet for å koble til og logge inn på nytt for hver enkelt."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Inschakelen"),
|
||||
("Reuse one connection for port forwarding", "Eén verbinding hergebruiken voor poortdoorschakeling"),
|
||||
("port-forward-mux-tip", "Alle verbindingen van een poortdoorschakeling via één enkele verbinding met de andere computer laten lopen, in plaats van voor elke verbinding opnieuw verbinding te maken en in te loggen."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Łączenie zrzutów ekranu z wielu wyświetlaczy nie jest obecnie obsługiwane. Przełącz się na pojedynczy wyświetlacz i spróbuj ponownie."),
|
||||
("screenshot-action-tip", "Wybierz sposób kontynuacji zrzutu ekranu."),
|
||||
("Save as", "Zapisz jako"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Eksportuj"),
|
||||
("Export Logs", "Eksportuj dzienniki"),
|
||||
("Import Folder", "Importuj folder"),
|
||||
("Copy to clipboard", "Kopiuj do schowka"),
|
||||
("Enable remote printer", "Włącz zdalne drukowanie"),
|
||||
("Downloading {}", "Pobieranie {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Włącz"),
|
||||
("Reuse one connection for port forwarding", "Użyj ponownie jednego połączenia do przekierowania portów"),
|
||||
("port-forward-mux-tip", "Przekazuj wszystkie połączenia jednego przekierowania portów przez jedno połączenie ze zdalnym komputerem, zamiast łączyć się i logować od nowa dla każdego z nich."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "A junção de capturas de ecrã de vários ecrãs não é atualmente suportada. Mude para um único ecrã e tente novamente."),
|
||||
("screenshot-action-tip", "Selecione como pretende continuar com a captura de ecrã."),
|
||||
("Save as", "Guardar como"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exportar"),
|
||||
("Export Logs", "Exportar Registos"),
|
||||
("Import Folder", "Importar Pasta"),
|
||||
("Copy to clipboard", "Copiar para a área de transferência"),
|
||||
("Enable remote printer", "Ativar impressora remota"),
|
||||
("Downloading {}", "A transferir {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Ativar"),
|
||||
("Reuse one connection for port forwarding", "Reutilizar uma ligação para o reencaminhamento de portas"),
|
||||
("port-forward-mux-tip", "Encaminhar todas as ligações de um reencaminhamento de portas por uma única ligação ao outro computador, em vez de ligar e iniciar sessão novamente para cada uma."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Habilitar"),
|
||||
("Reuse one connection for port forwarding", "Reutilizar uma conexão para encaminhamento de portas"),
|
||||
("port-forward-mux-tip", "Levar todas as conexões de um encaminhamento de portas por uma única conexão com o outro computador, em vez de conectar e fazer login novamente para cada uma."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Captura de ecran a ecranului combinat nu este suportată în prezent."),
|
||||
("screenshot-action-tip", "Selectează acțiunea pentru captura de ecran: salvează ca fișier sau copiază în clipboard."),
|
||||
("Save as", "Salvează ca"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exportă"),
|
||||
("Export Logs", "Exportă jurnalele"),
|
||||
("Import Folder", "Importă folder"),
|
||||
("Copy to clipboard", "Copiază în clipboard"),
|
||||
("Enable remote printer", "Activează imprimanta la distanță"),
|
||||
("Downloading {}", "Se descarcă {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Activează"),
|
||||
("Reuse one connection for port forwarding", "Reutilizează o singură conexiune pentru redirecționarea porturilor"),
|
||||
("port-forward-mux-tip", "Trece toate conexiunile unei redirecționări de porturi printr-o singură conexiune către celălalt calculator, în loc să se conecteze și să se autentifice din nou pentru fiecare."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Объединение снимков экранов с нескольких дисплеев в настоящее время не поддерживается. Переключитесь на один дисплей и повторите действие."),
|
||||
("screenshot-action-tip", "Выберите, что делать с полученным снимком экрана."),
|
||||
("Save as", "Сохранить в файл"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Экспортировать"),
|
||||
("Export Logs", "Экспортировать журналы"),
|
||||
("Import Folder", "Импортировать папку"),
|
||||
("Copy to clipboard", "Копировать в буфер обмена"),
|
||||
("Enable remote printer", "Использовать удалённый принтер"),
|
||||
("Downloading {}", "Скачивание"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Включить"),
|
||||
("Reuse one connection for port forwarding", "Использовать одно подключение для перенаправления портов"),
|
||||
("port-forward-mux-tip", "Передавать все соединения одного перенаправления портов через одно подключение к удалённому устройству вместо повторного подключения и входа для каждого из них."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "S'unione de sa catura de ischermadas de prus ischermos como no est suportada.\nCola a un'ischermu ebbia e torra a proare."),
|
||||
("screenshot-action-tip", "Seletziona comente sighire cun s'ischermada."),
|
||||
("Save as", "Sarva comente"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Esporta"),
|
||||
("Export Logs", "Esporta is registros"),
|
||||
("Import Folder", "Importa cartella"),
|
||||
("Copy to clipboard", "Còpia in punta de billete"),
|
||||
("Enable remote printer", "Abìlita imprentadora remota"),
|
||||
("Downloading {}", "Iscarrighende {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Abìlita"),
|
||||
("Reuse one connection for port forwarding", "Torra a impreare una connessione pro s'imbiu de is portas"),
|
||||
("port-forward-mux-tip", "Totu is connessiones de un'imbiu de portas passant in una connessione ebbia a s'àteru computadore, in logu de si connètere e intrare torra pro dontzi una."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Zlučovanie snímok obrazovky z viacerých displejov nie je momentálne podporované. Prepnite na jeden displej a skúste to znova."),
|
||||
("screenshot-action-tip", "Vyberte, ako pokračovať so snímkou obrazovky."),
|
||||
("Save as", "Uložiť ako"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Exportovať"),
|
||||
("Export Logs", "Exportovať protokoly"),
|
||||
("Import Folder", "Importovať priečinok"),
|
||||
("Copy to clipboard", "Kopírovať do schránky"),
|
||||
("Enable remote printer", "Povoliť vzdialenú tlačiareň"),
|
||||
("Downloading {}", "Sťahuje sa {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Povoliť"),
|
||||
("Reuse one connection for port forwarding", "Znovu použiť jedno pripojenie na presmerovanie portov"),
|
||||
("port-forward-mux-tip", "Vedie všetky pripojenia jedného presmerovania portov cez jediné pripojenie k druhej strane namiesto opakovaného pripájania a prihlasovania pre každé z nich."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Združevanje posnetkov zaslona z več zaslonov trenutno ni podprto. Preklopite na en zaslon in poskusite znova."),
|
||||
("screenshot-action-tip", "Izberite, kako nadaljevati s posnetkom zaslona."),
|
||||
("Save as", "Shrani kot"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Izvozi"),
|
||||
("Export Logs", "Izvozi dnevnike"),
|
||||
("Import Folder", "Uvozi mapo"),
|
||||
("Copy to clipboard", "Kopiraj v odložišče"),
|
||||
("Enable remote printer", "Omogoči oddaljeni tiskalnik"),
|
||||
("Downloading {}", "Prenašanje {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Omogoči"),
|
||||
("Reuse one connection for port forwarding", "Ponovno uporabi eno povezavo za posredovanje vrat"),
|
||||
("port-forward-mux-tip", "Vse povezave enega posredovanja vrat potekajo prek ene same povezave do druge strani, namesto ponovnega povezovanja in prijave za vsako od njih."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Bashkimi i pamjeve të ekranit nga disa ekrane aktualisht nuk mbështetet. Ju lutemi kaloni te një ekran i vetëm dhe provoni përsëri."),
|
||||
("screenshot-action-tip", "Ju lutemi zgjidhni si të vazhdoni me pamjen e ekranit."),
|
||||
("Save as", "Ruaj si"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Eksporto"),
|
||||
("Export Logs", "Eksporto regjistrat"),
|
||||
("Import Folder", "Importo dosjen"),
|
||||
("Copy to clipboard", "Kopjo te clipboard"),
|
||||
("Enable remote printer", "Aktivizo printerin në distancë"),
|
||||
("Downloading {}", "Duke shkarkuar {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Aktivizo"),
|
||||
("Reuse one connection for port forwarding", "Ripërdor një lidhje për përcjelljen e porteve"),
|
||||
("port-forward-mux-tip", "Të gjitha lidhjet e një përcjelljeje portesh kalojnë përmes një lidhjeje të vetme me kompjuterin tjetër, në vend që të lidhet dhe të hyjë sërish për secilën prej tyre."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Spajanje snimaka ekrana sa više prikaza trenutno nije podržano. Molimo prebacite na jedan prikaz i pokušajte ponovo."),
|
||||
("screenshot-action-tip", "Molimo izaberite kako da nastavite sa snimkom ekrana."),
|
||||
("Save as", "Sačuvaj kao"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Izvoz"),
|
||||
("Export Logs", "Izvoz dnevnika"),
|
||||
("Import Folder", "Uvoz fascikle"),
|
||||
("Copy to clipboard", "Kopiraj u clipboard"),
|
||||
("Enable remote printer", "Omogući udaljeni štampač"),
|
||||
("Downloading {}", "Preuzimanje {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Omogući"),
|
||||
("Reuse one connection for port forwarding", "Ponovo koristi jednu vezu za prosleđivanje portova"),
|
||||
("port-forward-mux-tip", "Sve veze jednog prosleđivanja portova idu kroz jednu vezu ka drugoj strani, umesto povezivanja i prijavljivanja iznova za svaku od njih."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Aktivera"),
|
||||
("Reuse one connection for port forwarding", "Återanvänd en anslutning för portvidarebefordran"),
|
||||
("port-forward-mux-tip", "Låt alla anslutningar i en portvidarebefordran gå via en enda anslutning till motparten, i stället för att ansluta och logga in på nytt för varje anslutning."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "ஸ்கிரீன்ஷாட்_இணைக்கப்பட்ட_திரை_ஆதரவற்ற_குறிப்பு"),
|
||||
("screenshot-action-tip", "ஸ்கிரீன்ஷாட்_செயல்_குறிப்பு"),
|
||||
("Save as", "இப்படி சேமி"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "ஏற்றுமதி"),
|
||||
("Export Logs", "பதிவுகளை ஏற்றுமதி செய்"),
|
||||
("Import Folder", "கோப்புறையை இறக்குமதி செய்"),
|
||||
("Copy to clipboard", "கிளிப்போர்டில் நகல்"),
|
||||
("Enable remote printer", "தொலை அச்சுப்பொறி இயக்கு"),
|
||||
("Downloading {}", "{} பதிவிறக்குகிறது"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "இயக்கு"),
|
||||
("Reuse one connection for port forwarding", "போர்ட் ஃபார்வேர்டிங்கிற்கு ஒரே இணைப்பை மீண்டும் பயன்படுத்து"),
|
||||
("port-forward-mux-tip", "ஒரு போர்ட் ஃபார்வேர்டிங்கின் அனைத்து இணைப்புகளும் மறுமுனைக்கான ஒரே இணைப்பின் வழியாகச் செல்லும், ஒவ்வொன்றுக்கும் மீண்டும் இணைந்து உள்நுழைவதற்குப் பதிலாக."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", ""),
|
||||
("Reuse one connection for port forwarding", ""),
|
||||
("port-forward-mux-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "ขณะนี้ยังไม่รองรับการรวมภาพหน้าจอจากหลายจอแสดงผล กรุณาสลับไปใช้จอแสดงผลเดียวแล้วลองใหม่"),
|
||||
("screenshot-action-tip", "กรุณาเลือกวิธีดำเนินการต่อกับภาพหน้าจอ"),
|
||||
("Save as", "บันทึกเป็น"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "ส่งออก"),
|
||||
("Export Logs", "ส่งออกบันทึกการทำงาน"),
|
||||
("Import Folder", "นำเข้าโฟลเดอร์"),
|
||||
("Copy to clipboard", "คัดลอกไปยังคลิปบอร์ด"),
|
||||
("Enable remote printer", "เปิดใช้งานเครื่องพิมพ์ระยะไกล"),
|
||||
("Downloading {}", "กำลังดาวน์โหลด {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "เปิดใช้งาน"),
|
||||
("Reuse one connection for port forwarding", "ใช้การเชื่อมต่อเดียวร่วมกันสำหรับการส่งต่อพอร์ต"),
|
||||
("port-forward-mux-tip", "ส่งการเชื่อมต่อทั้งหมดของการส่งต่อพอร์ตหนึ่งรายการผ่านการเชื่อมต่อเดียวไปยังอีกฝ่าย แทนการเชื่อมต่อและเข้าสู่ระบบใหม่ทุกครั้ง"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Birden fazla ekranın ekran görüntülerinin birleştirilmesi şu anda desteklenmiyor. Lütfen tek bir ekrana geçin ve tekrar deneyin."),
|
||||
("screenshot-action-tip", "Lütfen ekran görüntüsüyle nasıl devam edeceğinizi seçin."),
|
||||
("Save as", "Farklı kaydet"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Dışa aktar"),
|
||||
("Export Logs", "Günlükleri dışa aktar"),
|
||||
("Import Folder", "Klasör içe aktar"),
|
||||
("Copy to clipboard", "Panoya kopyala"),
|
||||
("Enable remote printer", "Uzak yazıcıyı etkinleştir"),
|
||||
("Downloading {}", "{} indiriliyor"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Etkinleştir"),
|
||||
("Reuse one connection for port forwarding", "Port yönlendirme için tek bağlantıyı yeniden kullan"),
|
||||
("port-forward-mux-tip", "Bir port yönlendirmesindeki tüm bağlantıları, her biri için yeniden bağlanıp oturum açmak yerine karşı tarafa açılan tek bir bağlantı üzerinden taşır."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "啟用"),
|
||||
("Reuse one connection for port forwarding", "連接埠轉送重複使用同一條連線"),
|
||||
("port-forward-mux-tip", "同一條連接埠轉送規則上的所有連線共用一條到對方的連線,而不是每條連線都重新連線並登入一次。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Об'єднання знімків кількох дисплеїв наразі не підтримується. Перейдіть на один дисплей і спробуйте знову."),
|
||||
("screenshot-action-tip", "Виберіть, що робити зі знімком екрана."),
|
||||
("Save as", "Зберегти як"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Експортувати"),
|
||||
("Export Logs", "Експортувати журнали"),
|
||||
("Import Folder", "Імпортувати теку"),
|
||||
("Copy to clipboard", "Скопіювати до буфера обміну"),
|
||||
("Enable remote printer", "Увімкнути віддалений принтер"),
|
||||
("Downloading {}", "Завантаження {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Увімкнути"),
|
||||
("Reuse one connection for port forwarding", "Використовувати одне з'єднання для перенаправлення портів"),
|
||||
("port-forward-mux-tip", "Передавати всі з'єднання одного перенаправлення портів через одне з'єднання з віддаленим пристроєм замість повторного під'єднання та входу для кожного з них."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
271
src/lang/ur.rs
271
src/lang/ur.rs
@@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "حالت"),
|
||||
("Your Desktop", "آپ کا ڈیسک ٹاپ"),
|
||||
("desk_tip", ""),
|
||||
("desk_tip", "آپ کے ڈیسک ٹاپ تک اس ID اور پاس ورڈ کے ذریعے رسائی حاصل کی جا سکتی ہے۔"),
|
||||
("Password", "پاس ورڈ"),
|
||||
("Ready", "تیار"),
|
||||
("Established", "قائم کیا گیا"),
|
||||
@@ -12,7 +12,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Start service", "سروس شروع کریں"),
|
||||
("Service is running", "سروس چل رہی ہے"),
|
||||
("Service is not running", "سروس نہیں چل رہی ہے"),
|
||||
("not_ready_status", ""),
|
||||
("not_ready_status", "تیار نہیں۔ براہِ کرم اپنا کنکشن جانچیں"),
|
||||
("Control Remote Desktop", "ریموٹ ڈیسک ٹاپ کو کنٹرول کریں"),
|
||||
("Transfer file", "فائل منتقل کریں"),
|
||||
("Connect", "کنیکٹ کریں"),
|
||||
@@ -41,12 +41,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("length %min% to %max%", "لمبائی %min% سے %max%"),
|
||||
("starts with a letter", "حرف سے شروع ہوتا ہے"),
|
||||
("allowed characters", "اجازت یافتہ حروف"),
|
||||
("id_change_tip", ""),
|
||||
("id_change_tip", "صرف a-z، A-Z، 0-9، - (ڈیش) اور _ (انڈر اسکور) حروف کی اجازت ہے۔ پہلا حرف a-z یا A-Z ہونا چاہیے۔ لمبائی 6 سے 16 کے درمیان ہو۔"),
|
||||
("Website", "ویب سائٹ"),
|
||||
("About", "کے بارے میں"),
|
||||
("Slogan_tip", "سلوگن_ٹپ"),
|
||||
("Privacy Statement", "رازداری کا بیان"),
|
||||
("License", "لائسنس"),
|
||||
("Mute", "خاموش"),
|
||||
("Build Date", "بنیاد کی تاریخ"),
|
||||
("Version", "ورژن"),
|
||||
@@ -149,21 +148,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("install_tip", "انسٹال کرنے کا مشورہ"),
|
||||
("Click to upgrade", "اپگریڈ کرنے کے لئے کلک کریں"),
|
||||
("Configure", "ترتیب دینا"),
|
||||
("config_acc", ""),
|
||||
("config_screen", ""),
|
||||
("config_acc", "اپنے ڈیسک ٹاپ کو دور سے کنٹرول کرنے کے لیے آپ کو RustDesk کو \"Accessibility\" کی اجازتیں دینا ہوں گی۔"),
|
||||
("config_screen", "اپنے ڈیسک ٹاپ تک دور سے رسائی کے لیے آپ کو RustDesk کو \"Screen Recording\" کی اجازتیں دینا ہوں گی۔"),
|
||||
("Installing ...", "انسٹال ہو رہا ہے..."),
|
||||
("Install", "انسٹال کریں"),
|
||||
("Installation", "انسٹالیشن"),
|
||||
("Installation Path", "انسٹالیشن کا راستہ"),
|
||||
("Create start menu shortcuts", "اسٹارٹ مینو شارٹ کٹس بنائیں"),
|
||||
("Create desktop icon", "ڈیسکٹاپ آئیکن بنائیں"),
|
||||
("agreement_tip", ""),
|
||||
("agreement_tip", "انسٹالیشن شروع کرنے سے آپ لائسنس معاہدہ قبول کرتے ہیں۔"),
|
||||
("Accept and Install", "قبول کریں اور انسٹال کریں"),
|
||||
("End-user license agreement", "اختتامی صارف کے لائسنس کا معاہدہ"),
|
||||
("Generating ...", "بنا رہے ہیں..."),
|
||||
("Your installation is lower version.", "آپ کی تنصیب کم ورژن ہے۔"),
|
||||
("Please install the latest version.", "براہِ مہربانی تازہ ترین ورژن انسٹال کریں۔"),
|
||||
("not_close_tcp_tip", ""),
|
||||
("not_close_tcp_tip", "جب تک آپ ٹنل استعمال کر رہے ہیں، یہ ونڈو بند نہ کریں"),
|
||||
("Listening ...", "سن رہا ہے..."),
|
||||
("Remote Host", "ریموٹ میزبان"),
|
||||
("Remote Port", "ریموٹ پورٹ"),
|
||||
@@ -212,7 +210,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Run without install", "انسٹال کے بغیر چلائیں"),
|
||||
("Connect via relay", "ریلے کے ذریعے کنیکٹ کریں"),
|
||||
("Always connect via relay", "ہمیشہ ریلے کے ذریعے کنیکٹ کریں"),
|
||||
("whitelist_tip", ""),
|
||||
("whitelist_tip", "صرف وائٹ لسٹ میں شامل IP مجھ تک رسائی حاصل کر سکتے ہیں"),
|
||||
("Login", "لاگ ان کریں"),
|
||||
("Verify", "تصدیق کریں"),
|
||||
("Remember me", "یاد رکھیں"),
|
||||
@@ -222,7 +220,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Logout", "لاگ آؤٹ"),
|
||||
("Tags", "ٹیگز"),
|
||||
("Search ID", "ID تلاش کریں"),
|
||||
("whitelist_sep", ""),
|
||||
("whitelist_sep", "کوما، سیمی کولن، خالی جگہ یا نئی سطر سے الگ کریں"),
|
||||
("Add ID", "ID شامل کریں"),
|
||||
("Add Tag", "ٹیگ شامل کریں"),
|
||||
("Unselect all tags", "تمام ٹیگز کو غیر منتخب کریں"),
|
||||
@@ -241,7 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Socks5 Proxy", "پروکسی ساکس5"),
|
||||
("Socks5/Http(s) Proxy", "ساکس5/Http(s) پروکسی"),
|
||||
("Discovered", "دریافت شدہ"),
|
||||
("install_daemon_tip", ""),
|
||||
("install_daemon_tip", "بوٹ پر شروع ہونے کے لیے آپ کو سسٹم سروس انسٹال کرنا ہوگی۔"),
|
||||
("Remote ID", "ریموٹ ID"),
|
||||
("Paste", "چسپاں کریں"),
|
||||
("Paste here?", "یہاں چسپاں کریں؟"),
|
||||
@@ -278,14 +276,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("android_input_permission_tip1", "کسی دور دراز آلے کو ماؤس یا ٹچ کے ذریعے آپ کے Android آلے کو کنٹرول کرنے کے لیے آپ کو RustDesk کو \"Accessibility\" سروس استعمال کرنے کی اجازت دینا ہوگی۔"),
|
||||
("android_input_permission_tip2", "براہِ کرم اگلے سسٹم سیٹنگز صفحے پر جائیں، [Installed Services] تلاش کر کے کھولیں اور [RustDesk Input] سروس آن کریں۔"),
|
||||
("android_new_connection_tip", "ایک نئی کنٹرول درخواست موصول ہوئی ہے، جو آپ کے موجودہ آلے کو کنٹرول کرنا چاہتی ہے۔"),
|
||||
("android_service_will_start_tip", "\"Screen Capture\" آن کرنے سے سروس خودکار طور پر شروع ہو جائے گی، جس سے دوسرے آلات آپ کے آلے سے کنکشن کی درخواست کر سکیں گے۔"),
|
||||
("android_stop_service_tip", "سروس بند کرنے سے تمام قائم شدہ کنکشن خودکار طور پر بند ہو جائیں گے۔"),
|
||||
("android_version_audio_tip", "موجودہ Android ورژن آڈیو کیپچر کی حمایت نہیں کرتا، براہِ کرم Android 10 یا اس سے نئے ورژن پر اپ گریڈ کریں۔"),
|
||||
("android_start_service_tip", "اسکرین شیئرنگ سروس شروع کرنے کے لیے [Start service] پر ٹیپ کریں یا [Screen Capture] کی اجازت فعال کریں۔"),
|
||||
("android_permission_may_not_change_tip", "قائم شدہ کنکشنز کی اجازتیں دوبارہ منسلک ہونے تک فوراً تبدیل نہیں ہو سکتیں۔"),
|
||||
("Account", "کھاتا"),
|
||||
("Overwrite", "اوور رائٹ کریں"),
|
||||
("This file exists, skip or overwrite this file?", "یہ فائل موجود ہے، اس فائل کو چھوڑیں یا اوور رائٹ کریں؟"),
|
||||
@@ -296,14 +294,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("android_open_battery_optimizations_tip", "اگر آپ یہ خصوصیت بند کرنا چاہتے ہیں تو براہِ کرم اگلے RustDesk ایپلیکیشن سیٹنگز صفحے پر جائیں، [Battery] تلاش کر کے کھولیں اور [Unrestricted] کا نشان ہٹا دیں"),
|
||||
("Start on boot", "شروع کرنے پر شروع کریں"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "بوٹ پر سکرین شیئرنگ سروس شروع کریں، خاص اجازتوں کی ضرورت ہے"),
|
||||
("Connection not allowed", "جڑنے کی اجازت نہیں ہے"),
|
||||
@@ -317,7 +314,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Restart remote device", "ریموٹ ڈیوائس کو ری اسٹارٹ کریں"),
|
||||
("Are you sure you want to restart", "کیا آپ واقعی ری اسٹارٹ کرنا چاہتے ہیں؟"),
|
||||
("Restarting remote device", "ریموٹ ڈیوائس ری اسٹارٹ ہو رہی ہے"),
|
||||
("remote_restarting_tip", ""),
|
||||
("remote_restarting_tip", "دور دراز آلہ دوبارہ شروع ہو رہا ہے، براہِ کرم یہ پیغام بند کریں اور کچھ دیر بعد مستقل پاس ورڈ کے ساتھ دوبارہ منسلک ہوں"),
|
||||
("Copied", "نقل ہو گیا"),
|
||||
("Exit Fullscreen", "مکمل سکرین سے باہر نکلیں"),
|
||||
("Fullscreen", "مکمل سکرین"),
|
||||
@@ -408,19 +405,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by web console", "ویب کنسول کے ذریعے دستی طور پر بند کیا گیا"),
|
||||
("Local keyboard type", "مقامی کیبورڈ کا قسم"),
|
||||
("Select local keyboard type", "مقامی کیبورڈ کا قسم منتخب کریں"),
|
||||
("software_render_tip", ""),
|
||||
("software_render_tip", "اگر آپ Linux پر Nvidia گرافکس کارڈ استعمال کر رہے ہیں اور منسلک ہونے کے فوراً بعد ریموٹ ونڈو بند ہو جاتی ہے، تو اوپن سورس Nouveau ڈرائیور پر منتقل ہونا اور سافٹ ویئر رینڈرنگ کا انتخاب مددگار ہو سکتا ہے۔ سافٹ ویئر کو دوبارہ شروع کرنا ضروری ہے۔"),
|
||||
("Always use software rendering", "ہم sempre سافٹ ویر رینڈرنگ استعمال کریں"),
|
||||
("config_input", "config_input"),
|
||||
("config_microphone", ""),
|
||||
("request_elevation_tip", ""),
|
||||
("config_microphone", "دور سے بات کرنے کے لیے آپ کو RustDesk کو \"Record Audio\" کی اجازتیں دینا ہوں گی۔"),
|
||||
("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", ""),
|
||||
("still_click_uac_tip", "پھر بھی ضروری ہے کہ دور دراز صارف چل رہے RustDesk کی UAC ونڈو پر OK پر کلک کرے۔"),
|
||||
("Request Elevation", "علیٰ کرنے کا درخواست دیں"),
|
||||
("wait_accept_uac_tip", ""),
|
||||
("wait_accept_uac_tip", "براہِ کرم انتظار کریں کہ دور دراز صارف UAC ڈائیلاگ قبول کرے۔"),
|
||||
("Elevate successfully", "علیٰ کامیابی سے ہو گئے"),
|
||||
("uppercase", "بڑے حروف"),
|
||||
("lowercase", "چھوٹے حروف"),
|
||||
@@ -438,7 +435,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Default Image Quality", "ڈیفالٹ تصویر کی معیار"),
|
||||
("Default Codec", "ڈیفالٹ کوڈک"),
|
||||
("Bitrate", "بٹ ریٹ"),
|
||||
("FPS", ""),
|
||||
("FPS", "FPS"),
|
||||
("Auto", "خودکار"),
|
||||
("Other Default Options", "دوسروں ڈیفالٹ اختیارات"),
|
||||
("Voice call", "صوتی کال"),
|
||||
@@ -464,20 +461,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Username", "خالی صارف نام"),
|
||||
("Empty Password", "خالی پاس ورڈ"),
|
||||
("Me", "میں"),
|
||||
("identical_file_tip", ""),
|
||||
("show_monitors_tip", ""),
|
||||
("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", ""),
|
||||
("verify_rustdesk_password_tip", "RustDesk پاس ورڈ کی تصدیق کریں"),
|
||||
("No need to elevate", "اپنے کو ہیں نہیں"),
|
||||
("System Sound", "سسٹم سائونڈ"),
|
||||
("Default", "ڈیفالٹ"),
|
||||
@@ -485,30 +472,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("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", ""),
|
||||
("accept_and_elevate_btn_tooltip", "کنکشن قبول کریں اور UAC اجازتیں بڑھائیں۔"),
|
||||
("clipboard_wait_response_timeout_tip", "کاپی کے جواب کا انتظار ختم ہو گیا۔"),
|
||||
("Incoming connection", "آنے والا کنکشن"),
|
||||
("Outgoing connection", "جانے والا کنکشن"),
|
||||
("Exit", "خارج ہوں"),
|
||||
("Open", "کھولیں"),
|
||||
("logout_tip", ""),
|
||||
("logout_tip", "کیا آپ واقعی لاگ آؤٹ کرنا چاہتے ہیں؟"),
|
||||
("Service", "سروس"),
|
||||
("Start", "شروع کریں"),
|
||||
("Stop", "روک دیں"),
|
||||
("exceed_max_devices", ""),
|
||||
("exceed_max_devices", "آپ زیرِ انتظام آلات کی زیادہ سے زیادہ تعداد تک پہنچ چکے ہیں۔"),
|
||||
("Sync with recent sessions", "پچھلے سیشنز کے ساتھ ہم آہنگ کریں"),
|
||||
("Sort tags", "ٹیگز کو ترتیب دیں"),
|
||||
("Open connection in new tab", "کنکشن کو نئے ٹیب میں کھولیں"),
|
||||
@@ -517,14 +498,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Already exists", "پہلے سے موجود ہے"),
|
||||
("Change Password", "پاسورڈ تبدیل کریں"),
|
||||
("Refresh Password", "پاسورڈ ریفریش کریں"),
|
||||
("ID", ""),
|
||||
("ID", "ID"),
|
||||
("Grid View", "گوڈ ویو"),
|
||||
("List View", "لسٹ ویو"),
|
||||
("Select", "منتخب کریں"),
|
||||
("Toggle Tags", "ٹیگز ٹوگل کریں"),
|
||||
("pull_ab_failed_tip", ""),
|
||||
("push_ab_failed_tip", ""),
|
||||
("synced_peer_readded_tip", ""),
|
||||
("pull_ab_failed_tip", "ایڈریس بک تازہ کرنے میں ناکامی"),
|
||||
("push_ab_failed_tip", "ایڈریس بک کو سرور سے ہم آہنگ کرنے میں ناکامی"),
|
||||
("synced_peer_readded_tip", "حالیہ سیشنز میں موجود آلات دوبارہ ایڈریس بک سے ہم آہنگ کر دیے جائیں گے۔"),
|
||||
("Change Color", "رنگ تبدیل کریں"),
|
||||
("Primary Color", "پرائمری رنگ"),
|
||||
("HSV Color", "HSV رنگ"),
|
||||
@@ -539,11 +520,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("I Agree", "میں قبول کرتا ہوں"),
|
||||
("Decline", "ناکام کریں"),
|
||||
("Timeout in minutes", "منٹوں میں ٹائیم آؤٹ"),
|
||||
("auto_disconnect_option_tip", ""),
|
||||
("auto_disconnect_option_tip", "صارف کی غیر فعالی پر آنے والے سیشنز خودکار طور پر بند کریں"),
|
||||
("Connection failed due to inactivity", "انفعال کی وजہ سے کنکشن ناکام ہو گیا"),
|
||||
("Check for software update on startup", "سٹارٹ اپ پر سافٹ ویر اپڈیٹ کے لیے چیک کریں"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
||||
("pull_group_failed_tip", ""),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "براہِ کرم RustDesk Server Pro کو ورژن {} یا اس سے نئے پر اپ گریڈ کریں!"),
|
||||
("pull_group_failed_tip", "گروپ تازہ کرنے میں ناکامی"),
|
||||
("Filter by intersection", "فلٹر بائی انسٹریکشن"),
|
||||
("Remove wallpaper during incoming sessions", "ان کلینگ سیشنز کے دوران والپیپر کو ہٹائیں"),
|
||||
("Test", "ٹیسٹ"),
|
||||
@@ -552,7 +533,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Open in new window", "نئی ونڈو میں کھولیں"),
|
||||
("Show displays as individual windows", "ڈسپلے کو افراد کے طور پر دکھائیں"),
|
||||
("Use all my displays for the remote session", "ریموٹ سیشن کے لیے میرے تمام ڈسپلے استعمال کریں"),
|
||||
("selinux_tip", ""),
|
||||
("selinux_tip", "آپ کے آلے پر SELinux فعال ہے، جو RustDesk کو بطور کنٹرول شدہ فریق درست طور پر چلنے سے روک سکتا ہے۔"),
|
||||
("Change view", "ویو تبدیل کریں"),
|
||||
("Big tiles", "بڑے ٹائل"),
|
||||
("Small tiles", "چھوٹے ٹائل"),
|
||||
@@ -561,14 +542,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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", ""),
|
||||
("id_input_tip", "آپ ایک ID، براہِ راست IP، یا پورٹ کے ساتھ ڈومین (<domain>:<port>) درج کر سکتے ہیں۔\nاگر آپ کسی دوسرے سرور پر موجود آلے تک رسائی چاہتے ہیں تو سرور کا پتہ ساتھ لگائیں (<id>@<server_address>?key=<key_value>)، مثلاً،\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=۔\nاگر آپ کسی عوامی سرور پر موجود آلے تک رسائی چاہتے ہیں تو \"<id>@public\" درج کریں، عوامی سرور کے لیے کلید درکار نہیں۔\n\nاگر آپ پہلے کنکشن پر ریلے کنکشن کا استعمال لازمی کرنا چاہتے ہیں تو ID کے آخر میں \"/r\" شامل کریں، مثلاً، \"9123456234/r\"۔"),
|
||||
("privacy_mode_impl_mag_tip", "موڈ 1"),
|
||||
("privacy_mode_impl_virtual_display_tip", "موڈ 2"),
|
||||
("Enter privacy mode", "خفیہ موڈ میں داخل ہوں"),
|
||||
("Exit privacy mode", "خفیہ موڈ سے باہر نکلیں"),
|
||||
("idd_not_support_under_win10_2004_tip", ""),
|
||||
("input_source_1_tip", ""),
|
||||
("input_source_2_tip", ""),
|
||||
("idd_not_support_under_win10_2004_tip", "بالواسطہ ڈسپلے ڈرائیور معاون نہیں ہے۔ Windows 10 ورژن 2004 یا اس سے نیا درکار ہے۔"),
|
||||
("input_source_1_tip", "ان پٹ ماخذ 1"),
|
||||
("input_source_2_tip", "ان پٹ ماخذ 2"),
|
||||
("Swap control-command key", "control-command کلید کو سوپ کریں"),
|
||||
("swap-left-right-mouse", "بائی-دائی ماؤس کو سوپ کریں"),
|
||||
("2FA code", "2FA کوڈ"),
|
||||
@@ -582,8 +563,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Multiple Windows sessions found", "متعدد ونڈوز سیشن ملے"),
|
||||
("Please select the session you want to connect to", "براہ کرم وہ سیشن منتخب کریں جس سے آپ منسلک ہونا چاہتے ہیں"),
|
||||
("powered_by_me", "میں کی طرف سے طاقتور"),
|
||||
("outgoing_only_desk_tip", ""),
|
||||
("preset_password_warning", ""),
|
||||
("outgoing_only_desk_tip", "یہ ایک حسبِ ضرورت ایڈیشن ہے۔\nآپ دوسرے آلات سے منسلک ہو سکتے ہیں، لیکن دوسرے آلات آپ کے آلے سے منسلک نہیں ہو سکتے۔"),
|
||||
("preset_password_warning", "یہ حسبِ ضرورت ایڈیشن پہلے سے مقرر پاس ورڈ کے ساتھ آتا ہے۔ جو بھی یہ پاس ورڈ جانتا ہو وہ آپ کے آلے کا مکمل کنٹرول حاصل کر سکتا ہے۔ اگر آپ کو اس کی توقع نہیں تھی تو سافٹ ویئر فوراً ان انسٹال کر دیں۔"),
|
||||
("Security Alert", "سیکورٹی الرٹ"),
|
||||
("My address book", "میری ایڈریس بک"),
|
||||
("Personal", "شخصی"),
|
||||
@@ -593,25 +574,25 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Read-only", "صرف پڑھنے کے لیے"),
|
||||
("Read/Write", "پڑھنے/لکھنے"),
|
||||
("Full Control", "پورا کنٹرول"),
|
||||
("share_warning_tip", ""),
|
||||
("share_warning_tip", "اوپر دیے گئے خانے مشترکہ ہیں اور دوسروں کو نظر آتے ہیں۔"),
|
||||
("Everyone", "ہر کوئی"),
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("ab_web_console_tip", "ویب کنسول پر مزید"),
|
||||
("allow-only-conn-window-open-tip", "کنکشن کی اجازت صرف اس صورت میں دیں جب RustDesk ونڈو کھلی ہو"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "کوئی طبعی ڈسپلے نہیں، پرائیویسی موڈ استعمال کرنے کی ضرورت نہیں۔"),
|
||||
("Follow remote cursor", "ریموٹ کرسر کی پیروی کریں"),
|
||||
("Follow remote window focus", "ریموٹ ونڈو فوکس کی پیروی کریں"),
|
||||
("default_proxy_tip", ""),
|
||||
("no_audio_input_device_tip", ""),
|
||||
("default_proxy_tip", "پہلے سے طے شدہ پروٹوکول اور پورٹ Socks5 اور 1080 ہیں"),
|
||||
("no_audio_input_device_tip", "کوئی آڈیو ان پٹ آلہ نہیں ملا۔"),
|
||||
("Incoming", "آنے والے"),
|
||||
("Outgoing", "بھیجے جا رہے"),
|
||||
("Clear Wayland screen selection", "Wayland سکرین کی انتخاب صاف کریں"),
|
||||
("clear_Wayland_screen_selection_tip", ""),
|
||||
("confirm_clear_Wayland_screen_selection_tip", ""),
|
||||
("android_new_voice_call_tip", ""),
|
||||
("texture_render_tip", ""),
|
||||
("clear_Wayland_screen_selection_tip", "اسکرین کا انتخاب صاف کرنے کے بعد آپ شیئر کرنے کے لیے اسکرین دوبارہ منتخب کر سکتے ہیں۔"),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "کیا آپ واقعی Wayland اسکرین کا انتخاب صاف کرنا چاہتے ہیں؟"),
|
||||
("android_new_voice_call_tip", "ایک نئی صوتی کال کی درخواست موصول ہوئی۔ اگر آپ قبول کرتے ہیں تو آڈیو صوتی رابطے پر منتقل ہو جائے گا۔"),
|
||||
("texture_render_tip", "تصاویر کو ہموار بنانے کے لیے ٹیکسچر رینڈرنگ استعمال کریں۔ اگر آپ کو رینڈرنگ کے مسائل درپیش ہوں تو یہ اختیار بند کر کے دیکھ سکتے ہیں۔"),
|
||||
("Use texture rendering", "ٹیکسچر رینڈرنگ کا استعمال کریں"),
|
||||
("Floating window", "فلوٹنگ ونڈو"),
|
||||
("floating_window_tip", ""),
|
||||
("floating_window_tip", "یہ RustDesk کی پس منظر سروس کو برقرار رکھنے میں مدد دیتا ہے"),
|
||||
("Keep screen on", "سکرین کو آن رکھیں"),
|
||||
("Never", "کبھی نہیں"),
|
||||
("During controlled", "کنٹرول کے دوران"),
|
||||
@@ -623,13 +604,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Volume down", "آواز کم کریں"),
|
||||
("Power", "پاور"),
|
||||
("Telegram bot", "ٹیلیگرام بات"),
|
||||
("enable-bot-tip", ""),
|
||||
("enable-bot-desc", ""),
|
||||
("cancel-2fa-confirm-tip", ""),
|
||||
("cancel-bot-confirm-tip", ""),
|
||||
("enable-bot-tip", "اگر آپ یہ خصوصیت فعال کریں تو آپ اپنے بوٹ سے 2FA کوڈ وصول کر سکتے ہیں۔ یہ کنکشن کی اطلاع کے طور پر بھی کام کر سکتا ہے۔"),
|
||||
("enable-bot-desc", "1. @BotFather کے ساتھ چیٹ کھولیں۔\n2. کمانڈ \"/newbot\" بھیجیں۔ یہ مرحلہ مکمل کرنے کے بعد آپ کو ایک ٹوکن ملے گا۔\n3. اپنے نئے بنائے گئے بوٹ کے ساتھ چیٹ شروع کریں۔ اسے فعال کرنے کے لیے فارورڈ سلیش (\"/\") سے شروع ہونے والا پیغام، جیسے \"/hello\"، بھیجیں۔\n"),
|
||||
("cancel-2fa-confirm-tip", "کیا آپ واقعی 2FA منسوخ کرنا چاہتے ہیں؟"),
|
||||
("cancel-bot-confirm-tip", "کیا آپ واقعی Telegram بوٹ منسوخ کرنا چاہتے ہیں؟"),
|
||||
("About RustDesk", "رستڈیسک کے بارے میں"),
|
||||
("Send clipboard keystrokes", "کلپ بورڈ کی چابیاں بھیجیں"),
|
||||
("network_error_tip", ""),
|
||||
("network_error_tip", "براہِ کرم اپنا نیٹ ورک کنکشن جانچیں، پھر دوبارہ کوشش پر کلک کریں۔"),
|
||||
("Unlock with PIN", "PIN کے ساتھ انلاک کریں"),
|
||||
("Requires at least {} characters", "کم از کم {} حروف کی ضرورت ہے"),
|
||||
("Wrong PIN", "غلط PIN"),
|
||||
@@ -638,56 +619,56 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Manage trusted devices", "معتبر آلے مینیج کریں"),
|
||||
("Platform", "پلیٹ فارم"),
|
||||
("Days remaining", "دن باقی"),
|
||||
("enable-trusted-devices-tip", ""),
|
||||
("enable-trusted-devices-tip", "قابلِ اعتماد آلات پر 2FA تصدیق چھوڑ دیں"),
|
||||
("Parent directory", "والد ڈائرکٹری"),
|
||||
("Resume", "جاری رکھیں"),
|
||||
("Invalid file name", "غلط فائل کا نام"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("one-way-file-transfer-tip", "کنٹرول شدہ فریق پر یک طرفہ فائل منتقلی فعال ہے۔"),
|
||||
("Authentication Required", "توثیق کی ضرورت ہے"),
|
||||
("Authenticate", "توثیق کریں"),
|
||||
("web_id_input_tip", ""),
|
||||
("web_id_input_tip", "آپ اسی سرور میں ایک ID درج کر سکتے ہیں، ویب کلائنٹ میں براہِ راست IP رسائی معاون نہیں ہے۔\nاگر آپ کسی دوسرے سرور پر موجود آلے تک رسائی چاہتے ہیں تو سرور کا پتہ ساتھ لگائیں (<id>@<server_address>?key=<key_value>)، مثلاً،\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=۔\nاگر آپ کسی عوامی سرور پر موجود آلے تک رسائی چاہتے ہیں تو \"<id>@public\" درج کریں، عوامی سرور کے لیے کلید درکار نہیں۔"),
|
||||
("Download", "ڈاؤن لوڈ کریں"),
|
||||
("Upload folder", "اپ لوڈ فولڈر"),
|
||||
("Upload files", "فائلیں اپ لوڈ کریں"),
|
||||
("Clipboard is synchronized", "کلپ بورڈ مطابق ہے"),
|
||||
("Update client clipboard", "کلپ بورڈ کو اپ ڈیٹ کریں"),
|
||||
("Untagged", "غیر تعلق یافتہ"),
|
||||
("new-version-of-{}-tip", ""),
|
||||
("new-version-of-{}-tip", "{} کا ایک نیا ورژن دستیاب ہے"),
|
||||
("Accessible devices", "قابلِ رسائی والے آلے"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
|
||||
("d3d_render_tip", ""),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "براہِ کرم دور دراز فریق پر RustDesk کلائنٹ کو ورژن {} یا اس سے نئے پر اپ گریڈ کریں!"),
|
||||
("d3d_render_tip", "جب D3D رینڈرنگ فعال ہو تو کچھ مشینوں پر ریموٹ کنٹرول اسکرین سیاہ ہو سکتی ہے۔"),
|
||||
("Use D3D rendering", "D3D رینڈرنگ کا استعمال کریں"),
|
||||
("Printer", "پرنٹر"),
|
||||
("printer-os-requirement-tip", ""),
|
||||
("printer-requires-installed-{}-client-tip", ""),
|
||||
("printer-{}-not-installed-tip", ""),
|
||||
("printer-{}-ready-tip", ""),
|
||||
("printer-os-requirement-tip", "پرنٹر کی بیرونی خصوصیت کے لیے Windows 10 یا اس سے نیا درکار ہے۔"),
|
||||
("printer-requires-installed-{}-client-tip", "دور دراز پرنٹنگ استعمال کرنے کے لیے اس آلے پر {} انسٹال ہونا ضروری ہے۔"),
|
||||
("printer-{}-not-installed-tip", "{} پرنٹر انسٹال نہیں ہے۔"),
|
||||
("printer-{}-ready-tip", "{} پرنٹر انسٹال ہے اور استعمال کے لیے تیار ہے۔"),
|
||||
("Install {} Printer", " {} پرنٹر انسٹال کریں"),
|
||||
("Outgoing Print Jobs", "بیرونی پرنٹ کام"),
|
||||
("Incoming Print Jobs", "اندر کے پرنٹ کام"),
|
||||
("Incoming Print Job", "اندر کا پرنٹ کام"),
|
||||
("use-the-default-printer-tip", ""),
|
||||
("use-the-selected-printer-tip", ""),
|
||||
("auto-print-tip", ""),
|
||||
("print-incoming-job-confirm-tip", ""),
|
||||
("remote-printing-disallowed-tile-tip", ""),
|
||||
("remote-printing-disallowed-text-tip", ""),
|
||||
("save-settings-tip", ""),
|
||||
("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-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", ""),
|
||||
("{}-to-update-tip", "{} اب بند ہو کر نیا ورژن انسٹال کرے گا۔"),
|
||||
("download-new-version-failed-tip", "ڈاؤن لوڈ ناکام۔ آپ دوبارہ کوشش کر سکتے ہیں یا \"Download\" بٹن پر کلک کر کے ریلیز صفحے سے ڈاؤن لوڈ کر کے دستی طور پر اپ گریڈ کر سکتے ہیں۔"),
|
||||
("Auto update", "خودکار اپ ڈیٹ"),
|
||||
("update-failed-check-msi-tip", ""),
|
||||
("websocket_tip", ""),
|
||||
("update-failed-check-msi-tip", "انسٹالیشن کے طریقے کی جانچ ناکام۔ براہِ کرم \"Download\" بٹن پر کلک کر کے ریلیز صفحے سے ڈاؤن لوڈ کریں اور دستی طور پر اپ گریڈ کریں۔"),
|
||||
("websocket_tip", "WebSocket استعمال کرتے وقت صرف ریلے کنکشنز معاون ہیں۔"),
|
||||
("Use WebSocket", "WebSocket استعمال کریں"),
|
||||
("Trackpad speed", "ٹریک پیڈ کی رفتار"),
|
||||
("Default trackpad speed", "ڈیفالٹ ٹریک پیڈ کی رفتار"),
|
||||
@@ -709,7 +690,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("The user is not an administrator.", "صارف ایڈمنسٹریٹر نہیں ہے"),
|
||||
("Failed to check if the user is an administrator.", "صارف ایڈمنسٹریٹر ہے یا نہیں چیک کرنے میں ناکام"),
|
||||
("Supported only in the installed version.", "صرف انسٹال شدہ ورژن میں معاونت کی جاتی ہے۔"),
|
||||
("elevation_username_tip", ""),
|
||||
("elevation_username_tip", "صارف نام یا ڈومین صارف نام درج کریں"),
|
||||
("Preparing for installation ...", "انسٹالیشن کی تیاری ..."),
|
||||
("Show my cursor", "میرا کرسر دکھائیں"),
|
||||
("Scale custom", "اپنی مرضی کے مطابق پیمانہ"),
|
||||
@@ -725,28 +706,68 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Alias", "عرف نام"),
|
||||
("ScrollEdge", "اسکرول ایج"),
|
||||
("Allow insecure TLS fallback", "غیر محفوظ TLS فالبیک کی اجازت دیں"),
|
||||
("allow-insecure-tls-fallback-tip", ""),
|
||||
("allow-insecure-tls-fallback-tip", "پہلے سے طے شدہ طور پر RustDesk TLS استعمال کرنے والے پروٹوکولز کے لیے سرور کے سرٹیفکیٹ کی تصدیق کرتا ہے۔\nیہ اختیار فعال ہونے پر، تصدیق ناکام ہونے کی صورت میں RustDesk تصدیق کا مرحلہ چھوڑ کر آگے بڑھ جائے گا۔"),
|
||||
("Disable UDP", "UDP کو غیر فعال کریں"),
|
||||
("disable-udp-tip", ""),
|
||||
("server-oss-not-support-tip", ""),
|
||||
("disable-udp-tip", "طے کرتا ہے کہ صرف TCP استعمال کیا جائے یا نہیں۔\nیہ اختیار فعال ہونے پر RustDesk UDP 21116 مزید استعمال نہیں کرے گا، اس کی جگہ TCP 21116 استعمال ہوگا۔"),
|
||||
("server-oss-not-support-tip", "نوٹ: RustDesk سرور OSS میں یہ خصوصیت شامل نہیں ہے۔"),
|
||||
("input note here", "نوٹ یہاں درج کریں"),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("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", ""),
|
||||
("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", ""),
|
||||
("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", ""),
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("password-hidden-tip", "مستقل پاس ورڈ مقرر ہے (پوشیدہ)۔"),
|
||||
("preset-password-in-use-tip", "پہلے سے مقرر پاس ورڈ اس وقت استعمال میں ہے۔"),
|
||||
("terminal-clipboard-write-tip", "ٹرمنل میں ایک ایپ اس ڈیوائس کے کلپ بورڈ پر متن کاپی کرنا چاہتی ہے۔ اجازت دینے پر یہ اجازت تمام کنکشن کی ٹرمنل ایپس پر لاگو رہے گی جب تک آپ اسے ترتیبات میں بند نہ کر دیں۔ دستی کاپی اور پیسٹ متاثر نہیں ہوں گے۔"),
|
||||
("Allow terminal apps to copy to clipboard", "ٹرمنل ایپس کو کلپ بورڈ پر کاپی کرنے کی اجازت دیں"),
|
||||
("Export", "برآمد کریں"),
|
||||
("Export Logs", "لاگز برآمد کریں"),
|
||||
("Import Folder", "فولڈر درآمد کریں"),
|
||||
("Enable privacy mode", "پرائیویسی موڈ فعال کریں"),
|
||||
("allow-remote-toolbar-docking-any-edge", "ریموٹ ٹول بار کو ونڈو کے کسی بھی کنارے پر لگانے کی اجازت دیں"),
|
||||
("API Token", "API ٹوکن"),
|
||||
("Deploy", "تعینات کریں"),
|
||||
("Custom ID (optional)", "حسبِ ضرورت ID (اختیاری)"),
|
||||
("server_requires_deployment_tip", "سرور کا تقاضا ہے کہ یہ آلہ واضح طور پر تعینات کیا جائے۔ ابھی تعینات کریں؟"),
|
||||
("The server does not require explicit deployment.", "سرور کو واضح تعیناتی کی ضرورت نہیں۔"),
|
||||
("Unknown response.", "نامعلوم جواب۔"),
|
||||
("wayland-keyboard-input-disabled-tip", "کی بورڈ ان پٹ کی اجازت دیں؟"),
|
||||
("wayland-keyboard-input-consent-tip", "اس دور دراز کمپیوٹر پر آپ جو کچھ ٹائپ کریں گے (بشمول پاس ورڈ) اسے اس پر موجود دوسری ایپس پڑھ سکتی ہیں۔"),
|
||||
("wayland-keyboard-input-applies-to-tip", "یہ انتخاب اس پر لاگو ہوتا ہے:"),
|
||||
("wayland-soft-keyboard-input-label", "سافٹ کی بورڈ ان پٹ"),
|
||||
("wayland-keyboard-input-reset-choice-tip", "کی بورڈ ان پٹ کا انتخاب دوبارہ ترتیب دیں"),
|
||||
("remember-wayland-keyboard-choice-tip", "اس دور دراز کمپیوٹر کے لیے دوبارہ نہ پوچھیں"),
|
||||
("Why this happens", "ایسا کیوں ہوتا ہے"),
|
||||
("Switch display", "ڈسپلے تبدیل کریں"),
|
||||
("Show monitor switch button on the main toolbar", "مرکزی ٹول بار پر مانیٹر تبدیل کرنے کا بٹن دکھائیں"),
|
||||
("Show on the minimized toolbar", "چھوٹے کیے گئے ٹول بار پر دکھائیں"),
|
||||
("All monitors", "تمام مانیٹر"),
|
||||
("#{} monitor", "#{} مانیٹر"),
|
||||
("conn-e2ee-unavailable-tip", "اینڈ ٹو اینڈ خفیہ کاری کی تصدیق نہیں ہو سکی۔\nدور دراز آلہ ابھی ترتیب دیا جا رہا ہو سکتا ہے۔ بعد میں دوبارہ کوشش کریں۔\nاگر ایسا بار بار ہو تو ممکن ہے سرور قابلِ اعتماد نہ ہو۔\nپھر بھی جاری رکھیں؟"),
|
||||
("ID whitelisting", "ID وائٹ لسٹنگ"),
|
||||
("Use ID whitelisting", "ID وائٹ لسٹنگ استعمال کریں"),
|
||||
("id_whitelist_tip", "صرف وائٹ لسٹ میں شامل IDs مجھ تک رسائی حاصل کر سکتی ہیں"),
|
||||
("id_whitelist_wildcard_tip", "وائلڈ کارڈ معاون ہیں: '*' کسی بھی تعداد میں حروف سے مطابقت رکھتا ہے، '?' بالکل ایک حرف سے"),
|
||||
("Invalid ID", "غلط ID"),
|
||||
("Your ID is blocked by the peer", "آپ کی ID دوسرے فریق نے مسدود کر دی ہے"),
|
||||
("Your ip is blocked by the peer", "آپ کا IP دوسرے فریق نے مسدود کر دیا ہے"),
|
||||
("id_whitelist_caveat_tip", "ID کی اطلاع منسلک ہونے والا کلائنٹ خود دیتا ہے۔ یہ وائٹ لسٹ خطرے کو کم کرتی ہے، پاس ورڈ یا 2FA کا متبادل نہیں۔"),
|
||||
("whitelist_cidr_tip", "CIDR اشاریہ معاون ہے، مثلاً 192.168.1.0/24"),
|
||||
("Continue", "جاری رکھیں"),
|
||||
("Browser didn't open? Use the url below to sign in.", "براؤزر نہیں کھلا؟ سائن اِن کرنے کے لیے نیچے دیا گیا URL استعمال کریں۔"),
|
||||
("Lock canvas", "کینوس مقفل کریں"),
|
||||
("Sync clipboard between sessions", "سیشنز کے درمیان کلپ بورڈ ہم آہنگ کریں"),
|
||||
("sync-clipboard-between-sessions-tip", "ایک ریموٹ سیشن میں کاپی کیا گیا متن یا تصاویر آپ کے دیگر منسلک سیشنز کے کلپ بورڈ پر بھی بھیجی جاتی ہیں۔"),
|
||||
("Reuse one connection for port forwarding", "پورٹ فارورڈنگ کے لیے ایک ہی کنکشن دوبارہ استعمال کریں"),
|
||||
("port-forward-mux-tip", "ایک پورٹ فارورڈنگ کے تمام کنکشن دوسرے کمپیوٹر کے ساتھ بنے ایک ہی کنکشن سے گزرتے ہیں، ہر ایک کے لیے دوبارہ منسلک ہو کر لاگ اِن کرنے کے بجائے۔"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
|
||||
@@ -659,9 +659,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-merged-screen-not-supported-tip", "Không hỗ trợ chụp gộp nhiều màn hình."),
|
||||
("screenshot-action-tip", "Hành động chụp màn hình"),
|
||||
("Save as", "Lưu thành"),
|
||||
("Export", ""),
|
||||
("Export Logs", ""),
|
||||
("Import Folder", ""),
|
||||
("Export", "Xuất"),
|
||||
("Export Logs", "Xuất nhật ký"),
|
||||
("Import Folder", "Nhập thư mục"),
|
||||
("Copy to clipboard", "Sao chép vào Clipboard"),
|
||||
("Enable remote printer", "Bật máy in từ xa"),
|
||||
("Downloading {}", "Đang tải xuống {}"),
|
||||
@@ -766,5 +766,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("terminal-clipboard-write-tip", ""),
|
||||
("Allow terminal apps to copy to clipboard", ""),
|
||||
("Enable", "Bật"),
|
||||
("Reuse one connection for port forwarding", "Dùng chung một kết nối cho chuyển tiếp cổng"),
|
||||
("port-forward-mux-tip", "Chuyển toàn bộ kết nối của một quy tắc chuyển tiếp cổng qua một kết nối duy nhất tới máy đối phương, thay vì kết nối và đăng nhập lại cho từng kết nối."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ mod custom_server;
|
||||
mod lang;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod port_forward;
|
||||
mod port_forward_mux;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod tray;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::client::*;
|
||||
use crate::port_forward_mux::{Claim, Tunnel, CHANNEL_WINDOW};
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
config::READ_TIMEOUT,
|
||||
@@ -88,10 +89,33 @@ pub async fn listen(
|
||||
run_rdp(addr.port(), &rdp_display_name(&lc, &id));
|
||||
}
|
||||
let mut ui_receiver = ui_receiver;
|
||||
// One tunnel per mapping; the listener drops it on its way out, and that
|
||||
// ends the tunnel.
|
||||
let tunnel = Tunnel::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
Ok((forward, addr)) = listener.accept() => {
|
||||
log::info!("new connection from {:?}", addr);
|
||||
// A multiplexed window takes the connection on the mapping's
|
||||
// tunnel, or probes for one on its first accept. Everything
|
||||
// else, the setting off or a peer without the feature, is the
|
||||
// raw pipe below, as it always was.
|
||||
let claim = if lc.read().unwrap().port_forward_mux { tunnel.claim() } else { Claim::Legacy };
|
||||
match claim {
|
||||
Claim::Muxed(handle) => {
|
||||
if let Err(e) = handle.open(&remote_host, remote_port, forward, Vec::new()) {
|
||||
log::debug!("cannot open channel for {:?}: {}", addr, e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Claim::Claimed => {
|
||||
if establish_tunnel(&tunnel, &id, &password, &mut ui_receiver, &interface, forward, addr, key, token, is_rdp, &remote_host, remote_port).await {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Claim::Legacy => {}
|
||||
}
|
||||
let id = id.clone();
|
||||
let password = password.clone();
|
||||
let mut forward = Framed::new(forward, BytesCodec::new());
|
||||
@@ -181,7 +205,7 @@ async fn connect_and_login(
|
||||
match msg_in.union {
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
challenge = Some(hash.clone());
|
||||
if !hash_arrived(&interface, password, hash, pending_login.take(), remote_host, remote_port, &mut stream).await {
|
||||
if !hash_arrived(&interface, password, hash, pending_login.take(), remote_host, remote_port, false, &mut stream).await {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
@@ -213,7 +237,7 @@ async fn connect_and_login(
|
||||
d = ui_receiver.recv() => {
|
||||
match d {
|
||||
Some(Data::Login(login)) => match &challenge {
|
||||
Some(hash) => login_from_ui(&interface, hash, login, remote_host, remote_port, &mut stream).await,
|
||||
Some(hash) => login_from_ui(&interface, hash, login, remote_host, remote_port, false, &mut stream).await,
|
||||
None => pending_login = Some(login),
|
||||
},
|
||||
Some(Data::Message(msg)) => {
|
||||
@@ -240,22 +264,24 @@ async fn connect_and_login(
|
||||
|
||||
|
||||
/// A mapping's login is built from the window's shared handler:
|
||||
/// `create_login_msg` reads `port_forward` and `handle_login_from_ui` reads
|
||||
/// `hash`. Mappings log in concurrently, so each fills them and sends under
|
||||
/// the window's turn lock, or one login carried another mapping's target or
|
||||
/// answered another's challenge.
|
||||
/// `create_login_msg` reads `port_forward` and `port_forward_multiplex`,
|
||||
/// `handle_login_from_ui` reads `hash`. Mappings log in concurrently, so each
|
||||
/// fills them and sends under the window's turn lock, or one login carried
|
||||
/// another mapping's target or answered another's challenge.
|
||||
async fn login_with_hash(
|
||||
interface: &impl Interface,
|
||||
password: &str,
|
||||
hash: Hash,
|
||||
remote_host: &str,
|
||||
remote_port: i32,
|
||||
mux: bool,
|
||||
stream: &mut Stream,
|
||||
) -> bool {
|
||||
let lc = interface.get_lch();
|
||||
let turn = lc.read().unwrap().port_forward_login_turn.clone();
|
||||
let _turn = turn.lock().await;
|
||||
lc.write().unwrap().port_forward = (remote_host.to_owned(), remote_port);
|
||||
lc.write().unwrap().port_forward_multiplex = mux;
|
||||
interface.handle_hash(password, hash, stream).await
|
||||
}
|
||||
|
||||
@@ -273,14 +299,15 @@ async fn hash_arrived(
|
||||
pending_login: Option<UiLogin>,
|
||||
remote_host: &str,
|
||||
remote_port: i32,
|
||||
mux: bool,
|
||||
stream: &mut Stream,
|
||||
) -> bool {
|
||||
match pending_login {
|
||||
Some(login) => {
|
||||
login_from_ui(interface, &hash, login, remote_host, remote_port, stream).await;
|
||||
login_from_ui(interface, &hash, login, remote_host, remote_port, mux, stream).await;
|
||||
true
|
||||
}
|
||||
None => login_with_hash(interface, password, hash, remote_host, remote_port, stream).await,
|
||||
None => login_with_hash(interface, password, hash, remote_host, remote_port, mux, stream).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +319,7 @@ async fn login_from_ui(
|
||||
login: UiLogin,
|
||||
remote_host: &str,
|
||||
remote_port: i32,
|
||||
mux: bool,
|
||||
stream: &mut Stream,
|
||||
) {
|
||||
let lc = interface.get_lch();
|
||||
@@ -300,6 +328,7 @@ async fn login_from_ui(
|
||||
{
|
||||
let mut lc = lc.write().unwrap();
|
||||
lc.port_forward = (remote_host.to_owned(), remote_port);
|
||||
lc.port_forward_multiplex = mux;
|
||||
lc.set_hash(hash.clone());
|
||||
}
|
||||
let (os_username, os_password, password, remember) = login;
|
||||
@@ -308,6 +337,227 @@ async fn login_from_ui(
|
||||
.await;
|
||||
}
|
||||
|
||||
/// The first accept of a multiplexed mapping. It logs in asking for the
|
||||
/// tunnel, and the peer's answer fixes this listener's mode until it closes:
|
||||
/// a peer with the feature gets a tunnel every later accept joins, one
|
||||
/// without gets today's raw pipe for this connection and `Legacy` for the
|
||||
/// rest. Re-adding the mapping is how a user picks up an upgraded peer;
|
||||
/// nothing switches modes underneath live connections. Returns `true` when
|
||||
/// the listener should stop.
|
||||
async fn establish_tunnel(
|
||||
tunnel: &Tunnel,
|
||||
id: &str,
|
||||
password: &str,
|
||||
ui_receiver: &mut mpsc::UnboundedReceiver<Data>,
|
||||
interface: &impl Interface,
|
||||
forward: TcpStream,
|
||||
addr: std::net::SocketAddr,
|
||||
key: &str,
|
||||
token: &str,
|
||||
is_rdp: bool,
|
||||
remote_host: &str,
|
||||
remote_port: i32,
|
||||
) -> bool {
|
||||
let mut forward = Framed::new(forward, BytesCodec::new());
|
||||
let mut close_port_forward = false;
|
||||
match connect_and_login_mux(id, password, ui_receiver, interface.clone(), &mut forward, key, token, is_rdp, &mut close_port_forward, remote_host, remote_port).await {
|
||||
Ok(Some(outcome)) if outcome.mux => {
|
||||
let handle = tunnel.set_muxed(outcome.stream, interface.clone());
|
||||
if !outcome.local_eof {
|
||||
let (socket, prebuf) = take_socket(forward, outcome.prebuf);
|
||||
if let Err(e) = handle.open(remote_host, remote_port, socket, prebuf) {
|
||||
log::debug!("cannot open channel for {:?}: {}", addr, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(outcome)) => {
|
||||
tunnel.set_legacy();
|
||||
if outcome.local_eof {
|
||||
log::debug!("legacy peer and local {:?} already gone", addr);
|
||||
} else {
|
||||
run_legacy(outcome, forward, addr, interface.clone());
|
||||
}
|
||||
}
|
||||
_ if close_port_forward => {
|
||||
tunnel.set_failed();
|
||||
return true;
|
||||
}
|
||||
Err(err) => {
|
||||
tunnel.set_failed();
|
||||
interface.on_establish_connection_error(err.to_string());
|
||||
}
|
||||
_ => tunnel.set_failed(),
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// `connect_and_login` for a mapping that wants the tunnel: the pre-read
|
||||
/// stops at one window rather than growing without bound, and a local EOF
|
||||
/// no longer ends the login, since the tunnel may still be wanted. It
|
||||
/// reports what the peer answered rather than a raw stream, because the
|
||||
/// caller's next step depends on it. The login itself is the raw pipe's,
|
||||
/// told to ask for the tunnel.
|
||||
async fn connect_and_login_mux(
|
||||
id: &str,
|
||||
password: &str,
|
||||
ui_receiver: &mut mpsc::UnboundedReceiver<Data>,
|
||||
interface: impl Interface,
|
||||
forward: &mut Framed<TcpStream, BytesCodec>,
|
||||
key: &str,
|
||||
token: &str,
|
||||
is_rdp: bool,
|
||||
close_port_forward: &mut bool,
|
||||
remote_host: &str,
|
||||
remote_port: i32,
|
||||
) -> ResultType<Option<LoginOutcome>> {
|
||||
let conn_type = if is_rdp {
|
||||
ConnType::RDP
|
||||
} else {
|
||||
ConnType::PORT_FORWARD
|
||||
};
|
||||
let ((mut stream, direct, _pk, _kcp, _stream_type), (feedback, rendezvous_server)) =
|
||||
Client::start(id, key, token, conn_type, interface.clone()).await?;
|
||||
interface.update_direct(Some(direct));
|
||||
if !stream.is_secured() && !crate::common::is_direct_ip_access(id) {
|
||||
if !confirm_insecure_connection(&interface, ui_receiver).await {
|
||||
*close_port_forward = true;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
let mut buffer = Vec::new();
|
||||
let mut local_eof = false;
|
||||
let mux;
|
||||
let mut received = false;
|
||||
let mut challenge = None;
|
||||
let mut pending_login = None;
|
||||
|
||||
let _keep_it = hc_connection(feedback, rendezvous_server, token).await;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = timeout(READ_TIMEOUT, stream.next()) => match res {
|
||||
Err(_) => {
|
||||
bail!("Timeout");
|
||||
}
|
||||
Ok(Some(Ok(bytes))) => {
|
||||
if !received {
|
||||
received = true;
|
||||
interface.update_received(true);
|
||||
}
|
||||
let msg_in = Message::parse_from_bytes(&bytes)?;
|
||||
match msg_in.union {
|
||||
Some(message::Union::Hash(hash)) => {
|
||||
challenge = Some(hash.clone());
|
||||
if !hash_arrived(&interface, password, hash, pending_login.take(), remote_host, remote_port, true, &mut stream).await {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Some(message::Union::LoginResponse(lr)) => match lr.union {
|
||||
Some(login_response::Union::Error(err)) => {
|
||||
if !interface.handle_login_error(&err) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
mux = peer_supports_mux(&pi);
|
||||
interface.handle_peer_info(pi);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(message::Union::TestDelay(t)) => {
|
||||
interface.handle_test_delay(t, &mut stream).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(Some(Err(err))) => {
|
||||
bail!("Connection closed: {}", err);
|
||||
}
|
||||
_ => {
|
||||
bail!("Reset by the peer");
|
||||
}
|
||||
},
|
||||
d = ui_receiver.recv() => {
|
||||
match d {
|
||||
Some(Data::Login(login)) => match &challenge {
|
||||
Some(hash) => login_from_ui(&interface, hash, login, remote_host, remote_port, true, &mut stream).await,
|
||||
None => pending_login = Some(login),
|
||||
},
|
||||
Some(Data::Message(msg)) => {
|
||||
allow_err!(stream.send(&msg).await);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
// Stop pulling once the pre-read buffer is a window deep; the
|
||||
// rest waits in the kernel until the channel opens. A local EOF
|
||||
// no longer aborts the login: the tunnel may still be wanted.
|
||||
res = forward.next(), if !local_eof && buffer.len() < CHANNEL_WINDOW as usize => {
|
||||
if let Some(Ok(bytes)) = res {
|
||||
buffer.extend(bytes);
|
||||
} else {
|
||||
local_eof = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(Some(LoginOutcome {
|
||||
stream,
|
||||
mux,
|
||||
prebuf: buffer,
|
||||
local_eof,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Today's raw pipe, for peers without multiplexing.
|
||||
fn run_legacy(
|
||||
outcome: LoginOutcome,
|
||||
forward: Framed<TcpStream, BytesCodec>,
|
||||
addr: std::net::SocketAddr,
|
||||
interface: impl Interface,
|
||||
) {
|
||||
let mut stream = outcome.stream;
|
||||
let prebuf = outcome.prebuf;
|
||||
tokio::spawn(async move {
|
||||
stream.set_raw();
|
||||
if !prebuf.is_empty() {
|
||||
allow_err!(stream.send_bytes(prebuf.into()).await);
|
||||
}
|
||||
if let Err(err) = run_forward(forward, stream).await {
|
||||
interface.msgbox("error", "Error", &err.to_string(), "");
|
||||
}
|
||||
log::info!("connection from {:?} closed", addr);
|
||||
});
|
||||
}
|
||||
|
||||
struct LoginOutcome {
|
||||
stream: Stream,
|
||||
mux: bool,
|
||||
prebuf: Vec<u8>,
|
||||
local_eof: bool,
|
||||
}
|
||||
|
||||
fn peer_supports_mux(pi: &PeerInfo) -> bool {
|
||||
pi.features.as_ref().map(|f| f.port_forward_mux).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// `into_inner()` would drop bytes the codec pulled but never yielded.
|
||||
fn take_socket(forward: Framed<TcpStream, BytesCodec>, mut prebuf: Vec<u8>) -> (TcpStream, Vec<u8>) {
|
||||
let parts = forward.into_parts();
|
||||
prebuf.extend_from_slice(&parts.read_buf);
|
||||
(parts.io, prebuf)
|
||||
}
|
||||
|
||||
/// The controlling side's `enable-port-forward-mux`: on unless set to `N`.
|
||||
pub fn mux_enabled() -> bool {
|
||||
use hbb_common::config::{keys, option2bool, LocalConfig};
|
||||
option2bool(
|
||||
keys::OPTION_ENABLE_PORT_FORWARD_MUX,
|
||||
&LocalConfig::get_option(keys::OPTION_ENABLE_PORT_FORWARD_MUX),
|
||||
)
|
||||
}
|
||||
|
||||
async fn run_forward(forward: Framed<TcpStream, BytesCodec>, stream: Stream) -> ResultType<()> {
|
||||
log::info!("new port forwarding connection started");
|
||||
let mut forward = forward;
|
||||
@@ -453,8 +703,8 @@ mod login_tests {
|
||||
let (mut a, mut a_peer) = loopback().await;
|
||||
let (mut b, mut b_peer) = loopback().await;
|
||||
tokio::join!(
|
||||
login_with_hash(&ui, "pw", hash("a"), "a", 1, &mut a),
|
||||
login_with_hash(&ui, "pw", hash("b"), "b", 2, &mut b),
|
||||
login_with_hash(&ui, "pw", hash("a"), "a", 1, false, &mut a),
|
||||
login_with_hash(&ui, "pw", hash("b"), "b", 2, false, &mut b),
|
||||
);
|
||||
assert_eq!(target(&login_at(&mut a_peer).await), ("a".to_owned(), 1));
|
||||
assert_eq!(target(&login_at(&mut b_peer).await), ("b".to_owned(), 2));
|
||||
@@ -472,10 +722,10 @@ mod login_tests {
|
||||
let (mut a, mut a_peer) = loopback().await;
|
||||
let (mut b, mut b_peer) = loopback().await;
|
||||
// A's hash arrived last, so it is the one the handler holds.
|
||||
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, &mut a).await);
|
||||
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, false, &mut a).await);
|
||||
login_at(&mut a_peer).await;
|
||||
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
||||
login_from_ui(&ui, &hash("b"), typed, "b", 2, &mut b).await;
|
||||
login_from_ui(&ui, &hash("b"), typed, "b", 2, false, &mut b).await;
|
||||
let lr = login_at(&mut b_peer).await;
|
||||
assert_eq!(lr.password, digest("b"));
|
||||
assert_eq!(target(&lr), ("b".to_owned(), 2));
|
||||
@@ -494,10 +744,110 @@ mod login_tests {
|
||||
// The prompt's password reached B before its hash, and no other
|
||||
// mapping has stored it in the handler yet.
|
||||
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
||||
assert!(hash_arrived(&ui, "", hash("b"), Some(typed), "b", 2, &mut b).await);
|
||||
assert!(hash_arrived(&ui, "", hash("b"), Some(typed), "b", 2, false, &mut b).await);
|
||||
let lr = login_at(&mut b_peer).await;
|
||||
assert_eq!(lr.password, digest("b"));
|
||||
assert_eq!(target(&lr), ("b".to_owned(), 2));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_raw_pipe_login_on_a_multiplexed_window_does_not_ask_for_the_tunnel() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
rt.block_on(async {
|
||||
let ui = window();
|
||||
// The window probes for the tunnel, but this mapping latched to
|
||||
// the raw pipe: its login must read as the raw pipe's, or an
|
||||
// upgraded peer answers with a tunnel it then never gets.
|
||||
ui.lc.write().unwrap().port_forward_mux = true;
|
||||
let (mut a, mut a_peer) = loopback().await;
|
||||
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, false, &mut a).await);
|
||||
assert!(!login_at(&mut a_peer).await.port_forward().multiplex);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_password_typed_at_the_prompt_keeps_a_raw_pipe_login_raw() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
rt.block_on(async {
|
||||
let ui = window();
|
||||
ui.lc.write().unwrap().port_forward_mux = true;
|
||||
let (mut b, mut b_peer) = loopback().await;
|
||||
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
||||
login_from_ui(&ui, &hash("b"), typed, "b", 2, false, &mut b).await;
|
||||
assert!(!login_at(&mut b_peer).await.port_forward().multiplex);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_probing_login_asks_for_the_tunnel() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
rt.block_on(async {
|
||||
let ui = window();
|
||||
let (mut a, mut a_peer) = loopback().await;
|
||||
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, true, &mut a).await);
|
||||
assert!(login_at(&mut a_peer).await.port_forward().multiplex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn peer_supports_mux_reads_the_features_bit() {
|
||||
let mut pi = PeerInfo::new();
|
||||
assert!(!peer_supports_mux(&pi));
|
||||
pi.features = Some(Features { port_forward_mux: false, ..Default::default() }).into();
|
||||
assert!(!peer_supports_mux(&pi));
|
||||
pi.features = Some(Features { port_forward_mux: true, ..Default::default() }).into();
|
||||
assert!(peer_supports_mux(&pi));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn port_forward_mux_defaults_to_on() {
|
||||
use hbb_common::config::{keys, option2bool};
|
||||
// option2bool's fallback branch is also "on unless N", so the value
|
||||
// assertions below would pass for a prefixless key too. The `enable-`
|
||||
// prefix is what actually guarantees the default, and renaming the key
|
||||
// to an `allow-` one would silently flip it — pin the prefix itself.
|
||||
assert!(keys::OPTION_ENABLE_PORT_FORWARD_MUX.starts_with("enable-"));
|
||||
assert!(option2bool(keys::OPTION_ENABLE_PORT_FORWARD_MUX, ""));
|
||||
assert!(option2bool(keys::OPTION_ENABLE_PORT_FORWARD_MUX, "Y"));
|
||||
assert!(!option2bool(keys::OPTION_ENABLE_PORT_FORWARD_MUX, "N"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take_socket_hands_back_a_working_socket_and_the_prebuf() {
|
||||
use hbb_common::tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
|
||||
rt.block_on(async {
|
||||
let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = l.local_addr().unwrap();
|
||||
let mut client = TcpStream::connect(addr).await.unwrap();
|
||||
let (server, _) = l.accept().await.unwrap();
|
||||
let mut framed = Framed::new(server, BytesCodec::new());
|
||||
client.write_all(b"abc").await.unwrap();
|
||||
// Read through the codec, as connect_and_login does during login.
|
||||
let pulled = framed.next().await.unwrap().unwrap();
|
||||
assert_eq!(&pulled[..], b"abc");
|
||||
let (mut sock, prebuf) = take_socket(framed, pulled.to_vec());
|
||||
assert_eq!(prebuf, b"abc".to_vec());
|
||||
// Bytes written after the handoff arrive on the bare socket.
|
||||
client.write_all(b"def").await.unwrap();
|
||||
let mut buf = [0u8; 3];
|
||||
sock.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, b"def");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
1739
src/port_forward_mux.rs
Normal file
1739
src/port_forward_mux.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@ pub mod input_service {
|
||||
|
||||
mod connection;
|
||||
mod login_failure_check;
|
||||
pub(crate) mod port_forward_mux;
|
||||
pub mod display_service;
|
||||
#[cfg(windows)]
|
||||
pub mod portable_service;
|
||||
|
||||
@@ -257,6 +257,7 @@ pub struct Connection {
|
||||
view_camera: bool,
|
||||
terminal: bool,
|
||||
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
|
||||
port_forward_mux: Option<super::port_forward_mux::PortForwardMux>,
|
||||
port_forward_address: String,
|
||||
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||
authorized: bool,
|
||||
@@ -469,6 +470,7 @@ impl Connection {
|
||||
view_camera: false,
|
||||
terminal: false,
|
||||
port_forward_socket: None,
|
||||
port_forward_mux: None,
|
||||
port_forward_address: "".to_owned(),
|
||||
tx_to_cm,
|
||||
authorized: false,
|
||||
@@ -1645,7 +1647,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_port_forward_target(pf: &mut PortForward) -> (String, bool) {
|
||||
pub(super) fn normalize_port_forward_target(pf: &mut PortForward) -> (String, bool) {
|
||||
let mut is_rdp = false;
|
||||
if pf.host == "RDP" && pf.port == 0 {
|
||||
pf.host = "localhost".to_owned();
|
||||
@@ -1659,12 +1661,21 @@ impl Connection {
|
||||
}
|
||||
|
||||
async fn connect_port_forward_if_needed(&mut self) -> bool {
|
||||
if self.port_forward_socket.is_some() {
|
||||
if self.is_port_forward() {
|
||||
return true;
|
||||
}
|
||||
let Some(login_request::Union::PortForward(pf)) = self.lr.union.as_ref() else {
|
||||
return true;
|
||||
};
|
||||
if pf.multiplex {
|
||||
crate::port_forward_mux::cap_packet_size(&mut self.stream);
|
||||
// `inner.tx` is set for the connection's whole life; `None` here is
|
||||
// unreachable, and refusing the login is the only honest answer.
|
||||
self.port_forward_mux = self.inner.tx.clone().map(|tx| {
|
||||
super::port_forward_mux::PortForwardMux::new(tx, self.port_forward_address.clone())
|
||||
});
|
||||
return self.port_forward_mux.is_some();
|
||||
}
|
||||
let mut pf = pf.clone();
|
||||
let (mut addr, is_rdp) = Self::normalize_port_forward_target(&mut pf);
|
||||
self.port_forward_address = addr.clone();
|
||||
@@ -1752,7 +1763,7 @@ impl Connection {
|
||||
self.clear_id_whitelist_failures();
|
||||
let (conn_type, auth_conn_type) = if self.file_transfer.is_some() {
|
||||
(1, AuthConnType::FileTransfer)
|
||||
} else if self.port_forward_socket.is_some() {
|
||||
} else if self.is_port_forward() {
|
||||
(2, AuthConnType::PortForward)
|
||||
} else if self.view_camera {
|
||||
(3, AuthConnType::ViewCamera)
|
||||
@@ -1865,7 +1876,12 @@ impl Connection {
|
||||
pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into());
|
||||
}
|
||||
|
||||
if self.port_forward_socket.is_some() {
|
||||
if self.is_port_forward() {
|
||||
pi.features = Some(Features {
|
||||
port_forward_mux: self.port_forward_mux.is_some(),
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
let mut msg_out = Message::new();
|
||||
res.set_peer_info(pi);
|
||||
msg_out.set_login_response(res);
|
||||
@@ -2063,11 +2079,16 @@ impl Connection {
|
||||
#[inline]
|
||||
fn is_remote(&self) -> bool {
|
||||
self.file_transfer.is_none()
|
||||
&& self.port_forward_socket.is_none()
|
||||
&& !self.is_port_forward()
|
||||
&& !self.view_camera
|
||||
&& !self.terminal
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_port_forward(&self) -> bool {
|
||||
self.port_forward_socket.is_some() || self.port_forward_mux.is_some()
|
||||
}
|
||||
|
||||
fn try_sub_monitor_services(&mut self) {
|
||||
let is_remote = self.is_remote();
|
||||
if is_remote && !self.services_subed {
|
||||
@@ -2211,6 +2232,16 @@ impl Connection {
|
||||
self.tx_to_cm.send(data).ok();
|
||||
}
|
||||
|
||||
fn handle_port_forward_channel(&mut self, ch: PortForwardChannel) {
|
||||
let Some(mux) = self.port_forward_mux.as_mut() else {
|
||||
log::debug!("port forward channel frame on a non-multiplexed connection");
|
||||
return;
|
||||
};
|
||||
mux.handle(ch, || {
|
||||
Self::permission(keys::OPTION_ENABLE_TUNNEL, &self.control_permissions)
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send_fs(&mut self, data: ipc::FS) {
|
||||
self.send_to_cm(ipc::Data::FS(data));
|
||||
@@ -2576,11 +2607,13 @@ impl Connection {
|
||||
let PortForward {
|
||||
host,
|
||||
port,
|
||||
multiplex,
|
||||
special_fields: _,
|
||||
} = pf;
|
||||
push(b"port_forward");
|
||||
push(host.as_bytes());
|
||||
push(&port.to_le_bytes());
|
||||
push(&[*multiplex as u8]);
|
||||
}
|
||||
// Variants this build does not know execute as remote, so they latch as remote.
|
||||
None | Some(_) => push(b"remote"),
|
||||
@@ -3865,6 +3898,7 @@ impl Connection {
|
||||
self.refresh_video_display(Some(request.display as usize));
|
||||
}
|
||||
}
|
||||
Some(message::Union::PortForwardChannel(ch)) => self.handle_port_forward_channel(ch),
|
||||
Some(message::Union::TerminalAction(action)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
allow_err!(self.handle_terminal_action(action).await);
|
||||
@@ -5074,6 +5108,9 @@ impl Connection {
|
||||
let data = ipc::Data::Close;
|
||||
self.tx_to_cm.send(data).ok();
|
||||
self.port_forward_socket.take();
|
||||
if let Some(mut mux) = self.port_forward_mux.take() {
|
||||
mux.close_all();
|
||||
}
|
||||
}
|
||||
|
||||
// The `reason` should be consistent with `check_if_retry` if not empty
|
||||
@@ -5675,7 +5712,7 @@ impl Connection {
|
||||
let allowed = match conn_type {
|
||||
AuthConnType::Remote => true,
|
||||
AuthConnType::FileTransfer => Self::is_file_transfer_scoped_message(msg),
|
||||
AuthConnType::PortForward => false,
|
||||
AuthConnType::PortForward => Self::is_port_forward_scoped_message(msg),
|
||||
AuthConnType::ViewCamera => Self::is_view_camera_scoped_message(msg),
|
||||
AuthConnType::Terminal => Self::is_terminal_scoped_message(msg),
|
||||
};
|
||||
@@ -5749,6 +5786,13 @@ impl Connection {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_port_forward_scoped_message(msg: &Message) -> bool {
|
||||
matches!(
|
||||
msg.union.as_ref(),
|
||||
Some(message::Union::PortForwardChannel(_))
|
||||
)
|
||||
}
|
||||
|
||||
fn is_terminal_scoped_message(msg: &Message) -> bool {
|
||||
match msg.union.as_ref() {
|
||||
Some(message::Union::TerminalAction(_)) => true,
|
||||
@@ -5899,6 +5943,7 @@ impl Connection {
|
||||
Some(message::Union::ScreenshotResponse(_)) => "screenshot_response",
|
||||
Some(message::Union::TerminalAction(_)) => "terminal_action",
|
||||
Some(message::Union::TerminalResponse(_)) => "terminal_response",
|
||||
Some(message::Union::PortForwardChannel(_)) => "port_forward_channel",
|
||||
Some(message::Union::Misc(misc)) => Self::misc_message_family(misc),
|
||||
Some(_) => "message.other",
|
||||
None => "empty",
|
||||
@@ -7228,6 +7273,10 @@ mod test {
|
||||
}),
|
||||
Some("misc.option"),
|
||||
),
|
||||
(
|
||||
msg(|m| m.set_port_forward_channel(PortForwardChannel::new())),
|
||||
Some("port_forward_channel"),
|
||||
),
|
||||
],
|
||||
),
|
||||
(
|
||||
@@ -7289,6 +7338,10 @@ mod test {
|
||||
}),
|
||||
Some("misc.option"),
|
||||
),
|
||||
(
|
||||
msg(|m| m.set_port_forward_channel(PortForwardChannel::new())),
|
||||
Some("port_forward_channel"),
|
||||
),
|
||||
],
|
||||
),
|
||||
(
|
||||
@@ -7389,6 +7442,10 @@ mod test {
|
||||
}),
|
||||
None,
|
||||
),
|
||||
(
|
||||
msg(|m| m.set_port_forward_channel(PortForwardChannel::new())),
|
||||
None,
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
565
src/server/port_forward_mux.rs
Normal file
565
src/server/port_forward_mux.rs
Normal file
@@ -0,0 +1,565 @@
|
||||
use super::connection::{Connection, Sender};
|
||||
use crate::port_forward_mux::{
|
||||
charge, close_msg, effective_window, opened_msg, run_channel, FrameSink, Inbound, RecvWindow,
|
||||
SendCredit, CHANNEL_WINDOW, INITIAL_WINDOW, MAX_CHANNELS,
|
||||
};
|
||||
use hbb_common::{
|
||||
bytes::Bytes,
|
||||
log,
|
||||
message_proto::*,
|
||||
timeout,
|
||||
tokio::{self, net::TcpStream, sync::{mpsc, watch}},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
const CONNECT_TIMEOUT_MS: u64 = 3000;
|
||||
|
||||
/// Before `opened` the controller may only have used `INITIAL_WINDOW`.
|
||||
/// `charged` is the running total of `charge(len)`, not of raw lengths.
|
||||
fn pending_fits(charged: usize, add_len: usize) -> bool {
|
||||
charged.saturating_add(charge(add_len) as usize) <= INITIAL_WINDOW as usize
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
inbound: mpsc::UnboundedSender<Inbound>,
|
||||
credit: Arc<SendCredit>,
|
||||
window: Arc<Mutex<RecvWindow>>,
|
||||
}
|
||||
|
||||
/// The controlled side of one multiplexed tunnel. The main loop owns it and
|
||||
/// forwards every `PortForwardChannel` frame here; each channel is a task.
|
||||
pub struct PortForwardMux {
|
||||
channels: HashMap<i32, Entry>,
|
||||
tx: Sender,
|
||||
login_target: String,
|
||||
/// Raised once, by `close_all`, for the channels its `clear` cannot reach:
|
||||
/// one parked on its target socket is not on the inbound queue.
|
||||
teardown: watch::Sender<bool>,
|
||||
}
|
||||
|
||||
impl PortForwardMux {
|
||||
pub fn new(tx: Sender, login_target: String) -> Self {
|
||||
Self {
|
||||
channels: HashMap::new(),
|
||||
tx,
|
||||
login_target,
|
||||
teardown: watch::channel(false).0,
|
||||
}
|
||||
}
|
||||
|
||||
/// `tunnel_permitted` is consulted for `open` alone, so the lookup is not
|
||||
/// made per 64 KiB of data.
|
||||
pub fn handle(&mut self, frame: PortForwardChannel, tunnel_permitted: impl FnOnce() -> bool) {
|
||||
match frame.union {
|
||||
Some(port_forward_channel::Union::Open(open)) => {
|
||||
let permitted = tunnel_permitted();
|
||||
self.on_open(open, permitted)
|
||||
}
|
||||
Some(port_forward_channel::Union::Data(d)) => {
|
||||
let len = d.data.len();
|
||||
let Some(entry) = self.channels.get(&d.channel_id) else {
|
||||
log::debug!("port forward data for unknown channel {}", d.channel_id);
|
||||
return;
|
||||
};
|
||||
let accepted = entry.window.lock().unwrap().accept(len);
|
||||
let delivered = accepted && entry.inbound.send(Inbound::Data(d.data)).is_ok();
|
||||
if delivered {
|
||||
return;
|
||||
}
|
||||
// Dropped here and now, so the peer cannot queue anything more
|
||||
// for this id while the task is still on its way out.
|
||||
let Some(entry) = self.channels.remove(&d.channel_id) else {
|
||||
return;
|
||||
};
|
||||
if !accepted {
|
||||
log::warn!("port forward channel {} overran its window", d.channel_id);
|
||||
entry.inbound.send(Inbound::Violation).ok();
|
||||
}
|
||||
}
|
||||
Some(port_forward_channel::Union::Close(c)) => {
|
||||
if let Some(entry) = self.channels.remove(&c.channel_id) {
|
||||
entry.inbound.send(Inbound::Close).ok();
|
||||
} else {
|
||||
log::debug!("port forward close for unknown channel {}", c.channel_id);
|
||||
}
|
||||
}
|
||||
Some(port_forward_channel::Union::WindowUpdate(u)) => {
|
||||
match self.channels.get(&u.channel_id) {
|
||||
Some(entry) => entry.credit.add(u.add),
|
||||
None => log::debug!(
|
||||
"port forward window update for unknown channel {}",
|
||||
u.channel_id
|
||||
),
|
||||
}
|
||||
}
|
||||
Some(port_forward_channel::Union::Opened(o)) => {
|
||||
log::debug!("ignoring opened for channel {} on the controlled side", o.channel_id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_open(&mut self, open: PortForwardOpen, permitted: bool) {
|
||||
let id = open.channel_id;
|
||||
self.channels.retain(|_, e| !e.inbound.is_closed());
|
||||
if !permitted {
|
||||
self.reply(opened_msg(id, false, "No permission of IP tunneling", 0));
|
||||
return;
|
||||
}
|
||||
if self.channels.len() >= MAX_CHANNELS {
|
||||
self.reply(opened_msg(id, false, "Too many port forward channels", 0));
|
||||
return;
|
||||
}
|
||||
if self.channels.contains_key(&id) {
|
||||
log::debug!("ignoring open for live channel {}", id);
|
||||
return;
|
||||
}
|
||||
let mut pf = PortForward {
|
||||
host: open.host,
|
||||
port: open.port,
|
||||
..Default::default()
|
||||
};
|
||||
let (addr, is_rdp) = Connection::normalize_port_forward_target(&mut pf);
|
||||
// Approval and permission checks saw the login's target; a tunnel
|
||||
// serves that one target and nothing else.
|
||||
if addr != self.login_target {
|
||||
log::warn!(
|
||||
"port forward channel {} asked for {} on a tunnel logged in for {}",
|
||||
id,
|
||||
addr,
|
||||
self.login_target
|
||||
);
|
||||
self.reply(opened_msg(id, false, "Port forward target not authorized", 0));
|
||||
return;
|
||||
}
|
||||
let (inbound_tx, inbound_rx) = mpsc::unbounded_channel();
|
||||
let credit = Arc::new(SendCredit::new(effective_window(open.window)));
|
||||
let window = Arc::new(Mutex::new(RecvWindow::new(INITIAL_WINDOW)));
|
||||
self.channels.insert(
|
||||
id,
|
||||
Entry {
|
||||
inbound: inbound_tx,
|
||||
credit: credit.clone(),
|
||||
window: window.clone(),
|
||||
},
|
||||
);
|
||||
tokio::spawn(run_controlled_channel(
|
||||
id,
|
||||
addr,
|
||||
is_rdp,
|
||||
credit,
|
||||
window,
|
||||
inbound_rx,
|
||||
FrameSink::Direct(self.tx.clone()),
|
||||
self.teardown.subscribe(),
|
||||
));
|
||||
}
|
||||
|
||||
fn reply(&self, msg: Message) {
|
||||
self.tx
|
||||
.send((tokio::time::Instant::now(), Arc::new(msg)))
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn live_channels(&self) -> usize {
|
||||
self.channels.len()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn recv_window_remaining(&self, id: i32) -> Option<u32> {
|
||||
self.channels.get(&id).map(|e| e.window.lock().unwrap().remaining())
|
||||
}
|
||||
|
||||
/// Every task ends and drops its target socket: the queue's senders go for
|
||||
/// a task on the queue, `teardown` reaches one parked on the socket.
|
||||
pub fn close_all(&mut self) {
|
||||
self.channels.clear();
|
||||
// Not `send`: with no channel live it stores nothing, and one opened
|
||||
// as the tunnel closes would never see it.
|
||||
self.teardown.send_replace(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Owns the whole channel lifecycle: connect under a `select!` in which a
|
||||
/// queued command always wins over the connect, buffer what arrives
|
||||
/// meanwhile, then relay.
|
||||
async fn run_controlled_channel(
|
||||
id: i32,
|
||||
addr: String,
|
||||
is_rdp: bool,
|
||||
credit: Arc<SendCredit>,
|
||||
window: Arc<Mutex<RecvWindow>>,
|
||||
mut inbound: mpsc::UnboundedReceiver<Inbound>,
|
||||
sink: FrameSink,
|
||||
teardown: watch::Receiver<bool>,
|
||||
) {
|
||||
let mut pending: Vec<Bytes> = Vec::new();
|
||||
let mut pending_len = 0usize;
|
||||
let connect = timeout(CONNECT_TIMEOUT_MS, TcpStream::connect(&addr));
|
||||
tokio::pin!(connect);
|
||||
let socket = loop {
|
||||
tokio::select! {
|
||||
// Biased with the command arm first: a `close` that is already
|
||||
// queued must win over a connect that completed on the same poll,
|
||||
// or `opened` would go out for a channel the controller has dropped.
|
||||
biased;
|
||||
cmd = inbound.recv() => match cmd {
|
||||
Some(Inbound::Data(b)) => {
|
||||
if !pending_fits(pending_len, b.len()) {
|
||||
log::warn!("port forward channel {} sent more than INITIAL_WINDOW before opened", id);
|
||||
sink.send_ordered(close_msg(id)).await.ok();
|
||||
return;
|
||||
}
|
||||
pending_len += charge(b.len()) as usize;
|
||||
pending.push(b);
|
||||
}
|
||||
Some(Inbound::Close) | None => return,
|
||||
Some(Inbound::Violation) => {
|
||||
sink.send_ordered(close_msg(id)).await.ok();
|
||||
return;
|
||||
}
|
||||
},
|
||||
res = &mut connect => {
|
||||
let err = match res {
|
||||
Ok(Ok(s)) => break s,
|
||||
Ok(Err(e)) => e.to_string(),
|
||||
Err(e) => e.to_string(),
|
||||
};
|
||||
log::debug!("port forward channel {} connect {} failed: {}", id, addr, err);
|
||||
sink.send_ordered(opened_msg(id, false, &unreachable_message(&addr, is_rdp), 0)).await.ok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Granted before `opened` leaves, so the peer can never be ahead of it.
|
||||
window.lock().unwrap().grant(CHANNEL_WINDOW - INITIAL_WINDOW);
|
||||
if sink
|
||||
.send_ordered(opened_msg(id, true, "", CHANNEL_WINDOW))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let (reader, writer) = socket.into_split();
|
||||
run_channel(id, reader, writer, Vec::new(), pending, credit, window, inbound, sink, teardown).await;
|
||||
}
|
||||
|
||||
/// The same words the raw pipe puts in its login error, so one problem reads
|
||||
/// the same whichever path the peer takes.
|
||||
fn unreachable_message(addr: &str, is_rdp: bool) -> String {
|
||||
format!(
|
||||
"Failed to access remote {}. Please make sure it is reachable/open.",
|
||||
if is_rdp { "RDP" } else { addr }
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::port_forward_mux::{CHANNEL_WINDOW, INITIAL_WINDOW, MAX_CHANNELS, MIN_FRAME_CHARGE};
|
||||
use hbb_common::{
|
||||
message_proto::{message, port_forward_channel},
|
||||
tokio::{
|
||||
self,
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpListener,
|
||||
sync::mpsc,
|
||||
time::Instant,
|
||||
},
|
||||
};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// An echo server standing in for the forward target.
|
||||
async fn echo_target() -> u16 {
|
||||
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = l.local_addr().unwrap().port();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let (mut s, _) = l.accept().await.unwrap();
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
let n = s.read(&mut buf).await.unwrap_or(0);
|
||||
if n == 0 || s.write_all(&buf[..n]).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
port
|
||||
}
|
||||
|
||||
fn open(id: i32, port: u16) -> PortForwardChannel {
|
||||
let mut ch = PortForwardChannel::new();
|
||||
ch.set_open(PortForwardOpen {
|
||||
channel_id: id,
|
||||
host: "127.0.0.1".to_owned(),
|
||||
port: port as i32,
|
||||
window: CHANNEL_WINDOW,
|
||||
..Default::default()
|
||||
});
|
||||
ch
|
||||
}
|
||||
|
||||
fn data(id: i32, bytes: &[u8]) -> PortForwardChannel {
|
||||
let mut ch = PortForwardChannel::new();
|
||||
ch.set_data(PortForwardData {
|
||||
channel_id: id,
|
||||
data: Bytes::copy_from_slice(bytes),
|
||||
..Default::default()
|
||||
});
|
||||
ch
|
||||
}
|
||||
|
||||
fn close(id: i32) -> PortForwardChannel {
|
||||
let mut ch = PortForwardChannel::new();
|
||||
ch.set_close(PortForwardClose { channel_id: id, ..Default::default() });
|
||||
ch
|
||||
}
|
||||
|
||||
async fn next_frame(rx: &mut mpsc::UnboundedReceiver<(Instant, Arc<Message>)>) -> PortForwardChannel {
|
||||
let (_, m) = rx.recv().await.unwrap();
|
||||
match &m.union {
|
||||
Some(message::Union::PortForwardChannel(ch)) => ch.clone(),
|
||||
other => panic!("unexpected {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn opened(ch: &PortForwardChannel) -> (i32, bool) {
|
||||
match &ch.union {
|
||||
Some(port_forward_channel::Union::Opened(o)) => (o.channel_id, o.success),
|
||||
other => panic!("expected opened, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_of(ch: &PortForwardChannel) -> (i32, Vec<u8>) {
|
||||
match &ch.union {
|
||||
Some(port_forward_channel::Union::Data(d)) => (d.channel_id, d.data.to_vec()),
|
||||
other => panic!("expected data, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_connects_and_echoes_pipelined_data() {
|
||||
rt().block_on(async {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(data(1, b"ping"), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, true));
|
||||
assert_eq!(data_of(&next_frame(&mut rx).await), (1, b"ping".to_vec()));
|
||||
mux.handle(close(1), || true);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unreachable_target_fails_open_and_discards_pipelined_data() {
|
||||
rt().block_on(async {
|
||||
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = l.local_addr().unwrap().port();
|
||||
drop(l);
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(data(1, b"lost"), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, false));
|
||||
assert!(tokio::time::timeout(std::time::Duration::from_millis(50), rx.recv()).await.is_err());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_denied_refuses_without_spawning() {
|
||||
rt().block_on(async {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || false);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, false));
|
||||
assert_eq!(mux.live_channels(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_revoked_permission_refuses_new_channels_and_keeps_live_ones() {
|
||||
rt().block_on(async {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, true));
|
||||
// `enable-tunnel` is consulted per `open`, so turning it off
|
||||
// mid-session stops new channels; the live one keeps relaying.
|
||||
mux.handle(open(2, port), || false);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (2, false));
|
||||
mux.handle(data(1, b"still relayed"), || false);
|
||||
assert_eq!(data_of(&next_frame(&mut rx).await), (1, b"still relayed".to_vec()));
|
||||
assert_eq!(mux.live_channels(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_while_connecting_sends_no_opened() {
|
||||
rt().block_on(async {
|
||||
// `close` is queued before the task is first polled. Its `select!` is
|
||||
// biased towards the command arm, so even a connect that completes on
|
||||
// that same poll loses: no `opened` may ever be sent.
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(close(1), || true);
|
||||
assert!(tokio::time::timeout(std::time::Duration::from_millis(200), rx.recv()).await.is_err());
|
||||
assert_eq!(mux.live_channels(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn over_window_data_closes_only_that_channel() {
|
||||
rt().block_on(async {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(open(2, port), || true);
|
||||
let mut seen = 0;
|
||||
while seen < 2 {
|
||||
opened(&next_frame(&mut rx).await);
|
||||
seen += 1;
|
||||
}
|
||||
let too_much = vec![0u8; CHANNEL_WINDOW as usize + 1];
|
||||
mux.handle(data(1, &too_much), || true);
|
||||
let ch = next_frame(&mut rx).await;
|
||||
match &ch.union {
|
||||
Some(port_forward_channel::Union::Close(c)) => assert_eq!(c.channel_id, 1),
|
||||
other => panic!("expected close, got {:?}", other),
|
||||
}
|
||||
mux.handle(data(2, b"still fine"), || true);
|
||||
assert_eq!(data_of(&next_frame(&mut rx).await), (2, b"still fine".to_vec()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_over_window_frame_drops_the_channel_at_once() {
|
||||
rt().block_on(async {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || true);
|
||||
opened(&next_frame(&mut rx).await);
|
||||
let too_much = vec![0u8; CHANNEL_WINDOW as usize + 1];
|
||||
mux.handle(data(1, &too_much), || true);
|
||||
// Gone before the channel task has run: whatever the peer keeps
|
||||
// sending for this id can no longer queue anything.
|
||||
assert_eq!(mux.live_channels(), 0);
|
||||
mux.handle(data(1, &too_much), || true);
|
||||
assert_eq!(mux.live_channels(), 0);
|
||||
let ch = next_frame(&mut rx).await;
|
||||
match &ch.union {
|
||||
Some(port_forward_channel::Union::Close(c)) => assert_eq!(c.channel_id, 1),
|
||||
other => panic!("expected close, got {:?}", other),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_to_a_target_other_than_the_login_target_is_refused() {
|
||||
rt().block_on(async {
|
||||
let a = echo_target().await;
|
||||
let b = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", a));
|
||||
mux.handle(open(1, a), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, true));
|
||||
// Approval was for target a; b needs a login of its own.
|
||||
mux.handle(open(2, b), || true);
|
||||
let ch = next_frame(&mut rx).await;
|
||||
match &ch.union {
|
||||
Some(port_forward_channel::Union::Opened(o)) => {
|
||||
assert_eq!((o.channel_id, o.success), (2, false));
|
||||
assert!(!o.message.is_empty());
|
||||
}
|
||||
other => panic!("expected opened, got {:?}", other),
|
||||
}
|
||||
assert_eq!(mux.live_channels(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demux_admits_only_the_initial_window_before_opened() {
|
||||
rt().block_on(async {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
mux.handle(open(1, port), || true);
|
||||
// The channel task has not run yet: the demultiplexer alone
|
||||
// decides what may sit in the queue before `opened`.
|
||||
assert_eq!(mux.recv_window_remaining(1), Some(INITIAL_WINDOW));
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, true));
|
||||
assert_eq!(mux.recv_window_remaining(1), Some(CHANNEL_WINDOW));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_bytes_are_bounded_by_initial_window_before_opened() {
|
||||
// A loopback connect completes before a task can observe "connecting",
|
||||
// so the bound is pinned on the pure predicate the task uses.
|
||||
assert!(pending_fits(0, INITIAL_WINDOW as usize));
|
||||
assert!(pending_fits(
|
||||
INITIAL_WINDOW as usize - MIN_FRAME_CHARGE as usize,
|
||||
1
|
||||
));
|
||||
// A 1-byte frame costs a whole minimum charge here too.
|
||||
assert!(!pending_fits(
|
||||
INITIAL_WINDOW as usize - MIN_FRAME_CHARGE as usize + 1,
|
||||
1
|
||||
));
|
||||
assert!(!pending_fits(usize::MAX, 1));
|
||||
}
|
||||
|
||||
/// A target that accepts and hangs up at once, so every channel ends on
|
||||
/// the target's EOF — the case where only the next `open` frees the entry.
|
||||
async fn drop_target() -> u16 {
|
||||
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = l.local_addr().unwrap().port();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let (s, _) = l.accept().await.unwrap();
|
||||
drop(s);
|
||||
}
|
||||
});
|
||||
port
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_frees_dead_entries_so_the_cap_counts_live_channels() {
|
||||
rt().block_on(async {
|
||||
let port = drop_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", port));
|
||||
for id in 1..=(MAX_CHANNELS as i32 * 2) {
|
||||
mux.handle(open(id, port), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (id, true));
|
||||
// The task sends `close` on the target's EOF and exits; the
|
||||
// entry is dead until the next `open` drops it.
|
||||
let ch = next_frame(&mut rx).await;
|
||||
match &ch.union {
|
||||
Some(port_forward_channel::Union::Close(c)) => assert_eq!(c.channel_id, id),
|
||||
other => panic!("expected close, got {:?}", other),
|
||||
}
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1944,6 +1944,7 @@ pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>, round: u32) {
|
||||
let key = crate::get_key(false).await;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if handler.is_port_forward() {
|
||||
handler.lc.write().unwrap().port_forward_mux = crate::port_forward::mux_enabled();
|
||||
if handler.is_rdp() {
|
||||
let port = handler
|
||||
.get_option("rdp_port".to_owned())
|
||||
|
||||
Reference in New Issue
Block a user