mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-23 02:08:45 +08:00
Compare commits
15 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25719ecd0d | ||
|
|
5d490b2bdd | ||
|
|
7de2390d23 | ||
|
|
16dde51e23 | ||
|
|
780f99bd32 | ||
|
|
1b87c33fdc | ||
|
|
ef59778720 | ||
|
|
4fa5e99e65 | ||
|
|
5ee9dcf42d | ||
|
|
6306f83316 | ||
|
|
96075fdf49 | ||
|
|
8c6dcf53a6 | ||
|
|
e1b1a927b8 | ||
|
|
1e6bfa7bb1 | ||
|
|
79ef4c4501 |
109
docs/NETWORK_BINDING.md
Normal file
109
docs/NETWORK_BINDING.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Network Interface Binding
|
||||
|
||||
RustDesk can be configured to bind to a specific network interface by IP address.
|
||||
|
||||
## Configuration
|
||||
|
||||
To bind RustDesk to a specific network interface, set the `bind-interface` option in your configuration.
|
||||
|
||||
### Option Name
|
||||
`bind-interface`
|
||||
|
||||
### Supported Values
|
||||
- Empty string (default): Bind to all available interfaces (0.0.0.0 for IPv4, :: for IPv6)
|
||||
- IPv4 address: e.g., `192.168.1.100`, `10.0.0.1`
|
||||
- IPv6 address: e.g., `::1`, `fe80::1`, `2001:db8::1`
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Bind to a specific IPv4 address
|
||||
To bind RustDesk to only listen on interface with IP address `192.168.1.100`:
|
||||
|
||||
```json
|
||||
{
|
||||
"options": {
|
||||
"bind-interface": "192.168.1.100"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bind to IPv6 localhost
|
||||
```json
|
||||
{
|
||||
"options": {
|
||||
"bind-interface": "::1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bind to all interfaces (default)
|
||||
```json
|
||||
{
|
||||
"options": {
|
||||
"bind-interface": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or simply omit the `bind-interface` option entirely.
|
||||
|
||||
## How It Works
|
||||
|
||||
When the `bind-interface` option is set:
|
||||
|
||||
1. RustDesk reads the configuration when starting the direct server
|
||||
2. If `bind-interface` is empty or not set, it binds to all available network interfaces
|
||||
3. If `bind-interface` contains a valid IP address:
|
||||
- RustDesk validates the IP address format (supports both IPv4 and IPv6)
|
||||
- Creates a TCP socket and binds it to the specified IP address
|
||||
- Starts listening for connections on that interface only
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Multiple Network Interfaces
|
||||
If your machine has multiple network interfaces (e.g., Ethernet, Wi-Fi, VPN), you can force RustDesk to use a specific one:
|
||||
|
||||
- **Example**: Force RustDesk to use the Ethernet interface at `192.168.1.100` instead of the Wi-Fi interface at `192.168.2.50`
|
||||
|
||||
### Security
|
||||
Restrict RustDesk to listen only on internal network interfaces:
|
||||
|
||||
- **Example**: Bind to `10.0.0.5` (internal network) instead of listening on all interfaces including public-facing ones
|
||||
|
||||
### VPN/Tunneling
|
||||
Force RustDesk to use a VPN or tunnel interface:
|
||||
|
||||
- **Example**: Bind to the VPN interface IP address to ensure all traffic goes through the VPN
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Invalid bind address error
|
||||
If you see an error like "Invalid bind interface address", check that:
|
||||
- The IP address format is correct (no typos)
|
||||
- The IP address is valid (e.g., not `999.999.999.999`)
|
||||
- The IP address exists on one of your machine's network interfaces
|
||||
|
||||
### Failed to start direct server
|
||||
If RustDesk fails to start with a bind error, it could be because:
|
||||
- The specified IP address doesn't exist on your machine
|
||||
- Another application is already using the port on that interface
|
||||
- You don't have permission to bind to that address
|
||||
|
||||
### Finding your network interface IP addresses
|
||||
|
||||
**Windows:**
|
||||
```cmd
|
||||
ipconfig
|
||||
```
|
||||
|
||||
**Linux/macOS:**
|
||||
```bash
|
||||
ip addr show # Linux
|
||||
ifconfig # macOS/Linux
|
||||
```
|
||||
|
||||
Look for the `inet` (IPv4) or `inet6` (IPv6) addresses associated with your network interfaces.
|
||||
|
||||
## Related Discussion
|
||||
|
||||
This feature was implemented to address: https://github.com/rustdesk/rustdesk/discussions/2286
|
||||
@@ -1124,18 +1124,23 @@ class CustomAlertDialog extends StatelessWidget {
|
||||
|
||||
Widget createDialogContent(String text) {
|
||||
final RegExp linkRegExp = RegExp(r'(https?://[^\s]+)');
|
||||
bool hasLink = linkRegExp.hasMatch(text);
|
||||
|
||||
// Early return: no link, use default theme color
|
||||
if (!hasLink) {
|
||||
return SelectableText(text, style: const TextStyle(fontSize: 15));
|
||||
}
|
||||
|
||||
final List<TextSpan> spans = [];
|
||||
int start = 0;
|
||||
bool hasLink = false;
|
||||
|
||||
linkRegExp.allMatches(text).forEach((match) {
|
||||
hasLink = true;
|
||||
if (match.start > start) {
|
||||
spans.add(TextSpan(text: text.substring(start, match.start)));
|
||||
}
|
||||
spans.add(TextSpan(
|
||||
text: match.group(0) ?? '',
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
@@ -1153,13 +1158,9 @@ Widget createDialogContent(String text) {
|
||||
spans.add(TextSpan(text: text.substring(start)));
|
||||
}
|
||||
|
||||
if (!hasLink) {
|
||||
return SelectableText(text, style: const TextStyle(fontSize: 15));
|
||||
}
|
||||
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(color: Colors.black, fontSize: 15),
|
||||
style: const TextStyle(fontSize: 15),
|
||||
children: spans,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -107,6 +107,8 @@ class _RawTouchGestureDetectorRegionState
|
||||
// For mouse mode, we need to block the events when the cursor is in a blocked area.
|
||||
// So we need to cache the last tap down position.
|
||||
Offset? _lastTapDownPositionForMouseMode;
|
||||
// Cache global position for onTap (which lacks position info).
|
||||
Offset? _lastTapDownGlobalPosition;
|
||||
|
||||
FFI get ffi => widget.ffi;
|
||||
FfiModel get ffiModel => widget.ffiModel;
|
||||
@@ -136,6 +138,7 @@ class _RawTouchGestureDetectorRegionState
|
||||
|
||||
onTapDown(TapDownDetails d) async {
|
||||
lastDeviceKind = d.kind;
|
||||
_lastTapDownGlobalPosition = d.globalPosition;
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
@@ -154,6 +157,10 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
// Filter duplicate touch tap events on iOS (Magic Mouse issue).
|
||||
if (inputModel.shouldIgnoreTouchTap(d.globalPosition)) {
|
||||
return;
|
||||
}
|
||||
if (handleTouch) {
|
||||
final isMoved =
|
||||
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||
@@ -171,6 +178,11 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
// Filter duplicate touch tap events on iOS (Magic Mouse issue).
|
||||
final lastPos = _lastTapDownGlobalPosition;
|
||||
if (lastPos != null && inputModel.shouldIgnoreTouchTap(lastPos)) {
|
||||
return;
|
||||
}
|
||||
if (!handleTouch) {
|
||||
// Cannot use `_lastTapDownDetails` because Flutter calls `onTapUp` before `onTap`, clearing the cached details.
|
||||
// Using `_lastTapDownPositionForMouseMode` instead.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
@@ -41,6 +42,9 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
final GlobalKey _keyboardKey = GlobalKey();
|
||||
double _keyboardHeight = 0;
|
||||
late bool _showTerminalExtraKeys;
|
||||
// For iOS edge swipe gesture
|
||||
double _swipeStartX = 0;
|
||||
double _swipeCurrentX = 0;
|
||||
|
||||
// For web only.
|
||||
// 'monospace' does not work on web, use Google Fonts, `??` is only for null safety.
|
||||
@@ -147,7 +151,7 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
}
|
||||
|
||||
Widget buildBody() {
|
||||
return Scaffold(
|
||||
final scaffold = Scaffold(
|
||||
resizeToAvoidBottomInset: false, // Disable automatic layout adjustment; manually control UI updates to prevent flickering when the keyboard shows/hides
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: Stack(
|
||||
@@ -192,9 +196,108 @@ class _TerminalPageState extends State<TerminalPage>
|
||||
),
|
||||
),
|
||||
if (_showTerminalExtraKeys) _buildFloatingKeyboard(),
|
||||
// iOS-style circular close button in top-right corner
|
||||
if (isIOS) _buildCloseButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Add iOS edge swipe gesture to exit (similar to Android back button)
|
||||
if (isIOS) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final screenWidth = constraints.maxWidth;
|
||||
// Base thresholds on screen width but clamp to reasonable logical pixel ranges
|
||||
// Edge detection region: ~10% of width, clamped between 20 and 80 logical pixels
|
||||
final edgeThreshold = (screenWidth * 0.1).clamp(20.0, 80.0);
|
||||
// Required horizontal movement: ~25% of width, clamped between 80 and 300 logical pixels
|
||||
final swipeThreshold = (screenWidth * 0.25).clamp(80.0, 300.0);
|
||||
|
||||
return RawGestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
gestures: <Type, GestureRecognizerFactory>{
|
||||
HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
|
||||
() => HorizontalDragGestureRecognizer(
|
||||
debugOwner: this,
|
||||
// Only respond to touch input, exclude mouse/trackpad
|
||||
supportedDevices: kTouchBasedDeviceKinds,
|
||||
),
|
||||
(HorizontalDragGestureRecognizer instance) {
|
||||
instance
|
||||
// Capture initial touch-down position (before touch slop)
|
||||
..onDown = (details) {
|
||||
_swipeStartX = details.localPosition.dx;
|
||||
_swipeCurrentX = details.localPosition.dx;
|
||||
}
|
||||
..onUpdate = (details) {
|
||||
_swipeCurrentX = details.localPosition.dx;
|
||||
}
|
||||
..onEnd = (details) {
|
||||
// Check if swipe started from left edge and moved right
|
||||
if (_swipeStartX < edgeThreshold && (_swipeCurrentX - _swipeStartX) > swipeThreshold) {
|
||||
clientClose(sessionId, _ffi);
|
||||
}
|
||||
_swipeStartX = 0;
|
||||
_swipeCurrentX = 0;
|
||||
}
|
||||
..onCancel = () {
|
||||
_swipeStartX = 0;
|
||||
_swipeCurrentX = 0;
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
child: scaffold,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return scaffold;
|
||||
}
|
||||
|
||||
Widget _buildCloseButton() {
|
||||
return Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: SafeArea(
|
||||
minimum: const EdgeInsets.only(
|
||||
top: 16, // iOS standard margin
|
||||
right: 16, // iOS standard margin
|
||||
),
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: translate('Close'),
|
||||
child: Container(
|
||||
width: 44, // iOS standard tap target size
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.5), // Half transparency
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
shape: const CircleBorder(),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () {
|
||||
clientClose(sessionId, _ffi);
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Close'),
|
||||
child: const Icon(
|
||||
Icons.chevron_left, // iOS-style back arrow
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFloatingKeyboard() {
|
||||
|
||||
@@ -59,7 +59,8 @@ class CanvasCoords {
|
||||
model.scale = json['scale'];
|
||||
model.scrollX = json['scrollX'];
|
||||
model.scrollY = json['scrollY'];
|
||||
model.scrollStyle = ScrollStyle.fromJson(json['scrollStyle'], ScrollStyle.scrollauto);
|
||||
model.scrollStyle =
|
||||
ScrollStyle.fromJson(json['scrollStyle'], ScrollStyle.scrollauto);
|
||||
model.size = Size(json['size']['w'], json['size']['h']);
|
||||
return model;
|
||||
}
|
||||
@@ -418,6 +419,74 @@ class InputModel {
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/flutter/flutter/issues/157241
|
||||
// Infer CapsLock state from the character output.
|
||||
// This is needed because Flutter's HardwareKeyboard.lockModesEnabled may report
|
||||
// incorrect CapsLock state on iOS.
|
||||
bool _getIosCapsFromCharacter(KeyEvent e) {
|
||||
if (!isIOS) return false;
|
||||
final ch = e.character;
|
||||
return _getIosCapsFromCharacterImpl(
|
||||
ch, HardwareKeyboard.instance.isShiftPressed);
|
||||
}
|
||||
|
||||
// RawKeyEvent version of _getIosCapsFromCharacter.
|
||||
bool _getIosCapsFromRawCharacter(RawKeyEvent e) {
|
||||
if (!isIOS) return false;
|
||||
final ch = e.character;
|
||||
return _getIosCapsFromCharacterImpl(ch, e.isShiftPressed);
|
||||
}
|
||||
|
||||
// Shared implementation for inferring CapsLock state from character.
|
||||
// Uses Unicode-aware case detection to support non-ASCII letters (e.g., ü/Ü, é/É).
|
||||
//
|
||||
// Limitations:
|
||||
// 1. This inference assumes the client and server use the same keyboard layout.
|
||||
// If layouts differ (e.g., client uses EN, server uses DE), the character output
|
||||
// may not match expectations. For example, ';' on EN layout maps to 'ö' on DE
|
||||
// layout, making it impossible to correctly infer CapsLock state from the
|
||||
// character alone.
|
||||
// 2. On iOS, CapsLock+Shift produces uppercase letters (unlike desktop where it
|
||||
// produces lowercase). This method cannot handle that case correctly.
|
||||
bool _getIosCapsFromCharacterImpl(String? ch, bool shiftPressed) {
|
||||
if (ch == null || ch.length != 1) return false;
|
||||
// Use Dart's built-in Unicode-aware case detection
|
||||
final upper = ch.toUpperCase();
|
||||
final lower = ch.toLowerCase();
|
||||
final isUpper = upper == ch && lower != ch;
|
||||
final isLower = lower == ch && upper != ch;
|
||||
// Skip non-letter characters (e.g., numbers, symbols, CJK characters without case)
|
||||
if (!isUpper && !isLower) return false;
|
||||
return isUpper != shiftPressed;
|
||||
}
|
||||
|
||||
int _buildLockModes(bool iosCapsLock) {
|
||||
const capslock = 1;
|
||||
const numlock = 2;
|
||||
const scrolllock = 3;
|
||||
int lockModes = 0;
|
||||
if (isIOS) {
|
||||
if (iosCapsLock) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
// Ignore "NumLock/ScrollLock" on iOS for now.
|
||||
} else {
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.capsLock)) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.numLock)) {
|
||||
lockModes |= (1 << numlock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.scrollLock)) {
|
||||
lockModes |= (1 << scrolllock);
|
||||
}
|
||||
}
|
||||
return lockModes;
|
||||
}
|
||||
|
||||
// This function must be called after the peer info is received.
|
||||
// Because `sessionGetKeyboardMode` relies on the peer version.
|
||||
updateKeyboardMode() async {
|
||||
@@ -550,6 +619,11 @@ class InputModel {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
bool iosCapsLock = false;
|
||||
if (isIOS && e is RawKeyDownEvent) {
|
||||
iosCapsLock = _getIosCapsFromRawCharacter(e);
|
||||
}
|
||||
|
||||
final key = e.logicalKey;
|
||||
if (e is RawKeyDownEvent) {
|
||||
if (!e.repeat) {
|
||||
@@ -586,7 +660,7 @@ class InputModel {
|
||||
|
||||
// * Currently mobile does not enable map mode
|
||||
if ((isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) {
|
||||
mapKeyboardModeRaw(e);
|
||||
mapKeyboardModeRaw(e, iosCapsLock);
|
||||
} else {
|
||||
legacyKeyboardModeRaw(e);
|
||||
}
|
||||
@@ -622,6 +696,11 @@ class InputModel {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
bool iosCapsLock = false;
|
||||
if (isIOS && (e is KeyDownEvent || e is KeyRepeatEvent)) {
|
||||
iosCapsLock = _getIosCapsFromCharacter(e);
|
||||
}
|
||||
|
||||
if (e is KeyUpEvent) {
|
||||
handleKeyUpEventModifiers(e);
|
||||
} else if (e is KeyDownEvent) {
|
||||
@@ -667,7 +746,8 @@ class InputModel {
|
||||
e.character ?? '',
|
||||
e.physicalKey.usbHidUsage & 0xFFFF,
|
||||
// Show repeat event be converted to "release+press" events?
|
||||
e is KeyDownEvent || e is KeyRepeatEvent);
|
||||
e is KeyDownEvent || e is KeyRepeatEvent,
|
||||
iosCapsLock);
|
||||
} else {
|
||||
legacyKeyboardMode(e);
|
||||
}
|
||||
@@ -676,23 +756,9 @@ class InputModel {
|
||||
}
|
||||
|
||||
/// Send Key Event
|
||||
void newKeyboardMode(String character, int usbHid, bool down) {
|
||||
const capslock = 1;
|
||||
const numlock = 2;
|
||||
const scrolllock = 3;
|
||||
int lockModes = 0;
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.capsLock)) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.numLock)) {
|
||||
lockModes |= (1 << numlock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.scrollLock)) {
|
||||
lockModes |= (1 << scrolllock);
|
||||
}
|
||||
void newKeyboardMode(
|
||||
String character, int usbHid, bool down, bool iosCapsLock) {
|
||||
final lockModes = _buildLockModes(iosCapsLock);
|
||||
bind.sessionHandleFlutterKeyEvent(
|
||||
sessionId: sessionId,
|
||||
character: character,
|
||||
@@ -701,7 +767,7 @@ class InputModel {
|
||||
downOrUp: down);
|
||||
}
|
||||
|
||||
void mapKeyboardModeRaw(RawKeyEvent e) {
|
||||
void mapKeyboardModeRaw(RawKeyEvent e, bool iosCapsLock) {
|
||||
int positionCode = -1;
|
||||
int platformCode = -1;
|
||||
bool down;
|
||||
@@ -732,27 +798,14 @@ class InputModel {
|
||||
} else {
|
||||
down = false;
|
||||
}
|
||||
inputRawKey(e.character ?? '', platformCode, positionCode, down);
|
||||
inputRawKey(
|
||||
e.character ?? '', platformCode, positionCode, down, iosCapsLock);
|
||||
}
|
||||
|
||||
/// Send raw Key Event
|
||||
void inputRawKey(String name, int platformCode, int positionCode, bool down) {
|
||||
const capslock = 1;
|
||||
const numlock = 2;
|
||||
const scrolllock = 3;
|
||||
int lockModes = 0;
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.capsLock)) {
|
||||
lockModes |= (1 << capslock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.numLock)) {
|
||||
lockModes |= (1 << numlock);
|
||||
}
|
||||
if (HardwareKeyboard.instance.lockModesEnabled
|
||||
.contains(KeyboardLockMode.scrollLock)) {
|
||||
lockModes |= (1 << scrolllock);
|
||||
}
|
||||
void inputRawKey(String name, int platformCode, int positionCode, bool down,
|
||||
bool iosCapsLock) {
|
||||
final lockModes = _buildLockModes(iosCapsLock);
|
||||
bind.sessionHandleFlutterRawKeyEvent(
|
||||
sessionId: sessionId,
|
||||
name: name,
|
||||
@@ -826,6 +879,9 @@ class InputModel {
|
||||
Map<String, dynamic> _getMouseEvent(PointerEvent evt, String type) {
|
||||
final Map<String, dynamic> out = {};
|
||||
|
||||
bool hasStaleButtonsOnMouseUp =
|
||||
type == _kMouseEventUp && evt.buttons == _lastButtons;
|
||||
|
||||
// Check update event type and set buttons to be sent.
|
||||
int buttons = _lastButtons;
|
||||
if (type == _kMouseEventMove) {
|
||||
@@ -850,7 +906,7 @@ class InputModel {
|
||||
buttons = evt.buttons;
|
||||
}
|
||||
}
|
||||
_lastButtons = evt.buttons;
|
||||
_lastButtons = hasStaleButtonsOnMouseUp ? 0 : evt.buttons;
|
||||
|
||||
out['buttons'] = buttons;
|
||||
out['type'] = type;
|
||||
@@ -1218,6 +1274,28 @@ class InputModel {
|
||||
_trackpadLastDelta = Offset.zero;
|
||||
}
|
||||
|
||||
// iOS Magic Mouse duplicate event detection.
|
||||
// When using Magic Mouse on iPad, iOS may emit both mouse and touch events
|
||||
// for the same click in certain areas (like top-left corner).
|
||||
int _lastMouseDownTimeMs = 0;
|
||||
ui.Offset _lastMouseDownPos = ui.Offset.zero;
|
||||
|
||||
/// Check if a touch tap event should be ignored because it's a duplicate
|
||||
/// of a recent mouse event (iOS Magic Mouse issue).
|
||||
bool shouldIgnoreTouchTap(ui.Offset pos) {
|
||||
if (!isIOS) return false;
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
final dt = nowMs - _lastMouseDownTimeMs;
|
||||
final distance = (_lastMouseDownPos - pos).distance;
|
||||
// If touch tap is within 2000ms and 80px of the last mouse down,
|
||||
// it's likely a duplicate event from the same Magic Mouse click.
|
||||
if (dt >= 0 && dt < 2000 && distance < 80.0) {
|
||||
debugPrint("shouldIgnoreTouchTap: IGNORED (dt=$dt, dist=$distance)");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onPointDownImage(PointerDownEvent e) {
|
||||
debugPrint("onPointDownImage ${e.kind}");
|
||||
_stopFling = true;
|
||||
@@ -1227,6 +1305,13 @@ class InputModel {
|
||||
if (isViewOnly && !showMyCursor) return;
|
||||
if (isViewCamera) return;
|
||||
|
||||
// Track mouse down events for duplicate detection on iOS.
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
if (e.kind == ui.PointerDeviceKind.mouse) {
|
||||
_lastMouseDownTimeMs = nowMs;
|
||||
_lastMouseDownPos = e.position;
|
||||
}
|
||||
|
||||
if (_relativeMouse.enabled.value) {
|
||||
_relativeMouse.updatePointerRegionTopLeftGlobal(e);
|
||||
}
|
||||
@@ -1768,9 +1853,9 @@ class InputModel {
|
||||
// Simulate a key press event.
|
||||
// `usbHidUsage` is the USB HID usage code of the key.
|
||||
Future<void> tapHidKey(int usbHidUsage) async {
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, true);
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, true, false);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, false);
|
||||
newKeyboardMode(kKeyFlutterKey, usbHidUsage, false, false);
|
||||
}
|
||||
|
||||
Future<void> onMobileVolumeUp() async =>
|
||||
|
||||
@@ -1072,10 +1072,6 @@ fn get_api_server_(api: String, custom: String) -> String {
|
||||
if !api.is_empty() {
|
||||
return api.to_owned();
|
||||
}
|
||||
let api = option_env!("API_SERVER").unwrap_or_default();
|
||||
if !api.is_empty() {
|
||||
return api.into();
|
||||
}
|
||||
let s0 = get_custom_rendezvous_server(custom);
|
||||
if !s0.is_empty() {
|
||||
let s = crate::increase_port(&s0, -2);
|
||||
@@ -1737,8 +1733,7 @@ pub fn create_symmetric_key_msg(their_pk_b: [u8; 32]) -> (Bytes, Bytes, secretbo
|
||||
|
||||
#[inline]
|
||||
pub fn using_public_server() -> bool {
|
||||
option_env!("RENDEZVOUS_SERVER").unwrap_or("").is_empty()
|
||||
&& crate::get_custom_rendezvous_server(get_option("custom-rendezvous-server")).is_empty()
|
||||
crate::get_custom_rendezvous_server(get_option("custom-rendezvous-server")).is_empty()
|
||||
}
|
||||
|
||||
pub struct ThrottledInterval {
|
||||
|
||||
132
src/lang/tr.rs
132
src/lang/tr.rs
@@ -3,8 +3,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Durum"),
|
||||
("Your Desktop", "Sizin Masaüstünüz"),
|
||||
("desk_tip", "Masaüstünüze bu ID ve şifre ile erişilebilir"),
|
||||
("Password", "Şifre"),
|
||||
("desk_tip", "Masaüstünüze bu ID ve parola ile erişilebilir"),
|
||||
("Password", "Parola"),
|
||||
("Ready", "Hazır"),
|
||||
("Established", "Bağlantı sağlandı"),
|
||||
("connecting_status", "Bağlanılıyor "),
|
||||
@@ -13,16 +13,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Service is running", "Servis çalışıyor"),
|
||||
("Service is not running", "Servis çalışmıyor"),
|
||||
("not_ready_status", "Hazır değil. Bağlantınızı kontrol edin"),
|
||||
("Control Remote Desktop", "Bağlanılacak Uzak Bağlantı ID"),
|
||||
("Control Remote Desktop", "Uzak Masaüstünü Denetle"),
|
||||
("Transfer file", "Dosya transferi"),
|
||||
("Connect", "Bağlan"),
|
||||
("Recent sessions", "Son Bağlanılanlar"),
|
||||
("Recent sessions", "Son oturumlar"),
|
||||
("Address book", "Adres Defteri"),
|
||||
("Confirmation", "Onayla"),
|
||||
("TCP tunneling", "TCP Tünelleri"),
|
||||
("TCP tunneling", "TCP tünelleri"),
|
||||
("Remove", "Kaldır"),
|
||||
("Refresh random password", "Yeni rastgele şifre oluştur"),
|
||||
("Set your own password", "Kendi şifreni oluştur"),
|
||||
("Refresh random password", "Yeni rastgele parola oluştur"),
|
||||
("Set your own password", "Kendi parolanı oluştur"),
|
||||
("Enable keyboard/mouse", "Klavye ve Fareye izin ver"),
|
||||
("Enable clipboard", "Kopyalanan geçici veriye izin ver"),
|
||||
("Enable file transfer", "Dosya Transferine izin ver"),
|
||||
@@ -47,9 +47,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Slogan_tip", "Bu kaotik dünyada gönülden yapıldı!"),
|
||||
("Privacy Statement", "Gizlilik Beyanı"),
|
||||
("Mute", "Sustur"),
|
||||
("Build Date", "Yapım Tarihi"),
|
||||
("Build Date", "Derleme Tarihi"),
|
||||
("Version", "Sürüm"),
|
||||
("Home", "Anasayfa"),
|
||||
("Home", "Ana Sayfa"),
|
||||
("Audio Input", "Ses Girişi"),
|
||||
("Enhancements", "Geliştirmeler"),
|
||||
("Hardware Codec", "Donanımsal Codec"),
|
||||
@@ -64,18 +64,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Not available", "Erişilebilir değil"),
|
||||
("Too frequent", "Çok sık"),
|
||||
("Cancel", "İptal"),
|
||||
("Skip", "Geç"),
|
||||
("Skip", "Atla"),
|
||||
("Close", "Kapat"),
|
||||
("Retry", "Tekrar Dene"),
|
||||
("OK", "Tamam"),
|
||||
("Password Required", "Şifre Gerekli"),
|
||||
("Please enter your password", "Lütfen şifrenizi giriniz"),
|
||||
("Remember password", "Şifreyi hatırla"),
|
||||
("Wrong Password", "Hatalı şifre"),
|
||||
("Password Required", "Parola Gerekli"),
|
||||
("Please enter your password", "Lütfen parolanızı giriniz"),
|
||||
("Remember password", "Parolayı hatırla"),
|
||||
("Wrong Password", "Hatalı parola"),
|
||||
("Do you want to enter again?", "Tekrar giriş yapmak ister misiniz?"),
|
||||
("Connection Error", "Bağlantı Hatası"),
|
||||
("Error", "Hata"),
|
||||
("Reset by the peer", "Eş tarafında sıfırla"),
|
||||
("Reset by the peer", "Eş tarafından sıfırlandı"),
|
||||
("Connecting...", "Bağlanılıyor..."),
|
||||
("Connection in progress. Please wait.", "Bağlantı sağlanıyor. Lütfen bekleyiniz."),
|
||||
("Please try 1 minute later", "Lütfen 1 dakika sonra tekrar deneyiniz"),
|
||||
@@ -141,10 +141,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Timeout", "Zaman aşımı"),
|
||||
("Failed to connect to relay server", "Relay sunucusuna bağlanılamadı"),
|
||||
("Failed to connect via rendezvous server", "ID oluşturma sunucusuna bağlanılamadı"),
|
||||
("Failed to connect via relay server", "Relay oluşturma sunucusuna bağlanılamadı"),
|
||||
("Failed to connect via relay server", "Aktarma sunucusuna bağlanılamadı"),
|
||||
("Failed to make direct connection to remote desktop", "Uzak masaüstüne doğrudan bağlantı kurulamadı"),
|
||||
("Set Password", "Şifre ayarla"),
|
||||
("OS Password", "İşletim Sistemi Şifresi"),
|
||||
("Set Password", "Parola ayarla"),
|
||||
("OS Password", "İşletim Sistemi Parolası"),
|
||||
("install_tip", "Kullanıcı Hesabı Denetimi nedeniyle, RustDesk bir uzak masaüstü olarak düzgün çalışmayabilir. Bu sorunu önlemek için, RustDesk'i sistem seviyesinde kurmak için aşağıdaki butona tıklayın."),
|
||||
("Click to upgrade", "Yükseltmek için tıklayınız"),
|
||||
("Configure", "Ayarla"),
|
||||
@@ -184,7 +184,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Direct and unencrypted connection", "Doğrudan ve şifrelenmemiş bağlantı"),
|
||||
("Relayed and unencrypted connection", "Aktarmalı ve şifrelenmemiş bağlantı"),
|
||||
("Enter Remote ID", "Uzak ID'yi Girin"),
|
||||
("Enter your password", "Şifrenizi girin"),
|
||||
("Enter your password", "Parolanızı girin"),
|
||||
("Logging in...", "Giriş yapılıyor..."),
|
||||
("Enable RDP session sharing", "RDP oturum paylaşımını etkinleştir"),
|
||||
("Auto Login", "Otomatik giriş"),
|
||||
@@ -208,8 +208,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Closed manually by the peer", "Eş tarafından manuel olarak kapatıldı"),
|
||||
("Enable remote configuration modification", "Uzaktan yapılandırma değişikliğini etkinleştir"),
|
||||
("Run without install", "Yüklemeden çalıştır"),
|
||||
("Connect via relay", ""),
|
||||
("Always connect via relay", "Always connect via relay"),
|
||||
("Connect via relay", "Aktarmalı üzerinden bağlan"),
|
||||
("Always connect via relay", "Her zaman aktarmalı üzerinden bağlan"),
|
||||
("whitelist_tip", "Bu masaüstüne yalnızca yetkili IP adresleri bağlanabilir"),
|
||||
("Login", "Giriş yap"),
|
||||
("Verify", "Doğrula"),
|
||||
@@ -226,11 +226,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unselect all tags", "Tüm etiketlerin seçimini kaldır"),
|
||||
("Network error", "Bağlantı hatası"),
|
||||
("Username missed", "Kullanıcı adı boş"),
|
||||
("Password missed", "Şifre boş"),
|
||||
("Password missed", "Parola boş"),
|
||||
("Wrong credentials", "Yanlış kimlik bilgileri"),
|
||||
("The verification code is incorrect or has expired", "Doğrulama kodu hatalı veya süresi dolmuş"),
|
||||
("Edit Tag", "Etiketi düzenle"),
|
||||
("Forget Password", "Şifreyi Unut"),
|
||||
("Forget Password", "Parolayı Unut"),
|
||||
("Favorites", "Favoriler"),
|
||||
("Add to Favorites", "Favorilere ekle"),
|
||||
("Remove from Favorites", "Favorilerden çıkar"),
|
||||
@@ -268,9 +268,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Share screen", "Ekranı Paylaş"),
|
||||
("Chat", "Mesajlaş"),
|
||||
("Total", "Toplam"),
|
||||
("items", "öğeler"),
|
||||
("items", "ögeler"),
|
||||
("Selected", "Seçildi"),
|
||||
("Screen Capture", "Ekran görüntüsü"),
|
||||
("Screen Capture", "Ekran Görüntüsü"),
|
||||
("Input Control", "Giriş Kontrolü"),
|
||||
("Audio Capture", "Ses Yakalama"),
|
||||
("Do you accept?", "Kabul ediyor musun?"),
|
||||
@@ -285,7 +285,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti başlat] ögesine dokunun veya [Ekran Görüntüsü] iznini etkinleştirin."),
|
||||
("android_permission_may_not_change_tip", "Kurulan bağlantılara ait izinler, yeniden bağlantı kurulana kadar anında değiştirilemez."),
|
||||
("Account", "Hesap"),
|
||||
("Overwrite", "üzerine yaz"),
|
||||
("Overwrite", "Üzerine yaz"),
|
||||
("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"),
|
||||
("Quit", "Çıkış"),
|
||||
("Help", "Yardım"),
|
||||
@@ -295,8 +295,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Unsupported", "desteklenmiyor"),
|
||||
("Peer denied", "eş reddedildi"),
|
||||
("Please install plugins", "Lütfen eklentileri yükleyin"),
|
||||
("Peer exit", "eş çıkışı"),
|
||||
("Failed to turn off", "kapatılamadı"),
|
||||
("Peer exit", "Eş çıkışı"),
|
||||
("Failed to turn off", "Kapatılamadı"),
|
||||
("Turned off", "Kapatıldı"),
|
||||
("Language", "Dil"),
|
||||
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
|
||||
@@ -308,32 +308,32 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Legacy mode", "Eski mod"),
|
||||
("Map mode", "Haritalama modu"),
|
||||
("Translate mode", "Çeviri modu"),
|
||||
("Use permanent password", "Kalıcı şifre kullan"),
|
||||
("Use both passwords", "İki şifreyi de kullan"),
|
||||
("Set permanent password", "Kalıcı şifre oluştur"),
|
||||
("Use permanent password", "Kalıcı parola kullan"),
|
||||
("Use both passwords", "İki parolayı da kullan"),
|
||||
("Set permanent password", "Kalıcı parola oluştur"),
|
||||
("Enable remote restart", "Uzaktan yeniden başlatmayı aktif et"),
|
||||
("Restart remote device", "Uzaktaki cihazı yeniden başlat"),
|
||||
("Are you sure you want to restart", "Yeniden başlatmak istediğinize emin misin?"),
|
||||
("Are you sure you want to restart", "Yeniden başlatmak istediğine emin misin?"),
|
||||
("Restarting remote device", "Uzaktan yeniden başlatılıyor"),
|
||||
("remote_restarting_tip", "Uzak cihaz yeniden başlatılıyor, lütfen bu mesaj kutusunu kapatın ve bir süre sonra kalıcı şifre ile yeniden bağlanın"),
|
||||
("remote_restarting_tip", "Uzak cihaz yeniden başlatılıyor, lütfen bu mesaj kutusunu kapatın ve bir süre sonra kalıcı parola ile yeniden bağlanın"),
|
||||
("Copied", "Kopyalandı"),
|
||||
("Exit Fullscreen", "Tam ekrandan çık"),
|
||||
("Fullscreen", "Tam ekran"),
|
||||
("Exit Fullscreen", "Tam Ekrandan Çık"),
|
||||
("Fullscreen", "Tam Ekran"),
|
||||
("Mobile Actions", "Mobil İşlemler"),
|
||||
("Select Monitor", "Monitörü Seç"),
|
||||
("Control Actions", "Kontrol Eylemleri"),
|
||||
("Display Settings", "Görüntü ayarları"),
|
||||
("Display Settings", "Görüntü Ayarları"),
|
||||
("Ratio", "Oran"),
|
||||
("Image Quality", "Görüntü kalitesi"),
|
||||
("Image Quality", "Görüntü Kalitesi"),
|
||||
("Scroll Style", "Kaydırma Stili"),
|
||||
("Show Toolbar", "Araç Çubuğunu Göster"),
|
||||
("Hide Toolbar", "Araç Çubuğunu Gizle"),
|
||||
("Direct Connection", "Doğrudan Bağlantı"),
|
||||
("Relay Connection", "Röle Bağlantısı"),
|
||||
("Relay Connection", "Aktarmalı Bağlantı"),
|
||||
("Secure Connection", "Güvenli Bağlantı"),
|
||||
("Insecure Connection", "Güvenli Olmayan Bağlantı"),
|
||||
("Scale original", "Orijinali ölçeklendir"),
|
||||
("Scale adaptive", "Ölçek uyarlanabilir"),
|
||||
("Scale original", "Orijinal ölçekte"),
|
||||
("Scale adaptive", "Uyarlanabilir ölçekte"),
|
||||
("General", "Genel"),
|
||||
("Security", "Güvenlik"),
|
||||
("Theme", "Tema"),
|
||||
@@ -347,18 +347,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable audio", "Sesi Aktif Et"),
|
||||
("Unlock Network Settings", "Ağ Ayarlarını Aç"),
|
||||
("Server", "Sunucu"),
|
||||
("Direct IP Access", "Direk IP Erişimi"),
|
||||
("Direct IP Access", "Doğrudan IP Erişimi"),
|
||||
("Proxy", "Vekil"),
|
||||
("Apply", "Uygula"),
|
||||
("Disconnect all devices?", "Tüm cihazların bağlantısını kes?"),
|
||||
("Disconnect all devices?", "Tüm cihazların bağlantısı kesilsin mi?"),
|
||||
("Clear", "Temizle"),
|
||||
("Audio Input Device", "Ses Giriş Aygıtı"),
|
||||
("Use IP Whitelisting", "IP Beyaz Listeyi Kullan"),
|
||||
("Network", "Ağ"),
|
||||
("Pin Toolbar", "Araç Çubuğunu Sabitle"),
|
||||
("Unpin Toolbar", "Araç Çubuğunu Sabitlemeyi Kaldır"),
|
||||
("Recording", "Kayıt Ediliyor"),
|
||||
("Directory", "Klasör"),
|
||||
("Recording", "Kaydediliyor"),
|
||||
("Directory", "Dizin"),
|
||||
("Automatically record incoming sessions", "Gelen oturumları otomatik olarak kaydet"),
|
||||
("Automatically record outgoing sessions", "Giden oturumları otomatik olarak kaydet"),
|
||||
("Change", "Değiştir"),
|
||||
@@ -384,16 +384,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show RustDesk", "RustDesk'i Göster"),
|
||||
("This PC", "Bu PC"),
|
||||
("or", "veya"),
|
||||
("Continue with", "bununla devam et"),
|
||||
("Continue with", "Bununla devam et"),
|
||||
("Elevate", "Yükseltme"),
|
||||
("Zoom cursor", "Yakınlaştırma imleci"),
|
||||
("Accept sessions via password", "Oturumları parola ile kabul etme"),
|
||||
("Accept sessions via click", "Tıklama yoluyla oturumları kabul edin"),
|
||||
("Accept sessions via both", "Her ikisi aracılığıyla oturumları kabul edin"),
|
||||
("Please wait for the remote side to accept your session request...", "Lütfen uzak tarafın oturum isteğinizi kabul etmesini bekleyin..."),
|
||||
("One-time Password", "Tek Kullanımlık Şifre"),
|
||||
("One-time Password", "Tek Kullanımlık Parola"),
|
||||
("Use one-time password", "Tek seferlik parola kullanın"),
|
||||
("One-time password length", "Tek seferlik şifre uzunluğu"),
|
||||
("One-time password length", "Tek seferlik parola uzunluğu"),
|
||||
("Request access to your device", "Cihazınıza erişim talep edin"),
|
||||
("Hide connection management window", "Bağlantı yönetimi penceresini gizle"),
|
||||
("hide_cm_tip", "Oturumları yalnızca parola ile kabul edebilir ve kalıcı parola kullanıyorsanız gizlemeye izin verin"),
|
||||
@@ -442,7 +442,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Sesli görüşme"),
|
||||
("Text chat", "Metin sohbeti"),
|
||||
("Stop voice call", "Sesli görüşmeyi durdur"),
|
||||
("relay_hint_tip", "Doğrudan bağlanmak mümkün olmayabilir; röle aracılığıyla bağlanmayı deneyebilirsiniz. Ayrıca, ilk denemenizde bir röle kullanmak istiyorsanız, ID'nin sonuna \"/r\" ekleyebilir veya son oturum kartındaki \"Her Zaman Röle Üzerinden Bağlan\" seçeneğini seçebilirsiniz."),
|
||||
("relay_hint_tip", "Doğrudan bağlanmak mümkün olmayabilir; aktarmalı bağlanmayı deneyebilirsiniz. Ayrıca, ilk denemenizde aktarma sunucusu kullanmak istiyorsanız ID'nin sonuna \"/r\" ekleyebilir veya son oturum kartındaki \"Her Zaman Aktarmalı Üzerinden Bağlan\" seçeneğini seçebilirsiniz."),
|
||||
("Reconnect", "Yeniden Bağlan"),
|
||||
("Codec", "Kodlayıcı"),
|
||||
("Resolution", "Çözünürlük"),
|
||||
@@ -477,7 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no_desktop_title_tip", "Masaüstü mevcut değil"),
|
||||
("no_desktop_text_tip", "Lütfen GNOME masaüstünü yükleyin"),
|
||||
("No need to elevate", "Yükseltmeye gerek yok"),
|
||||
("System Sound", "Sistem Ses"),
|
||||
("System Sound", "Sistem Sesi"),
|
||||
("Default", "Varsayılan"),
|
||||
("New RDP", "Yeni RDP"),
|
||||
("Fingerprint", "Parmak İzi"),
|
||||
@@ -495,7 +495,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("resolution_fit_local_tip", "Yerel çözünürlüğe sığdır"),
|
||||
("resolution_custom_tip", "Özel çözünürlük"),
|
||||
("Collapse toolbar", "Araç çubuğunu daralt"),
|
||||
("Accept and Elevate", "Kabul et ve yükselt"),
|
||||
("Accept and Elevate", "Kabul Et ve Yükselt"),
|
||||
("accept_and_elevate_btn_tooltip", "Bağlantıyı kabul et ve UAC izinlerini yükselt."),
|
||||
("clipboard_wait_response_timeout_tip", "Kopyalama yanıtı için zaman aşımına uğradı."),
|
||||
("Incoming connection", "Gelen bağlantı"),
|
||||
@@ -534,7 +534,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("scam_text1", "Eğer tanımadığınız ve güvenmediğiniz birisiyle telefonda konuşuyorsanız ve sizden RustDesk'i kullanmanızı ve hizmeti başlatmanızı istiyorsa devam etmeyin ve hemen telefonu kapatın."),
|
||||
("scam_text2", "Muhtemelen paranızı veya diğer özel bilgilerinizi çalmaya çalışan dolandırıcılardır."),
|
||||
("Don't show again", "Bir daha gösterme"),
|
||||
("I Agree", "Kabul ediyorum"),
|
||||
("I Agree", "Kabul Ediyorum"),
|
||||
("Decline", "Reddet"),
|
||||
("Timeout in minutes", "Zaman aşımı (dakika)"),
|
||||
("auto_disconnect_option_tip", "Kullanıcı etkin olmadığında gelen oturumları otomatik olarak kapat"),
|
||||
@@ -559,7 +559,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Plug out all", "Tümünü çıkar"),
|
||||
("True color (4:4:4)", "Gerçek renk (4:4:4)"),
|
||||
("Enable blocking user input", "Kullanıcı girişini engellemeyi etkinleştir"),
|
||||
("id_input_tip", "Bir ID, doğrudan IP veya portlu bir etki alanı (<domain>:<port>) girebilirsiniz.\nBaşka bir sunucudaki bir cihaza erişmek istiyorsanız lütfen sunucu adresini (<id>@<server_address>?key=<key_value>) ekleyin, örneğin,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nGenel bir sunucudaki bir cihaza erişmek istiyorsanız lütfen \"<id>@public\" girin, genel sunucu için anahtara gerek yoktur.\n\nİlk bağlantıda bir röle bağlantısının kullanılmasını zorlamak istiyorsanız ID'nin sonuna \"/r\" ekleyin, örneğin, \"9123456234/r\"."),
|
||||
("id_input_tip", "Bir ID, doğrudan IP veya portlu bir etki alanı (<domain>:<port>) girebilirsiniz.\nBaşka bir sunucudaki bir cihaza erişmek istiyorsanız lütfen sunucu adresini (<id>@<server_address>?key=<key_value>) ekleyin, örneğin,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nGenel bir sunucudaki bir cihaza erişmek istiyorsanız lütfen \"<id>@public\" girin, genel sunucu için anahtara gerek yoktur.\n\nİlk bağlantıda bir aktarma bağlantısının kullanılmasını zorlamak istiyorsanız ID'nin sonuna \"/r\" ekleyin, örneğin, \"9123456234/r\"."),
|
||||
("privacy_mode_impl_mag_tip", "Mod 1"),
|
||||
("privacy_mode_impl_virtual_display_tip", "Mod 2"),
|
||||
("Enter privacy mode", "Gizlilik moduna gir"),
|
||||
@@ -581,12 +581,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please select the session you want to connect to", "Lütfen bağlanmak istediğiniz oturumu seçin"),
|
||||
("powered_by_me", "RustDesk tarafından desteklenmektedir"),
|
||||
("outgoing_only_desk_tip", "Bu özelleştirilmiş bir sürümdür.\nDiğer cihazlara bağlanabilirsiniz, ancak diğer cihazlar cihazınıza bağlanamaz."),
|
||||
("preset_password_warning", "Bu özelleştirilmiş sürüm, önceden ayarlanmış bir şifre ile birlikte gelir. Bu parolayı bilen herkes cihazınızın tam kontrolünü ele geçirebilir. Bunu beklemiyorsanız yazılımı hemen kaldırın."),
|
||||
("preset_password_warning", "Bu özelleştirilmiş sürüm, önceden ayarlanmış bir parola ile birlikte gelir. Bu parolayı bilen herkes cihazınızın tam kontrolünü ele geçirebilir. Bunu beklemiyorsanız yazılımı hemen kaldırın."),
|
||||
("Security Alert", "Güvenlik Uyarısı"),
|
||||
("My address book", "Adres defterim"),
|
||||
("Personal", "Kişisel"),
|
||||
("Owner", "Sahip"),
|
||||
("Set shared password", "Paylaşılan şifreyi ayarla"),
|
||||
("Set shared password", "Paylaşılan parolayı ayarla"),
|
||||
("Exist in", "İçinde varolan"),
|
||||
("Read-only", "Salt okunur"),
|
||||
("Read/Write", "Okuma/Yazma"),
|
||||
@@ -599,7 +599,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Follow remote cursor", "Uzak imleci takip et"),
|
||||
("Follow remote window focus", "Uzak pencere odağını takip et"),
|
||||
("default_proxy_tip", "Varsayılan protokol ve port Socks5 ve 1080'dir."),
|
||||
("no_audio_input_device_tip", "Varsayılan protokol ve port, Socks5 ve 1080'dir"),
|
||||
("no_audio_input_device_tip", "Ses girişi aygıtı bulunamadı."),
|
||||
("Incoming", "Gelen"),
|
||||
("Outgoing", "Giden"),
|
||||
("Clear Wayland screen selection", "Wayland ekran seçimini temizle"),
|
||||
@@ -612,7 +612,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("floating_window_tip", "RustDesk arka plan hizmetini açık tutmaya yardımcı olur"),
|
||||
("Keep screen on", "Ekranı açık tut"),
|
||||
("Never", "Asla"),
|
||||
("During controlled", "Kontrol sırasınd"),
|
||||
("During controlled", "Kontrol sırasında"),
|
||||
("During service is on", "Servis açıkken"),
|
||||
("Capture screen using DirectX", "DirectX kullanarak ekran görüntüsü al"),
|
||||
("Back", "Geri"),
|
||||
@@ -620,7 +620,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Volume up", "Sesi yükselt"),
|
||||
("Volume down", "Sesi azalt"),
|
||||
("Power", "Güç"),
|
||||
("Telegram bot", "Telegram bot"),
|
||||
("Telegram bot", "Telegram botu"),
|
||||
("enable-bot-tip", "Bu özelliği etkinleştirirseniz botunuzdan 2FA kodunu alabilirsiniz. Aynı zamanda bağlantı bildirimi işlevi de görebilir."),
|
||||
("enable-bot-desc", "1. @BotFather ile bir sohbet açın.\n2. \"/newbot\" komutunu gönderin. Bu adımı tamamladıktan sonra bir jeton alacaksınız.\n3. Yeni oluşturduğunuz botla bir sohbet başlatın. Etkinleştirmek için eğik çizgiyle (\"/\") başlayan \"/merhaba\" gibi bir mesaj gönderin.\n"),
|
||||
("cancel-2fa-confirm-tip", "2FA'yı iptal etmek istediğinizden emin misiniz?"),
|
||||
@@ -642,7 +642,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Invalid file name", "Geçersiz dosya adı"),
|
||||
("one-way-file-transfer-tip", "Kontrol edilen tarafta tek yönlü dosya transferi aktiftir."),
|
||||
("Authentication Required", "Kimlik Doğrulama Gerekli"),
|
||||
("Authenticate", "Kimlik doğrulaması"),
|
||||
("Authenticate", "Kimlik Doğrula"),
|
||||
("web_id_input_tip", "Aynı sunucuda bir kimlik girebilirsiniz, web istemcisinde doğrudan IP erişimi desteklenmez.\nBaşka bir sunucudaki bir cihaza erişmek istiyorsanız lütfen sunucu adresini (<id>@<server_address>?key=<key_value>) ekleyin, örneğin,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nGenel bir sunucudaki bir cihaza erişmek istiyorsanız, lütfen \"<id>@public\" girin, genel sunucu için anahtara gerek yoktur."),
|
||||
("Download", "İndir"),
|
||||
("Upload folder", "Klasör yükle"),
|
||||
@@ -661,9 +661,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("printer-{}-not-installed-tip", "{} Yazıcısı yüklü değil."),
|
||||
("printer-{}-ready-tip", "{} Yazıcısı kuruldu ve kullanıma hazır."),
|
||||
("Install {} Printer", "{} Yazıcısını Yükle"),
|
||||
("Outgoing Print Jobs", "Giden Baskı İşleri"),
|
||||
("Incoming Print Jobs", "Gelen Baskı İşleri"),
|
||||
("Incoming Print Job", "Gelen Baskı İşi"),
|
||||
("Outgoing Print Jobs", "Giden Yazdırma İşleri"),
|
||||
("Incoming Print Jobs", "Gelen Yazdırma İşleri"),
|
||||
("Incoming Print Job", "Gelen Yazdırma İşi"),
|
||||
("use-the-default-printer-tip", "Varsayılan yazıcıyı kullan"),
|
||||
("use-the-selected-printer-tip", "Seçili yazıcıyı kullan"),
|
||||
("auto-print-tip", "Seçili yazıcıyı kullanarak otomatik olarak yazdır."),
|
||||
@@ -685,11 +685,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("download-new-version-failed-tip", "İndirme başarısız oldu. Tekrar deneyebilir veya 'İndir' düğmesine tıklayarak sürüm sayfasından manuel olarak indirip güncelleyebilirsiniz."),
|
||||
("Auto update", "Otomatik güncelleme"),
|
||||
("update-failed-check-msi-tip", "Kurulum yöntemi denetimi başarısız oldu. Sürüm sayfasından indirmek ve manuel olarak yükseltmek için lütfen \"İndir\" düğmesine tıklayın."),
|
||||
("websocket_tip", "WebSocket kullanıldığında yalnızca röle bağlantıları desteklenir."),
|
||||
("websocket_tip", "WebSocket kullanıldığında yalnızca aktarma bağlantıları desteklenir."),
|
||||
("Use WebSocket", "WebSocket'ı kullan"),
|
||||
("Trackpad speed", "İzleme paneli hızı"),
|
||||
("Default trackpad speed", "Varsayılan izleme paneli hızı"),
|
||||
("Numeric one-time password", "Sayısal tek seferlik şifre"),
|
||||
("Numeric one-time password", "Sayısal tek seferlik parola"),
|
||||
("Enable IPv6 P2P connection", "IPv6 P2P bağlantısını etkinleştir"),
|
||||
("Enable UDP hole punching", "UDP delik açmayı etkinleştir"),
|
||||
("View camera", "Kamerayı görüntüle"),
|
||||
@@ -701,16 +701,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("New tab", "Yeni sekme"),
|
||||
("Keep terminal sessions on disconnect", "Bağlantı kesildiğinde terminal oturumlarını açık tut"),
|
||||
("Terminal (Run as administrator)", "Terminal (Yönetici olarak çalıştır)"),
|
||||
("terminal-admin-login-tip", "Lütfen kontrol edilen tarafın yönetici kullanıcı adı ve şifresini giriniz."),
|
||||
("terminal-admin-login-tip", "Lütfen kontrol edilen tarafın yönetici kullanıcı adı ve parolasını giriniz."),
|
||||
("Failed to get user token.", "Kullanıcı belirteci alınamadı."),
|
||||
("Incorrect username or password.", "Hatalı kullanıcı adı veya şifre."),
|
||||
("Incorrect username or password.", "Hatalı kullanıcı adı veya parola."),
|
||||
("The user is not an administrator.", "Kullanıcı bir yönetici değil."),
|
||||
("Failed to check if the user is an administrator.", "Kullanıcının yönetici olup olmadığı kontrol edilemedi."),
|
||||
("Supported only in the installed version.", "Sadece yüklü sürümde desteklenir."),
|
||||
("elevation_username_tip", "Kullanıcı adı veya etki alanı\\kullanıcı adı girin"),
|
||||
("Preparing for installation ...", "Kuruluma hazırlanıyor..."),
|
||||
("Show my cursor", "İmlecimi göster"),
|
||||
("Scale custom", "Özel boyutlandır"),
|
||||
("Scale custom", "Özel ölçekte"),
|
||||
("Custom scale slider", "Özel ölçek kaydırıcısı"),
|
||||
("Decrease", "Azalt"),
|
||||
("Increase", "Arttır"),
|
||||
|
||||
@@ -729,15 +729,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("server-oss-not-support-tip", "注意:RustDesk 開源伺服器 (OSS server) 不包含此功能。"),
|
||||
("input note here", "輸入備註"),
|
||||
("note-at-conn-end-tip", "在連接結束時請求備註"),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
("Changelog", ""),
|
||||
("keep-awake-during-outgoing-sessions-label", ""),
|
||||
("keep-awake-during-incoming-sessions-label", ""),
|
||||
("Show terminal extra keys", "顯示終端機額外按鍵"),
|
||||
("Relative mouse mode", "相對滑鼠模式"),
|
||||
("rel-mouse-not-supported-peer-tip", "被控端不支援相對滑鼠模式"),
|
||||
("rel-mouse-not-ready-tip", "相對滑鼠模式尚未就緒,請稍候再試"),
|
||||
("rel-mouse-lock-failed-tip", "無法鎖定游標,相對滑鼠模式已停用"),
|
||||
("rel-mouse-exit-{}-tip", "按下 {} 退出"),
|
||||
("rel-mouse-permission-lost-tip", "鍵盤權限被撤銷,相對滑鼠模式已被停用"),
|
||||
("Changelog", "更新日誌"),
|
||||
("keep-awake-during-outgoing-sessions-label", "在連出工作階段期間保持螢幕喚醒"),
|
||||
("keep-awake-during-incoming-sessions-label", "在連入工作階段期間保持螢幕喚醒"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -357,27 +357,6 @@ static std::string GetDisplayUUID(CGDirectDisplayID displayId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Helper function to get display name from DisplayID
|
||||
static std::string GetDisplayName(CGDirectDisplayID displayId) {
|
||||
NSArray<NSScreen *> *screens = [NSScreen screens];
|
||||
for (NSScreen *screen in screens) {
|
||||
NSDictionary *deviceDescription = [screen deviceDescription];
|
||||
NSNumber *screenNumber = [deviceDescription objectForKey:@"NSScreenNumber"];
|
||||
CGDirectDisplayID screenDisplayID = [screenNumber unsignedIntValue];
|
||||
if (screenDisplayID == displayId) {
|
||||
// localizedName is available on macOS 10.15+
|
||||
if (@available(macOS 10.15, *)) {
|
||||
NSString *name = [screen localizedName];
|
||||
if (name) {
|
||||
return std::string([name UTF8String]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
// Helper function to find DisplayID by UUID from current online displays
|
||||
static CGDirectDisplayID FindDisplayIdByUUID(const std::string& targetUuid) {
|
||||
uint32_t count = 0;
|
||||
@@ -415,9 +394,7 @@ static bool RestoreAllGammas() {
|
||||
const CGGammaValue* blue = green + sampleCount;
|
||||
CGError error = CGSetDisplayTransferByTable(d, sampleCount, red, green, blue);
|
||||
if (error != kCGErrorSuccess) {
|
||||
std::string displayName = GetDisplayName(d);
|
||||
NSLog(@"Failed to restore gamma for display (Name: %s, ID: %u, UUID: %s, error: %d)",
|
||||
displayName.c_str(), (unsigned)d, uuid.c_str(), error);
|
||||
NSLog(@"Failed to restore gamma for display (ID: %u, UUID: %s, error: %d)", (unsigned)d, uuid.c_str(), error);
|
||||
allSuccess = false;
|
||||
}
|
||||
}
|
||||
@@ -897,8 +874,7 @@ extern "C" bool MacSetPrivacyMode(bool on) {
|
||||
blackoutAttemptCount++;
|
||||
CGError error = CGSetDisplayTransferByTable(d, capacity, zeros.data(), zeros.data(), zeros.data());
|
||||
if (error != kCGErrorSuccess) {
|
||||
std::string displayName = GetDisplayName(d);
|
||||
NSLog(@"MacSetPrivacyMode: Failed to blackout display (Name: %s, ID: %u, UUID: %s, error: %d)", displayName.c_str(), (unsigned)d, uuid.c_str(), error);
|
||||
NSLog(@"MacSetPrivacyMode: Failed to blackout display (ID: %u, UUID: %s, error: %d)", (unsigned)d, uuid.c_str(), error);
|
||||
} else {
|
||||
blackoutSuccessCount++;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use uuid::Uuid;
|
||||
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{self, bail},
|
||||
anyhow::{self, bail, Context},
|
||||
config::{
|
||||
self, keys::*, option2bool, use_ws, Config, CONNECT_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT,
|
||||
},
|
||||
@@ -749,6 +749,9 @@ impl RendezvousMediator {
|
||||
}
|
||||
}
|
||||
|
||||
// Socket backlog value used for TCP listeners, matching the value in hbb_common::tcp
|
||||
const SOCKET_BACKLOG: u32 = 128;
|
||||
|
||||
fn get_direct_port() -> i32 {
|
||||
let mut port = Config::get_option("direct-access-port")
|
||||
.parse::<i32>()
|
||||
@@ -759,6 +762,37 @@ fn get_direct_port() -> i32 {
|
||||
port
|
||||
}
|
||||
|
||||
async fn listen_with_bind_interface(port: u16) -> ResultType<hbb_common::tokio::net::TcpListener> {
|
||||
use hbb_common::tokio::net::{TcpListener, TcpSocket};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
let bind_interface = Config::get_option("bind-interface");
|
||||
|
||||
if bind_interface.is_empty() {
|
||||
// Use the default listen_any behavior
|
||||
hbb_common::tcp::listen_any(port).await
|
||||
} else {
|
||||
// Parse the bind interface address
|
||||
let addr: IpAddr = bind_interface.parse()
|
||||
.with_context(|| format!("Invalid bind interface address: {}", bind_interface))?;
|
||||
let socket_addr = SocketAddr::new(addr, port);
|
||||
|
||||
// Create and bind socket
|
||||
let socket = match socket_addr {
|
||||
SocketAddr::V4(..) => TcpSocket::new_v4()?,
|
||||
SocketAddr::V6(..) => TcpSocket::new_v6()?,
|
||||
};
|
||||
|
||||
// Set socket options
|
||||
#[cfg(all(unix, not(target_os = "illumos")))]
|
||||
allow_err!(socket.set_reuseport(true));
|
||||
allow_err!(socket.set_reuseaddr(true));
|
||||
|
||||
socket.bind(socket_addr)?;
|
||||
Ok(socket.listen(SOCKET_BACKLOG)?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn direct_server(server: ServerPtr) {
|
||||
let mut listener = None;
|
||||
let mut port = 0;
|
||||
@@ -769,7 +803,7 @@ async fn direct_server(server: ServerPtr) {
|
||||
) || option2bool("stop-service", &Config::get_option("stop-service"));
|
||||
if !disabled && listener.is_none() {
|
||||
port = get_direct_port();
|
||||
match hbb_common::tcp::listen_any(port as _).await {
|
||||
match listen_with_bind_interface(port as _).await {
|
||||
Ok(l) => {
|
||||
listener = Some(l);
|
||||
log::info!(
|
||||
@@ -904,3 +938,27 @@ async fn udp_nat_listen(
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::IpAddr;
|
||||
|
||||
#[test]
|
||||
fn test_bind_interface_parsing() {
|
||||
// Test valid IPv4 addresses
|
||||
assert!("192.168.1.100".parse::<IpAddr>().is_ok());
|
||||
assert!("10.0.0.1".parse::<IpAddr>().is_ok());
|
||||
assert!("127.0.0.1".parse::<IpAddr>().is_ok());
|
||||
|
||||
// Test valid IPv6 addresses
|
||||
assert!("::1".parse::<IpAddr>().is_ok());
|
||||
assert!("fe80::1".parse::<IpAddr>().is_ok());
|
||||
assert!("2001:db8::1".parse::<IpAddr>().is_ok());
|
||||
|
||||
// Test invalid addresses
|
||||
assert!("invalid".parse::<IpAddr>().is_err());
|
||||
assert!("999.999.999.999".parse::<IpAddr>().is_err());
|
||||
assert!("".parse::<IpAddr>().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user