20 Commits

39 changed files with 339 additions and 233 deletions

View File

@@ -33,8 +33,8 @@ env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2024.07.12
VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1"
VERSION: "1.3.1"
NDK_VERSION: "r27"
VERSION: "1.3.2"
NDK_VERSION: "r27b"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"

View File

@@ -18,7 +18,7 @@ env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2024.06.15
VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625"
VERSION: "1.3.1"
VERSION: "1.3.2"
NDK_VERSION: "r26d"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"

4
Cargo.lock generated
View File

@@ -5480,7 +5480,7 @@ dependencies = [
[[package]]
name = "rustdesk"
version = "1.3.1"
version = "1.3.2"
dependencies = [
"android-wakelock",
"android_logger",
@@ -5580,7 +5580,7 @@ dependencies = [
[[package]]
name = "rustdesk-portable-packer"
version = "1.3.1"
version = "1.3.2"
dependencies = [
"brotli",
"dirs 5.0.1",

View File

@@ -1,6 +1,6 @@
[package]
name = "rustdesk"
version = "1.3.1"
version = "1.3.2"
authors = ["rustdesk <info@rustdesk.com>"]
edition = "2021"
build= "build.rs"

View File

@@ -18,7 +18,7 @@ AppDir:
id: rustdesk
name: rustdesk
icon: rustdesk
version: 1.3.1
version: 1.3.2
exec: usr/lib/rustdesk/rustdesk
exec_args: $@
apt:

View File

@@ -18,7 +18,7 @@ AppDir:
id: rustdesk
name: rustdesk
icon: rustdesk
version: 1.3.1
version: 1.3.2
exec: usr/lib/rustdesk/rustdesk
exec_args: $@
apt:

View File

@@ -8,7 +8,7 @@
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
</p>
Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
与我们交流: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
@@ -32,7 +32,9 @@ RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING-Z
## 依赖
桌面版本界面使用[sciter](https://sciter.com/), 请自行下载
桌面版本使用 Flutter 或 Sciter已弃用作为 GUI本教程仅适用于 Sciter因为它更简单且更易于上手。查看我们的[CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)以构建 Flutter 版本
请自行下载Sciter动态库。
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
@@ -207,12 +209,13 @@ target/release/rustdesk
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 视频编解码, 配置, tcp/udp 封装, protobuf, 文件传输相关文件系统操作函数, 以及一些其他实用函数
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 屏幕截取
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 平台相关的鼠标键盘输入
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Windows、Linux、macOS 的文件复制和粘贴实现
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: 过时的 Sciter UI已弃用
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 被控端服务音频、剪切板、输入、视频服务、网络连接的实现
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 控制端
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: 与[rustdesk-server](https://github.com/rustdesk/rustdesk-server)保持UDP通讯, 等待远程连接(通过打洞直连或者中继)
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 平台服务相关代码
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 移动版本的Flutter代码
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 适用于桌面和移动设备的 Flutter 代码
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter Web版本中的Javascript代码
## 截图

View File

@@ -17,7 +17,7 @@
"sources": [
{
"type": "archive",
"url": "https://github.com/linux-pam/linux-pam/releases/download/v1.3.1/Linux-PAM-1.3.1.tar.xz",
"url": "https://github.com/linux-pam/linux-pam/releases/download/v1.3.2/Linux-PAM-1.3.2.tar.xz",
"sha256": "eff47a4ecd833fbf18de9686632a70ee8d0794b79aecb217ebd0ce11db4cd0db"
}
]

View File

@@ -302,6 +302,7 @@ prebuild)
sed \
-i \
-e 's/extended_text: .*/extended_text: 11.1.0/' \
-e 's/uni_links_desktop/#uni_links_desktop/g' \
flutter/pubspec.yaml

View File

@@ -241,14 +241,15 @@ class _AddressBookState extends State<AddressBook> {
bind.setLocalFlutterOption(k: kOptionCurrentAbName, v: value);
}
},
customButton: Obx(()=>Container(
height: stateGlobal.isPortrait.isFalse ? 48 : 40,
child: Row(children: [
Expanded(
child: buildItem(gFFI.abModel.currentName.value, button: true)),
Icon(Icons.arrow_drop_down),
]),
)),
customButton: Obx(() => Container(
height: stateGlobal.isPortrait.isFalse ? 48 : 40,
child: Row(children: [
Expanded(
child:
buildItem(gFFI.abModel.currentName.value, button: true)),
Icon(Icons.arrow_drop_down),
]),
)),
underline: Container(
height: 0.7,
color: Theme.of(context).dividerColor.withOpacity(0.1),
@@ -358,7 +359,6 @@ class _AddressBookState extends State<AddressBook> {
alignment: Alignment.topLeft,
child: AddressBookPeersView(
menuPadding: widget.menuPadding,
getInitPeers: () => gFFI.abModel.currentAbPeers,
)),
);
}
@@ -509,19 +509,19 @@ class _AddressBookState extends State<AddressBook> {
row({required Widget lable, required Widget input}) {
makeChild(bool isPortrait) => Row(
children: [
!isPortrait
? ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: lable.marginOnly(right: 10))
: SizedBox.shrink(),
Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 200),
child: input),
),
],
).marginOnly(bottom: !isPortrait ? 8 : 0);
children: [
!isPortrait
? ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: lable.marginOnly(right: 10))
: SizedBox.shrink(),
Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 200),
child: input),
),
],
).marginOnly(bottom: !isPortrait ? 8 : 0);
return Obx(() => makeChild(stateGlobal.isPortrait.isTrue));
}
@@ -546,23 +546,28 @@ class _AddressBookState extends State<AddressBook> {
],
),
input: Obx(() => TextField(
controller: idController,
inputFormatters: [IDTextInputFormatter()],
decoration: InputDecoration(
labelText: stateGlobal.isPortrait.isFalse ? null : translate('ID'),
errorText: errorMsg,
errorMaxLines: 5),
))),
controller: idController,
inputFormatters: [IDTextInputFormatter()],
decoration: InputDecoration(
labelText: stateGlobal.isPortrait.isFalse
? null
: translate('ID'),
errorText: errorMsg,
errorMaxLines: 5),
))),
row(
lable: Text(
translate('Alias'),
style: style,
),
input: Obx(() => TextField(
controller: aliasController,
decoration: InputDecoration(
labelText: stateGlobal.isPortrait.isFalse ? null : translate('Alias'),
),)),
controller: aliasController,
decoration: InputDecoration(
labelText: stateGlobal.isPortrait.isFalse
? null
: translate('Alias'),
),
)),
),
if (isCurrentAbShared)
row(
@@ -570,25 +575,29 @@ class _AddressBookState extends State<AddressBook> {
translate('Password'),
style: style,
),
input: Obx(() => TextField(
controller: passwordController,
obscureText: !passwordVisible,
decoration: InputDecoration(
labelText: stateGlobal.isPortrait.isFalse ? null : translate('Password'),
suffixIcon: IconButton(
icon: Icon(
passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: MyTheme.lightTheme.primaryColor),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
input: Obx(
() => TextField(
controller: passwordController,
obscureText: !passwordVisible,
decoration: InputDecoration(
labelText: stateGlobal.isPortrait.isFalse
? null
: translate('Password'),
suffixIcon: IconButton(
icon: Icon(
passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: MyTheme.lightTheme.primaryColor),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
),
),)),
)),
if (gFFI.abModel.currentAbTags.isNotEmpty)
Align(
alignment: Alignment.centerLeft,

View File

@@ -83,8 +83,8 @@ class _MyGroupState extends State<MyGroup> {
child: Align(
alignment: Alignment.topLeft,
child: MyGroupPeerView(
menuPadding: widget.menuPadding,
getInitPeers: () => gFFI.groupModel.peers)),
menuPadding: widget.menuPadding,
)),
)
],
);
@@ -115,8 +115,8 @@ class _MyGroupState extends State<MyGroup> {
child: Align(
alignment: Alignment.topLeft,
child: MyGroupPeerView(
menuPadding: widget.menuPadding,
getInitPeers: () => gFFI.groupModel.peers)),
menuPadding: widget.menuPadding,
)),
)
],
);

View File

@@ -595,8 +595,7 @@ class QualityMonitor extends StatelessWidget {
"${qualityMonitorModel.data.targetBitrate ?? '-'}kb"),
_row(
"Codec", qualityMonitorModel.data.codecFormat ?? '-'),
if (!isWeb)
_row("Chroma", qualityMonitorModel.data.chroma ?? '-'),
_row("Chroma", qualityMonitorModel.data.chroma ?? '-'),
],
),
)

View File

@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
@@ -42,6 +43,14 @@ class LoadEvent {
static const String group = 'load_group_peers';
}
class PeersModelName {
static const String recent = 'recent peer';
static const String favorite = 'fav peer';
static const String lan = 'discovered peer';
static const String addressBook = 'address book peer';
static const String group = 'group peer';
}
/// for peer search text, global obs value
final peerSearchText = "".obs;
@@ -128,8 +137,9 @@ class _PeersViewState extends State<_PeersView>
//
// Although `onWindowRestore()` is called after `onWindowBlur()` in my test,
// we need the following comparison to ensure that `_isActive` is true in the end.
if (isWindows && DateTime.now().difference(_lastWindowRestoreTime) <
const Duration(milliseconds: 300)) {
if (isWindows &&
DateTime.now().difference(_lastWindowRestoreTime) <
const Duration(milliseconds: 300)) {
return;
}
_queryCount = _maxQueryCount;
@@ -170,8 +180,8 @@ class _PeersViewState extends State<_PeersView>
// We should avoid too many rebuilds. MacOS(m1, 14.6.1) on Flutter 3.19.6.
// Continious rebuilds of `ChangeNotifierProvider` will cause memory leak.
// Simple demo can reproduce this issue.
return ChangeNotifierProvider<Peers>(
create: (context) => widget.peers,
return ChangeNotifierProvider<Peers>.value(
value: widget.peers,
child: Consumer<Peers>(builder: (context, peers, child) {
if (peers.peers.isEmpty) {
gFFI.peerTabModel.setCurrentTabCachedPeers([]);
@@ -403,28 +413,39 @@ class _PeersViewState extends State<_PeersView>
}
abstract class BasePeersView extends StatelessWidget {
final String name;
final String loadEvent;
final PeerTabIndex peerTabIndex;
final PeerFilter? peerFilter;
final PeerCardBuilder peerCardBuilder;
final GetInitPeers? getInitPeers;
const BasePeersView({
Key? key,
required this.name,
required this.loadEvent,
required this.peerTabIndex,
this.peerFilter,
required this.peerCardBuilder,
required this.getInitPeers,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Peers peers;
switch (peerTabIndex) {
case PeerTabIndex.recent:
peers = gFFI.recentPeersModel;
break;
case PeerTabIndex.fav:
peers = gFFI.favoritePeersModel;
break;
case PeerTabIndex.lan:
peers = gFFI.lanPeersModel;
break;
case PeerTabIndex.ab:
peers = gFFI.abModel.peersModel;
break;
case PeerTabIndex.group:
peers = gFFI.groupModel.peersModel;
break;
}
return _PeersView(
peers:
Peers(name: name, loadEvent: loadEvent, getInitPeers: getInitPeers),
peerFilter: peerFilter,
peerCardBuilder: peerCardBuilder);
peers: peers, peerFilter: peerFilter, peerCardBuilder: peerCardBuilder);
}
}
@@ -433,13 +454,11 @@ class RecentPeersView extends BasePeersView {
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super(
key: key,
name: 'recent peer',
loadEvent: LoadEvent.recent,
peerTabIndex: PeerTabIndex.recent,
peerCardBuilder: (Peer peer) => RecentPeerCard(
peer: peer,
menuPadding: menuPadding,
),
getInitPeers: null,
);
@override
@@ -455,13 +474,11 @@ class FavoritePeersView extends BasePeersView {
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super(
key: key,
name: 'favorite peer',
loadEvent: LoadEvent.favorite,
peerTabIndex: PeerTabIndex.fav,
peerCardBuilder: (Peer peer) => FavoritePeerCard(
peer: peer,
menuPadding: menuPadding,
),
getInitPeers: null,
);
@override
@@ -477,13 +494,11 @@ class DiscoveredPeersView extends BasePeersView {
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super(
key: key,
name: 'discovered peer',
loadEvent: LoadEvent.lan,
peerTabIndex: PeerTabIndex.lan,
peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
peer: peer,
menuPadding: menuPadding,
),
getInitPeers: null,
);
@override
@@ -496,21 +511,16 @@ class DiscoveredPeersView extends BasePeersView {
class AddressBookPeersView extends BasePeersView {
AddressBookPeersView(
{Key? key,
EdgeInsets? menuPadding,
ScrollController? scrollController,
required GetInitPeers getInitPeers})
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super(
key: key,
name: 'address book peer',
loadEvent: LoadEvent.addressBook,
peerTabIndex: PeerTabIndex.ab,
peerFilter: (Peer peer) =>
_hitTag(gFFI.abModel.selectedTags, peer.tags),
peerCardBuilder: (Peer peer) => AddressBookPeerCard(
peer: peer,
menuPadding: menuPadding,
),
getInitPeers: getInitPeers,
);
static bool _hitTag(List<dynamic> selectedTags, List<dynamic> idents) {
@@ -537,20 +547,15 @@ class AddressBookPeersView extends BasePeersView {
class MyGroupPeerView extends BasePeersView {
MyGroupPeerView(
{Key? key,
EdgeInsets? menuPadding,
ScrollController? scrollController,
required GetInitPeers getInitPeers})
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super(
key: key,
name: 'group peer',
loadEvent: LoadEvent.group,
peerTabIndex: PeerTabIndex.group,
peerFilter: filter,
peerCardBuilder: (Peer peer) => MyGroupPeerCard(
peer: peer,
menuPadding: menuPadding,
),
getInitPeers: getInitPeers,
);
static bool filter(Peer peer) {

View File

@@ -570,3 +570,5 @@ enum WindowsTarget {
extension WindowsTargetExt on int {
WindowsTarget get windowsVersion => getWindowsTarget(this);
}
const kCheckSoftwareUpdateFinish = 'check_software_update_finish';

View File

@@ -664,9 +664,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
void initState() {
super.initState();
if (!bind.isCustomClient()) {
platformFFI.registerEventHandler(
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
(Map<String, dynamic> evt) async {
if (evt['url'] is String) {
setState(() {
updateUrl = evt['url'];
});
}
});
Timer(const Duration(seconds: 1), () async {
updateUrl = await bind.mainGetSoftwareUpdateUrl();
if (updateUrl.isNotEmpty) setState(() {});
bind.mainGetSoftwareUpdateUrl();
});
}
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
@@ -824,6 +832,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
_uniLinksSubscription?.cancel();
Get.delete<RxBool>(tag: 'stop-service');
_updateTimer?.cancel();
if (!bind.isCustomClient()) {
platformFFI.unregisterEventHandler(
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish);
}
super.dispose();
}

View File

@@ -70,9 +70,17 @@ class _ConnectionPageState extends State<ConnectionPage> {
}
if (isAndroid) {
if (!bind.isCustomClient()) {
platformFFI.registerEventHandler(
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
(Map<String, dynamic> evt) async {
if (evt['url'] is String) {
setState(() {
_updateUrl = evt['url'];
});
}
});
Timer(const Duration(seconds: 1), () async {
_updateUrl = await bind.mainGetSoftwareUpdateUrl();
if (_updateUrl.isNotEmpty) setState(() {});
bind.mainGetSoftwareUpdateUrl();
});
}
}
@@ -353,6 +361,10 @@ class _ConnectionPageState extends State<ConnectionPage> {
if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>();
}
if (!bind.isCustomClient()) {
platformFFI.unregisterEventHandler(
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish);
}
super.dispose();
}
}

View File

@@ -19,95 +19,48 @@ class ScanPage extends StatefulWidget {
class _ScanPageState extends State<ScanPage> {
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
StreamSubscription? scanSubscription;
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
@override
void reassemble() {
super.reassemble();
if (isAndroid) {
if (isAndroid && controller != null) {
controller!.pauseCamera();
} else if (controller != null) {
controller!.resumeCamera();
}
controller!.resumeCamera();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scan QR'),
actions: [
IconButton(
color: Colors.white,
icon: Icon(Icons.image_search),
iconSize: 32.0,
onPressed: () async {
final ImagePicker picker = ImagePicker();
final XFile? file =
await picker.pickImage(source: ImageSource.gallery);
if (file != null) {
var image = img.decodeNamedImage(
file.path, File(file.path).readAsBytesSync())!;
LuminanceSource source = RGBLuminanceSource(
image.width,
image.height,
image
.getBytes(order: img.ChannelOrder.abgr)
.buffer
.asInt32List());
var bitmap = BinaryBitmap(HybridBinarizer(source));
var reader = QRCodeReader();
try {
var result = reader.decode(bitmap);
if (result.text.startsWith(bind.mainUriPrefixSync())) {
handleUriLink(uriString: result.text);
} else {
showServerSettingFromQr(result.text);
}
} catch (e) {
showToast('No QR code found');
}
}
}),
IconButton(
color: Colors.yellow,
icon: Icon(Icons.flash_on),
iconSize: 32.0,
onPressed: () async {
await controller?.toggleFlash();
}),
IconButton(
color: Colors.white,
icon: Icon(Icons.switch_camera),
iconSize: 32.0,
onPressed: () async {
await controller?.flipCamera();
},
),
],
),
body: _buildQrView(context));
appBar: AppBar(
title: const Text('Scan QR'),
actions: [
_buildImagePickerButton(),
_buildFlashToggleButton(),
_buildCameraSwitchButton(),
],
),
body: _buildQrView(context),
);
}
Widget _buildQrView(BuildContext context) {
// For this example we check how width or tall the device is and change the scanArea and overlay accordingly.
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
var scanArea = MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400
? 150.0
: 300.0;
// To ensure the Scanner view is properly sizes after rotation
// we need to listen for Flutter SizeChanged notification and update controller
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea),
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea,
),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
@@ -116,7 +69,7 @@ class _ScanPageState extends State<ScanPage> {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
scanSubscription = controller.scannedDataStream.listen((scanData) {
if (scanData.code != null) {
showServerSettingFromQr(scanData.code!);
}
@@ -129,8 +82,66 @@ class _ScanPageState extends State<ScanPage> {
}
}
Future<void> _pickImage() async {
final ImagePicker picker = ImagePicker();
final XFile? file = await picker.pickImage(source: ImageSource.gallery);
if (file != null) {
try {
var image = img.decodeImage(await File(file.path).readAsBytes())!;
LuminanceSource source = RGBLuminanceSource(
image.width,
image.height,
image.getBytes(order: img.ChannelOrder.abgr).buffer.asInt32List(),
);
var bitmap = BinaryBitmap(HybridBinarizer(source));
var reader = QRCodeReader();
var result = reader.decode(bitmap);
if (result.text.startsWith(bind.mainUriPrefixSync())) {
handleUriLink(uriString: result.text);
} else {
showServerSettingFromQr(result.text);
}
} catch (e) {
showToast('No QR code found');
}
}
}
Widget _buildImagePickerButton() {
return IconButton(
color: Colors.white,
icon: Icon(Icons.image_search),
iconSize: 32.0,
onPressed: _pickImage,
);
}
Widget _buildFlashToggleButton() {
return IconButton(
color: Colors.yellow,
icon: Icon(Icons.flash_on),
iconSize: 32.0,
onPressed: () async {
await controller?.toggleFlash();
},
);
}
Widget _buildCameraSwitchButton() {
return IconButton(
color: Colors.white,
icon: Icon(Icons.switch_camera),
iconSize: 32.0,
onPressed: () async {
await controller?.flipCamera();
},
);
}
@override
void dispose() {
scanSubscription?.cancel();
controller?.dispose();
super.dispose();
}

View File

@@ -66,10 +66,16 @@ class AbModel {
var listInitialized = false;
var _maxPeerOneAb = 0;
late final Peers peersModel;
WeakReference<FFI> parent;
AbModel(this.parent) {
addressbooks.clear();
peersModel = Peers(
name: PeersModelName.addressBook,
getInitPeers: () => currentAbPeers,
loadEvent: LoadEvent.addressBook);
if (desktopType == DesktopType.main) {
Timer.periodic(Duration(milliseconds: 500), (timer) async {
if (_timerCounter++ % 6 == 0) {

View File

@@ -23,7 +23,14 @@ class GroupModel {
bool get emtpy => users.isEmpty && peers.isEmpty;
GroupModel(this.parent);
late final Peers peersModel;
GroupModel(this.parent) {
peersModel = Peers(
name: PeersModelName.group,
getInitPeers: () => peers,
loadEvent: LoadEvent.group);
}
Future<void> pull({force = true, quiet = false}) async {
if (bind.isDisableGroupPanel()) return;

View File

@@ -469,8 +469,12 @@ class InputModel {
KeyEventResult handleRawKeyEvent(RawKeyEvent e) {
if (isViewOnly) return KeyEventResult.handled;
if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) {
return KeyEventResult.handled;
if (!isInputSourceFlutter) {
if (isDesktop) {
return KeyEventResult.handled;
} else if (isWeb) {
return KeyEventResult.ignored;
}
}
final key = e.logicalKey;
@@ -519,8 +523,12 @@ class InputModel {
KeyEventResult handleKeyEvent(KeyEvent e) {
if (isViewOnly) return KeyEventResult.handled;
if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) {
return KeyEventResult.handled;
if (!isInputSourceFlutter) {
if (isDesktop) {
return KeyEventResult.handled;
} else if (isWeb) {
return KeyEventResult.ignored;
}
}
if (isWindows || isLinux) {
// Ignore meta keys. Because flutter window will loose focus if meta key is pressed.

View File

@@ -7,12 +7,14 @@ import 'dart:ui' as ui;
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/ab_model.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/cm_file_model.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_hbb/models/group_model.dart';
import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart';
@@ -839,7 +841,7 @@ class FfiModel with ChangeNotifier {
for (final mode in [kKeyMapMode, kKeyLegacyMode]) {
if (bind.sessionIsKeyboardModeSupported(
sessionId: sessionId, mode: mode)) {
bind.sessionSetKeyboardMode(sessionId: sessionId, value: mode);
await bind.sessionSetKeyboardMode(sessionId: sessionId, value: mode);
break;
}
}
@@ -2397,6 +2399,9 @@ class FFI {
late final ElevationModel elevationModel; // session
late final CmFileModel cmFileModel; // cm
late final TextureModel textureModel; //session
late final Peers recentPeersModel; // global
late final Peers favoritePeersModel; // global
late final Peers lanPeersModel; // global
FFI(SessionID? sId) {
sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId);
@@ -2417,6 +2422,16 @@ class FFI {
elevationModel = ElevationModel(WeakReference(this));
cmFileModel = CmFileModel(WeakReference(this));
textureModel = TextureModel(WeakReference(this));
recentPeersModel = Peers(
name: PeersModelName.recent,
loadEvent: LoadEvent.recent,
getInitPeers: null);
favoritePeersModel = Peers(
name: PeersModelName.favorite,
loadEvent: LoadEvent.favorite,
getInitPeers: null);
lanPeersModel = Peers(
name: PeersModelName.lan, loadEvent: LoadEvent.lan, getInitPeers: null);
}
/// Mobile reuse FFI

View File

@@ -352,7 +352,11 @@ class RustdeskImpl {
bool sessionIsKeyboardModeSupported(
{required UuidValue sessionId, required String mode, dynamic hint}) {
return [kKeyLegacyMode, kKeyMapMode].contains(mode);
if (mainGetInputSource(hint: hint) == 'Input source 1') {
return [kKeyMapMode, kKeyTranslateMode].contains(mode);
} else {
return [kKeyLegacyMode, kKeyMapMode].contains(mode);
}
}
bool sessionIsMultiUiSession({required UuidValue sessionId, dynamic hint}) {
@@ -429,7 +433,7 @@ class RustdeskImpl {
void sessionEnterOrLeave(
{required UuidValue sessionId, required bool enter, dynamic hint}) {
throw UnimplementedError();
js.context.callMethod('setByName', ['enter_or_leave', enter]);
}
Future<void> sessionInputKey(
@@ -472,7 +476,7 @@ class RustdeskImpl {
required String name,
required String value,
dynamic hint}) {
return Future(() => js.context.callMethod('SetByName', [
return Future(() => js.context.callMethod('setByName', [
'option:session',
jsonEncode({'name': name, 'value': value})
]));
@@ -604,7 +608,7 @@ class RustdeskImpl {
Future<void> sessionElevateDirect(
{required UuidValue sessionId, dynamic hint}) {
throw UnimplementedError();
return Future(() => js.context.callMethod('setByName', ['elevate_direct']));
}
Future<void> sessionElevateWithLogon(
@@ -614,7 +618,7 @@ class RustdeskImpl {
dynamic hint}) {
return Future(() => js.context.callMethod('setByName', [
'elevate_with_logon',
jsonEncode({username, password})
jsonEncode({'username': username, 'password': password})
]));
}
@@ -846,16 +850,21 @@ class RustdeskImpl {
}
String mainGetInputSource({dynamic hint}) {
// // rdev grab mode
// const CONFIG_INPUT_SOURCE_1 = "Input source 1";
final inputSource =
js.context.callMethod('getByName', ['option:local', 'input-source']);
// // js grab mode
// export const CONFIG_INPUT_SOURCE_1 = "Input source 1";
// // flutter grab mode
// const CONFIG_INPUT_SOURCE_2 = "Input source 2";
return 'Input source 2';
// export const CONFIG_INPUT_SOURCE_2 = "Input source 2";
return inputSource != '' ? inputSource : 'Input source 1';
}
Future<void> mainSetInputSource(
{required UuidValue sessionId, required String value, dynamic hint}) {
return Future.value();
return Future(() => js.context.callMethod('setByName', [
'option:local',
jsonEncode({'name': 'input-source', 'value': value})
]));
}
Future<String> mainGetMyId({dynamic hint}) {
@@ -1054,7 +1063,7 @@ class RustdeskImpl {
() => js.context.callMethod('getByName', ['option', 'last_remote_id']));
}
Future<String> mainGetSoftwareUpdateUrl({dynamic hint}) {
Future<void> mainGetSoftwareUpdateUrl({dynamic hint}) {
throw UnimplementedError();
}
@@ -1610,6 +1619,7 @@ class RustdeskImpl {
String mainSupportedInputSource({dynamic hint}) {
return jsonEncode([
['Input source 1', 'input_source_1_tip'],
['Input source 2', 'input_source_2_tip']
]);
}

View File

@@ -1,5 +1,7 @@
import 'dart:js' as js;
import 'dart:html' as html;
// cycle imports, maybe we can improve this
import 'package:flutter_hbb/consts.dart';
final isAndroid_ = false;
final isIOS_ = false;
@@ -13,8 +15,7 @@ final isDesktop_ = false;
String get screenInfo_ => js.context.callMethod('getByName', ['screen_info']);
final _userAgent = html.window.navigator.userAgent.toLowerCase();
final isWebOnWindows_ = _userAgent.contains('win');
final isWebOnLinux_ = _userAgent.contains('linux');
final isWebOnMacOS_ = _userAgent.contains('mac');
final _localOs = js.context.callMethod('getByName', ['local_os', '']);
final isWebOnWindows_ = _localOs == kPeerPlatformWindows;
final isWebOnLinux_ = _localOs == kPeerPlatformLinux;
final isWebOnMacOS_ = _localOs == kPeerPlatformMacOS;

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
version: 1.3.1+47
version: 1.3.2+51
environment:
sdk: '^3.1.0'

View File

@@ -1,6 +1,6 @@
[package]
name = "rustdesk-portable-packer"
version = "1.3.1"
version = "1.3.2"
edition = "2021"
description = "RustDesk Remote Desktop"

View File

@@ -1,5 +1,5 @@
pkgname=rustdesk
pkgver=1.3.1
pkgver=1.3.2
pkgrel=0
epoch=
pkgdesc=""

View File

@@ -1,5 +1,5 @@
Name: rustdesk
Version: 1.3.1
Version: 1.3.2
Release: 0
Summary: RPM package
License: GPL-3.0

View File

@@ -1,5 +1,5 @@
Name: rustdesk
Version: 1.3.1
Version: 1.3.2
Release: 0
Summary: RPM package
License: GPL-3.0

View File

@@ -1,5 +1,5 @@
Name: rustdesk
Version: 1.3.1
Version: 1.3.2
Release: 0
Summary: RPM package
License: GPL-3.0

View File

@@ -828,7 +828,16 @@ async fn check_software_update_() -> hbb_common::ResultType<()> {
let response_url = latest_release_response.url().to_string();
if get_version_number(&latest_release_version) > get_version_number(crate::VERSION) {
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
#[cfg(feature = "flutter")]
{
let mut m = HashMap::new();
m.insert("name", "check_software_update_finish");
m.insert("url", &response_url);
if let Ok(data) = serde_json::to_string(&m) {
let _ = crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, data);
}
}
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
}
Ok(())
}

View File

@@ -61,7 +61,6 @@ fn initialize(app_dir: &str, custom_client_config: &str) {
scrap::mediacodec::check_mediacodec();
crate::common::test_rendezvous_server();
crate::common::test_nat_type();
crate::common::check_software_update();
}
#[cfg(target_os = "ios")]
{
@@ -1376,11 +1375,10 @@ pub fn main_get_last_remote_id() -> String {
LocalConfig::get_remote_id()
}
pub fn main_get_software_update_url() -> String {
pub fn main_get_software_update_url() {
if get_local_option("enable-check-update".to_string()) != "N" {
crate::common::check_software_update();
}
crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone()
}
pub fn main_get_home_dir() -> String {

View File

@@ -644,7 +644,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Parent directory", "父目录"),
("Resume", "继续"),
("Invalid file name", "无效文件名"),
("one-way-file-transfer-tip", "被控端启用了单文件传输"),
("one-way-file-transfer-tip", "被控端启用了单文件传输"),
("Authentication Required", "需要身份验证"),
("Authenticate", "认证"),
].iter().cloned().collect();

View File

@@ -644,8 +644,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Parent directory", "Directorio superior"),
("Resume", "Continuar"),
("Invalid file name", "Nombre de archivo no válido"),
("one-way-file-transfer-tip", ""),
("Authentication Required", ""),
("Authenticate", ""),
("one-way-file-transfer-tip", "La transferencia en un sentido está habilitada en el lado controlado."),
("Authentication Required", "Se requiere autenticación"),
("Authenticate", "Autenticar"),
].iter().cloned().collect();
}

View File

@@ -369,7 +369,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", "Ferma registrazione sessione"),
("Enable recording session", "Abilita registrazione sessione"),
("Enable LAN discovery", "Abilita rilevamento LAN"),
("Deny LAN discovery", "Nega rilevamento LAN"),
("Deny LAN discovery", "Disabilita rilevamento LAN"),
("Write a message", "Scrivi un messaggio"),
("Prompt", "Richiedi"),
("Please wait for confirmation of UAC...", "Attendi la conferma dell'UAC..."),
@@ -532,7 +532,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("HSV Color", "Colore HSV"),
("Installation Successful!", "Installazione completata"),
("Installation failed!", "Installazione fallita"),
("Reverse mouse wheel", "Rotella mouse inversa"),
("Reverse mouse wheel", "Funzione rotellina mouse inversa"),
("{} sessions", "{} sessioni"),
("scam_title", "Potresti essere stato TRUFFATO!"),
("scam_text1", "Se sei al telefono con qualcuno che NON conosci NON DI TUA FIDUCIA che ti ha chiesto di usare RustDesk e di avviare il servizio, non procedere e riattacca subito."),
@@ -632,7 +632,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("About RustDesk", "Info su RustDesk"),
("Send clipboard keystrokes", "Invia sequenze tasti appunti"),
("network_error_tip", "Controlla la connessione di rete, quindi seleziona 'Riprova'."),
("Unlock with PIN", "Sblocca con PIN"),
("Unlock with PIN", "Abilita sblocco con PIN"),
("Requires at least {} characters", "Richiede almeno {} caratteri"),
("Wrong PIN", "PIN errato"),
("Set PIN", "Imposta PIN"),
@@ -644,7 +644,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Parent directory", "Cartella principale"),
("Resume", "Riprendi"),
("Invalid file name", "Nome file non valido"),
("one-way-file-transfer-tip", "Il trasferimento file unidirezionale è abilitato sul lato controllato."),
("one-way-file-transfer-tip", "Sul lato controllato è abilitato il trasferimento file unidirezionale."),
("Authentication Required", "Richiesta autenticazione"),
("Authenticate", "Autentica"),
].iter().cloned().collect();

View File

@@ -644,8 +644,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Parent directory", "Vecākdirektorijs"),
("Resume", "Atsākt"),
("Invalid file name", "Nederīgs faila nosaukums"),
("one-way-file-transfer-tip", ""),
("Authentication Required", ""),
("Authenticate", ""),
("one-way-file-transfer-tip", "Kontrolējamajā pusē ir iespējota vienvirziena failu pārsūtīšana."),
("Authentication Required", "Nepieciešama autentifikācija"),
("Authenticate", "Autentificēt"),
].iter().cloned().collect();
}

View File

@@ -644,8 +644,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Parent directory", "Hoofdmap"),
("Resume", "Hervatten"),
("Invalid file name", "Ongeldige bestandsnaam"),
("one-way-file-transfer-tip", ""),
("Authentication Required", ""),
("Authenticate", ""),
("one-way-file-transfer-tip", "Eenzijdige bestandsoverdracht is ingeschakeld aan de gecontroleerde kant."),
("Authentication Required", "Verificatie vereist"),
("Authenticate", "Verificatie"),
].iter().cloned().collect();
}

View File

@@ -645,7 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resume", "Продолжить"),
("Invalid file name", "Неправильное имя файла"),
("one-way-file-transfer-tip", "На управляемой стороне включена односторонняя передача файлов."),
("Authentication Required", ""),
("Authenticate", ""),
("Authentication Required", "Требуется аутентификация"),
("Authenticate", "Аутентификация"),
].iter().cloned().collect();
}

View File

@@ -644,8 +644,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Parent directory", "父目錄"),
("Resume", "繼續"),
("Invalid file name", "無效文件名"),
("one-way-file-transfer-tip", ""),
("Authentication Required", ""),
("Authenticate", ""),
("one-way-file-transfer-tip", "被控端啟用了單向文件傳輸"),
("Authentication Required", "需要身分驗證"),
("Authenticate", "認證"),
].iter().cloned().collect();
}

View File

@@ -12,8 +12,6 @@ fn main() {
}
common::test_rendezvous_server();
common::test_nat_type();
#[cfg(target_os = "android")]
crate::common::check_software_update();
common::global_clean();
}