From 8745fcbb6adc5729b3571486083c27fdd07e67ab Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 20 Aug 2024 10:53:55 +0800 Subject: [PATCH 001/210] opt desktop file manager status list (#9117) * Show delete file/dir log * Show full path rather than base file name * Show files count * Opt status card layout * Change selected color to accent Signed-off-by: 21pages --- .../lib/desktop/pages/file_manager_page.dart | 93 ++++--- flutter/lib/models/file_model.dart | 252 +++++++++++++++--- flutter/lib/models/model.dart | 9 +- 3 files changed, 272 insertions(+), 82 deletions(-) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index c9e565fd776..682ffa831e8 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -173,10 +173,25 @@ class _FileManagerPageState extends State /// transfer status list /// watch transfer status Widget statusList() { + Widget getIcon(JobProgress job) { + final color = Theme.of(context).tabBarTheme.labelColor; + switch (job.type) { + case JobType.deleteDir: + case JobType.deleteFile: + return Icon(Icons.delete_outline, color: color); + default: + return Transform.rotate( + angle: job.isRemoteToLocal ? pi : 0, + child: Icon(Icons.arrow_forward_ios, color: color), + ); + } + } + statusListView(List jobs) => ListView.builder( controller: ScrollController(), itemBuilder: (BuildContext context, int index) { final item = jobs[index]; + final status = item.getStatus(); return Padding( padding: const EdgeInsets.only(bottom: 5), child: generateCard( @@ -186,15 +201,8 @@ class _FileManagerPageState extends State Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Transform.rotate( - angle: item.isRemoteToLocal ? pi : 0, - child: SvgPicture.asset("assets/arrow.svg", - colorFilter: svgColor( - Theme.of(context).tabBarTheme.labelColor)), - ).paddingOnly(left: 15), - const SizedBox( - width: 16.0, - ), + getIcon(item) + .marginSymmetric(horizontal: 10, vertical: 12), Expanded( child: Column( mainAxisSize: MainAxisSize.min, @@ -204,44 +212,24 @@ class _FileManagerPageState extends State waitDuration: Duration(milliseconds: 500), message: item.jobName, child: Text( - item.fileName, + item.jobName, maxLines: 1, overflow: TextOverflow.ellipsis, - ).paddingSymmetric(vertical: 10), - ), - Text( - '${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}', - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray, - ), - ), - Offstage( - offstage: item.state != JobState.inProgress, - child: Text( - '${translate("Speed")} ${readableFileSize(item.speed)}/s', - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray, - ), ), ), - Offstage( - offstage: item.state == JobState.inProgress, - child: Text( - translate( - item.display(), - ), - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray, - ), - ), + Tooltip( + waitDuration: Duration(milliseconds: 500), + message: status, + child: Text(status, + style: TextStyle( + fontSize: 12, + color: MyTheme.darkGray, + )).marginOnly(top: 6), ), Offstage( - offstage: item.state != JobState.inProgress, + offstage: item.type != JobType.transfer || + item.state != JobState.inProgress, child: LinearPercentIndicator( - padding: EdgeInsets.only(right: 15), animateFromLastPercent: true, center: Text( '${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%', @@ -251,7 +239,7 @@ class _FileManagerPageState extends State progressColor: MyTheme.accent, backgroundColor: Theme.of(context).hoverColor, lineHeight: kDesktopFileTransferRowHeight, - ).paddingSymmetric(vertical: 15), + ).paddingSymmetric(vertical: 8), ), ], ), @@ -276,7 +264,6 @@ class _FileManagerPageState extends State ), MenuButton( tooltip: translate("Delete"), - padding: EdgeInsets.only(right: 15), child: SvgPicture.asset( "assets/close.svg", colorFilter: svgColor(Colors.white), @@ -289,11 +276,11 @@ class _FileManagerPageState extends State hoverColor: MyTheme.accent80, ), ], - ), + ).marginAll(12), ], ), ], - ).paddingSymmetric(vertical: 10), + ), ), ); }, @@ -1007,7 +994,7 @@ class _FileManagerViewState extends State { child: Obx(() => Container( decoration: BoxDecoration( color: selectedItems.items.contains(entry) - ? Theme.of(context).hoverColor + ? MyTheme.button : Theme.of(context).cardColor, borderRadius: BorderRadius.all( Radius.circular(5.0), @@ -1050,6 +1037,11 @@ class _FileManagerViewState extends State { ), Expanded( child: Text(entry.name.nonBreaking, + style: TextStyle( + color: selectedItems.items + .contains(entry) + ? Colors.white + : null), overflow: TextOverflow.ellipsis)) ]), @@ -1111,7 +1103,10 @@ class _FileManagerViewState extends State { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, - color: MyTheme.darkGray, + color: selectedItems.items + .contains(entry) + ? Colors.white70 + : MyTheme.darkGray, ), )), ), @@ -1131,7 +1126,11 @@ class _FileManagerViewState extends State { sizeStr, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 10, color: MyTheme.darkGray), + fontSize: 10, + color: + selectedItems.items.contains(entry) + ? Colors.white70 + : MyTheme.darkGray), ), ), ), diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 0838c8b0673..68af293804e 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -451,7 +451,7 @@ class FileController { final isWindows = otherSideData.options.isWindows; final showHidden = otherSideData.options.showHidden; for (var from in items.items) { - final jobID = jobController.add(from, isRemoteToLocal); + final jobID = jobController.addTransferJob(from, isRemoteToLocal); bind.sessionSendFiles( sessionId: sessionId, actId: jobID, @@ -494,13 +494,21 @@ class FileController { fd.format(isWindows); dialogManager?.dismissAll(); if (fd.entries.isEmpty) { + var deleteJobId = jobController.addDeleteDirJob(item, !isLocal, 0); final confirm = await showRemoveDialog( translate( "Are you sure you want to delete this empty directory?"), item.name, false); if (confirm == true) { - sendRemoveEmptyDir(item.path, 0); + sendRemoveEmptyDir( + item.path, + 0, + deleteJobId, + ); + } else { + jobController.updateJobStatus(deleteJobId, + error: "cancel", state: JobState.done); } return; } @@ -508,6 +516,13 @@ class FileController { } else { entries = []; } + int deleteJobId; + if (item.isDirectory) { + deleteJobId = + jobController.addDeleteDirJob(item, !isLocal, entries.length); + } else { + deleteJobId = jobController.addDeleteFileJob(item, !isLocal); + } for (var i = 0; i < entries.length; i++) { final dirShow = item.isDirectory @@ -522,24 +537,32 @@ class FileController { ); try { if (confirm == true) { - sendRemoveFile(entries[i].path, i); + sendRemoveFile(entries[i].path, i, deleteJobId); final res = await jobController.jobResultListener.start(); // handle remove res; if (item.isDirectory && res['file_num'] == (entries.length - 1).toString()) { - sendRemoveEmptyDir(item.path, i); + sendRemoveEmptyDir(item.path, i, deleteJobId); } + } else { + jobController.updateJobStatus(deleteJobId, + file_num: i, error: "cancel"); } if (_removeCheckboxRemember) { if (confirm == true) { for (var j = i + 1; j < entries.length; j++) { - sendRemoveFile(entries[j].path, j); + sendRemoveFile(entries[j].path, j, deleteJobId); final res = await jobController.jobResultListener.start(); if (item.isDirectory && res['file_num'] == (entries.length - 1).toString()) { - sendRemoveEmptyDir(item.path, i); + sendRemoveEmptyDir(item.path, i, deleteJobId); } } + } else { + jobController.updateJobStatus(deleteJobId, + error: "cancel", + file_num: entries.length, + state: JobState.done); } break; } @@ -618,22 +641,19 @@ class FileController { }, useAnimation: false); } - void sendRemoveFile(String path, int fileNum) { + void sendRemoveFile(String path, int fileNum, int actId) { bind.sessionRemoveFile( sessionId: sessionId, - actId: JobController.jobID.next(), + actId: actId, path: path, isRemote: !isLocal, fileNum: fileNum); } - void sendRemoveEmptyDir(String path, int fileNum) { + void sendRemoveEmptyDir(String path, int fileNum, int actId) { history.removeWhere((element) => element.contains(path)); bind.sessionRemoveAllEmptyDirs( - sessionId: sessionId, - actId: JobController.jobID.next(), - path: path, - isRemote: !isLocal); + sessionId: sessionId, actId: actId, path: path, isRemote: !isLocal); } Future createDir(String path) async { @@ -729,14 +749,11 @@ class JobController { return jobTable.indexWhere((element) => element.id == id); } - // JobProgress? getJob(int id) { - // return jobTable.firstWhere((element) => element.id == id); - // } - // return jobID - int add(Entry from, bool isRemoteToLocal) { + int addTransferJob(Entry from, bool isRemoteToLocal) { final jobID = JobController.jobID.next(); jobTable.add(JobProgress() + ..type = JobType.transfer ..fileName = path.basename(from.path) ..jobName = from.path ..totalSize = from.size @@ -746,6 +763,33 @@ class JobController { return jobID; } + int addDeleteFileJob(Entry file, bool isRemote) { + final jobID = JobController.jobID.next(); + jobTable.add(JobProgress() + ..type = JobType.deleteFile + ..fileName = path.basename(file.path) + ..jobName = file.path + ..totalSize = file.size + ..state = JobState.none + ..id = jobID + ..isRemoteToLocal = isRemote); + return jobID; + } + + int addDeleteDirJob(Entry file, bool isRemote, int fileCount) { + final jobID = JobController.jobID.next(); + jobTable.add(JobProgress() + ..type = JobType.deleteDir + ..fileName = path.basename(file.path) + ..jobName = file.path + ..fileCount = fileCount + ..totalSize = file.size + ..state = JobState.none + ..id = jobID + ..isRemoteToLocal = isRemote); + return jobID; + } + void tryUpdateJobProgress(Map evt) { try { int id = int.parse(evt['id']); @@ -756,6 +800,7 @@ class JobController { job.fileNum = int.parse(evt['file_num']); job.speed = double.parse(evt['speed']); job.finishedSize = int.parse(evt['finished_size']); + job.recvJobRes = true; debugPrint("update job $id with $evt"); jobTable.refresh(); } @@ -764,20 +809,48 @@ class JobController { } } - void jobDone(Map evt) async { + Future jobDone(Map evt) async { if (jobResultListener.isListening) { jobResultListener.complete(evt); - return; + // return; } - - int id = int.parse(evt['id']); + int id = -1; + int? fileNum = 0; + double? speed = 0; + try { + id = int.parse(evt['id']); + } catch (_) {} final jobIndex = getJob(id); - if (jobIndex != -1) { - final job = jobTable[jobIndex]; - job.finishedSize = job.totalSize; + if (jobIndex == -1) return true; + final job = jobTable[jobIndex]; + job.recvJobRes = true; + if (job.type == JobType.deleteFile) { job.state = JobState.done; - job.fileNum = int.parse(evt['file_num']); - jobTable.refresh(); + } else if (job.type == JobType.deleteDir) { + try { + fileNum = int.tryParse(evt['file_num']); + } catch (_) {} + if (fileNum != null) { + if (fileNum < job.fileNum) return true; // file_num can be 0 at last + job.fileNum = fileNum; + if (fileNum >= job.fileCount - 1) { + job.state = JobState.done; + } + } + } else { + try { + fileNum = int.tryParse(evt['file_num']); + speed = double.tryParse(evt['speed']); + } catch (_) {} + if (fileNum != null) job.fileNum = fileNum; + if (speed != null) job.speed = speed; + job.state = JobState.done; + } + jobTable.refresh(); + if (job.type == JobType.deleteDir) { + return job.state == JobState.done; + } else { + return true; } } @@ -788,16 +861,52 @@ class JobController { final job = jobTable[jobIndex]; job.state = JobState.error; job.err = err; - job.fileNum = int.parse(evt['file_num']); - if (err == "skipped") { - job.state = JobState.done; - job.finishedSize = job.totalSize; + job.recvJobRes = true; + if (job.type == JobType.transfer) { + int? fileNum = int.tryParse(evt['file_num']); + if (fileNum != null) job.fileNum = fileNum; + if (err == "skipped") { + job.state = JobState.done; + job.finishedSize = job.totalSize; + } + } else if (job.type == JobType.deleteDir) { + if (jobResultListener.isListening) { + jobResultListener.complete(evt); + } + int? fileNum = int.tryParse(evt['file_num']); + if (fileNum != null) job.fileNum = fileNum; + } else if (job.type == JobType.deleteFile) { + if (jobResultListener.isListening) { + jobResultListener.complete(evt); + } } jobTable.refresh(); } debugPrint("jobError $evt"); } + void updateJobStatus(int id, + {int? file_num, String? error, JobState? state}) { + final jobIndex = getJob(id); + if (jobIndex < 0) return; + final job = jobTable[jobIndex]; + job.recvJobRes = true; + if (file_num != null) { + job.fileNum = file_num; + } + if (error != null) { + job.err = error; + job.state = JobState.error; + } + if (state != null) { + job.state = state; + } + if (job.type == JobType.deleteFile && error == null) { + job.state = JobState.done; + } + jobTable.refresh(); + } + Future cancelJob(int id) async { await bind.sessionCancelJob(sessionId: sessionId, actId: id); } @@ -814,6 +923,7 @@ class JobController { final currJobId = JobController.jobID.next(); String fileName = path.basename(isRemote ? remote : to); var jobProgress = JobProgress() + ..type = JobType.transfer ..fileName = fileName ..jobName = isRemote ? remote : to ..id = currJobId @@ -1088,8 +1198,12 @@ extension JobStateDisplay on JobState { } } +enum JobType { none, transfer, deleteFile, deleteDir } + class JobProgress { + JobType type = JobType.none; JobState state = JobState.none; + var recvJobRes = false; var id = 0; var fileNum = 0; var speed = 0.0; @@ -1109,7 +1223,9 @@ class JobProgress { int lastTransferredSize = 0; clear() { + type = JobType.none; state = JobState.none; + recvJobRes = false; id = 0; fileNum = 0; speed = 0; @@ -1123,11 +1239,81 @@ class JobProgress { } String display() { - if (state == JobState.done && err == "skipped") { - return translate("Skipped"); + if (type == JobType.transfer) { + if (state == JobState.done && err == "skipped") { + return translate("Skipped"); + } + } else if (type == JobType.deleteFile) { + if (err == "cancel") { + return translate("Cancel"); + } } + return state.display(); } + + String getStatus() { + int handledFileCount = recvJobRes ? fileNum + 1 : fileNum; + if (handledFileCount >= fileCount) { + handledFileCount = fileCount; + } + if (state == JobState.done) { + handledFileCount = fileCount; + finishedSize = totalSize; + } + final filesStr = "$handledFileCount/$fileCount files"; + final sizeStr = totalSize > 0 ? readableFileSize(totalSize.toDouble()) : ""; + final sizePercentStr = totalSize > 0 && finishedSize > 0 + ? "${readableFileSize(finishedSize.toDouble())} / ${readableFileSize(totalSize.toDouble())}" + : ""; + if (type == JobType.deleteFile) { + return display(); + } else if (type == JobType.deleteDir) { + var res = ''; + if (state == JobState.done || state == JobState.error) { + res = display(); + } + if (filesStr.isNotEmpty) { + if (res.isNotEmpty) { + res += " "; + } + res += filesStr; + } + + if (sizeStr.isNotEmpty) { + if (res.isNotEmpty) { + res += ", "; + } + res += sizeStr; + } + return res; + } else if (type == JobType.transfer) { + var res = ""; + if (state != JobState.inProgress && state != JobState.none) { + res += display(); + } + if (filesStr.isNotEmpty) { + if (res.isNotEmpty) { + res += ", "; + } + res += filesStr; + } + if (sizeStr.isNotEmpty && state != JobState.inProgress) { + if (res.isNotEmpty) { + res += ", "; + } + res += sizeStr; + } + if (sizePercentStr.isNotEmpty && state == JobState.inProgress) { + if (res.isNotEmpty) { + res += ", "; + } + res += sizePercentStr; + } + return res; + } + return ''; + } } class _PathStat { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 050a92a5f64..d5377ea9a45 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -304,8 +304,13 @@ class FfiModel with ChangeNotifier { } else if (name == 'job_progress') { parent.target?.fileModel.jobController.tryUpdateJobProgress(evt); } else if (name == 'job_done') { - parent.target?.fileModel.jobController.jobDone(evt); - parent.target?.fileModel.refreshAll(); + bool? refresh = + await parent.target?.fileModel.jobController.jobDone(evt); + if (refresh == true) { + // many job done for delete directory + // todo: refresh may not work when confirm delete local directory + parent.target?.fileModel.refreshAll(); + } } else if (name == 'job_error') { parent.target?.fileModel.jobController.jobError(evt); } else if (name == 'override_file_confirm') { From f34b8411a795ef16208ab1e2ace7e53147c41d3f Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 20 Aug 2024 15:34:10 +0800 Subject: [PATCH 002/210] Fix new cm tab not replace the old persisted tab (#9127) * This happens when after changing DesktopTab to StatefulWidget, 1.2.7 and 1.3.0 have this problem. * When `addConnection` in server_model.dart is called, the old closed client is removed, the client parameter of buildConnectionCard is new, but client id inside Consumer is old. * The only state in cm page is timer, its value is kept in test. * There may be a better way to solve the ui update. Signed-off-by: 21pages --- flutter/lib/desktop/widgets/tabbar_widget.dart | 7 +++++++ flutter/lib/models/server_model.dart | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 0b48b3b92fc..75ecacbfe58 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -552,6 +552,13 @@ class _DesktopTabState extends State controller: state.value.pageController, physics: NeverScrollableScrollPhysics(), children: () { + if (DesktopTabType.cm == tabType) { + // Fix when adding a new tab still showing closed tabs with the same peer id, which would happen after the DesktopTab was stateful. + return state.value.tabs.map((tab) { + return tab.page; + }).toList(); + } + /// to-do refactor, separate connection state and UI state for remote session. /// [workaround] PageView children need an immutable list, after it has been passed into PageView final tabLen = state.value.tabs.length; diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 613fee6ad11..1d800ef6967 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -826,7 +826,7 @@ class Client { Map toJson() { final Map data = {}; data['id'] = id; - data['is_start'] = authorized; + data['authorized'] = authorized; data['is_file_transfer'] = isFileTransfer; data['port_forward'] = portForward; data['name'] = name; @@ -840,6 +840,8 @@ class Client { data['block_input'] = blockInput; data['disconnected'] = disconnected; data['from_switch'] = fromSwitch; + data['in_voice_call'] = inVoiceCall; + data['incoming_voice_call'] = incomingVoiceCall; return data; } From e3cce2824d19cd74107f69f6f9e7428b93d189f6 Mon Sep 17 00:00:00 2001 From: Daniel Ehrhardt Date: Wed, 21 Aug 2024 03:28:02 +0200 Subject: [PATCH 003/210] Added Public Server to Readme (#9132) --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e2d9adb5b9..daf4a590743 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

RustDesk - Your remote desktop
- Servers • + ServersBuildDockerStructure • @@ -171,3 +171,13 @@ Please ensure that you are running these commands from the root of the RustDesk ![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) ![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) + +## [Public Servers](#public-servers) + +RustDesk is supported by a free EU server, graciously provided by Codext GmbH + +

+ + Codext GmbH + +

\ No newline at end of file From f300d797e2ddadf728c1cb4bfa0fa72b5c66ec89 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:21:25 +0800 Subject: [PATCH 004/210] Fix/ios query onlines (#9134) * fix: ios query onlines Signed-off-by: fufesou * comments Signed-off-by: fufesou --------- Signed-off-by: fufesou --- src/client.rs | 150 +++++++++++++++++++++++++++++++++++++ src/flutter.rs | 3 +- src/rendezvous_mediator.rs | 143 +---------------------------------- 3 files changed, 152 insertions(+), 144 deletions(-) diff --git a/src/client.rs b/src/client.rs index 665560d6215..b687c8a84c1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3407,3 +3407,153 @@ async fn hc_connection_( } Ok(()) } + +pub mod peer_online { + use hbb_common::{ + anyhow::bail, + config::{Config, CONNECT_TIMEOUT, READ_TIMEOUT}, + log, + rendezvous_proto::*, + sleep, + socket_client::connect_tcp, + tcp::FramedStream, + ResultType, + }; + use std::time::Instant; + + pub async fn query_online_states, Vec)>(ids: Vec, f: F) { + let test = false; + if test { + sleep(1.5).await; + let mut onlines = ids; + let offlines = onlines.drain((onlines.len() / 2)..).collect(); + f(onlines, offlines) + } else { + let query_begin = Instant::now(); + let query_timeout = std::time::Duration::from_millis(3_000); + loop { + match query_online_states_(&ids, query_timeout).await { + Ok((onlines, offlines)) => { + f(onlines, offlines); + break; + } + Err(e) => { + log::debug!("{}", &e); + } + } + + if query_begin.elapsed() > query_timeout { + log::debug!( + "query onlines timeout {:?} ({:?})", + query_begin.elapsed(), + query_timeout + ); + break; + } + + sleep(1.5).await; + } + } + } + + async fn create_online_stream() -> ResultType { + let (rendezvous_server, _servers, _contained) = + crate::get_rendezvous_server(READ_TIMEOUT).await; + let tmp: Vec<&str> = rendezvous_server.split(":").collect(); + if tmp.len() != 2 { + bail!("Invalid server address: {}", rendezvous_server); + } + let port: u16 = tmp[1].parse()?; + if port == 0 { + bail!("Invalid server address: {}", rendezvous_server); + } + let online_server = format!("{}:{}", tmp[0], port - 1); + connect_tcp(online_server, CONNECT_TIMEOUT).await + } + + async fn query_online_states_( + ids: &Vec, + timeout: std::time::Duration, + ) -> ResultType<(Vec, Vec)> { + let query_begin = Instant::now(); + + let mut msg_out = RendezvousMessage::new(); + msg_out.set_online_request(OnlineRequest { + id: Config::get_id(), + peers: ids.clone(), + ..Default::default() + }); + + loop { + let mut socket = match create_online_stream().await { + Ok(s) => s, + Err(e) => { + log::debug!("Failed to create peers online stream, {e}"); + return Ok((vec![], ids.clone())); + } + }; + // TODO: Use long connections to avoid socket creation + // If we use a Arc>> to hold and reuse the previous socket, + // we may face the following error: + // An established connection was aborted by the software in your host machine. (os error 10053) + if let Err(e) = socket.send(&msg_out).await { + log::debug!("Failed to send peers online states query, {e}"); + return Ok((vec![], ids.clone())); + } + if let Some(msg_in) = + crate::common::get_next_nonkeyexchange_msg(&mut socket, None).await + { + match msg_in.union { + Some(rendezvous_message::Union::OnlineResponse(online_response)) => { + let states = online_response.states; + let mut onlines = Vec::new(); + let mut offlines = Vec::new(); + for i in 0..ids.len() { + // bytes index from left to right + let bit_value = 0x01 << (7 - i % 8); + if (states[i / 8] & bit_value) == bit_value { + onlines.push(ids[i].clone()); + } else { + offlines.push(ids[i].clone()); + } + } + return Ok((onlines, offlines)); + } + _ => { + // ignore + } + } + } else { + // TODO: Make sure socket closed? + bail!("Online stream receives None"); + } + + if query_begin.elapsed() > timeout { + bail!("Try query onlines timeout {:?}", &timeout); + } + + sleep(300.0).await; + } + } + + #[cfg(test)] + mod tests { + use hbb_common::tokio; + + #[tokio::test] + async fn test_query_onlines() { + super::query_online_states( + vec![ + "152183996".to_owned(), + "165782066".to_owned(), + "155323351".to_owned(), + "460952777".to_owned(), + ], + |onlines: Vec, offlines: Vec| { + println!("onlines: {:?}, offlines: {:?}", &onlines, &offlines); + }, + ) + .await; + } + } +} diff --git a/src/flutter.rs b/src/flutter.rs index d408202a96d..03b5a575030 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -2093,8 +2093,7 @@ pub(super) mod async_tasks { ids = rx_onlines.recv() => { match ids { Some(_ids) => { - #[cfg(not(any(target_os = "ios")))] - crate::rendezvous_mediator::query_online_states(_ids, handle_query_onlines).await + crate::client::peer_online::query_online_states(_ids, handle_query_onlines).await } None => { break; diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index 4ae222966e9..bd628ec66ef 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -12,10 +12,7 @@ use uuid::Uuid; use hbb_common::{ allow_err, anyhow::{self, bail}, - config::{ - self, keys::*, option2bool, Config, CONNECT_TIMEOUT, READ_TIMEOUT, REG_INTERVAL, - RENDEZVOUS_PORT, - }, + config::{self, keys::*, option2bool, Config, CONNECT_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT}, futures::future::join_all, log, protobuf::Message as _, @@ -703,123 +700,6 @@ async fn direct_server(server: ServerPtr) { } } -pub async fn query_online_states, Vec)>(ids: Vec, f: F) { - let test = false; - if test { - sleep(1.5).await; - let mut onlines = ids; - let offlines = onlines.drain((onlines.len() / 2)..).collect(); - f(onlines, offlines) - } else { - let query_begin = Instant::now(); - let query_timeout = std::time::Duration::from_millis(3_000); - loop { - if SHOULD_EXIT.load(Ordering::SeqCst) { - break; - } - match query_online_states_(&ids, query_timeout).await { - Ok((onlines, offlines)) => { - f(onlines, offlines); - break; - } - Err(e) => { - log::debug!("{}", &e); - } - } - - if query_begin.elapsed() > query_timeout { - log::debug!( - "query onlines timeout {:?} ({:?})", - query_begin.elapsed(), - query_timeout - ); - break; - } - - sleep(1.5).await; - } - } -} - -async fn create_online_stream() -> ResultType { - let (rendezvous_server, _servers, _contained) = - crate::get_rendezvous_server(READ_TIMEOUT).await; - let tmp: Vec<&str> = rendezvous_server.split(":").collect(); - if tmp.len() != 2 { - bail!("Invalid server address: {}", rendezvous_server); - } - let port: u16 = tmp[1].parse()?; - if port == 0 { - bail!("Invalid server address: {}", rendezvous_server); - } - let online_server = format!("{}:{}", tmp[0], port - 1); - connect_tcp(online_server, CONNECT_TIMEOUT).await -} - -async fn query_online_states_( - ids: &Vec, - timeout: std::time::Duration, -) -> ResultType<(Vec, Vec)> { - let query_begin = Instant::now(); - - let mut msg_out = RendezvousMessage::new(); - msg_out.set_online_request(OnlineRequest { - id: Config::get_id(), - peers: ids.clone(), - ..Default::default() - }); - - loop { - if SHOULD_EXIT.load(Ordering::SeqCst) { - // No need to care about onlines - return Ok((Vec::new(), Vec::new())); - } - - let mut socket = match create_online_stream().await { - Ok(s) => s, - Err(e) => { - log::debug!("Failed to create peers online stream, {e}"); - return Ok((vec![], ids.clone())); - } - }; - if let Err(e) = socket.send(&msg_out).await { - log::debug!("Failed to send peers online states query, {e}"); - return Ok((vec![], ids.clone())); - } - if let Some(msg_in) = crate::common::get_next_nonkeyexchange_msg(&mut socket, None).await { - match msg_in.union { - Some(rendezvous_message::Union::OnlineResponse(online_response)) => { - let states = online_response.states; - let mut onlines = Vec::new(); - let mut offlines = Vec::new(); - for i in 0..ids.len() { - // bytes index from left to right - let bit_value = 0x01 << (7 - i % 8); - if (states[i / 8] & bit_value) == bit_value { - onlines.push(ids[i].clone()); - } else { - offlines.push(ids[i].clone()); - } - } - return Ok((onlines, offlines)); - } - _ => { - // ignore - } - } - } else { - // TODO: Make sure socket closed? - bail!("Online stream receives None"); - } - - if query_begin.elapsed() > timeout { - bail!("Try query onlines timeout {:?}", &timeout); - } - - sleep(300.0).await; - } -} - enum Sink<'a> { Framed(&'a mut FramedSocket, &'a TargetAddr<'a>), Stream(&'a mut FramedStream), @@ -833,24 +713,3 @@ impl Sink<'_> { } } } - -#[cfg(test)] -mod tests { - use hbb_common::tokio; - - #[tokio::test] - async fn test_query_onlines() { - super::query_online_states( - vec![ - "152183996".to_owned(), - "165782066".to_owned(), - "155323351".to_owned(), - "460952777".to_owned(), - ], - |onlines: Vec, offlines: Vec| { - println!("onlines: {:?}, offlines: {:?}", &onlines, &offlines); - }, - ) - .await; - } -} From 529e70910dd80ba2688eb6867890f01bfee0b9c0 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 21 Aug 2024 18:29:43 +0800 Subject: [PATCH 005/210] build 47 --- flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 965e7f7bd16..7aadc1c4115 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -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.0+46 +version: 1.3.0+47 environment: sdk: '^3.1.0' From fc607d678997766c64c01a7a9836430011b65843 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:35:50 +0800 Subject: [PATCH 006/210] fix: privacy mode 2, restore (#9141) Signed-off-by: fufesou --- src/privacy_mode/win_virtual_display.rs | 42 +++++++++++++++++++------ src/server/display_service.rs | 36 ++++++++++++++++++--- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/privacy_mode/win_virtual_display.rs b/src/privacy_mode/win_virtual_display.rs index de85e7ba8dc..25997f03671 100644 --- a/src/privacy_mode/win_virtual_display.rs +++ b/src/privacy_mode/win_virtual_display.rs @@ -34,7 +34,7 @@ const CONFIG_KEY_REG_RECOVERY: &str = "reg_recovery"; struct Display { dm: DEVMODEW, name: [WCHAR; 32], - _primary: bool, + primary: bool, } pub struct PrivacyModeImpl { @@ -135,7 +135,7 @@ impl PrivacyModeImpl { let display = Display { dm, name: dd.DeviceName, - _primary: primary, + primary, }; let ds = virtual_display_manager::get_cur_device_string(); @@ -357,6 +357,35 @@ impl PrivacyModeImpl { } Ok(()) } + + fn restore(&mut self) { + Self::restore_displays(&self.displays); + Self::restore_displays(&self.virtual_displays); + allow_err!(Self::commit_change_display(0)); + self.restore_plug_out_monitor(); + self.displays.clear(); + self.virtual_displays.clear(); + } + + fn restore_displays(displays: &[Display]) { + for display in displays { + unsafe { + let mut dm = display.dm.clone(); + let flags = if display.primary { + CDS_NORESET | CDS_UPDATEREGISTRY | CDS_SET_PRIMARY + } else { + CDS_NORESET | CDS_UPDATEREGISTRY + }; + ChangeDisplaySettingsExW( + display.name.as_ptr(), + &mut dm, + std::ptr::null_mut(), + flags, + std::ptr::null_mut(), + ); + } + } + } } impl PrivacyMode for PrivacyModeImpl { @@ -431,14 +460,9 @@ impl PrivacyMode for PrivacyModeImpl { ) -> ResultType<()> { self.check_off_conn_id(conn_id)?; super::win_input::unhook()?; - let virtual_display_added = self.virtual_displays_added.len() > 0; - if virtual_display_added { - self.restore_plug_out_monitor(); - } + let _tmp_ignore_changed_holder = crate::display_service::temp_ignore_displays_changed(); + self.restore(); restore_reg_connectivity(false); - if !virtual_display_added { - Self::commit_change_display(CDS_RESET)?; - } if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { if let Some(state) = state { diff --git a/src/server/display_service.rs b/src/server/display_service.rs index 7260b9c7a23..e099e25a096 100644 --- a/src/server/display_service.rs +++ b/src/server/display_service.rs @@ -1,4 +1,5 @@ use super::*; +use crate::common::SimpleCallOnReturn; #[cfg(target_os = "linux")] use crate::platform::linux::is_x11; #[cfg(windows)] @@ -7,6 +8,7 @@ use crate::virtual_display_manager; use hbb_common::get_version_number; use hbb_common::protobuf::MessageField; use scrap::Display; +use std::sync::atomic::{AtomicBool, Ordering}; // https://github.com/rustdesk/rustdesk/discussions/6042, avoiding dbus call @@ -29,6 +31,9 @@ lazy_static::lazy_static! { static ref SYNC_DISPLAYS: Arc> = Default::default(); } +// https://github.com/rustdesk/rustdesk/pull/8537 +static TEMP_IGNORE_DISPLAYS_CHANGED: AtomicBool = AtomicBool::new(false); + #[derive(Default)] struct SyncDisplaysInfo { displays: Vec, @@ -39,13 +44,17 @@ impl SyncDisplaysInfo { fn check_changed(&mut self, displays: Vec) { if self.displays.len() != displays.len() { self.displays = displays; - self.is_synced = false; + if !TEMP_IGNORE_DISPLAYS_CHANGED.load(Ordering::Relaxed) { + self.is_synced = false; + } return; } for (i, d) in displays.iter().enumerate() { if d != &self.displays[i] { self.displays = displays; - self.is_synced = false; + if !TEMP_IGNORE_DISPLAYS_CHANGED.load(Ordering::Relaxed) { + self.is_synced = false; + } return; } } @@ -60,6 +69,21 @@ impl SyncDisplaysInfo { } } +pub fn temp_ignore_displays_changed() -> SimpleCallOnReturn { + TEMP_IGNORE_DISPLAYS_CHANGED.store(true, std::sync::atomic::Ordering::Relaxed); + SimpleCallOnReturn { + b: true, + f: Box::new(move || { + // Wait for a while to make sure check_display_changed() is called + // after video service has sending its `SwitchDisplay` message(`try_broadcast_display_changed()`). + std::thread::sleep(Duration::from_millis(1000)); + TEMP_IGNORE_DISPLAYS_CHANGED.store(false, Ordering::Relaxed); + // Trigger the display changed message. + SYNC_DISPLAYS.lock().unwrap().is_synced = false; + }), + } +} + // This function is really useful, though a duplicate check if display changed. // The video server will then send the following messages to the client: // 1. the supported resolutions of the {idx} display @@ -204,9 +228,11 @@ fn get_displays_msg() -> Option { fn run(sp: EmptyExtraFieldService) -> ResultType<()> { while sp.ok() { sp.snapshot(|sps| { - if sps.has_subscribes() { - SYNC_DISPLAYS.lock().unwrap().is_synced = false; - bail!("new subscriber"); + if !TEMP_IGNORE_DISPLAYS_CHANGED.load(Ordering::Relaxed) { + if sps.has_subscribes() { + SYNC_DISPLAYS.lock().unwrap().is_synced = false; + bail!("new subscriber"); + } } Ok(()) })?; From 5931af460e3efa01a4d42955837535a3b55ad325 Mon Sep 17 00:00:00 2001 From: jxdv Date: Thu, 22 Aug 2024 01:36:03 +0000 Subject: [PATCH 007/210] Update trs (#9144) * update sk tr * update cz tr --- src/lang/cs.rs | 6 +++--- src/lang/sk.rs | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 41d94d97846..8ff3d806918 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -641,8 +641,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Platform", "Platforma"), ("Days remaining", "Zbývajících dnů"), ("enable-trusted-devices-tip", "Přeskočte 2FA ověření na důvěryhodných zařízeních"), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("Parent directory", "Rodičovský adresář"), + ("Resume", "Pokračovat"), + ("Invalid file name", "Nesprávný název souboru"), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index d1f5467afaa..51d09032834 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -632,17 +632,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("About RustDesk", "O RustDesk"), ("Send clipboard keystrokes", "Odoslať stlačenia klávesov zo schránky"), ("network_error_tip", "Skontrolujte svoje sieťové pripojenie a potom kliknite na tlačidlo Opakovať."), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("Unlock with PIN", "Odomknutie pomocou PIN kódu"), + ("Requires at least {} characters", "Vyžaduje aspoň {} znakov"), + ("Wrong PIN", "Nesprávny PIN kód"), + ("Set PIN", "Nastavenie PIN kódu"), + ("Enable trusted devices", "Povolenie dôveryhodných zariadení"), + ("Manage trusted devices", "Správa dôveryhodných zariadení"), + ("Platform", "Platforma"), + ("Days remaining", "Zostávajúce dni"), + ("enable-trusted-devices-tip", "Vynechanie overovania 2FA na dôveryhodných zariadeniach"), + ("Parent directory", "Rodičovský adresár"), + ("Resume", "Obnoviť"), + ("Invalid file name", "Nesprávny názov súboru"), ].iter().cloned().collect(); } From 50aa8e12ad8c84d66139d46e3afe9cb3a2ad917a Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 23 Aug 2024 10:00:36 +0800 Subject: [PATCH 008/210] desktop file transfer, all columns respond to tap, add right click item border (#9153) When right click selected item, the border is not obvious but can feel some change. Signed-off-by: 21pages --- .../lib/desktop/pages/file_manager_page.dart | 103 +++++++++++------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 682ffa831e8..05af83cd38d 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -930,6 +930,7 @@ class _FileManagerViewState extends State { BuildContext context, ScrollController scrollController) { final fd = controller.directory.value; final entries = fd.entries; + Rx rightClickEntry = Rx(null); return ListSearchActionListener( node: _keyboardNode, @@ -989,6 +990,53 @@ class _FileManagerViewState extends State { ? " " : "${entry.lastModified().toString().replaceAll(".000", "")} "; var secondaryPosition = RelativeRect.fromLTRB(0, 0, 0, 0); + onTap() { + final items = selectedItems; + // handle double click + if (_checkDoubleClick(entry)) { + controller.openDirectory(entry.path); + items.clear(); + return; + } + _onSelectedChanged(items, filteredEntries, entry, isLocal); + } + + onSecondaryTap() { + final items = [ + if (!entry.isDrive && + versionCmp(_ffi.ffiModel.pi.version, "1.3.0") >= 0) + mod_menu.PopupMenuItem( + child: Text("Rename"), + height: CustomPopupMenuTheme.height, + onTap: () { + controller.renameAction(entry, isLocal); + }, + ) + ]; + if (items.isNotEmpty) { + rightClickEntry.value = entry; + final future = mod_menu.showMenu( + context: context, + position: secondaryPosition, + items: items, + ); + future.then((value) { + rightClickEntry.value = null; + }); + future.onError((error, stackTrace) { + rightClickEntry.value = null; + }); + } + } + + onSecondaryTapDown(details) { + secondaryPosition = RelativeRect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx, + details.globalPosition.dy); + } + return Padding( padding: EdgeInsets.symmetric(vertical: 1), child: Obx(() => Container( @@ -999,6 +1047,12 @@ class _FileManagerViewState extends State { borderRadius: BorderRadius.all( Radius.circular(5.0), ), + border: rightClickEntry.value == entry + ? Border.all( + color: MyTheme.button, + width: 1.0, + ) + : null, ), key: ValueKey(entry.name), height: kDesktopFileTransferRowHeight, @@ -1047,46 +1101,9 @@ class _FileManagerViewState extends State { ]), )), ), - onTap: () { - final items = selectedItems; - // handle double click - if (_checkDoubleClick(entry)) { - controller.openDirectory(entry.path); - items.clear(); - return; - } - _onSelectedChanged( - items, filteredEntries, entry, isLocal); - }, - onSecondaryTap: () { - final items = [ - if (!entry.isDrive && - versionCmp(_ffi.ffiModel.pi.version, - "1.3.0") >= - 0) - mod_menu.PopupMenuItem( - child: Text("Rename"), - height: CustomPopupMenuTheme.height, - onTap: () { - controller.renameAction(entry, isLocal); - }, - ) - ]; - if (items.isNotEmpty) { - mod_menu.showMenu( - context: context, - position: secondaryPosition, - items: items, - ); - } - }, - onSecondaryTapDown: (details) { - secondaryPosition = RelativeRect.fromLTRB( - details.globalPosition.dx, - details.globalPosition.dy, - details.globalPosition.dx, - details.globalPosition.dy); - }, + onTap: onTap, + onSecondaryTap: onSecondaryTap, + onSecondaryTapDown: onSecondaryTapDown, ), SizedBox( width: 2.0, @@ -1111,6 +1128,9 @@ class _FileManagerViewState extends State { )), ), ), + onTap: onTap, + onSecondaryTap: onSecondaryTap, + onSecondaryTapDown: onSecondaryTapDown, ), // Divider from header. SizedBox( @@ -1133,6 +1153,9 @@ class _FileManagerViewState extends State { : MyTheme.darkGray), ), ), + onTap: onTap, + onSecondaryTap: onSecondaryTap, + onSecondaryTapDown: onSecondaryTapDown, ), ), ], From 9d9741f18efd3ef1361c5b0e0d8e562c5f93acdd Mon Sep 17 00:00:00 2001 From: Kleofass <4000163+Kleofass@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:27:52 +0300 Subject: [PATCH 009/210] Update lv.rs (#9155) --- src/lang/lv.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lang/lv.rs b/src/lang/lv.rs index df2324def57..bb1090dca9c 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -629,20 +629,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enable-bot-desc", "1. Atveriet tērzēšanu ar @BotFather.\n2. Nosūtiet komandu \"/newbot\". Pēc šīs darbības pabeigšanas jūs saņemsit pilnvaru.\n3. Sāciet tērzēšanu ar jaunizveidoto robotprogrammatūru. Lai to aktivizētu, nosūtiet ziņojumu, kas sākas ar slīpsvītru (\"/\"), piemēram, \"/hello\".\n"), ("cancel-2fa-confirm-tip", "Vai tiešām vēlaties atcelt 2FA?"), ("cancel-bot-confirm-tip", "Vai tiešām vēlaties atcelt Telegram robotu?"), - ("About RustDesk", ""), - ("Send clipboard keystrokes", ""), - ("network_error_tip", ""), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("About RustDesk", "Par RustDesk"), + ("Send clipboard keystrokes", "Nosūtīt starpliktuves taustiņsitienus"), + ("network_error_tip", "Lūdzu, pārbaudiet tīkla savienojumu un pēc tam noklikšķiniet uz Mēģināt vēlreiz."), + ("Unlock with PIN", "Atbloķēt ar PIN"), + ("Requires at least {} characters", "Nepieciešamas vismaz {} rakstzīmes"), + ("Wrong PIN", "Nepareizs PIN"), + ("Set PIN", "Iestatīt PIN"), + ("Enable trusted devices", "Iespējot uzticamas ierīces"), + ("Manage trusted devices", "Pārvaldīt uzticamas ierīces"), + ("Platform", "Platforma"), + ("Days remaining", "Atlikušas dienas"), + ("enable-trusted-devices-tip", "Izlaist 2FA verifikāciju uzticamās ierīcēs"), + ("Parent directory", "Vecākdirektorijs"), + ("Resume", "Atsākt"), + ("Invalid file name", "Nederīgs faila nosaukums"), ].iter().cloned().collect(); } From 1d416f6626ad02a47b245697b32fb2c374e928f4 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sat, 24 Aug 2024 12:10:36 +0800 Subject: [PATCH 010/210] refact: flutter keyboard, map mode (#9160) Signed-off-by: fufesou --- Cargo.lock | 2 +- flutter/lib/common/widgets/remote_input.dart | 3 +- .../lib/models/desktop_render_texture.dart | 1 + flutter/lib/models/input_model.dart | 292 ++++++++---------- flutter/lib/web/bridge.dart | 71 ++++- flutter/lib/web/texture_rgba_renderer.dart | 2 +- src/flutter_ffi.rs | 10 +- src/ui_session_interface.rs | 60 ++-- 8 files changed, 231 insertions(+), 210 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef2cab92c80..1b1e66826c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5187,7 +5187,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/rustdesk-org/rdev#b3434caee84c92412b45a2f655a15ac5dad33488" +source = "git+https://github.com/rustdesk-org/rdev#d4c1759926d693ba269e2cb8cf9f87b13e424e4e" dependencies = [ "cocoa 0.24.1", "core-foundation 0.9.4", diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart index 61bd4dd31bc..73e5d8f39a8 100644 --- a/flutter/lib/common/widgets/remote_input.dart +++ b/flutter/lib/common/widgets/remote_input.dart @@ -34,8 +34,7 @@ class RawKeyFocusScope extends StatelessWidget { canRequestFocus: true, focusNode: focusNode, onFocusChange: onFocusChange, - onKey: (FocusNode data, RawKeyEvent e) => - inputModel.handleRawKeyEvent(e), + onKeyEvent: (node, event) => inputModel.handleKeyEvent(event), child: child)); } } diff --git a/flutter/lib/models/desktop_render_texture.dart b/flutter/lib/models/desktop_render_texture.dart index ab8df3c4539..c6cf55256de 100644 --- a/flutter/lib/models/desktop_render_texture.dart +++ b/flutter/lib/models/desktop_render_texture.dart @@ -181,6 +181,7 @@ class TextureModel { } updateCurrentDisplay(int curDisplay) { + if (isWeb) return; final ffi = parent.target; if (ffi == null) return; tryCreateTexture(int idx) { diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index dde815789ae..35f00c3e1c3 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -178,15 +178,15 @@ class PointerEventToRust { } class ToReleaseKeys { - RawKeyEvent? lastLShiftKeyEvent; - RawKeyEvent? lastRShiftKeyEvent; - RawKeyEvent? lastLCtrlKeyEvent; - RawKeyEvent? lastRCtrlKeyEvent; - RawKeyEvent? lastLAltKeyEvent; - RawKeyEvent? lastRAltKeyEvent; - RawKeyEvent? lastLCommandKeyEvent; - RawKeyEvent? lastRCommandKeyEvent; - RawKeyEvent? lastSuperKeyEvent; + KeyEvent? lastLShiftKeyEvent; + KeyEvent? lastRShiftKeyEvent; + KeyEvent? lastLCtrlKeyEvent; + KeyEvent? lastRCtrlKeyEvent; + KeyEvent? lastLAltKeyEvent; + KeyEvent? lastRAltKeyEvent; + KeyEvent? lastLCommandKeyEvent; + KeyEvent? lastRCommandKeyEvent; + KeyEvent? lastSuperKeyEvent; reset() { lastLShiftKeyEvent = null; @@ -200,67 +200,7 @@ class ToReleaseKeys { lastSuperKeyEvent = null; } - updateKeyDown(LogicalKeyboardKey logicKey, RawKeyDownEvent e) { - if (e.isAltPressed) { - if (logicKey == LogicalKeyboardKey.altLeft) { - lastLAltKeyEvent = e; - } else if (logicKey == LogicalKeyboardKey.altRight) { - lastRAltKeyEvent = e; - } - } else if (e.isControlPressed) { - if (logicKey == LogicalKeyboardKey.controlLeft) { - lastLCtrlKeyEvent = e; - } else if (logicKey == LogicalKeyboardKey.controlRight) { - lastRCtrlKeyEvent = e; - } - } else if (e.isShiftPressed) { - if (logicKey == LogicalKeyboardKey.shiftLeft) { - lastLShiftKeyEvent = e; - } else if (logicKey == LogicalKeyboardKey.shiftRight) { - lastRShiftKeyEvent = e; - } - } else if (e.isMetaPressed) { - if (logicKey == LogicalKeyboardKey.metaLeft) { - lastLCommandKeyEvent = e; - } else if (logicKey == LogicalKeyboardKey.metaRight) { - lastRCommandKeyEvent = e; - } else if (logicKey == LogicalKeyboardKey.superKey) { - lastSuperKeyEvent = e; - } - } - } - - updateKeyUp(LogicalKeyboardKey logicKey, RawKeyUpEvent e) { - if (e.isAltPressed) { - if (logicKey == LogicalKeyboardKey.altLeft) { - lastLAltKeyEvent = null; - } else if (logicKey == LogicalKeyboardKey.altRight) { - lastRAltKeyEvent = null; - } - } else if (e.isControlPressed) { - if (logicKey == LogicalKeyboardKey.controlLeft) { - lastLCtrlKeyEvent = null; - } else if (logicKey == LogicalKeyboardKey.controlRight) { - lastRCtrlKeyEvent = null; - } - } else if (e.isShiftPressed) { - if (logicKey == LogicalKeyboardKey.shiftLeft) { - lastLShiftKeyEvent = null; - } else if (logicKey == LogicalKeyboardKey.shiftRight) { - lastRShiftKeyEvent = null; - } - } else if (e.isMetaPressed) { - if (logicKey == LogicalKeyboardKey.metaLeft) { - lastLCommandKeyEvent = null; - } else if (logicKey == LogicalKeyboardKey.metaRight) { - lastRCommandKeyEvent = null; - } else if (logicKey == LogicalKeyboardKey.superKey) { - lastSuperKeyEvent = null; - } - } - } - - release(KeyEventResult Function(RawKeyEvent e) handleRawKeyEvent) { + release(KeyEventResult Function(KeyEvent e) handleKeyEvent) { for (final key in [ lastLShiftKeyEvent, lastRShiftKeyEvent, @@ -273,10 +213,7 @@ class ToReleaseKeys { lastSuperKeyEvent, ]) { if (key != null) { - handleRawKeyEvent(RawKeyUpEvent( - data: key.data, - character: key.character, - )); + handleKeyEvent(key); } } } @@ -339,49 +276,116 @@ class InputModel { } } - KeyEventResult handleRawKeyEvent(RawKeyEvent e) { + void handleKeyDownEventModifiers(KeyEvent e) { + KeyUpEvent upEvent(e) => KeyUpEvent( + physicalKey: e.physicalKey, + logicalKey: e.logicalKey, + timeStamp: e.timeStamp, + ); + if (e.logicalKey == LogicalKeyboardKey.altLeft) { + if (!alt) { + alt = true; + } + toReleaseKeys.lastLAltKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.altRight) { + if (!alt) { + alt = true; + } + toReleaseKeys.lastLAltKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.controlLeft) { + if (!ctrl) { + ctrl = true; + } + toReleaseKeys.lastLCtrlKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.controlRight) { + if (!ctrl) { + ctrl = true; + } + toReleaseKeys.lastRCtrlKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.shiftLeft) { + if (!shift) { + shift = true; + } + toReleaseKeys.lastLShiftKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.shiftRight) { + if (!shift) { + shift = true; + } + toReleaseKeys.lastRShiftKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.metaLeft) { + if (!command) { + command = true; + } + toReleaseKeys.lastLCommandKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.metaRight) { + if (!command) { + command = true; + } + toReleaseKeys.lastRCommandKeyEvent = upEvent(e); + } else if (e.logicalKey == LogicalKeyboardKey.superKey) { + if (!command) { + command = true; + } + toReleaseKeys.lastSuperKeyEvent = upEvent(e); + } + } + + void handleKeyUpEventModifiers(KeyEvent e) { + if (e.logicalKey == LogicalKeyboardKey.altLeft) { + alt = false; + toReleaseKeys.lastLAltKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.altRight) { + alt = false; + toReleaseKeys.lastRAltKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.controlLeft) { + ctrl = false; + toReleaseKeys.lastLCtrlKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.controlRight) { + ctrl = false; + toReleaseKeys.lastRCtrlKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.shiftLeft) { + shift = false; + toReleaseKeys.lastLShiftKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.shiftRight) { + shift = false; + toReleaseKeys.lastRShiftKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.metaLeft) { + command = false; + toReleaseKeys.lastLCommandKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.metaRight) { + command = false; + toReleaseKeys.lastRCommandKeyEvent = null; + } else if (e.logicalKey == LogicalKeyboardKey.superKey) { + command = false; + toReleaseKeys.lastSuperKeyEvent = null; + } + } + + KeyEventResult handleKeyEvent(KeyEvent e) { if (isViewOnly) return KeyEventResult.handled; if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) { return KeyEventResult.handled; } - - final key = e.logicalKey; - if (e is RawKeyDownEvent) { - if (!e.repeat) { - if (e.isAltPressed && !alt) { - alt = true; - } else if (e.isControlPressed && !ctrl) { - ctrl = true; - } else if (e.isShiftPressed && !shift) { - shift = true; - } else if (e.isMetaPressed && !command) { - command = true; - } + if (isWindows || isLinux) { + // Ignore meta keys. Because flutter window will loose focus if meta key is pressed. + if (e.physicalKey == PhysicalKeyboardKey.metaLeft || + e.physicalKey == PhysicalKeyboardKey.metaRight) { + return KeyEventResult.handled; } - toReleaseKeys.updateKeyDown(key, e); } - if (e is RawKeyUpEvent) { - if (key == LogicalKeyboardKey.altLeft || - key == LogicalKeyboardKey.altRight) { - alt = false; - } else if (key == LogicalKeyboardKey.controlLeft || - key == LogicalKeyboardKey.controlRight) { - ctrl = false; - } else if (key == LogicalKeyboardKey.shiftRight || - key == LogicalKeyboardKey.shiftLeft) { - shift = false; - } else if (key == LogicalKeyboardKey.metaLeft || - key == LogicalKeyboardKey.metaRight || - key == LogicalKeyboardKey.superKey) { - command = false; - } - toReleaseKeys.updateKeyUp(key, e); + if (e is KeyUpEvent) { + handleKeyUpEventModifiers(e); + } else if (e is KeyDownEvent) { + handleKeyDownEventModifiers(e); } // * Currently mobile does not enable map mode - if ((isDesktop || isWebDesktop) && keyboardMode == 'map') { - mapKeyboardMode(e); + if ((isDesktop || isWebDesktop)) { + // FIXME: e.character is wrong for dead keys, eg: ^ in de + newKeyboardMode(e.character ?? '', e.physicalKey.usbHidUsage & 0xFFFF, + // Show repeat event be converted to "release+press" events? + e is KeyDownEvent || e is KeyRepeatEvent); } else { legacyKeyboardMode(e); } @@ -389,42 +393,8 @@ class InputModel { return KeyEventResult.handled; } - void mapKeyboardMode(RawKeyEvent e) { - int positionCode = -1; - int platformCode = -1; - bool down; - - if (e.data is RawKeyEventDataMacOs) { - RawKeyEventDataMacOs newData = e.data as RawKeyEventDataMacOs; - positionCode = newData.keyCode; - platformCode = newData.keyCode; - } else if (e.data is RawKeyEventDataWindows) { - RawKeyEventDataWindows newData = e.data as RawKeyEventDataWindows; - positionCode = newData.scanCode; - platformCode = newData.keyCode; - } else if (e.data is RawKeyEventDataLinux) { - RawKeyEventDataLinux newData = e.data as RawKeyEventDataLinux; - // scanCode and keyCode of RawKeyEventDataLinux are incorrect. - // 1. scanCode means keycode - // 2. keyCode means keysym - positionCode = newData.scanCode; - platformCode = newData.keyCode; - } else if (e.data is RawKeyEventDataAndroid) { - RawKeyEventDataAndroid newData = e.data as RawKeyEventDataAndroid; - positionCode = newData.scanCode + 8; - platformCode = newData.keyCode; - } else {} - - if (e is RawKeyDownEvent) { - down = true; - } else { - down = false; - } - inputRawKey(e.character ?? '', platformCode, positionCode, down); - } - - /// Send raw Key Event - void inputRawKey(String name, int platformCode, int positionCode, bool down) { + /// Send Key Event + void newKeyboardMode(String character, int usbHid, bool down) { const capslock = 1; const numlock = 2; const scrolllock = 3; @@ -443,27 +413,23 @@ class InputModel { } bind.sessionHandleFlutterKeyEvent( sessionId: sessionId, - name: name, - platformCode: platformCode, - positionCode: positionCode, + character: character, + usbHid: usbHid, lockModes: lockModes, downOrUp: down); } - void legacyKeyboardMode(RawKeyEvent e) { - if (e is RawKeyDownEvent) { - if (e.repeat) { - sendRawKey(e, press: true); - } else { - sendRawKey(e, down: true); - } - } - if (e is RawKeyUpEvent) { - sendRawKey(e); + void legacyKeyboardMode(KeyEvent e) { + if (e is KeyDownEvent) { + sendKey(e, down: true); + } else if (e is KeyRepeatEvent) { + sendKey(e, press: true); + } else if (e is KeyUpEvent) { + sendKey(e); } } - void sendRawKey(RawKeyEvent e, {bool? down, bool? press}) { + void sendKey(KeyEvent e, {bool? down, bool? press}) { // for maximum compatibility final label = physicalKeyMap[e.physicalKey.usbHidUsage] ?? logicalKeyMap[e.logicalKey.keyId] ?? @@ -566,7 +532,7 @@ class InputModel { } void enterOrLeave(bool enter) { - toReleaseKeys.release(handleRawKeyEvent); + toReleaseKeys.release(handleKeyEvent); _pointerMovedAfterEnter = false; // Fix status @@ -1164,15 +1130,15 @@ class InputModel { // Simulate a key press event. // `usbHidUsage` is the USB HID usage code of the key. Future tapHidKey(int usbHidUsage) async { - inputRawKey(kKeyFlutterKey, usbHidUsage, 0, true); + newKeyboardMode(kKeyFlutterKey, usbHidUsage, true); await Future.delayed(Duration(milliseconds: 100)); - inputRawKey(kKeyFlutterKey, usbHidUsage, 0, false); + newKeyboardMode(kKeyFlutterKey, usbHidUsage, false); } Future onMobileVolumeUp() async => - await tapHidKey(PhysicalKeyboardKey.audioVolumeUp.usbHidUsage); + await tapHidKey(PhysicalKeyboardKey.audioVolumeUp.usbHidUsage & 0xFFFF); Future onMobileVolumeDown() async => - await tapHidKey(PhysicalKeyboardKey.audioVolumeDown.usbHidUsage); + await tapHidKey(PhysicalKeyboardKey.audioVolumeDown.usbHidUsage & 0xFFFF); Future onMobilePower() async => - await tapHidKey(PhysicalKeyboardKey.power.usbHidUsage); + await tapHidKey(PhysicalKeyboardKey.power.usbHidUsage & 0xFFFF); } diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 457911458ce..5f6f5adef87 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -23,6 +23,7 @@ sealed class EventToUI { ) = EventToUI_Rgba; const factory EventToUI.texture( int field0, + bool field1, ) = EventToUI_Texture; } @@ -33,15 +34,19 @@ class EventToUI_Event implements EventToUI { } class EventToUI_Rgba implements EventToUI { - const EventToUI_Rgba(final int field0) : this.field = field0; + const EventToUI_Rgba(final int field0) : field = field0; final int field; int get field0 => field; } class EventToUI_Texture implements EventToUI { - const EventToUI_Texture(final int field0) : this.field = field0; - final int field; - int get field0 => field; + const EventToUI_Texture(final int field0, final bool field1) + : f0 = field0, + f1 = field1; + final int f0; + final bool f1; + int get field0 => f0; + bool get field1 => f1; } class RustdeskImpl { @@ -394,14 +399,20 @@ class RustdeskImpl { Future sessionHandleFlutterKeyEvent( {required UuidValue sessionId, - required String name, - required int platformCode, - required int positionCode, + required String character, + required int usbHid, required int lockModes, required bool downOrUp, dynamic hint}) { - // TODO: map mode - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'flutter_key_event', + jsonEncode({ + 'name': character, + 'usb_hid': usbHid, + 'lock_modes': lockModes, + if (downOrUp) 'down': 'true', + }) + ])); } void sessionEnterOrLeave( @@ -702,11 +713,11 @@ class RustdeskImpl { } Future mainGetAppName({dynamic hint}) { - throw UnimplementedError(); + return Future.value(mainGetAppNameSync(hint: hint)); } String mainGetAppNameSync({dynamic hint}) { - throw UnimplementedError(); + return 'RustDesk'; } String mainUriPrefixSync({dynamic hint}) { @@ -758,8 +769,9 @@ class RustdeskImpl { } Future mainIsUsingPublicServer({dynamic hint}) { - return Future( - () => js.context.callMethod('setByName', ["is_using_public_server"])); + return Future(() => + js.context.callMethod('getByName', ["is_using_public_server"]) == + 'true'); } Future mainDiscover({dynamic hint}) { @@ -1610,7 +1622,7 @@ class RustdeskImpl { } bool mainIsOptionFixed({required String key, dynamic hint}) { - throw UnimplementedError(); + return false; } bool mainGetUseTextureRender({dynamic hint}) { @@ -1650,5 +1662,36 @@ class RustdeskImpl { throw UnimplementedError(); } + Future getVoiceCallInputDevice({required bool isCm, dynamic hint}) { + throw UnimplementedError(); + } + + Future setVoiceCallInputDevice( + {required bool isCm, required String device, dynamic hint}) { + throw UnimplementedError(); + } + + bool isPresetPasswordMobileOnly({dynamic hint}) { + throw UnimplementedError(); + } + + String mainGetBuildinOption({required String key, dynamic hint}) { + return ''; + } + + String installInstallOptions({dynamic hint}) { + throw UnimplementedError(); + } + + sessionRenameFile( + {required UuidValue sessionId, + required int actId, + required String path, + required String newName, + required bool isRemote, + dynamic hint}) { + throw UnimplementedError(); + } + void dispose() {} } diff --git a/flutter/lib/web/texture_rgba_renderer.dart b/flutter/lib/web/texture_rgba_renderer.dart index 83407773583..9a4a1879b12 100644 --- a/flutter/lib/web/texture_rgba_renderer.dart +++ b/flutter/lib/web/texture_rgba_renderer.dart @@ -6,7 +6,7 @@ class TextureRgbaRenderer { } Future closeTexture(int key) { - throw UnimplementedError(); + return Future(() => true); } Future onRgba( diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 9b9914cfd0f..e310febb150 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -491,9 +491,8 @@ pub fn session_switch_display(is_desktop: bool, session_id: SessionID, value: Ve pub fn session_handle_flutter_key_event( session_id: SessionID, - name: String, - platform_code: i32, - position_code: i32, + character: String, + usb_hid: i32, lock_modes: i32, down_or_up: bool, ) { @@ -501,9 +500,8 @@ pub fn session_handle_flutter_key_event( let keyboard_mode = session.get_keyboard_mode(); session.handle_flutter_key_event( &keyboard_mode, - &name, - platform_code, - position_code, + &character, + usb_hid, lock_modes, down_or_up, ); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 0e030aa8ba4..423794be58e 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -803,19 +803,18 @@ impl Session { pub fn handle_flutter_key_event( &self, keyboard_mode: &str, - name: &str, - platform_code: i32, - position_code: i32, + character: &str, + usb_hid: i32, lock_modes: i32, down_or_up: bool, ) { - if name == "flutter_key" { - self._handle_key_flutter_simulation(keyboard_mode, platform_code, down_or_up); + if character == "flutter_key" { + self._handle_key_flutter_simulation(keyboard_mode, usb_hid, down_or_up); } else { self._handle_key_non_flutter_simulation( keyboard_mode, - platform_code, - position_code, + character, + usb_hid, lock_modes, down_or_up, ); @@ -831,10 +830,10 @@ impl Session { ) { // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_key.g.dart#L4356 let ctrl_key = match platform_code { - 0x0007007f => Some(ControlKey::VolumeMute), - 0x00070080 => Some(ControlKey::VolumeUp), - 0x00070081 => Some(ControlKey::VolumeDown), - 0x00070066 => Some(ControlKey::Power), + 0x007f => Some(ControlKey::VolumeMute), + 0x0080 => Some(ControlKey::VolumeUp), + 0x0081 => Some(ControlKey::VolumeDown), + 0x0066 => Some(ControlKey::Power), _ => None, }; let Some(ctrl_key) = ctrl_key else { return }; @@ -851,22 +850,28 @@ impl Session { fn _handle_key_non_flutter_simulation( &self, keyboard_mode: &str, - platform_code: i32, - position_code: i32, + character: &str, + usb_hid: i32, lock_modes: i32, down_or_up: bool, ) { - if position_code < 0 || platform_code < 0 { - return; - } - let platform_code: u32 = platform_code as _; - let position_code: KeyCode = position_code as _; + let key = rdev::usb_hid_key_from_code(usb_hid as _); - #[cfg(not(target_os = "windows"))] - let key = rdev::key_from_code(position_code) as rdev::Key; - // Windows requires special handling #[cfg(target_os = "windows")] - let key = rdev::get_win_key(platform_code, position_code); + let platform_code: u32 = rdev::win_code_from_key(key).unwrap_or(0); + #[cfg(target_os = "windows")] + let position_code: KeyCode = rdev::win_scancode_from_key(key).unwrap_or(0) as _; + + #[cfg(not(target_os = "windows"))] + let position_code: KeyCode = rdev::code_from_key(key).unwrap_or(0) as _; + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + let platform_code: u32 = position_code as _; + // For translate mode. + // We need to set the platform code (keysym) if is AltGr. + // https://github.com/rustdesk/rustdesk/blob/07cf1b4db5ef2f925efd3b16b87c33ce03c94809/src/keyboard.rs#L1029 + // https://github.com/flutter/flutter/issues/153811 + #[cfg(target_os = "linux")] + let platform_code: u32 = position_code as _; let event_type = if down_or_up { KeyPress(key) @@ -875,7 +880,16 @@ impl Session { }; let event = Event { time: SystemTime::now(), - unicode: None, + unicode: if character.is_empty() { + None + } else { + Some(rdev::UnicodeInfo { + name: Some(character.to_string()), + unicode: character.encode_utf16().collect(), + // is_dead: is not correct here, because flutter cannot detect deadcode for now. + is_dead: false, + }) + }, platform_code, position_code: position_code as _, event_type, From d400999b9c5f3ce5dadc363a00199702c8d963fa Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sat, 24 Aug 2024 19:02:04 +0800 Subject: [PATCH 011/210] bump to 1.3.1 --- .github/workflows/flutter-build.yml | 2 +- .github/workflows/playground.yml | 2 +- Cargo.lock | 4 ++-- Cargo.toml | 2 +- appimage/AppImageBuilder-aarch64.yml | 2 +- appimage/AppImageBuilder-x86_64.yml | 2 +- flutter/pubspec.yaml | 2 +- libs/portable/Cargo.toml | 2 +- res/PKGBUILD | 2 +- res/rpm-flutter-suse.spec | 2 +- res/rpm-flutter.spec | 2 +- res/rpm.spec | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index 74ebe72a4b6..aea6127478c 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -33,7 +33,7 @@ env: VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" # vcpkg version: 2024.07.12 VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1" - VERSION: "1.3.0" + VERSION: "1.3.1" NDK_VERSION: "r27" #signing keys env variable checks ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index 3fdcc4cfee8..fb7b8961450 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -18,7 +18,7 @@ env: VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" # vcpkg version: 2024.06.15 VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625" - VERSION: "1.3.0" + VERSION: "1.3.1" NDK_VERSION: "r26d" #signing keys env variable checks ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" diff --git a/Cargo.lock b/Cargo.lock index 1b1e66826c6..fbf1f8ebb9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5462,7 +5462,7 @@ dependencies = [ [[package]] name = "rustdesk" -version = "1.3.0" +version = "1.3.1" dependencies = [ "android-wakelock", "android_logger", @@ -5559,7 +5559,7 @@ dependencies = [ [[package]] name = "rustdesk-portable-packer" -version = "1.3.0" +version = "1.3.1" dependencies = [ "brotli", "dirs 5.0.1", diff --git a/Cargo.toml b/Cargo.toml index 5a052af0dfd..28c2c363d46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustdesk" -version = "1.3.0" +version = "1.3.1" authors = ["rustdesk "] edition = "2021" build= "build.rs" diff --git a/appimage/AppImageBuilder-aarch64.yml b/appimage/AppImageBuilder-aarch64.yml index c64966f2819..e23cde7172d 100644 --- a/appimage/AppImageBuilder-aarch64.yml +++ b/appimage/AppImageBuilder-aarch64.yml @@ -18,7 +18,7 @@ AppDir: id: rustdesk name: rustdesk icon: rustdesk - version: 1.3.0 + version: 1.3.1 exec: usr/lib/rustdesk/rustdesk exec_args: $@ apt: diff --git a/appimage/AppImageBuilder-x86_64.yml b/appimage/AppImageBuilder-x86_64.yml index e1b460364b3..83df32ec87b 100644 --- a/appimage/AppImageBuilder-x86_64.yml +++ b/appimage/AppImageBuilder-x86_64.yml @@ -18,7 +18,7 @@ AppDir: id: rustdesk name: rustdesk icon: rustdesk - version: 1.3.0 + version: 1.3.1 exec: usr/lib/rustdesk/rustdesk exec_args: $@ apt: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 7aadc1c4115..56022b6ec3f 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -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.0+47 +version: 1.3.1+47 environment: sdk: '^3.1.0' diff --git a/libs/portable/Cargo.toml b/libs/portable/Cargo.toml index ce1c10c09e4..10d16605b2b 100644 --- a/libs/portable/Cargo.toml +++ b/libs/portable/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustdesk-portable-packer" -version = "1.3.0" +version = "1.3.1" edition = "2021" description = "RustDesk Remote Desktop" diff --git a/res/PKGBUILD b/res/PKGBUILD index 94ccce6937d..a805997cd88 100644 --- a/res/PKGBUILD +++ b/res/PKGBUILD @@ -1,5 +1,5 @@ pkgname=rustdesk -pkgver=1.3.0 +pkgver=1.3.1 pkgrel=0 epoch= pkgdesc="" diff --git a/res/rpm-flutter-suse.spec b/res/rpm-flutter-suse.spec index 053099c07fa..2995c54d435 100644 --- a/res/rpm-flutter-suse.spec +++ b/res/rpm-flutter-suse.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.3.0 +Version: 1.3.1 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm-flutter.spec b/res/rpm-flutter.spec index f962a2ed1f6..48a7e2ac000 100644 --- a/res/rpm-flutter.spec +++ b/res/rpm-flutter.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.3.0 +Version: 1.3.1 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm.spec b/res/rpm.spec index 633c2a220a7..8c99f9bb0b4 100644 --- a/res/rpm.spec +++ b/res/rpm.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.3.0 +Version: 1.3.1 Release: 0 Summary: RPM package License: GPL-3.0 From aa1e1225320e50ceb8227a04a880afa06e113b6b Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 25 Aug 2024 00:03:31 +0800 Subject: [PATCH 012/210] fix: revert key events to raw key events on Linux (#9161) Signed-off-by: fufesou --- flutter/lib/common/widgets/remote_input.dart | 13 +- flutter/lib/models/input_model.dart | 245 ++++++++++++++++++- src/flutter_ffi.rs | 21 ++ src/ui_session_interface.rs | 76 +++++- 4 files changed, 350 insertions(+), 5 deletions(-) diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart index 73e5d8f39a8..fb19c4c2345 100644 --- a/flutter/lib/common/widgets/remote_input.dart +++ b/flutter/lib/common/widgets/remote_input.dart @@ -27,6 +27,10 @@ class RawKeyFocusScope extends StatelessWidget { @override Widget build(BuildContext context) { + // https://github.com/flutter/flutter/issues/154053 + final useRawKeyEvents = isLinux && !isWeb; + // FIXME: On Windows, `AltGr` will generate `Alt` and `Control` key events, + // while `Alt` and `Control` are seperated key events for en-US input method. return FocusScope( autofocus: true, child: Focus( @@ -34,7 +38,14 @@ class RawKeyFocusScope extends StatelessWidget { canRequestFocus: true, focusNode: focusNode, onFocusChange: onFocusChange, - onKeyEvent: (node, event) => inputModel.handleKeyEvent(event), + onKey: useRawKeyEvents + ? (FocusNode data, RawKeyEvent event) => + inputModel.handleRawKeyEvent(event) + : null, + onKeyEvent: useRawKeyEvents + ? null + : (FocusNode node, KeyEvent event) => + inputModel.handleKeyEvent(event), child: child)); } } diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 35f00c3e1c3..54e7a3f4dc0 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -177,6 +177,111 @@ class PointerEventToRust { } } +class ToReleaseRawKeys { + RawKeyEvent? lastLShiftKeyEvent; + RawKeyEvent? lastRShiftKeyEvent; + RawKeyEvent? lastLCtrlKeyEvent; + RawKeyEvent? lastRCtrlKeyEvent; + RawKeyEvent? lastLAltKeyEvent; + RawKeyEvent? lastRAltKeyEvent; + RawKeyEvent? lastLCommandKeyEvent; + RawKeyEvent? lastRCommandKeyEvent; + RawKeyEvent? lastSuperKeyEvent; + + reset() { + lastLShiftKeyEvent = null; + lastRShiftKeyEvent = null; + lastLCtrlKeyEvent = null; + lastRCtrlKeyEvent = null; + lastLAltKeyEvent = null; + lastRAltKeyEvent = null; + lastLCommandKeyEvent = null; + lastRCommandKeyEvent = null; + lastSuperKeyEvent = null; + } + + updateKeyDown(LogicalKeyboardKey logicKey, RawKeyDownEvent e) { + if (e.isAltPressed) { + if (logicKey == LogicalKeyboardKey.altLeft) { + lastLAltKeyEvent = e; + } else if (logicKey == LogicalKeyboardKey.altRight) { + lastRAltKeyEvent = e; + } + } else if (e.isControlPressed) { + if (logicKey == LogicalKeyboardKey.controlLeft) { + lastLCtrlKeyEvent = e; + } else if (logicKey == LogicalKeyboardKey.controlRight) { + lastRCtrlKeyEvent = e; + } + } else if (e.isShiftPressed) { + if (logicKey == LogicalKeyboardKey.shiftLeft) { + lastLShiftKeyEvent = e; + } else if (logicKey == LogicalKeyboardKey.shiftRight) { + lastRShiftKeyEvent = e; + } + } else if (e.isMetaPressed) { + if (logicKey == LogicalKeyboardKey.metaLeft) { + lastLCommandKeyEvent = e; + } else if (logicKey == LogicalKeyboardKey.metaRight) { + lastRCommandKeyEvent = e; + } else if (logicKey == LogicalKeyboardKey.superKey) { + lastSuperKeyEvent = e; + } + } + } + + updateKeyUp(LogicalKeyboardKey logicKey, RawKeyUpEvent e) { + if (e.isAltPressed) { + if (logicKey == LogicalKeyboardKey.altLeft) { + lastLAltKeyEvent = null; + } else if (logicKey == LogicalKeyboardKey.altRight) { + lastRAltKeyEvent = null; + } + } else if (e.isControlPressed) { + if (logicKey == LogicalKeyboardKey.controlLeft) { + lastLCtrlKeyEvent = null; + } else if (logicKey == LogicalKeyboardKey.controlRight) { + lastRCtrlKeyEvent = null; + } + } else if (e.isShiftPressed) { + if (logicKey == LogicalKeyboardKey.shiftLeft) { + lastLShiftKeyEvent = null; + } else if (logicKey == LogicalKeyboardKey.shiftRight) { + lastRShiftKeyEvent = null; + } + } else if (e.isMetaPressed) { + if (logicKey == LogicalKeyboardKey.metaLeft) { + lastLCommandKeyEvent = null; + } else if (logicKey == LogicalKeyboardKey.metaRight) { + lastRCommandKeyEvent = null; + } else if (logicKey == LogicalKeyboardKey.superKey) { + lastSuperKeyEvent = null; + } + } + } + + release(KeyEventResult Function(RawKeyEvent e) handleRawKeyEvent) { + for (final key in [ + lastLShiftKeyEvent, + lastRShiftKeyEvent, + lastLCtrlKeyEvent, + lastRCtrlKeyEvent, + lastLAltKeyEvent, + lastRAltKeyEvent, + lastLCommandKeyEvent, + lastRCommandKeyEvent, + lastSuperKeyEvent, + ]) { + if (key != null) { + handleRawKeyEvent(RawKeyUpEvent( + data: key.data, + character: key.character, + )); + } + } + } +} + class ToReleaseKeys { KeyEvent? lastLShiftKeyEvent; KeyEvent? lastRShiftKeyEvent; @@ -229,6 +334,7 @@ class InputModel { var alt = false; var command = false; + final ToReleaseRawKeys toReleaseRawKeys = ToReleaseRawKeys(); final ToReleaseKeys toReleaseKeys = ToReleaseKeys(); // trackpad @@ -361,6 +467,56 @@ class InputModel { } } + KeyEventResult handleRawKeyEvent(RawKeyEvent e) { + if (isViewOnly) return KeyEventResult.handled; + if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) { + return KeyEventResult.handled; + } + + final key = e.logicalKey; + if (e is RawKeyDownEvent) { + if (!e.repeat) { + if (e.isAltPressed && !alt) { + alt = true; + } else if (e.isControlPressed && !ctrl) { + ctrl = true; + } else if (e.isShiftPressed && !shift) { + shift = true; + } else if (e.isMetaPressed && !command) { + command = true; + } + } + toReleaseRawKeys.updateKeyDown(key, e); + } + if (e is RawKeyUpEvent) { + if (key == LogicalKeyboardKey.altLeft || + key == LogicalKeyboardKey.altRight) { + alt = false; + } else if (key == LogicalKeyboardKey.controlLeft || + key == LogicalKeyboardKey.controlRight) { + ctrl = false; + } else if (key == LogicalKeyboardKey.shiftRight || + key == LogicalKeyboardKey.shiftLeft) { + shift = false; + } else if (key == LogicalKeyboardKey.metaLeft || + key == LogicalKeyboardKey.metaRight || + key == LogicalKeyboardKey.superKey) { + command = false; + } + + toReleaseRawKeys.updateKeyUp(key, e); + } + + // * Currently mobile does not enable map mode + if ((isDesktop || isWebDesktop) && keyboardMode == 'map') { + mapKeyboardModeRaw(e); + } else { + legacyKeyboardModeRaw(e); + } + + return KeyEventResult.handled; + } + KeyEventResult handleKeyEvent(KeyEvent e) { if (isViewOnly) return KeyEventResult.handled; if ((isDesktop || isWebDesktop) && !isInputSourceFlutter) { @@ -383,8 +539,10 @@ class InputModel { // * Currently mobile does not enable map mode if ((isDesktop || isWebDesktop)) { // FIXME: e.character is wrong for dead keys, eg: ^ in de - newKeyboardMode(e.character ?? '', e.physicalKey.usbHidUsage & 0xFFFF, - // Show repeat event be converted to "release+press" events? + newKeyboardMode( + e.character ?? '', + e.physicalKey.usbHidUsage & 0xFFFF, + // Show repeat event be converted to "release+press" events? e is KeyDownEvent || e is KeyRepeatEvent); } else { legacyKeyboardMode(e); @@ -419,6 +577,88 @@ class InputModel { downOrUp: down); } + void mapKeyboardModeRaw(RawKeyEvent e) { + int positionCode = -1; + int platformCode = -1; + bool down; + + if (e.data is RawKeyEventDataMacOs) { + RawKeyEventDataMacOs newData = e.data as RawKeyEventDataMacOs; + positionCode = newData.keyCode; + platformCode = newData.keyCode; + } else if (e.data is RawKeyEventDataWindows) { + RawKeyEventDataWindows newData = e.data as RawKeyEventDataWindows; + positionCode = newData.scanCode; + platformCode = newData.keyCode; + } else if (e.data is RawKeyEventDataLinux) { + RawKeyEventDataLinux newData = e.data as RawKeyEventDataLinux; + // scanCode and keyCode of RawKeyEventDataLinux are incorrect. + // 1. scanCode means keycode + // 2. keyCode means keysym + positionCode = newData.scanCode; + platformCode = newData.keyCode; + } else if (e.data is RawKeyEventDataAndroid) { + RawKeyEventDataAndroid newData = e.data as RawKeyEventDataAndroid; + positionCode = newData.scanCode + 8; + platformCode = newData.keyCode; + } else {} + + if (e is RawKeyDownEvent) { + down = true; + } else { + down = false; + } + inputRawKey(e.character ?? '', platformCode, positionCode, down); + } + + /// 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); + } + bind.sessionHandleFlutterRawKeyEvent( + sessionId: sessionId, + name: name, + platformCode: platformCode, + positionCode: positionCode, + lockModes: lockModes, + downOrUp: down); + } + + void legacyKeyboardModeRaw(RawKeyEvent e) { + if (e is RawKeyDownEvent) { + if (e.repeat) { + sendRawKey(e, press: true); + } else { + sendRawKey(e, down: true); + } + } + if (e is RawKeyUpEvent) { + sendRawKey(e); + } + } + + void sendRawKey(RawKeyEvent e, {bool? down, bool? press}) { + // for maximum compatibility + final label = physicalKeyMap[e.physicalKey.usbHidUsage] ?? + logicalKeyMap[e.logicalKey.keyId] ?? + e.logicalKey.keyLabel; + inputKey(label, down: down, press: press ?? false); + } + void legacyKeyboardMode(KeyEvent e) { if (e is KeyDownEvent) { sendKey(e, down: true); @@ -533,6 +773,7 @@ class InputModel { void enterOrLeave(bool enter) { toReleaseKeys.release(handleKeyEvent); + toReleaseRawKeys.release(handleRawKeyEvent); _pointerMovedAfterEnter = false; // Fix status diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index e310febb150..48efda3af7a 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -508,6 +508,27 @@ pub fn session_handle_flutter_key_event( } } +pub fn session_handle_flutter_raw_key_event( + session_id: SessionID, + name: String, + platform_code: i32, + position_code: i32, + lock_modes: i32, + down_or_up: bool, +) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + let keyboard_mode = session.get_keyboard_mode(); + session.handle_flutter_raw_key_event( + &keyboard_mode, + &name, + platform_code, + position_code, + lock_modes, + down_or_up, + ); + } +} + // SyncReturn<()> is used to make sure enter() and leave() are executed in the sequence this function is called. // // If the cursor jumps between remote page of two connections, leave view and enter view will be called. diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 423794be58e..6686e5419e5 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -788,7 +788,7 @@ impl Session { } #[cfg(any(target_os = "ios"))] - pub fn handle_flutter_key_event( + pub fn handle_flutter_raw_key_event( &self, _keyboard_mode: &str, _name: &str, @@ -799,6 +799,78 @@ impl Session { ) { } + #[cfg(not(any(target_os = "ios")))] + pub fn handle_flutter_raw_key_event( + &self, + keyboard_mode: &str, + name: &str, + platform_code: i32, + position_code: i32, + lock_modes: i32, + down_or_up: bool, + ) { + if name == "flutter_key" { + self._handle_key_flutter_simulation(keyboard_mode, platform_code, down_or_up); + } else { + self._handle_raw_key_non_flutter_simulation( + keyboard_mode, + platform_code, + position_code, + lock_modes, + down_or_up, + ); + } + } + + #[cfg(not(any(target_os = "ios")))] + fn _handle_raw_key_non_flutter_simulation( + &self, + keyboard_mode: &str, + platform_code: i32, + position_code: i32, + lock_modes: i32, + down_or_up: bool, + ) { + if position_code < 0 || platform_code < 0 { + return; + } + let platform_code: u32 = platform_code as _; + let position_code: KeyCode = position_code as _; + + #[cfg(not(target_os = "windows"))] + let key = rdev::key_from_code(position_code) as rdev::Key; + // Windows requires special handling + #[cfg(target_os = "windows")] + let key = rdev::get_win_key(platform_code, position_code); + + let event_type = if down_or_up { + KeyPress(key) + } else { + KeyRelease(key) + }; + let event = Event { + time: SystemTime::now(), + unicode: None, + platform_code, + position_code: position_code as _, + event_type, + #[cfg(any(target_os = "windows", target_os = "macos"))] + extra_data: 0, + }; + keyboard::client::process_event(keyboard_mode, &event, Some(lock_modes)); + } + + #[cfg(any(target_os = "ios"))] + pub fn handle_flutter_key_event( + &self, + _keyboard_mode: &str, + _character: &str, + _usb_hid: i32, + _lock_modes: i32, + _down_or_up: bool, + ) { + } + #[cfg(not(any(target_os = "ios")))] pub fn handle_flutter_key_event( &self, @@ -870,7 +942,7 @@ impl Session { // We need to set the platform code (keysym) if is AltGr. // https://github.com/rustdesk/rustdesk/blob/07cf1b4db5ef2f925efd3b16b87c33ce03c94809/src/keyboard.rs#L1029 // https://github.com/flutter/flutter/issues/153811 - #[cfg(target_os = "linux")] + #[cfg(target_os = "linux")] let platform_code: u32 = position_code as _; let event_type = if down_or_up { From 24f4b94082135d1cb1fbb356d8a608db350eb91f Mon Sep 17 00:00:00 2001 From: ELForcer <30798063+ELForcer@users.noreply.github.com> Date: Sun, 25 Aug 2024 11:12:08 +0400 Subject: [PATCH 013/210] Update ru.rs (#9163) --- src/lang/ru.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index dc4b2133f9e..b13c4a1b663 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -636,13 +636,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "Требуется не менее {} символов"), ("Wrong PIN", "Неправильный PIN-код"), ("Set PIN", "Установить PIN-код"), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("Enable trusted devices", "Включение доверенных устройств"), + ("Manage trusted devices", "Управление доверенными устройствами"), + ("Platform", "Платформа"), + ("Days remaining", "Дней осталось"), + ("enable-trusted-devices-tip", "Разрешить доверенным устройствам пропускать проверку подлинности 2FA"), + ("Parent directory", "Родительская директория"), + ("Resume", "Продолжить"), + ("Invalid file name", "Неверное имя файла"), ].iter().cloned().collect(); } From a946d4d0c9249824a167f5ce4f8a5a1fbdc72cff Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 25 Aug 2024 20:46:21 +0800 Subject: [PATCH 014/210] file transfer status text overflow at start (#9166) Signed-off-by: 21pages --- .github/workflows/bridge.yml | 2 +- .github/workflows/flutter-build.yml | 14 +++++++++---- .../lib/desktop/pages/file_manager_page.dart | 6 +++++- flutter/pubspec.lock | 20 +++++++++++++++++-- flutter/pubspec.yaml | 1 + 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/.github/workflows/bridge.yml b/.github/workflows/bridge.yml index fbaf459a4d8..54180ccdd34 100644 --- a/.github/workflows/bridge.yml +++ b/.github/workflows/bridge.yml @@ -6,7 +6,7 @@ on: workflow_call: env: - FLUTTER_VERSION: "3.16.9" + FLUTTER_VERSION: "3.19.6" FLUTTER_RUST_BRIDGE_VERSION: "1.80.1" RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503 diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index aea6127478c..d7a36b4005a 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -976,8 +976,11 @@ jobs: - name: fix android for flutter 3.13 if: $${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }} run: | - sed -i 's/uni_links_desktop/#uni_links_desktop/g' flutter/pubspec.yaml - cd flutter/lib + cd flutter + sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml + sed -i 's/extended_text: .*/extended_text: 11.1.0/' pubspec.yaml + flutter pub get + cd lib find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' - name: Build rustdesk lib @@ -1210,8 +1213,11 @@ jobs: - name: fix android for flutter 3.13 if: $${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }} run: | - sed -i 's/uni_links_desktop/#uni_links_desktop/g' flutter/pubspec.yaml - cd flutter/lib + cd flutter + sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml + sed -i 's/extended_text: .*/extended_text: 11.1.0/' pubspec.yaml + flutter pub get + cd lib find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' - name: Build rustdesk diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 05af83cd38d..c498bf1010b 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; +import 'package:extended_text/extended_text.dart'; import 'package:flutter_hbb/desktop/widgets/dragable_divider.dart'; import 'package:percent_indicator/percent_indicator.dart'; import 'package:desktop_drop/desktop_drop.dart'; @@ -211,10 +212,13 @@ class _FileManagerPageState extends State Tooltip( waitDuration: Duration(milliseconds: 500), message: item.jobName, - child: Text( + child: ExtendedText( item.jobName, maxLines: 1, overflow: TextOverflow.ellipsis, + overflowWidget: TextOverflowWidget( + child: Text("..."), + position: TextOverflowPosition.start), ), ), Tooltip( diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index bbd91b045ce..61d57bcba4d 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -380,6 +380,22 @@ packages: url: "https://github.com/rustdesk-org/dynamic_layouts.git" source: git version: "0.0.1+1" + extended_text: + dependency: "direct main" + description: + name: extended_text + sha256: "7f382de3af12992e34bd72ddd36becf90c4720900af126cb9859f0189af71ffe" + url: "https://pub.dev" + source: hosted + version: "13.0.0" + extended_text_library: + dependency: transitive + description: + name: extended_text_library + sha256: "55d09098ec56fab0d9a8a68950ca0bbf2efa1327937f7cec6af6dfa066234829" + url: "https://pub.dev" + source: hosted + version: "12.0.0" external_path: dependency: "direct main" description: @@ -1613,5 +1629,5 @@ packages: source: hosted version: "0.2.1" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 56022b6ec3f..db15c74cc0a 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -104,6 +104,7 @@ dependencies: pull_down_button: ^0.9.3 device_info_plus: ^9.1.0 qr_flutter: ^4.1.0 + extended_text: 13.0.0 dev_dependencies: icons_launcher: ^2.0.4 From 48aec6484c20aa0b00ac496e30916f501bf53c62 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 25 Aug 2024 21:29:41 +0800 Subject: [PATCH 015/210] refresh file transfer table on resume (#9167) Signed-off-by: 21pages --- flutter/lib/desktop/pages/file_manager_page.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index c498bf1010b..4677744197e 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -69,7 +69,7 @@ class FileManagerPage extends StatefulWidget { } class _FileManagerPageState extends State - with AutomaticKeepAliveClientMixin { + with AutomaticKeepAliveClientMixin, WidgetsBindingObserver { final _mouseFocusScope = Rx(MouseFocusScope.none); final _dropMaskVisible = false.obs; // TODO impl drop mask @@ -103,6 +103,7 @@ class _FileManagerPageState extends State WidgetsBinding.instance.addPostFrameCallback((_) { widget.tabController.onSelected?.call(widget.id); }); + WidgetsBinding.instance.addObserver(this); } @override @@ -115,12 +116,21 @@ class _FileManagerPageState extends State } Get.delete(tag: 'ft_${widget.id}'); }); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override bool get wantKeepAlive => true; + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.resumed) { + jobController.jobTable.refresh(); + } + } + @override Widget build(BuildContext context) { super.build(context); From 5abe42f66cd907e876360aa9a4090d9b25076346 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 26 Aug 2024 10:37:35 +0800 Subject: [PATCH 016/210] not run get window focus if no multiple displays (#9174) Signed-off-by: 21pages --- src/server/input_service.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 2f9b86480a2..06ab61a25c0 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -385,6 +385,9 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()> fn run_window_focus(sp: EmptyExtraFieldService, state: &mut StateWindowFocus) -> ResultType<()> { let displays = super::display_service::get_sync_displays(); + if displays.len() <= 1 { + return Ok(()); + } let disp_idx = crate::get_focused_display(displays); if let Some(disp_idx) = disp_idx.map(|id| id as i32) { if state.is_changed(disp_idx) { From 4b4fd94f3eb463346e2171fff211fbf43da08cb8 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:13:11 +0800 Subject: [PATCH 017/210] feat: web v2 keyboard (#9175) Signed-off-by: fufesou --- flutter/lib/common.dart | 3 +++ flutter/lib/consts.dart | 1 + .../lib/desktop/widgets/kb_layout_type_chooser.dart | 3 ++- flutter/lib/native/common.dart | 4 ++++ flutter/lib/web/bridge.dart | 13 ++++++++++++- flutter/lib/web/common.dart | 7 +++++++ 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 078bda8a420..fa1ea11b595 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -50,6 +50,9 @@ final isLinux = isLinux_; final isDesktop = isDesktop_; final isWeb = isWeb_; final isWebDesktop = isWebDesktop_; +final isWebOnWindows = isWebOnWindows_; +final isWebOnLinux = isWebOnLinux_; +final isWebOnMacOs = isWebOnMacOS_; var isMobile = isAndroid || isIOS; var version = ''; int androidVersion = 0; diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index a5414dd0db0..edc7f427853 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -32,6 +32,7 @@ const String kPeerPlatformWindows = "Windows"; const String kPeerPlatformLinux = "Linux"; const String kPeerPlatformMacOS = "Mac OS"; const String kPeerPlatformAndroid = "Android"; +const String kPeerPlatformWebDesktop = "WebDesktop"; const double kScrollbarThickness = 12.0; diff --git a/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart index 7828dd4a0ec..0984eea5801 100644 --- a/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart +++ b/flutter/lib/desktop/widgets/kb_layout_type_chooser.dart @@ -178,8 +178,9 @@ String getLocalPlatformForKBLayoutType(String peerPlatform) { localPlatform = kPeerPlatformWindows; } else if (isLinux) { localPlatform = kPeerPlatformLinux; + } else if (isWebOnWindows || isWebOnLinux) { + localPlatform = kPeerPlatformWebDesktop; } - // to-do: web desktop support ? return localPlatform; } diff --git a/flutter/lib/native/common.dart b/flutter/lib/native/common.dart index d3888a245d1..96d5bd6e82a 100644 --- a/flutter/lib/native/common.dart +++ b/flutter/lib/native/common.dart @@ -11,3 +11,7 @@ final isWebDesktop_ = false; final isDesktop_ = Platform.isWindows || Platform.isMacOS || Platform.isLinux; String get screenInfo_ => ''; + +final isWebOnWindows_ = false; +final isWebOnLinux_ = false; +final isWebOnMacOS_ = false; diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 5f6f5adef87..8327bdd0ab1 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -234,7 +234,7 @@ class RustdeskImpl { } String getLocalKbLayoutType({dynamic hint}) { - throw js.context.callMethod('getByName', ['option:local', 'kb_layout']); + return js.context.callMethod('getByName', ['option:local', 'kb_layout']); } Future setLocalKbLayoutType( @@ -415,6 +415,17 @@ class RustdeskImpl { ])); } + Future sessionHandleFlutterRawKeyEvent( + {required UuidValue sessionId, + required String name, + required int platformCode, + required int positionCode, + required int lockModes, + required bool downOrUp, + dynamic hint}) { + throw UnimplementedError(); + } + void sessionEnterOrLeave( {required UuidValue sessionId, required bool enter, dynamic hint}) { throw UnimplementedError(); diff --git a/flutter/lib/web/common.dart b/flutter/lib/web/common.dart index 93b53f94802..0f3a996816d 100644 --- a/flutter/lib/web/common.dart +++ b/flutter/lib/web/common.dart @@ -1,4 +1,5 @@ import 'dart:js' as js; +import 'dart:html' as html; final isAndroid_ = false; final isIOS_ = false; @@ -11,3 +12,9 @@ final isWebDesktop_ = !js.context.callMethod('isMobile'); 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'); From 690a2c8399f9724754b77ddd11026096c9038ad1 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Mon, 26 Aug 2024 17:07:02 +0800 Subject: [PATCH 018/210] still find delegate failure when my mac restarted automatically sometimes --- src/platform/macos.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 3d14485d269..bb803bdaae7 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -507,6 +507,10 @@ pub fn start_os_service() { .map(|p| p.start_time()) .unwrap_or_default() as i64; log::info!("Startime: {my_start_time} vs {:?}", server); + if my_start_time < server.unwrap().0 + 3 { + log::error!("Please start --server first to make delegate work, earlier more 3 seconds",); + std::process::exit(-1); + } std::thread::spawn(move || loop { std::thread::sleep(std::time::Duration::from_secs(1)); @@ -519,9 +523,9 @@ pub fn start_os_service() { ); std::process::exit(-1); }; - if my_start_time <= start_time + 1 { + if my_start_time < start_time + 3 { log::error!( - "Agent start later, {my_start_time} vs {start_time}, please start --server first to make delegate work", + "Agent start later, {my_start_time} vs {start_time}, please start --server first to make delegate work, earlier more 3 seconds", ); std::process::exit(-1); } From c68ce7dd84db405b93b5fa411a5e033b71ef75d2 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 27 Aug 2024 00:00:33 +0800 Subject: [PATCH 019/210] fix: web v2, keyboard mode (#9180) Signed-off-by: fufesou --- flutter/lib/desktop/widgets/remote_toolbar.dart | 4 +++- flutter/lib/models/input_model.dart | 4 ++-- flutter/lib/web/bridge.dart | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 98fa676144b..af177f84050 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1612,7 +1612,9 @@ class _KeyboardMenu extends StatelessWidget { // If use flutter to grab keys, we can only use one mode. // Map mode and Legacy mode, at least one of them is supported. String? modeOnly; - if (isInputSourceFlutter) { + // Keep both map and legacy mode on web at the moment. + // TODO: Remove legacy mode after web supports translate mode on web. + if (isInputSourceFlutter && isDesktop) { if (bind.sessionIsKeyboardModeSupported( sessionId: ffi.sessionId, mode: kKeyMapMode)) { modeOnly = kKeyMapMode; diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 54e7a3f4dc0..4cdbf88e5f0 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -508,7 +508,7 @@ class InputModel { } // * Currently mobile does not enable map mode - if ((isDesktop || isWebDesktop) && keyboardMode == 'map') { + if ((isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) { mapKeyboardModeRaw(e); } else { legacyKeyboardModeRaw(e); @@ -537,7 +537,7 @@ class InputModel { } // * Currently mobile does not enable map mode - if ((isDesktop || isWebDesktop)) { + if ((isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) { // FIXME: e.character is wrong for dead keys, eg: ^ in de newKeyboardMode( e.character ?? '', diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 8327bdd0ab1..3aa65a5be69 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -351,7 +351,7 @@ class RustdeskImpl { bool sessionIsKeyboardModeSupported( {required UuidValue sessionId, required String mode, dynamic hint}) { - return mode == kKeyLegacyMode; + return [kKeyLegacyMode, kKeyMapMode].contains(mode); } bool sessionIsMultiUiSession({required UuidValue sessionId, dynamic hint}) { From 40239a1c414158a3248bc400d9dca049292848e4 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:20:29 +0800 Subject: [PATCH 020/210] feat: macos, mouse button, back&forward (#9185) Signed-off-by: fufesou --- libs/enigo/src/macos/macos_impl.rs | 57 ++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/libs/enigo/src/macos/macos_impl.rs b/libs/enigo/src/macos/macos_impl.rs index b56beff129f..a1f5d2e4a9f 100644 --- a/libs/enigo/src/macos/macos_impl.rs +++ b/libs/enigo/src/macos/macos_impl.rs @@ -37,6 +37,9 @@ const kUCKeyActionDisplay: u16 = 3; const kUCKeyTranslateDeadKeysBit: OptionBits = 1 << 31; const BUF_LEN: usize = 4; +const MOUSE_EVENT_BUTTON_NUMBER_BACK: i64 = 3; +const MOUSE_EVENT_BUTTON_NUMBER_FORWARD: i64 = 4; + /// The event source user data value of cgevent. pub const ENIGO_INPUT_EXTRA_VALUE: i64 = 100; @@ -226,14 +229,24 @@ impl MouseControllable for Enigo { } self.last_click_time = Some(now); let (current_x, current_y) = Self::mouse_location(); - let (button, event_type) = match button { - MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseDown), - MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseDown), - MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseDown), + let (button, event_type, btn_value) = match button { + MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseDown, None), + MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseDown, None), + MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseDown, None), + MouseButton::Back => ( + CGMouseButton::Left, + CGEventType::OtherMouseDown, + Some(MOUSE_EVENT_BUTTON_NUMBER_BACK), + ), + MouseButton::Forward => ( + CGMouseButton::Left, + CGEventType::OtherMouseDown, + Some(MOUSE_EVENT_BUTTON_NUMBER_FORWARD), + ), _ => { log::info!("Unsupported button {:?}", button); return Ok(()); - }, + } }; let dest = CGPoint::new(current_x as f64, current_y as f64); if let Some(src) = self.event_source.as_ref() { @@ -244,6 +257,9 @@ impl MouseControllable for Enigo { self.multiple_click, ); } + if let Some(v) = btn_value { + event.set_integer_value_field(EventField::MOUSE_EVENT_BUTTON_NUMBER, v); + } self.post(event); } } @@ -252,14 +268,24 @@ impl MouseControllable for Enigo { fn mouse_up(&mut self, button: MouseButton) { let (current_x, current_y) = Self::mouse_location(); - let (button, event_type) = match button { - MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseUp), - MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseUp), - MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseUp), + let (button, event_type, btn_value) = match button { + MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseUp, None), + MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseUp, None), + MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseUp, None), + MouseButton::Back => ( + CGMouseButton::Left, + CGEventType::OtherMouseUp, + Some(MOUSE_EVENT_BUTTON_NUMBER_BACK), + ), + MouseButton::Forward => ( + CGMouseButton::Left, + CGEventType::OtherMouseUp, + Some(MOUSE_EVENT_BUTTON_NUMBER_FORWARD), + ), _ => { log::info!("Unsupported button {:?}", button); return; - }, + } }; let dest = CGPoint::new(current_x as f64, current_y as f64); if let Some(src) = self.event_source.as_ref() { @@ -270,6 +296,9 @@ impl MouseControllable for Enigo { self.multiple_click, ); } + if let Some(v) = btn_value { + event.set_integer_value_field(EventField::MOUSE_EVENT_BUTTON_NUMBER, v); + } self.post(event); } } @@ -345,7 +374,7 @@ impl KeyboardControllable for Enigo { fn as_mut_any(&mut self) -> &mut dyn std::any::Any { self } - + fn key_sequence(&mut self, sequence: &str) { // NOTE(dustin): This is a fix for issue https://github.com/enigo-rs/enigo/issues/68 // TODO(dustin): This could be improved by aggregating 20 bytes worth of graphemes at a time @@ -382,12 +411,10 @@ impl KeyboardControllable for Enigo { fn key_down(&mut self, key: Key) -> crate::ResultType { let code = self.key_to_keycode(key); if code == u16::MAX { - return Err("".into()); + return Err("".into()); } if let Some(src) = self.event_source.as_ref() { - if let Ok(event) = - CGEvent::new_keyboard_event(src.clone(), code, true) - { + if let Ok(event) = CGEvent::new_keyboard_event(src.clone(), code, true) { self.post(event); } } From 55de573a0162f0dc39dd516ccd878513a1a08b33 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 27 Aug 2024 19:30:51 +0800 Subject: [PATCH 021/210] fix: keyboard input, mulit windows (#9189) Signed-off-by: fufesou --- flutter/lib/models/input_model.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 4cdbf88e5f0..fd1beb6b5d6 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -784,6 +784,9 @@ class InputModel { if (!isInputSourceFlutter) { bind.sessionEnterOrLeave(sessionId: sessionId, enter: enter); } + if (enter) { + bind.setCurSessionId(sessionId: sessionId); + } } /// Send mouse movement event with distance in [x] and [y]. From f3a2733d75d141b64f411be342b62325b38e2d22 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 27 Aug 2024 20:25:01 +0800 Subject: [PATCH 022/210] start mac service more later --- src/platform/macos.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index bb803bdaae7..71a1022db44 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -507,10 +507,6 @@ pub fn start_os_service() { .map(|p| p.start_time()) .unwrap_or_default() as i64; log::info!("Startime: {my_start_time} vs {:?}", server); - if my_start_time < server.unwrap().0 + 3 { - log::error!("Please start --server first to make delegate work, earlier more 3 seconds",); - std::process::exit(-1); - } std::thread::spawn(move || loop { std::thread::sleep(std::time::Duration::from_secs(1)); @@ -523,7 +519,7 @@ pub fn start_os_service() { ); std::process::exit(-1); }; - if my_start_time < start_time + 3 { + if my_start_time <= start_time + 3 { log::error!( "Agent start later, {my_start_time} vs {start_time}, please start --server first to make delegate work, earlier more 3 seconds", ); From fd178a7b6c1315516c61016107a4a17377aaec37 Mon Sep 17 00:00:00 2001 From: 9amm Date: Tue, 27 Aug 2024 17:38:40 +0200 Subject: [PATCH 023/210] Fix minor typo (#9191) Co-authored-by: 9amm <> --- src/lang/es.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index 579d770bd50..794c751c00f 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -394,7 +394,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Accept sessions via click", "Aceptar sesiones a través de clic"), ("Accept sessions via both", "Aceptar sesiones a través de ambos"), ("Please wait for the remote side to accept your session request...", "Por favor, espere a que el lado remoto acepte su solicitud de sesión"), - ("One-time Password", "Constaseña de un solo uso"), + ("One-time Password", "Contraseña de un solo uso"), ("Use one-time password", "Usar contraseña de un solo uso"), ("One-time password length", "Longitud de la contraseña de un solo uso"), ("Request access to your device", "Solicitud de acceso a su dispositivo"), From cf06d1028f1d0f605b4a3c1163f27086547590f3 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 27 Aug 2024 23:58:04 +0800 Subject: [PATCH 024/210] fix: web, reset cursor on disconn, back to main page (#9192) Signed-off-by: fufesou --- flutter/lib/models/input_model.dart | 2 +- flutter/lib/models/model.dart | 1 + flutter/lib/native/custom_cursor.dart | 1 + flutter/lib/web/custom_cursor.dart | 6 ++++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index fd1beb6b5d6..ac78e17309b 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -784,7 +784,7 @@ class InputModel { if (!isInputSourceFlutter) { bind.sessionEnterOrLeave(sessionId: sessionId, enter: enter); } - if (enter) { + if (!isWeb && enter) { bind.setCurSessionId(sessionId: sessionId); } } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index d5377ea9a45..6f2a9eb2e2d 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -2183,6 +2183,7 @@ class CursorModel with ChangeNotifier { debugPrint("deleting cursor with key $k"); deleteCustomCursor(k); } + resetSystemCursor(); } trySetRemoteWindowCoords() { diff --git a/flutter/lib/native/custom_cursor.dart b/flutter/lib/native/custom_cursor.dart index 3e53f3cc5b2..e85d42a5589 100644 --- a/flutter/lib/native/custom_cursor.dart +++ b/flutter/lib/native/custom_cursor.dart @@ -9,6 +9,7 @@ import 'package:flutter_hbb/models/model.dart'; deleteCustomCursor(String key) => custom_cursor_manager.CursorManager.instance.deleteCursor(key); +resetSystemCursor() {} MouseCursor buildCursorOfCache( CursorModel cursor, double scale, CursorData? cache) { diff --git a/flutter/lib/web/custom_cursor.dart b/flutter/lib/web/custom_cursor.dart index fd1fc4a18f8..54df77e98b5 100644 --- a/flutter/lib/web/custom_cursor.dart +++ b/flutter/lib/web/custom_cursor.dart @@ -58,6 +58,11 @@ class CursorManager { ]); } } + + Future resetSystemCursor() async { + latestKey = ''; + js.context.callMethod('setByName', ['cursor', 'auto']); + } } class FlutterCustomMemoryImageCursor extends MouseCursor { @@ -92,6 +97,7 @@ class _FlutterCustomMemoryImageCursorSession extends MouseCursorSession { } deleteCustomCursor(String key) => CursorManager.instance.deleteCursor(key); +resetSystemCursor() => CursorManager.instance.resetSystemCursor(); MouseCursor buildCursorOfCache( model.CursorModel cursor, double scale, model.CursorData? cache) { From 6a5d5875c85125f0535770c7a4199c16ff2add72 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:23:40 +0800 Subject: [PATCH 025/210] Logo broken (#9195) --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index daf4a590743..02c1eee7595 100644 --- a/README.md +++ b/README.md @@ -174,10 +174,4 @@ Please ensure that you are running these commands from the root of the RustDesk ## [Public Servers](#public-servers) -RustDesk is supported by a free EU server, graciously provided by Codext GmbH - -

- - Codext GmbH - -

\ No newline at end of file +RustDesk is supported by a free EU server, graciously provided by [Codext GmbH](https://codext.link/rustdesk?utm_source=github) From d335cdbb0c9f840f8ad2518497025670b6bc9cf5 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:25:00 +0800 Subject: [PATCH 026/210] Update README.md (#9196) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02c1eee7595..c193967d0b5 100644 --- a/README.md +++ b/README.md @@ -174,4 +174,4 @@ Please ensure that you are running these commands from the root of the RustDesk ## [Public Servers](#public-servers) -RustDesk is supported by a free EU server, graciously provided by [Codext GmbH](https://codext.link/rustdesk?utm_source=github) +RustDesk is supported by a free EU server, graciously provided by [Codext GmbH](https://codext.link/rustdesk?utm_source=github) From 832002a10f51142ee38ee09ea14210af05c89a7d Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 29 Aug 2024 23:38:08 +0800 Subject: [PATCH 027/210] refact: web, remote toolbar, pin (#9206) Signed-off-by: fufesou --- flutter/lib/desktop/widgets/remote_toolbar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index af177f84050..f881bc84007 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -452,8 +452,8 @@ class _RemoteToolbarState extends State { Widget _buildToolbar(BuildContext context) { final List toolbarItems = []; + toolbarItems.add(_PinMenu(state: widget.state)); if (!isWebDesktop) { - toolbarItems.add(_PinMenu(state: widget.state)); toolbarItems.add(_MobileActionMenu(ffi: widget.ffi)); } From e3f6829d025152adb1d1fcac7d9d38dfee4f2cf3 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:37:38 +0800 Subject: [PATCH 028/210] refact: android ios, lan discovery (#9207) * refact: android ios, lan discovery Signed-off-by: fufesou * fix: build and runtime error Signed-off-by: fufesou --------- Signed-off-by: fufesou --- src/lan.rs | 38 +++++++++++++++++++++++++++++++------- src/lib.rs | 2 -- src/platform/mod.rs | 1 + src/rendezvous_mediator.rs | 5 ++++- src/ui_interface.rs | 1 - 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/lan.rs b/src/lan.rs index 666e73c7c3d..7d3f4f05fed 100644 --- a/src/lan.rs +++ b/src/lan.rs @@ -1,4 +1,3 @@ -#[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::config::Config; use hbb_common::{ allow_err, @@ -22,7 +21,7 @@ use std::{ type Message = RendezvousMessage; -#[cfg(not(any(target_os = "android", target_os = "ios")))] +#[cfg(not(target_os = "ios"))] pub(super) fn start_listening() -> ResultType<()> { let addr = SocketAddr::from(([0, 0, 0, 0], get_broadcast_port())); let socket = std::net::UdpSocket::bind(addr)?; @@ -40,13 +39,22 @@ pub(super) fn start_listening() -> ResultType<()> { &Config::get_option("enable-lan-discovery"), ) { + let id = Config::get_id(); + if p.id == id { + continue; + } if let Some(self_addr) = get_ipaddr_by_peer(&addr) { let mut msg_out = Message::new(); + let mut hostname = whoami::hostname(); + // The default hostname is "localhost" which is a bit confusing + if hostname == "localhost" { + hostname = "unknown".to_owned(); + } let peer = PeerDiscovery { cmd: "pong".to_owned(), mac: get_mac(&self_addr), - id: Config::get_id(), - hostname: whoami::hostname(), + id, + hostname, username: crate::platform::get_active_username(), platform: whoami::platform().to_string(), ..Default::default() @@ -100,17 +108,17 @@ fn get_broadcast_port() -> u16 { } fn get_mac(_ip: &IpAddr) -> String { - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(target_os = "ios"))] if let Ok(mac) = get_mac_by_ip(_ip) { mac.to_string() } else { "".to_owned() } - #[cfg(any(target_os = "android", target_os = "ios"))] + #[cfg(target_os = "ios")] "".to_owned() } -#[cfg(not(any(target_os = "android", target_os = "ios")))] +#[cfg(not(target_os = "ios"))] fn get_mac_by_ip(ip: &IpAddr) -> ResultType { for interface in default_net::get_interfaces() { match ip { @@ -153,6 +161,10 @@ fn get_ipaddr_by_peer(peer: A) -> Option { fn create_broadcast_sockets() -> Vec { let mut ipv4s = Vec::new(); + // TODO: maybe we should use a better way to get ipv4 addresses. + // But currently, it's ok to use `[Ipv4Addr::UNSPECIFIED]` for discovery. + // `default_net::get_interfaces()` causes undefined symbols error when `flutter build` on iOS simulator x86_64 + #[cfg(not(any(target_os = "ios")))] for interface in default_net::get_interfaces() { for ipv4 in &interface.ipv4 { ipv4s.push(ipv4.addr.clone()); @@ -178,8 +190,20 @@ fn send_query() -> ResultType> { } let mut msg_out = Message::new(); + // We may not be able to get the mac address on mobile platforms. + // So we need to use the id to avoid discovering ourselves. + #[cfg(any(target_os = "android", target_os = "ios"))] + let id = crate::ui_interface::get_id(); + // `crate::ui_interface::get_id()` will cause error: + // `get_id()` uses async code with `current_thread`, which is not allowed in this context. + // + // No need to get id for desktop platforms. + // We can use the mac address to identify the device. + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let id = "".to_owned(); let peer = PeerDiscovery { cmd: "ping".to_owned(), + id, ..Default::default() }; msg_out.set_peer_discovery(peer); diff --git a/src/lib.rs b/src/lib.rs index f8d917a518e..7f9ca4e9aaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ mod keyboard; -#[cfg(not(any(target_os = "ios")))] /// cbindgen:ignore pub mod platform; #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -12,7 +11,6 @@ mod server; #[cfg(not(any(target_os = "ios")))] pub use self::server::*; mod client; -#[cfg(not(any(target_os = "ios")))] mod lan; #[cfg(not(any(target_os = "ios")))] mod rendezvous_mediator; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index fe66e50dc69..7bb503fdd5c 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -100,6 +100,7 @@ impl WakeLock { } } +#[cfg(not(target_os = "ios"))] pub fn get_wakelock(_display: bool) -> WakeLock { hbb_common::log::info!("new wakelock, require display on: {_display}"); #[cfg(target_os = "android")] diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index bd628ec66ef..f5d81eaffa6 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -76,8 +76,11 @@ impl RendezvousMediator { tokio::spawn(async move { direct_server(server_cloned).await; }); + #[cfg(target_os = "android")] + let start_lan_listening = true; #[cfg(not(any(target_os = "android", target_os = "ios")))] - if crate::platform::is_installed() { + let start_lan_listening = crate::platform::is_installed(); + if start_lan_listening { std::thread::spawn(move || { allow_err!(super::lan::start_listening()); }); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 9c3864f0c3c..8489179ba67 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -692,7 +692,6 @@ pub fn create_shortcut(_id: String) { #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn discover() { - #[cfg(not(any(target_os = "ios")))] std::thread::spawn(move || { allow_err!(crate::lan::discover()); }); From bf390611ab1c44c3492d329ae949ebed15ce3b60 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:02:50 +0800 Subject: [PATCH 029/210] fix: keyboard, sciter (#9216) Signed-off-by: fufesou --- src/flutter_ffi.rs | 14 ++------------ src/keyboard.rs | 2 ++ src/ui/header.tis | 7 +++++-- src/ui/remote.rs | 1 + src/ui_session_interface.rs | 12 ++++++++++++ 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 48efda3af7a..8cc47531614 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1,6 +1,6 @@ use crate::{ client::file_trait::FileManager, - common::{is_keyboard_mode_supported, make_fd_to_json}, + common::make_fd_to_json, flutter::{ self, session_add, session_add_existed, session_start_, sessions, try_sync_peer_option, }, @@ -19,13 +19,11 @@ use hbb_common::allow_err; use hbb_common::{ config::{self, LocalConfig, PeerConfig, PeerInfoSerde}, fs, lazy_static, log, - message_proto::KeyboardMode, rendezvous_proto::ConnType, ResultType, }; use std::{ collections::HashMap, - str::FromStr, sync::{ atomic::{AtomicI32, Ordering}, Arc, @@ -447,15 +445,7 @@ pub fn session_get_custom_image_quality(session_id: SessionID) -> Option SyncReturn { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - if let Ok(mode) = KeyboardMode::from_str(&mode[..]) { - SyncReturn(is_keyboard_mode_supported( - &mode, - session.get_peer_version(), - &session.peer_platform(), - )) - } else { - SyncReturn(false) - } + SyncReturn(session.is_keyboard_mode_supported(mode)) } else { SyncReturn(false) } diff --git a/src/keyboard.rs b/src/keyboard.rs index 6b4b0988fb2..e9bff5d8118 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -34,6 +34,7 @@ const OS_LOWER_ANDROID: &str = "android"; #[cfg(any(target_os = "windows", target_os = "macos"))] static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false); +#[cfg(feature = "flutter")] #[cfg(not(any(target_os = "android", target_os = "ios")))] static IS_RDEV_ENABLED: AtomicBool = AtomicBool::new(false); @@ -71,6 +72,7 @@ pub mod client { #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn change_grab_status(state: GrabState, keyboard_mode: &str) { + #[cfg(feature = "flutter")] if !IS_RDEV_ENABLED.load(Ordering::SeqCst) { return; } diff --git a/src/ui/header.tis b/src/ui/header.tis index c4e76528097..e99d398aa73 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -152,10 +152,13 @@ class Header: Reactor.Component { } function renderKeyboardPop(){ + const is_map_mode_supported = handler.is_keyboard_mode_supported("map"); + const is_translate_mode_supported = handler.is_keyboard_mode_supported("translate"); return -
  • {svg_checkmark}{translate('Legacy mode')}
  • -
  • {svg_checkmark}{translate('Map mode')}
  • +
  • {svg_checkmark}{translate('Legacy mode')}
  • + { is_map_mode_supported &&
  • {svg_checkmark}{translate('Map mode')}
  • } + { is_translate_mode_supported &&
  • {svg_checkmark}{translate('Translate mode')}
  • }
    ; } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 0ad84e8ed15..baf9d1f6451 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -487,6 +487,7 @@ impl sciter::EventHandler for SciterSession { fn peer_platform(); fn set_write_override(i32, i32, bool, bool, bool); fn get_keyboard_mode(); + fn is_keyboard_mode_supported(String); fn save_keyboard_mode(String); fn alternative_codecs(); fn change_prefer_codec(); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 6686e5419e5..ce28b78d8e9 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -252,6 +252,18 @@ impl Session { self.fallback_keyboard_mode() } + pub fn is_keyboard_mode_supported(&self, mode: String) -> bool { + if let Ok(mode) = KeyboardMode::from_str(&mode[..]) { + crate::common::is_keyboard_mode_supported( + &mode, + self.get_peer_version(), + &self.peer_platform(), + ) + } else { + false + } + } + pub fn save_keyboard_mode(&self, value: String) { self.lc.write().unwrap().save_keyboard_mode(value); } From ae339f039df1b5acdc149b8079a0d77d04c4f132 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 1 Sep 2024 00:30:07 +0800 Subject: [PATCH 030/210] refact: web ui (#9217) * refact: web ui Signed-off-by: fufesou * refact: remove AppBar shadow Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/common.dart | 10 +- .../desktop/pages/desktop_setting_page.dart | 104 +++++++++++------- flutter/lib/mobile/pages/connection_page.dart | 77 +------------ flutter/lib/mobile/pages/home_page.dart | 9 +- flutter/lib/web/bridge.dart | 16 +-- flutter/lib/web/settings_page.dart | 98 +++++++++++++++++ 6 files changed, 188 insertions(+), 126 deletions(-) create mode 100644 flutter/lib/web/settings_page.dart diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index fa1ea11b595..efcff4f83f4 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -350,6 +350,9 @@ class MyTheme { hoverColor: Color.fromARGB(255, 224, 224, 224), scaffoldBackgroundColor: Colors.white, dialogBackgroundColor: Colors.white, + appBarTheme: AppBarTheme( + shadowColor: Colors.transparent, + ), dialogTheme: DialogTheme( elevation: 15, shape: RoundedRectangleBorder( @@ -445,6 +448,9 @@ class MyTheme { hoverColor: Color.fromARGB(255, 45, 46, 53), scaffoldBackgroundColor: Color(0xFF18191E), dialogBackgroundColor: Color(0xFF18191E), + appBarTheme: AppBarTheme( + shadowColor: Colors.transparent, + ), dialogTheme: DialogTheme( elevation: 15, shape: RoundedRectangleBorder( @@ -550,7 +556,7 @@ class MyTheme { static void changeDarkMode(ThemeMode mode) async { Get.changeThemeMode(mode); - if (desktopType == DesktopType.main || isAndroid || isIOS) { + if (desktopType == DesktopType.main || isAndroid || isIOS || isWeb) { if (mode == ThemeMode.system) { await bind.mainSetLocalOption( key: kCommConfKeyTheme, value: defaultOptionTheme); @@ -558,7 +564,7 @@ class MyTheme { await bind.mainSetLocalOption( key: kCommConfKeyTheme, value: mode.toShortString()); } - await bind.mainChangeTheme(dark: mode.toShortString()); + if (!isWeb) await bind.mainChangeTheme(dark: mode.toShortString()); // Synchronize the window theme of the system. updateSystemWindowTheme(); } diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 6d9f92b59dc..4ab1a8c5667 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -61,7 +61,8 @@ class DesktopSettingPage extends StatefulWidget { final SettingsTabKey initialTabkey; static final List tabKeys = [ SettingsTabKey.general, - if (!bind.isOutgoingOnly() && + if (!isWeb && + !bind.isOutgoingOnly() && !bind.isDisableSettings() && bind.mainGetBuildinOption(key: kOptionHideSecuritySetting) != 'Y') SettingsTabKey.safety, @@ -216,7 +217,7 @@ class _DesktopSettingPageState extends State width: _kTabWidth, child: Column( children: [ - _header(), + _header(context), Flexible(child: _listView(tabs: _settingTabs())), ], ), @@ -239,21 +240,40 @@ class _DesktopSettingPageState extends State ); } - Widget _header() { + Widget _header(BuildContext context) { + final settingsText = Text( + translate('Settings'), + textAlign: TextAlign.left, + style: const TextStyle( + color: _accentColor, + fontSize: _kTitleFontSize, + fontWeight: FontWeight.w400, + ), + ); return Row( children: [ - SizedBox( - height: 62, - child: Text( - translate('Settings'), - textAlign: TextAlign.left, - style: const TextStyle( - color: _accentColor, - fontSize: _kTitleFontSize, - fontWeight: FontWeight.w400, + if (isWeb) + IconButton( + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + }, + icon: Icon(Icons.arrow_back), + ).marginOnly(left: 5), + if (isWeb) + SizedBox( + height: 62, + child: Align( + alignment: Alignment.center, + child: settingsText, ), - ), - ).marginOnly(left: 20, top: 10), + ).marginOnly(left: 20), + if (!isWeb) + SizedBox( + height: 62, + child: settingsText, + ).marginOnly(left: 20, top: 10), const Spacer(), ], ); @@ -322,7 +342,8 @@ class _General extends StatefulWidget { } class _GeneralState extends State<_General> { - final RxBool serviceStop = Get.find(tag: 'stop-service'); + final RxBool serviceStop = + isWeb ? RxBool(false) : Get.find(tag: 'stop-service'); RxBool serviceBtnEnabled = true.obs; @override @@ -334,13 +355,13 @@ class _GeneralState extends State<_General> { physics: DraggableNeverScrollableScrollPhysics(), controller: scrollController, children: [ - service(), + if (!isWeb) service(), theme(), _Card(title: 'Language', children: [language()]), - hwcodec(), - audio(context), - record(context), - WaylandCard(), + if (!isWeb) hwcodec(), + if (!isWeb) audio(context), + if (!isWeb) record(context), + if (!isWeb) WaylandCard(), other() ], ).marginOnly(bottom: _kListViewBottomMargin)); @@ -394,13 +415,13 @@ class _GeneralState extends State<_General> { Widget other() { final children = [ - if (!bind.isIncomingOnly()) + if (!isWeb && !bind.isIncomingOnly()) _OptionCheckBox(context, 'Confirm before closing multiple tabs', kOptionEnableConfirmClosingTabs, isServer: false), _OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr), - wallpaper(), - if (!bind.isIncomingOnly()) ...[ + if (!isWeb) wallpaper(), + if (!isWeb && !bind.isIncomingOnly()) ...[ _OptionCheckBox( context, 'Open connection in new tab', @@ -417,18 +438,19 @@ class _GeneralState extends State<_General> { kOptionAllowAlwaysSoftwareRender, ), ), - Tooltip( - message: translate('texture_render_tip'), - child: _OptionCheckBox( - context, - "Use texture rendering", - kOptionTextureRender, - optGetter: bind.mainGetUseTextureRender, - optSetter: (k, v) async => - await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'), + if (!isWeb) + Tooltip( + message: translate('texture_render_tip'), + child: _OptionCheckBox( + context, + "Use texture rendering", + kOptionTextureRender, + optGetter: bind.mainGetUseTextureRender, + optSetter: (k, v) async => + await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'), + ), ), - ), - if (!bind.isCustomClient()) + if (!isWeb && !bind.isCustomClient()) _OptionCheckBox( context, 'Check for software update on startup', @@ -443,7 +465,7 @@ class _GeneralState extends State<_General> { ) ], ]; - if (bind.mainShowOption(key: kOptionAllowLinuxHeadless)) { + if (!isWeb && bind.mainShowOption(key: kOptionAllowLinuxHeadless)) { children.add(_OptionCheckBox( context, 'Allow linux headless', kOptionAllowLinuxHeadless)); } @@ -641,8 +663,9 @@ class _GeneralState extends State<_General> { initialKey: currentKey, onChanged: (key) async { await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key); - reloadAllWindows(); - bind.mainChangeLanguage(lang: key); + if (isWeb) reloadCurrentWindow(); + if (!isWeb) reloadAllWindows(); + if (!isWeb) bind.mainChangeLanguage(lang: key); }, enabled: !isOptFixed, ).marginOnly(left: _kContentHMargin); @@ -1337,7 +1360,7 @@ class _Network extends StatefulWidget { class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; - bool locked = bind.mainIsInstalled(); + bool locked = !isWeb && bind.mainIsInstalled(); @override Widget build(BuildContext context) { @@ -1346,8 +1369,9 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { final scrollController = ScrollController(); final hideServer = bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y'; + // TODO: support web proxy final hideProxy = - bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; + isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; return DesktopScrollWrapper( scrollController: scrollController, child: ListView( @@ -1467,7 +1491,7 @@ class _DisplayState extends State<_Display> { scrollStyle(context), imageQuality(context), codec(context), - privacyModeImpl(context), + if (!isWeb) privacyModeImpl(context), other(context), ]).marginOnly(bottom: _kListViewBottomMargin)); } diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 02c552d716d..9fcef8e3f14 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -9,19 +9,16 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import '../../common.dart'; -import '../../common/widgets/login.dart'; import '../../common/widgets/peer_tab_page.dart'; import '../../common/widgets/autocomplete.dart'; import '../../consts.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; import 'home_page.dart'; -import 'scan_page.dart'; -import 'settings_page.dart'; /// Connection page for connecting to a remote peer. class ConnectionPage extends StatefulWidget implements PageShape { - ConnectionPage({Key? key}) : super(key: key); + ConnectionPage({Key? key, required this.appBarActions}) : super(key: key); @override final icon = const Icon(Icons.connected_tv); @@ -30,7 +27,7 @@ class ConnectionPage extends StatefulWidget implements PageShape { final title = translate("Connection"); @override - final appBarActions = isWeb ? [const WebMenu()] : []; + final List appBarActions; @override State createState() => _ConnectionPageState(); @@ -356,73 +353,3 @@ class _ConnectionPageState extends State { super.dispose(); } } - -class WebMenu extends StatefulWidget { - const WebMenu({Key? key}) : super(key: key); - - @override - State createState() => _WebMenuState(); -} - -class _WebMenuState extends State { - @override - Widget build(BuildContext context) { - Provider.of(context); - return PopupMenuButton( - tooltip: "", - icon: const Icon(Icons.more_vert), - itemBuilder: (context) { - return (isIOS - ? [ - const PopupMenuItem( - value: "scan", - child: Icon(Icons.qr_code_scanner, color: Colors.black), - ) - ] - : >[]) + - [ - PopupMenuItem( - value: "server", - child: Text(translate('ID/Relay Server')), - ) - ] + - [ - PopupMenuItem( - value: "login", - child: Text(gFFI.userModel.userName.value.isEmpty - ? translate("Login") - : '${translate("Logout")} (${gFFI.userModel.userName.value})'), - ) - ] + - [ - PopupMenuItem( - value: "about", - child: Text(translate('About RustDesk')), - ) - ]; - }, - onSelected: (value) { - if (value == 'server') { - showServerSettings(gFFI.dialogManager); - } - if (value == 'about') { - showAbout(gFFI.dialogManager); - } - if (value == 'login') { - if (gFFI.userModel.userName.value.isEmpty) { - loginDialog(); - } else { - logOutConfirmDialog(); - } - } - if (value == 'scan') { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => ScanPage(), - ), - ); - } - }); - } -} diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index 078e2b2f7b3..0db7a2b91e3 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/mobile/pages/server_page.dart'; import 'package:flutter_hbb/mobile/pages/settings_page.dart'; +import 'package:flutter_hbb/web/settings_page.dart'; import 'package:get/get.dart'; import '../../common.dart'; import '../../common/widgets/chat_page.dart'; @@ -45,7 +46,10 @@ class HomePageState extends State { void initPages() { _pages.clear(); - if (!bind.isIncomingOnly()) _pages.add(ConnectionPage()); + if (!bind.isIncomingOnly()) + _pages.add(ConnectionPage( + appBarActions: [], + )); if (isAndroid && !bind.isOutgoingOnly()) { _chatPageTabIndex = _pages.length; _pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]); @@ -149,7 +153,8 @@ class HomePageState extends State { } class WebHomePage extends StatelessWidget { - final connectionPage = ConnectionPage(); + final connectionPage = + ConnectionPage(appBarActions: [const WebSettingsPage()]); @override Widget build(BuildContext context) { diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 3aa65a5be69..305ed0b7550 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -736,7 +736,8 @@ class RustdeskImpl { } Future mainGetLicense({dynamic hint}) { - throw UnimplementedError(); + // TODO: implement + return Future(() => ''); } Future mainGetVersion({dynamic hint}) { @@ -975,10 +976,11 @@ class RustdeskImpl { Future mainSetUserDefaultOption( {required String key, required String value, dynamic hint}) { - return js.context.callMethod('getByName', [ + js.context.callMethod('setByName', [ 'option:user:default', jsonEncode({'name': key, 'value': value}) ]); + return Future.value(); } String mainGetUserDefaultOption({required String key, dynamic hint}) { @@ -1052,7 +1054,7 @@ class RustdeskImpl { } Future mainGetLangs({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['langs'])); } Future mainGetTemporaryPassword({dynamic hint}) { @@ -1064,7 +1066,8 @@ class RustdeskImpl { } Future mainGetFingerprint({dynamic hint}) { - throw UnimplementedError(); + // TODO: implement + return Future.value(''); } Future cmGetClientsState({dynamic hint}) { @@ -1106,7 +1109,7 @@ class RustdeskImpl { } String mainSupportedHwdecodings({dynamic hint}) { - throw UnimplementedError(); + return '{}'; } Future mainIsRoot({dynamic hint}) { @@ -1295,8 +1298,7 @@ class RustdeskImpl { } Future mainGetBuildDate({dynamic hint}) { - // TODO - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['build_date'])); } String translate( diff --git a/flutter/lib/web/settings_page.dart b/flutter/lib/web/settings_page.dart new file mode 100644 index 00000000000..13ba6cb2f27 --- /dev/null +++ b/flutter/lib/web/settings_page.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; +import 'package:flutter_hbb/mobile/pages/scan_page.dart'; +import 'package:flutter_hbb/mobile/pages/settings_page.dart'; +import 'package:provider/provider.dart'; + +import '../../common.dart'; +import '../../common/widgets/login.dart'; +import '../../models/model.dart'; + +class WebSettingsPage extends StatelessWidget { + const WebSettingsPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (isWebDesktop) { + return _buildDesktopButton(context); + } else { + return _buildMobileMenu(context); + } + } + + Widget _buildDesktopButton(BuildContext context) { + return IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + DesktopSettingPage(initialTabkey: SettingsTabKey.general), + ), + ); + }, + ); + } + + Widget _buildMobileMenu(BuildContext context) { + Provider.of(context); + return PopupMenuButton( + tooltip: "", + icon: const Icon(Icons.more_vert), + itemBuilder: (context) { + return (isIOS + ? [ + const PopupMenuItem( + value: "scan", + child: Icon(Icons.qr_code_scanner, color: Colors.black), + ) + ] + : >[]) + + [ + PopupMenuItem( + value: "server", + child: Text(translate('ID/Relay Server')), + ) + ] + + [ + PopupMenuItem( + value: "login", + child: Text(gFFI.userModel.userName.value.isEmpty + ? translate("Login") + : '${translate("Logout")} (${gFFI.userModel.userName.value})'), + ) + ] + + [ + PopupMenuItem( + value: "about", + child: Text(translate('About RustDesk')), + ) + ]; + }, + onSelected: (value) { + if (value == 'server') { + showServerSettings(gFFI.dialogManager); + } + if (value == 'about') { + showAbout(gFFI.dialogManager); + } + if (value == 'login') { + if (gFFI.userModel.userName.value.isEmpty) { + loginDialog(); + } else { + logOutConfirmDialog(); + } + } + if (value == 'scan') { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => ScanPage(), + ), + ); + } + }); + } +} + From 532fe6aefbecffc4bfc9799f67839f8b7dc55e9f Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 1 Sep 2024 23:14:57 +0800 Subject: [PATCH 031/210] refact: web ui, login (#9225) Signed-off-by: fufesou --- flutter/lib/web/bridge.dart | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 305ed0b7550..d4b09430aa1 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:uuid/uuid.dart'; +import 'dart:html' as html; import 'package:flutter_hbb/consts.dart'; @@ -655,7 +656,15 @@ class RustdeskImpl { } String mainGetLoginDeviceInfo({dynamic hint}) { - throw UnimplementedError(); + String userAgent = html.window.navigator.userAgent; + String appName = html.window.navigator.appName; + String appVersion = html.window.navigator.appVersion; + String? platform = html.window.navigator.platform; + return jsonEncode({ + 'os': '$userAgent, $appName $appVersion ($platform)', + 'type': 'Web client', + 'name': js.context.callMethod('getByName', ['my_name']), + }); } Future mainChangeId({required String newId, dynamic hint}) { @@ -850,11 +859,11 @@ class RustdeskImpl { } Future mainGetMyId({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['my_id'])); } Future mainGetUuid({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['uuid'])); } Future mainGetPeerOption( @@ -1066,8 +1075,7 @@ class RustdeskImpl { } Future mainGetFingerprint({dynamic hint}) { - // TODO: implement - return Future.value(''); + return Future(() => js.context.callMethod('getByName', ['fingerprint'])); } Future cmGetClientsState({dynamic hint}) { From 827efabbc00403654e914e4f4663951e5ca06b25 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 2 Sep 2024 02:00:59 +0800 Subject: [PATCH 032/210] refact: remove fingerprint for web (#9226) Signed-off-by: fufesou --- flutter/lib/desktop/pages/desktop_setting_page.dart | 7 ++++--- flutter/lib/web/bridge.dart | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 4ab1a8c5667..0e5fac4caf5 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1902,9 +1902,10 @@ class _AboutState extends State<_About> { SelectionArea( child: Text('${translate('Build Date')}: $buildDate') .marginSymmetric(vertical: 4.0)), - SelectionArea( - child: Text('${translate('Fingerprint')}: $fingerprint') - .marginSymmetric(vertical: 4.0)), + if (!isWeb) + SelectionArea( + child: Text('${translate('Fingerprint')}: $fingerprint') + .marginSymmetric(vertical: 4.0)), InkWell( onTap: () { launchUrlString('https://rustdesk.com/privacy.html'); diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index d4b09430aa1..8805831bc53 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1075,7 +1075,7 @@ class RustdeskImpl { } Future mainGetFingerprint({dynamic hint}) { - return Future(() => js.context.callMethod('getByName', ['fingerprint'])); + return Future.value(''); } Future cmGetClientsState({dynamic hint}) { From 75a4671bda78d0adba0d347cb2081bc650161647 Mon Sep 17 00:00:00 2001 From: SimonHanel <43139362+SimonHanel@users.noreply.github.com> Date: Tue, 3 Sep 2024 04:09:25 +0200 Subject: [PATCH 033/210] Update da.rs (#9238) * Update da.rs Filled out every empty string and adjusted some for better translation. Some translations might be janky due to my lack of context for what the string is used for, but it's good enough for now. * Update da.rs Minor tweaks --- src/lang/da.rs | 442 ++++++++++++++++++++++++------------------------- 1 file changed, 221 insertions(+), 221 deletions(-) diff --git a/src/lang/da.rs b/src/lang/da.rs index a9e286600b6..03cf47d4bb8 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ("Status", "Status"), ("Your Desktop", "Dit skrivebord"), - ("desk_tip", "Du kan få adgang til dit skrivebord med dette ID og adgangskode."), + ("desk_tip", "Du kan give adgang til dit skrivebord med dette ID og denne adgangskode."), ("Password", "Adgangskode"), ("Ready", "Klar"), ("Established", "Etableret"), @@ -38,18 +38,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop service", "Sluk for forbindelsesserveren"), ("Change ID", "Ændr ID"), ("Your new ID", "Dit nye ID"), - ("length %min% to %max%", ""), - ("starts with a letter", ""), - ("allowed characters", ""), - ("id_change_tip", "Kun tegnene a-z, A-Z, 0-9 og _ (understregning) er tilladt. Det første bogstav skal være a-z, A-Z. Længde mellem 6 og 16."), + ("length %min% to %max%", "længde %min% til %max%"), + ("starts with a letter", "starter med ét bogstav"), + ("allowed characters", "tilladte tegn"), + ("id_change_tip", "Kun tegnene a-z, A-Z, 0-9 og _ (understregning) er tilladt. Det første bogstav skal være a-z, A-Z. Antal tegn skal være mellem 6 og 16."), ("Website", "Hjemmeside"), ("About", "Om"), - ("Slogan_tip", ""), - ("Privacy Statement", ""), + ("Slogan_tip", "Lavet med kærlighed i denne kaotiske verden!"), + ("Privacy Statement", "Privatlivspolitik"), ("Mute", "Sluk for mikrofonen"), - ("Build Date", ""), - ("Version", ""), - ("Home", ""), + ("Build Date", "Build dato"), + ("Version", "Version"), + ("Home", "Hjem"), ("Audio Input", "Lydinput"), ("Enhancements", "Forbedringer"), ("Hardware Codec", "Hardware-codec"), @@ -120,8 +120,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Original", "Original"), ("Shrink", "Krymp"), ("Stretch", "Stræk ud"), - ("Scrollbar", "Rullebar"), - ("ScrollAuto", "Auto-rul"), + ("Scrollbar", "Scrollbar"), + ("ScrollAuto", "Auto-scroll"), ("Good image quality", "God billedkvalitet"), ("Balanced", "Afbalanceret"), ("Optimize reaction time", "Optimeret responstid"), @@ -139,9 +139,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Remote desktop is offline", "Fjernskrivebord er offline"), ("Key mismatch", "Nøgle uoverensstemmelse"), ("Timeout", "Timeout"), - ("Failed to connect to relay server", "Forbindelse til relæ-serveren mislykkedes"), + ("Failed to connect to relay server", "Forbindelse til relay-serveren mislykkedes"), ("Failed to connect via rendezvous server", "Forbindelse via Rendezvous-server mislykkedes"), - ("Failed to connect via relay server", "Forbindelse via relæ-serveren mislykkedes"), + ("Failed to connect via relay server", "Forbindelse via relay-serveren mislykkedes"), ("Failed to make direct connection to remote desktop", "Direkte forbindelse til fjernskrivebord kunne ikke etableres"), ("Set Password", "Indstil adgangskode"), ("OS Password", "Operativsystemadgangskode"), @@ -218,7 +218,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Remember me", "Husk mig"), ("Trust this device", "Husk denne enhed"), ("Verification code", "Verifikationskode"), - ("verification_tip", ""), + ("verification_tip", "En bekræftelseskode er blevet sendt til den registrerede e-mail adresse. Indtast bekræftelseskoden for at logge på."), ("Logout", "Logger af"), ("Tags", "Nøgleord"), ("Search ID", "Søg efter ID"), @@ -230,7 +230,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Username missed", "Glemt brugernavn"), ("Password missed", "Glemt kodeord"), ("Wrong credentials", "Forkerte registreringsdata"), - ("The verification code is incorrect or has expired", ""), + ("The verification code is incorrect or has expired", "Bekræftelsesnøglen er forkert eller er udløbet"), ("Edit Tag", "Rediger nøgleord"), ("Forget Password", "Glem adgangskoden"), ("Favorites", "Favoritter"), @@ -248,7 +248,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Are you sure to close the connection?", "Er du sikker på at du vil afslutte forbindelsen?"), ("Download new version", "Download ny version"), ("Touch mode", "Touch-tilstand"), - ("Mouse mode", "Musse-tilstand"), + ("Mouse mode", "Muse-tilstand"), ("One-Finger Tap", "En-finger-tryk"), ("Left Mouse", "Venstre mus"), ("One-Long Tap", "Tryk og hold med en finger"), @@ -286,8 +286,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Ved at tænde for skærmoptagelsen startes tjenesten automatisk, så andre enheder kan anmode om en forbindelse fra denne enhed."), ("android_stop_service_tip", "Ved at lukke tjenesten lukkes alle fremstillede forbindelser automatisk."), ("android_version_audio_tip", "Den aktuelle Android-version understøtter ikke lydoptagelse. Android 10 eller højere er påkrævet."), - ("android_start_service_tip", ""), - ("android_permission_may_not_change_tip", ""), + ("android_start_service_tip", "Tryk [Start tjeneste] eller aktivér [Skærmoptagelse] tilladelse for at dele skærmen."), + ("android_permission_may_not_change_tip", "Rettigheder til oprettede forbindelser ændres ikke med det samme før der forbindelsen genoprettes."), ("Account", "Konto"), ("Overwrite", "Overskriv"), ("This file exists, skip or overwrite this file?", "Denne fil findes allerede, vil du springe over eller overskrive denne fil?"), @@ -305,7 +305,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Language", "Sprog"), ("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"), ("Ignore Battery Optimizations", "Ignorér betteri optimeringer"), - ("android_open_battery_optimizations_tip", ""), + ("android_open_battery_optimizations_tip", "Hvis du ønsker at slukke for denne funktion, åbn RustDesk appens indstillinger, tryk på [Batteri], og fjern flueben ved [Uden begrænsninger]"), ("Start on boot", "Start under opstart"), ("Start the screen sharing service on boot, requires special permissions", "Start skærmdelingstjenesten under opstart, kræver specielle rettigheder"), ("Connection not allowed", "Forbindelse ikke tilladt"), @@ -313,7 +313,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Map mode", "Kortmodus"), ("Translate mode", "Oversættelsesmodus"), ("Use permanent password", "Brug permanent adgangskode"), - ("Use both passwords", "Brug begge adgangskoder"), + ("Use both passwords", "Brug begge typer adgangskoder"), ("Set permanent password", "Sæt permanent adgangskode"), ("Enable remote restart", "Aktivér fjerngenstart"), ("Restart remote device", "Genstart fjernenhed"), @@ -330,8 +330,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ratio", "Forhold"), ("Image Quality", "Billedkvalitet"), ("Scroll Style", "Rullestil"), - ("Show Toolbar", ""), - ("Hide Toolbar", ""), + ("Show Toolbar", "Vis værktøjslinje"), + ("Hide Toolbar", "Skjul værktøjslinje"), ("Direct Connection", "Direkte forbindelse"), ("Relay Connection", "Viderestillingsforbindelse"), ("Secure Connection", "Sikker forbindelse"), @@ -359,8 +359,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Lydindgangsenhed"), ("Use IP Whitelisting", "Brug IP Whitelisting"), ("Network", "Netværk"), - ("Pin Toolbar", ""), - ("Unpin Toolbar", ""), + ("Pin Toolbar", "Fastgør værktøjslinjen"), + ("Unpin Toolbar", "Frigiv værktøjslinjen"), ("Recording", "Optager"), ("Directory", "Mappe"), ("Automatically record incoming sessions", "Optag automatisk indgående sessioner"), @@ -368,15 +368,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Start session recording", "Start sessionsoptagelse"), ("Stop session recording", "Stop sessionsoptagelse"), ("Enable recording session", "Aktivér optagelsessession"), - ("Enable LAN discovery", "Aktivér LAN Discovery"), - ("Deny LAN discovery", "Afvis LAN Discovery"), + ("Enable LAN discovery", "Aktivér opdagelse via det lokale netværk"), + ("Deny LAN discovery", "Afvis opdagelse via det lokale netværk"), ("Write a message", "Skriv en besked"), ("Prompt", "Prompt"), ("Please wait for confirmation of UAC...", "Vent venligst på UAC-bekræftelse..."), - ("elevated_foreground_window_tip", ""), + ("elevated_foreground_window_tip", "Det nuværende vindue på fjernskrivebordet kræver højere rettigheder for at køre, så det er midlertidigt ikke muligt at bruge musen og tastaturet. Du kan bede fjernbrugeren om at minimere vinduet, eller trykke på elevér knappen i forbindelsesvinduet. For at undgå dette problem, er det anbefalet at installere RustDesk på fjernenheden."), ("Disconnected", "Afbrudt"), ("Other", "Andre"), - ("Confirm before closing multiple tabs", "Bekræft før du lukker flere faner"), + ("Confirm before closing multiple tabs", "Bekræft nedlukning hvis der er flere faner"), ("Keyboard Settings", "Tastaturindstillinger"), ("Full Access", "Fuld adgang"), ("Screen Share", "Skærmdeling"), @@ -399,8 +399,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("One-time password length", "Engangskode længde"), ("Request access to your device", "Efterspørg adgang til din enhed"), ("Hide connection management window", "Skjul forbindelseshåndteringsvindue"), - ("hide_cm_tip", ""), - ("wayland_experiment_tip", ""), + ("hide_cm_tip", "Tillad at skjule, hvis der kun forbindes ved brug af midlertidige og permanente adgangskoder"), + ("wayland_experiment_tip", "Wayland understøttelse er stadigvæk under udvikling. Hvis du har brug for ubemandet adgang, bedes du bruge X11."), ("Right click to select tabs", "Højreklik for at vælge faner"), ("Skipped", "Sprunget over"), ("Add to address book", "Tilføj til adressebog"), @@ -409,19 +409,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by web console", "Lukket ned manuelt af webkonsollen"), ("Local keyboard type", "Lokal tastatur type"), ("Select local keyboard type", "Vælg lokal tastatur type"), - ("software_render_tip", ""), + ("software_render_tip", "Hvis du bruger et Nvidia grafikkort på Linux, og fjernskrivebordsvinduet lukker ned med det samme efter forbindelsen er oprettet, kan det hjælpe at skifte til Nouveau open-source driveren, og aktivere software rendering. Et genstart af RustDesk er nødvendigt."), ("Always use software rendering", "Brug altid software rendering"), - ("config_input", ""), - ("config_microphone", ""), - ("request_elevation_tip", ""), + ("config_input", "For at styre fjernskrivebordet med tastaturet, skal du give Rustdesk rettigheder til at optage tastetryk"), + ("config_microphone", "For at tale sammen over fjernstyring, skal du give RustDesk rettigheder til at optage lyd"), + ("request_elevation_tip", "Du kan også spørge om elevationsrettigheder, hvis der er nogen i nærheden af fjernenheden."), ("Wait", "Vent"), ("Elevation Error", "Elevationsfejl"), ("Ask the remote user for authentication", "Spørg fjernbrugeren for godkendelse"), ("Choose this if the remote account is administrator", "Vælg dette hvis fjernbrugeren er en administrator"), ("Transmit the username and password of administrator", "Send brugernavnet og adgangskoden på administratoren"), - ("still_click_uac_tip", ""), + ("still_click_uac_tip", "Kræver stadigvæk at fjernbrugeren skal trykke OK på UAC vinduet ved kørsel af RustDesk."), ("Request Elevation", "Efterspørger elevation"), - ("wait_accept_uac_tip", ""), + ("wait_accept_uac_tip", "Vent venligst på at fjernbrugeren accepterer UAC dialog forespørgslen."), ("Elevate successfully", "Elevation lykkedes"), ("uppercase", "store bogstaver"), ("lowercase", "små bogstaver"), @@ -435,7 +435,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please confirm if you want to share your desktop?", "Bekræft venligst, om du vil dele dit skrivebord?"), ("Display", "Visning"), ("Default View Style", "Standard visningsstil"), - ("Default Scroll Style", "Standard rulle stil"), + ("Default Scroll Style", "Standard scrollestil"), ("Default Image Quality", "Standard billedkvalitet"), ("Default Codec", "Standard codec"), ("Bitrate", "Bitrate"), @@ -445,7 +445,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Voice call", "Stemmeopkald"), ("Text chat", "Tekstchat"), ("Stop voice call", "Stop stemmeopkald"), - ("relay_hint_tip", ""), + ("relay_hint_tip", "Det kan ske, at det ikke er muligt at forbinde direkte; du kan forsøge at forbinde via en relay-server. Derudover, hvis du ønsker at bruge en relay-server på dit første forsøg, kan du tilføje \"/r\" efter ID'et, eller bruge valgmuligheden \"Forbind altid via relay-server\" i fanen for seneste sessioner, hvis den findes."), ("Reconnect", "Genopret"), ("Codec", "Codec"), ("Resolution", "Opløsning"), @@ -458,191 +458,191 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Minimize", "Minimér"), ("Maximize", "Maksimér"), ("Your Device", "Din enhed"), - ("empty_recent_tip", ""), - ("empty_favorite_tip", ""), - ("empty_lan_tip", ""), - ("empty_address_book_tip", ""), + ("empty_recent_tip", "Ups, ingen seneste sessioner!\nTid til at oprette en ny."), + ("empty_favorite_tip", "Ingen yndlings modparter endnu?\nLad os finde én at forbinde til, og tilføje den til dine favoritter!"), + ("empty_lan_tip", "Åh nej, det ser ud til, at vi ikke kunne finde nogen modparter endnu."), + ("empty_address_book_tip", "Åh nej, det ser ud til at der ikke er nogle modparter der er tilføjet til din adressebog."), ("eg: admin", "fx: admin"), ("Empty Username", "Tom brugernavn"), ("Empty Password", "Tom adgangskode"), ("Me", "Mig"), - ("identical_file_tip", ""), - ("show_monitors_tip", ""), - ("View Mode", ""), - ("login_linux_tip", ""), - ("verify_rustdesk_password_tip", ""), - ("remember_account_tip", ""), - ("os_account_desk_tip", ""), - ("OS Account", ""), - ("another_user_login_title_tip", ""), - ("another_user_login_text_tip", ""), - ("xorg_not_found_title_tip", ""), - ("xorg_not_found_text_tip", ""), - ("no_desktop_title_tip", ""), - ("no_desktop_text_tip", ""), - ("No need to elevate", ""), - ("System Sound", ""), - ("Default", ""), - ("New RDP", ""), - ("Fingerprint", ""), - ("Copy Fingerprint", ""), - ("no fingerprints", ""), - ("Select a peer", ""), - ("Select peers", ""), - ("Plugins", ""), - ("Uninstall", ""), - ("Update", ""), - ("Enable", ""), - ("Disable", ""), - ("Options", ""), - ("resolution_original_tip", ""), - ("resolution_fit_local_tip", ""), - ("resolution_custom_tip", ""), - ("Collapse toolbar", ""), - ("Accept and Elevate", ""), - ("accept_and_elevate_btn_tooltip", ""), - ("clipboard_wait_response_timeout_tip", ""), - ("Incoming connection", ""), - ("Outgoing connection", ""), - ("Exit", ""), - ("Open", ""), - ("logout_tip", ""), - ("Service", ""), - ("Start", ""), - ("Stop", ""), - ("exceed_max_devices", ""), - ("Sync with recent sessions", ""), - ("Sort tags", ""), - ("Open connection in new tab", ""), - ("Move tab to new window", ""), - ("Can not be empty", ""), - ("Already exists", ""), - ("Change Password", ""), - ("Refresh Password", ""), - ("ID", ""), - ("Grid View", ""), - ("List View", ""), - ("Select", ""), - ("Toggle Tags", ""), - ("pull_ab_failed_tip", ""), - ("push_ab_failed_tip", ""), - ("synced_peer_readded_tip", ""), - ("Change Color", ""), - ("Primary Color", ""), - ("HSV Color", ""), - ("Installation Successful!", ""), - ("Installation failed!", ""), - ("Reverse mouse wheel", ""), - ("{} sessions", ""), - ("scam_title", ""), - ("scam_text1", ""), - ("scam_text2", ""), - ("Don't show again", ""), - ("I Agree", ""), - ("Decline", ""), - ("Timeout in minutes", ""), - ("auto_disconnect_option_tip", ""), - ("Connection failed due to inactivity", ""), - ("Check for software update on startup", ""), - ("upgrade_rustdesk_server_pro_to_{}_tip", ""), - ("pull_group_failed_tip", ""), - ("Filter by intersection", ""), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), - ("Use all my displays for the remote session", ""), - ("selinux_tip", ""), - ("Change view", ""), - ("Big tiles", ""), - ("Small tiles", ""), - ("List", ""), - ("Virtual display", ""), - ("Plug out all", ""), - ("True color (4:4:4)", ""), - ("Enable blocking user input", ""), - ("id_input_tip", ""), - ("privacy_mode_impl_mag_tip", ""), - ("privacy_mode_impl_virtual_display_tip", ""), - ("Enter privacy mode", ""), - ("Exit privacy mode", ""), - ("idd_not_support_under_win10_2004_tip", ""), - ("input_source_1_tip", ""), - ("input_source_2_tip", ""), - ("Swap control-command key", ""), - ("swap-left-right-mouse", ""), - ("2FA code", ""), - ("More", ""), - ("enable-2fa-title", ""), - ("enable-2fa-desc", ""), - ("wrong-2fa-code", ""), - ("enter-2fa-title", ""), - ("Email verification code must be 6 characters.", ""), - ("2FA code must be 6 digits.", ""), - ("Multiple Windows sessions found", ""), - ("Please select the session you want to connect to", ""), - ("powered_by_me", ""), - ("outgoing_only_desk_tip", ""), - ("preset_password_warning", ""), - ("Security Alert", ""), - ("My address book", ""), - ("Personal", ""), - ("Owner", ""), - ("Set shared password", ""), - ("Exist in", ""), - ("Read-only", ""), - ("Read/Write", ""), - ("Full Control", ""), - ("share_warning_tip", ""), - ("Everyone", ""), - ("ab_web_console_tip", ""), - ("allow-only-conn-window-open-tip", ""), - ("no_need_privacy_mode_no_physical_displays_tip", ""), - ("Follow remote cursor", ""), - ("Follow remote window focus", ""), - ("default_proxy_tip", ""), - ("no_audio_input_device_tip", ""), - ("Incoming", ""), - ("Outgoing", ""), - ("Clear Wayland screen selection", ""), - ("clear_Wayland_screen_selection_tip", ""), - ("confirm_clear_Wayland_screen_selection_tip", ""), - ("android_new_voice_call_tip", ""), - ("texture_render_tip", ""), - ("Use texture rendering", ""), - ("Floating window", ""), - ("floating_window_tip", ""), - ("Keep screen on", ""), - ("Never", ""), - ("During controlled", ""), - ("During service is on", ""), - ("Capture screen using DirectX", ""), - ("Back", ""), - ("Apps", ""), - ("Volume up", ""), - ("Volume down", ""), - ("Power", ""), - ("Telegram bot", ""), - ("enable-bot-tip", ""), - ("enable-bot-desc", ""), - ("cancel-2fa-confirm-tip", ""), - ("cancel-bot-confirm-tip", ""), - ("About RustDesk", ""), - ("Send clipboard keystrokes", ""), - ("network_error_tip", ""), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("identical_file_tip", "Denne fil er identisk med modpartens."), + ("show_monitors_tip", "Vis skærme i værktøjsbjælken"), + ("View Mode", "Visningstilstand"), + ("login_linux_tip", "Du skal logge på en fjernstyret Linux konto for at aktivere en X skrivebordssession"), + ("verify_rustdesk_password_tip", "Bekræft RustDesk adgangskode"), + ("remember_account_tip", "Husk denne konto"), + ("os_account_desk_tip", "Denne konto benyttes til at logge på fjernsystemet, og aktivere skrivebordssessionen i hovedløs tilstand"), + ("OS Account", "Styresystem konto"), + ("another_user_login_title_tip", "En anden bruger er allerede logget ind"), + ("another_user_login_text_tip", "Frakobl"), + ("xorg_not_found_title_tip", "Xorg ikke fundet"), + ("xorg_not_found_text_tip", "Installér venlist Xorg"), + ("no_desktop_title_tip", "Intet skrivebordsmiljø er tilgængeligt"), + ("no_desktop_text_tip", "Installér venligst GNOME skrivebordet"), + ("No need to elevate", "Ingen grund til at elevere"), + ("System Sound", "Systemlyd"), + ("Default", "Standard"), + ("New RDP", "Ny RDP"), + ("Fingerprint", "Fingeraftryk"), + ("Copy Fingerprint", "Kopiér fingeraftryk"), + ("no fingerprints", "Ingen fingeraftryk"), + ("Select a peer", "Vælg en peer"), + ("Select peers", "Vælg peers"), + ("Plugins", "Plugins"), + ("Uninstall", "Afinstallér"), + ("Update", "Opdatér"), + ("Enable", "Aktivér"), + ("Disable", "Deaktivér"), + ("Options", "Valgmuligheder"), + ("resolution_original_tip", "Original skærmopløsning"), + ("resolution_fit_local_tip", "Tilpas lokal skærmopløsning"), + ("resolution_custom_tip", "Bruger-tilpasset skærmopløsning"), + ("Collapse toolbar", "Skjul værktøjsbjælke"), + ("Accept and Elevate", "Acceptér og elevér"), + ("accept_and_elevate_btn_tooltip", "Acceptér forbindelsen og elevér UAC tilladelser"), + ("clipboard_wait_response_timeout_tip", "Tiden for at vente på en kopieringsforespørgsel udløb"), + ("Incoming connection", "Indgående forbindelse"), + ("Outgoing connection", "Udgående forbindelse"), + ("Exit", "Afslut"), + ("Open", "Åben"), + ("logout_tip", "Er du sikker på at du vil logge af?"), + ("Service", "Tjeneste"), + ("Start", "Start"), + ("Stop", "Stop"), + ("exceed_max_devices", "Du har nået det maksimale antal håndtérbare enheder."), + ("Sync with recent sessions", "Synkronisér med tidligere sessioner"), + ("Sort tags", "Sortér nøgleord"), + ("Open connection in new tab", "Åbn forbindelse i en ny fane"), + ("Move tab to new window", "Flyt fane i et nyt vindue"), + ("Can not be empty", "Kan ikke være tom"), + ("Already exists", "Findes allerede"), + ("Change Password", "Skift adgangskode"), + ("Refresh Password", "Genopfrisk adgangskode"), + ("ID", "ID"), + ("Grid View", "Gittervisning"), + ("List View", "Listevisning"), + ("Select", "Vælg"), + ("Toggle Tags", "Slå nøgleord til/fra"), + ("pull_ab_failed_tip", "Opdatering af adressebog mislykkedes"), + ("push_ab_failed_tip", "Synkronisering af adressebog til serveren mislykkedes"), + ("synced_peer_readded_tip", "Enhederne, som var til stede i de seneste sessioner, vil blive synkroniseret tilbage til adressebogen."), + ("Change Color", "Skift farve"), + ("Primary Color", "Primær farve"), + ("HSV Color", "HSV farve"), + ("Installation Successful!", "Installation fuldført!"), + ("Installation failed!", "Installation mislykkedes!"), + ("Reverse mouse wheel", "Invertér musehjul"), + ("{} sessions", "{} sessioner"), + ("scam_title", "ADVARSEL: Du kan blive SVINDLET!"), + ("scam_text1", "Hvis du taler telefon med en person du IKKE kender, og IKKE stoler på, som har bedt dig om at bruge RustDesk til at forbinde til din PC, stop med det samme, og læg på omgående."), + ("scam_text2", "Det er højest sandsynligvis en svinder som forsøger at stjæle dine penge eller andre personlige oplysninger."), + ("Don't show again", "Vis ikke igen"), + ("I Agree", "Jeg accepterer"), + ("Decline", "Afvis"), + ("Timeout in minutes", "Udløbstid i minutter"), + ("auto_disconnect_option_tip", "Luk automatisk indkommende sessioner ved inaktivitet"), + ("Connection failed due to inactivity", "Forbindelsen blev afbrudt grundet inaktivitet"), + ("Check for software update on startup", "Søg efter opdateringer ved opstart"), + ("upgrade_rustdesk_server_pro_to_{}_tip", "Opgradér venligst RustDesk Server Pro til version {} eller nyere!"), + ("pull_group_failed_tip", "Genindlæsning af gruppe mislykkedes"), + ("Filter by intersection", "Filtrér efter intersection"), + ("Remove wallpaper during incoming sessions", "Skjul baggrundsskærm ved indgående forbindelser"), + ("Test", "Test"), + ("display_is_plugged_out_msg", "Skærmen er slukket, skift til den første skærm."), + ("No displays", "Ingen skærme"), + ("Open in new window", "Åbn i et nyt vindue"), + ("Show displays as individual windows", "Vis skærme som selvstændige vinduer"), + ("Use all my displays for the remote session", "Brug alle mine skærme til fjernforbindelsen"), + ("selinux_tip", "SELinux er aktiveret på din enhed, som kan forhindre RustDesk i at køre normalt."), + ("Change view", "Skift visning"), + ("Big tiles", "Store fliser"), + ("Small tiles", "Små fliser"), + ("List", "Liste"), + ("Virtual display", "Virtuel skærm"), + ("Plug out all", "Frakobl alt"), + ("True color (4:4:4)", "True color (4:4:4)"), + ("Enable blocking user input", "Aktivér blokering af brugerstyring"), + ("id_input_tip", "Du kan indtaste ét ID, en direkte IP adresse, eller et domæne med en port (:).\nHvis du ønsker at forbinde til en enhed på en anden server, tilføj da server adressen (@?key=), fx,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHvis du ønsker adgang til en enhed på en offentlig server, indtast venligst \"@offentlig server\", nøglen er ikke nødvendig for offentlige servere.\n\nHvis du gerne vil tvinge brugen af en relay-forbindelse på den første forbindelse, tilføj \"/r\" efter ID'et, fx, \"9123456234/r\"."), + ("privacy_mode_impl_mag_tip", "Tilstand 1"), + ("privacy_mode_impl_virtual_display_tip", "Tilstand 2"), + ("Enter privacy mode", "Start privatlivstilstand"), + ("Exit privacy mode", "Afslut privatlivstilstand"), + ("idd_not_support_under_win10_2004_tip", "Indirekte grafik drivere er ikke understøttet. Windows 10 version 2004 eller nyere er påkrævet."), + ("input_source_1_tip", "Input kilde 1"), + ("input_source_2_tip", "Input kilde 2"), + ("Swap control-command key", "Byt rundt på Control & Command tasterne"), + ("swap-left-right-mouse", "Byt rundt på venstre og højre musetaster"), + ("2FA code", "To-faktor kode"), + ("More", "Mere"), + ("enable-2fa-title", "Tænd for to-faktor godkendelse"), + ("enable-2fa-desc", "Åbn din godkendelsesapp nu. Du kan bruge en godkendelsesapp så som Authy, Microsoft eller Google Authenticator på din telefon eller din PC.\n\nScan QR koden med din app og indtast koden som din app fremviser, for at aktivere for to-faktor godkendelse."), + ("wrong-2fa-code", "Kan ikke verificere koden. Forsikr at koden og tidsindstillingerne på enheden er korrekte"), + ("enter-2fa-title", "To-faktor godkendelse"), + ("Email verification code must be 6 characters.", "E-mail bekræftelseskode skal være mindst 6 tegn"), + ("2FA code must be 6 digits.", "To-faktor kode skal være mindst 6 cifre"), + ("Multiple Windows sessions found", "Flere Windows sessioner fundet"), + ("Please select the session you want to connect to", "Vælg venligst sessionen du ønsker at forbinde til"), + ("powered_by_me", "Drives af RustDesk"), + ("outgoing_only_desk_tip", "Dette er en brugertilpasset udgave.\nDu kan forbinde til andre enheder, men andre enheder kan ikke forbinde til din enhed."), + ("preset_password_warning", "Denne brugertilpassede udgave har en forudbestemt adgangskode. Alle der kender til denne adgangskode, kan få fuld adgang til din enhed. Hvis du ikke forventede dette, bør du afinstallere denne udgave af RustDesk med det samme."), + ("Security Alert", "Sikkerhedsalarm"), + ("My address book", "Min adressebog"), + ("Personal", "Personlig"), + ("Owner", "Ejer"), + ("Set shared password", "Sæt delt adgangskode"), + ("Exist in", "Findes i"), + ("Read-only", "Skrivebeskyttet"), + ("Read/Write", "Læse/Skrive"), + ("Full Control", "Fuld kontrol"), + ("share_warning_tip", "Felterne for oven er delt og synlige for andre."), + ("Everyone", "Alle"), + ("ab_web_console_tip", "Mere på web konsollen"), + ("allow-only-conn-window-open-tip", "Tillad kun fjernforbindelser hvis RustDesk vinduet er synligt"), + ("no_need_privacy_mode_no_physical_displays_tip", "Ingen fysiske skærme, ingen nødvendighed for at bruge privatlivstilstanden."), + ("Follow remote cursor", "Følg musemarkør på fjernforbindelse"), + ("Follow remote window focus", "Følg vinduefokus på fjernforbindelse"), + ("default_proxy_tip", "Protokollen og porten som anvendes som standard er Socks5 og 1080"), + ("no_audio_input_device_tip", "Ingen lydinput enhed fundet"), + ("Incoming", "Indgående"), + ("Outgoing", "Udgående"), + ("Clear Wayland screen selection", "Ryd Wayland skærmvalg"), + ("clear_Wayland_screen_selection_tip", "Efter at fravælge den valgte skærm, kan du genvælge skærmen som skal deles."), + ("confirm_clear_Wayland_screen_selection_tip", "Er du sikker på at du vil fjerne Wayland skærmvalget?"), + ("android_new_voice_call_tip", "Du har modtaget en ny stemmeopkaldsforespørgsel. Hvis du accepterer, vil lyden skifte til stemmekommunikation."), + ("texture_render_tip", "Brug tekstur-rendering for at gøre billedkvaliteten blødere. Du kan også prøve at deaktivere denne funktion, hvis du oplever problemer."), + ("Use texture rendering", "Anvend tekstur-rendering"), + ("Floating window", "Svævende vindue"), + ("floating_window_tip", "Det hjælper på at RustDesk baggrundstjenesten kører"), + ("Keep screen on", "Hold skærmen tændt"), + ("Never", "Aldrig"), + ("During controlled", "Imens under kontrol"), + ("During service is on", "Imens tjenesten kører"), + ("Capture screen using DirectX", "Optag skærm med DirectX"), + ("Back", "Tilbage"), + ("Apps", "Apps"), + ("Volume up", "Skru op for lyd"), + ("Volume down", "Skru ned for lyd"), + ("Power", "Tænd/Sluk"), + ("Telegram bot", "Telegram bot"), + ("enable-bot-tip", "Hvis du aktiverer denne funktion, kan du modtage to-faktor godkendelseskoden fra din robot. Den kan også fungere som en notifikation for forbindelsesanmodninger."), + ("enable-bot-desc", "1. Åbn en chat med @BotFather.\n2. Send kommandoen \"/newbot\". Du vil modtage en nøgle efter at have gennemført dette trin.\n3. Start en chat med din nyoprettede bot. Send en besked som begynder med skråstreg \"/\", som fx \"/hello\", for at aktivere den.\n"), + ("cancel-2fa-confirm-tip", "Er du sikker på at du vil afbryde to-faktor godkendelse?"), + ("cancel-bot-confirm-tip", "Er du sikker på at du vil afbryde Telegram robotten?"), + ("About RustDesk", "Om RustDesk"), + ("Send clipboard keystrokes", "Send udklipsholder tastetryk"), + ("network_error_tip", "Tjek venligst din internetforbindelse, og forsøg igen."), + ("Unlock with PIN", "Lås op med PIN"), + ("Requires at least {} characters", "Kræver mindst {} tegn"), + ("Wrong PIN", "Forkert PIN"), + ("Set PIN", "Sæt PIN"), + ("Enable trusted devices", "Aktivér troværdige enheder"), + ("Manage trusted devices", "Administrér troværdige enheder"), + ("Platform", "Platform"), + ("Days remaining", "Dage tilbage"), + ("enable-trusted-devices-tip", "Spring to-faktor godkendelse over på troværdige enheder"), + ("Parent directory", "mappe"), + ("Resume", "Fortsæt"), + ("Invalid file name", "Ugyldigt filnavn"), ].iter().cloned().collect(); } From 39e713838f96e4ab20031831e74a16a22d37d3c2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 3 Sep 2024 18:48:17 +0800 Subject: [PATCH 034/210] Use fallback codec if first frame fails (#9242) * Both encoding and decoding use fallback if first frame fails * More aggresive max fail counter * Update hwcodec, add judgement when length of the encoded data is zero, https://github.com/rustdesk/rustdesk-server-pro/discussions/382#discussioncomment-10525997 * Fix serde hwcodec config toml fails when the non-first vec![] is empty, https://github.com/toml-rs/toml-rs/issues/384, the config file is used for cache, when check process is not finished, the cache is used. * Allow cm not start for pro user Signed-off-by: 21pages --- Cargo.lock | 2 +- libs/scrap/src/common/hwcodec.rs | 19 +++++++++++++++++-- src/client.rs | 21 +++++++++++++++++++-- src/server/connection.rs | 2 ++ src/server/video_service.rs | 20 +++++++++++++++----- 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fbf1f8ebb9c..a6839b9eb12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3045,7 +3045,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hwcodec" version = "0.7.0" -source = "git+https://github.com/rustdesk-org/hwcodec#6abd1898f3a03481ed0c038507b5218d6ea94267" +source = "git+https://github.com/rustdesk-org/hwcodec#b78a69c81631dd9ccfed9df68709808193082242" dependencies = [ "bindgen 0.59.2", "cc", diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs index 4e653215eb6..a0e730c91db 100644 --- a/libs/scrap/src/common/hwcodec.rs +++ b/libs/scrap/src/common/hwcodec.rs @@ -498,6 +498,15 @@ pub struct HwCodecConfig { pub vram_decode: Vec, } +// HwCodecConfig2 is used to store the config in json format, +// confy can't serde HwCodecConfig successfully if the non-first struct Vec is empty due to old toml version. +// struct T { a: Vec, b: Vec} will fail if b is empty, but struct T { a: Vec, b: Vec} is ok. +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +struct HwCodecConfig2 { + #[serde(default)] + pub config: String, +} + // ipc server process start check process once, other process get from ipc server once // install: --server start check process, check process send to --server, ui get from --server // portable: ui start check process, check process send to ui @@ -509,7 +518,12 @@ impl HwCodecConfig { log::info!("set hwcodec config"); log::debug!("{config:?}"); #[cfg(any(windows, target_os = "macos"))] - hbb_common::config::common_store(&config, "_hwcodec"); + hbb_common::config::common_store( + &HwCodecConfig2 { + config: serde_json::to_string_pretty(&config).unwrap_or_default(), + }, + "_hwcodec", + ); *CONFIG.lock().unwrap() = Some(config); *CONFIG_SET_BY_IPC.lock().unwrap() = true; } @@ -587,7 +601,8 @@ impl HwCodecConfig { Some(c) => c, None => { log::info!("try load cached hwcodec config"); - let c = hbb_common::config::common_load::("_hwcodec"); + let c = hbb_common::config::common_load::("_hwcodec"); + let c: HwCodecConfig = serde_json::from_str(&c.config).unwrap_or_default(); let new_signature = hwcodec::common::get_gpu_signature(); if c.signature == new_signature { log::debug!("load cached hwcodec config: {c:?}"); diff --git a/src/client.rs b/src/client.rs index b687c8a84c1..f1df8d20e48 100644 --- a/src/client.rs +++ b/src/client.rs @@ -84,7 +84,7 @@ pub mod io_loop; pub const MILLI1: Duration = Duration::from_millis(1); pub const SEC30: Duration = Duration::from_secs(30); pub const VIDEO_QUEUE_SIZE: usize = 120; -const MAX_DECODE_FAIL_COUNTER: usize = 10; // Currently, failed decode cause refresh_video, so make it small +const MAX_DECODE_FAIL_COUNTER: usize = 3; #[cfg(target_os = "linux")] pub const LOGIN_MSG_DESKTOP_NOT_INITED: &str = "Desktop env is not inited"; @@ -1151,6 +1151,7 @@ pub struct VideoHandler { record: bool, _display: usize, // useful for debug fail_counter: usize, + first_frame: bool, } impl VideoHandler { @@ -1176,6 +1177,7 @@ impl VideoHandler { record: false, _display, fail_counter: 0, + first_frame: true, } } @@ -1204,9 +1206,19 @@ impl VideoHandler { self.fail_counter = 0; } else { if self.fail_counter < usize::MAX { - self.fail_counter += 1 + if self.first_frame && self.fail_counter < MAX_DECODE_FAIL_COUNTER { + log::error!("decode first frame failed"); + self.fail_counter = MAX_DECODE_FAIL_COUNTER; + } else { + self.fail_counter += 1; + } + log::error!( + "Failed to handle video frame, fail counter: {}", + self.fail_counter + ); } } + self.first_frame = false; if self.record { self.recorder .lock() @@ -1222,12 +1234,17 @@ impl VideoHandler { /// Reset the decoder, change format if it is Some pub fn reset(&mut self, format: Option) { + log::info!( + "reset video handler for display #{}, format: {format:?}", + self._display + ); #[cfg(target_os = "macos")] self.rgb.set_align(crate::get_dst_align_rgba()); let luid = Self::get_adapter_luid(); let format = format.unwrap_or(self.decoder.format()); self.decoder = Decoder::new(format, luid); self.fail_counter = 0; + self.first_frame = true; } /// Start or stop screen record. diff --git a/src/server/connection.rs b/src/server/connection.rs index 7b160ec2116..3a2b0a22e91 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1670,9 +1670,11 @@ impl Connection { .await { log::error!("ipc to connection manager exit: {}", err); + // https://github.com/rustdesk/rustdesk-server-pro/discussions/382#discussioncomment-10525725, cm may start failed #[cfg(windows)] if !crate::platform::is_prelogin() && !err.to_string().contains(crate::platform::EXPLORER_EXE) + && !crate::hbbs_http::sync::is_pro() { allow_err!(tx_from_cm_clone.send(Data::CmErr(err.to_string()))); } diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 8b326a2ffdd..e091966034b 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -486,6 +486,7 @@ fn run(vs: VideoService) -> ResultType<()> { let mut repeat_encode_counter = 0; let repeat_encode_max = 10; let mut encode_fail_counter = 0; + let mut first_frame = true; while sp.ok() { #[cfg(windows)] @@ -574,6 +575,7 @@ fn run(vs: VideoService) -> ResultType<()> { &mut encoder, recorder.clone(), &mut encode_fail_counter, + &mut first_frame, )?; frame_controller.set_send(now, send_conn_ids); } @@ -629,6 +631,7 @@ fn run(vs: VideoService) -> ResultType<()> { &mut encoder, recorder.clone(), &mut encode_fail_counter, + &mut first_frame, )?; frame_controller.set_send(now, send_conn_ids); } @@ -906,6 +909,7 @@ fn handle_one_frame( encoder: &mut Encoder, recorder: Arc>>, encode_fail_counter: &mut usize, + first_frame: &mut bool, ) -> ResultType> { sp.snapshot(|sps| { // so that new sub and old sub share the same encoder after switch @@ -917,6 +921,8 @@ fn handle_one_frame( })?; let mut send_conn_ids: HashSet = Default::default(); + let first = *first_frame; + *first_frame = false; match encoder.encode_to_message(frame, ms) { Ok(mut vf) => { *encode_fail_counter = 0; @@ -931,17 +937,21 @@ fn handle_one_frame( send_conn_ids = sp.send_video_frame(msg); } Err(e) => { + *encode_fail_counter += 1; + // Encoding errors are not frequent except on Android + if !cfg!(target_os = "android") { + log::error!("encode fail: {e:?}, times: {}", *encode_fail_counter,); + } let max_fail_times = if cfg!(target_os = "android") && encoder.is_hardware() { - 12 + 9 } else { - 6 + 3 }; - *encode_fail_counter += 1; - if *encode_fail_counter >= max_fail_times { + if first || *encode_fail_counter >= max_fail_times { *encode_fail_counter = 0; if encoder.is_hardware() { encoder.disable(); - log::error!("switch due to encoding fails more than {max_fail_times} times"); + log::error!("switch due to encoding fails, first frame: {first}, error: {e:?}"); bail!("SWITCH"); } } From d4377a13c5062d4d887f4768b39d1f54723db596 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 3 Sep 2024 19:06:11 +0800 Subject: [PATCH 035/210] refact: peer card, orientation (#9235) * refact: peer card, orientation Signed-off-by: fufesou * Do not change landscape/portrait on Desktop Signed-off-by: fufesou * comments Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/common/widgets/address_book.dart | 53 ++++++------ flutter/lib/common/widgets/autocomplete.dart | 2 +- flutter/lib/common/widgets/dialog.dart | 3 +- flutter/lib/common/widgets/my_group.dart | 21 ++--- flutter/lib/common/widgets/peer_card.dart | 73 ++++++++--------- flutter/lib/common/widgets/peer_tab_page.dart | 73 ++++++++--------- flutter/lib/common/widgets/peers_view.dart | 80 +++++++++---------- flutter/lib/common/widgets/remote_input.dart | 4 +- flutter/lib/main.dart | 30 ++++++- flutter/lib/models/state_model.dart | 2 + 10 files changed, 186 insertions(+), 155 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 67b262cd11a..a0a456807a8 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -11,6 +11,7 @@ import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import 'package:get/get.dart'; @@ -61,15 +62,16 @@ class _AddressBookState extends State { retry: null, // remove retry close: () => gFFI.abModel.currentAbPushError.value = ''), Expanded( - child: (isDesktop || isWebDesktop) - ? _buildAddressBookDesktop() - : _buildAddressBookMobile()) + child: Obx(() => stateGlobal.isPortrait.isTrue + ? _buildAddressBookPortrait() + : _buildAddressBookLandscape()), + ), ], ); } }); - Widget _buildAddressBookDesktop() { + Widget _buildAddressBookLandscape() { return Row( children: [ Offstage( @@ -106,7 +108,7 @@ class _AddressBookState extends State { ); } - Widget _buildAddressBookMobile() { + Widget _buildAddressBookPortrait() { const padding = 8.0; return Column( children: [ @@ -239,14 +241,14 @@ class _AddressBookState extends State { bind.setLocalFlutterOption(k: kOptionCurrentAbName, v: value); } }, - customButton: Container( - height: isDesktop ? 48 : 40, + 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), @@ -335,8 +337,8 @@ class _AddressBookState extends State { showActionMenu: editPermission); } - final gridView = DynamicGridView.builder( - shrinkWrap: isMobile, + gridView(bool isPortrait) => DynamicGridView.builder( + shrinkWrap: isPortrait, gridDelegate: SliverGridDelegateWithWrapping(), itemCount: tags.length, itemBuilder: (BuildContext context, int index) { @@ -344,9 +346,9 @@ class _AddressBookState extends State { return tagBuilder(e); }); final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return (isDesktop || isWebDesktop) - ? gridView - : LimitedBox(maxHeight: maxHeight, child: gridView); + return Obx(() => stateGlobal.isPortrait.isFalse + ? gridView(false) + : LimitedBox(maxHeight: maxHeight, child: gridView(true))); }); } @@ -506,9 +508,9 @@ class _AddressBookState extends State { double marginBottom = 4; row({required Widget lable, required Widget input}) { - return Row( + makeChild(bool isPortrait) => Row( children: [ - !isMobile + !isPortrait ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 100), child: lable.marginOnly(right: 10)) @@ -519,7 +521,8 @@ class _AddressBookState extends State { child: input), ), ], - ).marginOnly(bottom: !isMobile ? 8 : 0); + ).marginOnly(bottom: !isPortrait ? 8 : 0); + return Obx(() => makeChild(stateGlobal.isPortrait.isTrue)); } return CustomAlertDialog( @@ -542,24 +545,24 @@ class _AddressBookState extends State { ), ], ), - input: TextField( + input: Obx(() => TextField( controller: idController, inputFormatters: [IDTextInputFormatter()], decoration: InputDecoration( - labelText: !isMobile ? null : translate('ID'), + labelText: stateGlobal.isPortrait.isFalse ? null : translate('ID'), errorText: errorMsg, errorMaxLines: 5), - )), + ))), row( lable: Text( translate('Alias'), style: style, ), - input: TextField( + input: Obx(() => TextField( controller: aliasController, decoration: InputDecoration( - labelText: !isMobile ? null : translate('Alias'), - )), + labelText: stateGlobal.isPortrait.isFalse ? null : translate('Alias'), + ),)), ), if (isCurrentAbShared) row( @@ -567,11 +570,11 @@ class _AddressBookState extends State { translate('Password'), style: style, ), - input: TextField( + input: Obx(() => TextField( controller: passwordController, obscureText: !passwordVisible, decoration: InputDecoration( - labelText: !isMobile ? null : translate('Password'), + labelText: stateGlobal.isPortrait.isFalse ? null : translate('Password'), suffixIcon: IconButton( icon: Icon( passwordVisible @@ -585,7 +588,7 @@ class _AddressBookState extends State { }, ), ), - )), + ),)), if (gFFI.abModel.currentAbTags.isNotEmpty) Align( alignment: Alignment.centerLeft, diff --git a/flutter/lib/common/widgets/autocomplete.dart b/flutter/lib/common/widgets/autocomplete.dart index 07a11904da1..978d053df4b 100644 --- a/flutter/lib/common/widgets/autocomplete.dart +++ b/flutter/lib/common/widgets/autocomplete.dart @@ -189,7 +189,7 @@ class AutocompletePeerTileState extends State { .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) .toList(); return Tooltip( - message: isMobile + message: !(isDesktop || isWebDesktop) ? '' : widget.peer.tags.isNotEmpty ? '${translate('Tags')}: ${widget.peer.tags.join(', ')}' diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 7cc76d6c6a1..5a72f5dc217 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -10,6 +10,7 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/models/peer_model.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:qr_flutter/qr_flutter.dart'; @@ -1123,7 +1124,7 @@ void showRequestElevationDialog( errorText: errPwd.isEmpty ? null : errPwd.value, ), ], - ).marginOnly(left: (isDesktop || isWebDesktop) ? 35 : 0), + ).marginOnly(left: stateGlobal.isPortrait.isFalse ? 35 : 0), ).marginOnly(top: 10), ], ), diff --git a/flutter/lib/common/widgets/my_group.dart b/flutter/lib/common/widgets/my_group.dart index 0d9cc007ccc..2d26536eb8f 100644 --- a/flutter/lib/common/widgets/my_group.dart +++ b/flutter/lib/common/widgets/my_group.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/hbbs/hbbs.dart'; import 'package:flutter_hbb/common/widgets/login.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:get/get.dart'; import '../../common.dart'; @@ -45,15 +46,15 @@ class _MyGroupState extends State { retry: null, close: () => gFFI.groupModel.groupLoadError.value = ''), Expanded( - child: (isDesktop || isWebDesktop) - ? _buildDesktop() - : _buildMobile()) + child: Obx(() => stateGlobal.isPortrait.isTrue + ? _buildPortrait() + : _buildLandscape())), ], ); }); } - Widget _buildDesktop() { + Widget _buildLandscape() { return Row( children: [ Container( @@ -89,7 +90,7 @@ class _MyGroupState extends State { ); } - Widget _buildMobile() { + Widget _buildPortrait() { return Column( children: [ Container( @@ -159,14 +160,14 @@ class _MyGroupState extends State { } return true; }).toList(); - final listView = ListView.builder( - shrinkWrap: isMobile, + listView(bool isPortrait) => ListView.builder( + shrinkWrap: isPortrait, itemCount: items.length, itemBuilder: (context, index) => _buildUserItem(items[index])); var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return (isDesktop || isWebDesktop) - ? listView - : LimitedBox(maxHeight: maxHeight, child: listView); + return Obx(() => stateGlobal.isPortrait.isFalse + ? listView(false) + : LimitedBox(maxHeight: maxHeight, child: listView(true))); }); } diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index f9dd73bbf6b..7760f7a03ae 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/consts.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'; @@ -53,14 +54,11 @@ class _PeerCardState extends State<_PeerCard> @override Widget build(BuildContext context) { super.build(context); - if (isDesktop || isWebDesktop) { - return _buildDesktop(); - } else { - return _buildMobile(); - } + return Obx(() => + stateGlobal.isPortrait.isTrue ? _buildPortrait() : _buildLandscape()); } - Widget _buildMobile() { + Widget _buildPortrait() { final peer = super.widget.peer; final PeerTabModel peerTabModel = Provider.of(context); return Card( @@ -87,7 +85,7 @@ class _PeerCardState extends State<_PeerCard> )); } - Widget _buildDesktop() { + Widget _buildLandscape() { final PeerTabModel peerTabModel = Provider.of(context); final peer = super.widget.peer; var deco = Rx( @@ -140,13 +138,13 @@ class _PeerCardState extends State<_PeerCard> final greyStyle = TextStyle( fontSize: 11, color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); - final child = Row( + makeChild(bool isPortrait) => Row( mainAxisSize: MainAxisSize.max, children: [ Container( decoration: BoxDecoration( color: str2color('${peer.id}${peer.platform}', 0x7f), - borderRadius: isMobile + borderRadius: isPortrait ? BorderRadius.circular(_tileRadius) : BorderRadius.only( topLeft: Radius.circular(_tileRadius), @@ -154,11 +152,11 @@ class _PeerCardState extends State<_PeerCard> ), ), alignment: Alignment.center, - width: isMobile ? 50 : 42, - height: isMobile ? 50 : null, + width: isPortrait ? 50 : 42, + height: isPortrait ? 50 : null, child: Stack( children: [ - getPlatformImage(peer.platform, size: isMobile ? 38 : 30) + getPlatformImage(peer.platform, size: isPortrait ? 38 : 30) .paddingAll(6), if (_shouldBuildPasswordIcon(peer)) Positioned( @@ -183,19 +181,19 @@ class _PeerCardState extends State<_PeerCard> child: Column( children: [ Row(children: [ - getOnline(isMobile ? 4 : 8, peer.online), + getOnline(isPortrait ? 4 : 8, peer.online), Expanded( child: Text( peer.alias.isEmpty ? formatID(peer.id) : peer.alias, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleSmall, )), - ]).marginOnly(top: isMobile ? 0 : 2), + ]).marginOnly(top: isPortrait ? 0 : 2), Align( alignment: Alignment.centerLeft, child: Text( name, - style: isMobile ? null : greyStyle, + style: isPortrait ? null : greyStyle, textAlign: TextAlign.start, overflow: TextOverflow.ellipsis, ), @@ -203,9 +201,9 @@ class _PeerCardState extends State<_PeerCard> ], ).marginOnly(top: 2), ), - isMobile - ? checkBoxOrActionMoreMobile(peer) - : checkBoxOrActionMoreDesktop(peer, isTile: true), + isPortrait + ? checkBoxOrActionMorePortrait(peer) + : checkBoxOrActionMoreLandscape(peer, isTile: true), ], ).paddingOnly(left: 10.0, top: 3.0), ), @@ -216,28 +214,27 @@ class _PeerCardState extends State<_PeerCard> .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) .toList(); return Tooltip( - message: isMobile + message: !(isDesktop || isWebDesktop) ? '' : peer.tags.isNotEmpty ? '${translate('Tags')}: ${peer.tags.join(', ')}' : '', child: Stack(children: [ - deco == null - ? child - : Obx( - () => Container( + Obx(() => deco == null + ? makeChild(stateGlobal.isPortrait.isTrue) + : Container( foregroundDecoration: deco.value, - child: child, + child: makeChild(stateGlobal.isPortrait.isTrue), ), ), if (colors.isNotEmpty) - Positioned( + Obx(()=> Positioned( top: 2, - right: isMobile ? 20 : 10, + right: stateGlobal.isPortrait.isTrue ? 20 : 10, child: CustomPaint( painter: TagPainter(radius: 3, colors: colors), ), - ) + )) ]), ); } @@ -316,7 +313,7 @@ class _PeerCardState extends State<_PeerCard> style: Theme.of(context).textTheme.titleSmall, )), ]).paddingSymmetric(vertical: 8)), - checkBoxOrActionMoreDesktop(peer, isTile: false), + checkBoxOrActionMoreLandscape(peer, isTile: false), ], ).paddingSymmetric(horizontal: 12.0), ) @@ -362,7 +359,7 @@ class _PeerCardState extends State<_PeerCard> } } - Widget checkBoxOrActionMoreMobile(Peer peer) { + Widget checkBoxOrActionMorePortrait(Peer peer) { final PeerTabModel peerTabModel = Provider.of(context); final selected = peerTabModel.isPeerSelected(peer.id); if (peerTabModel.multiSelectionMode) { @@ -390,7 +387,7 @@ class _PeerCardState extends State<_PeerCard> } } - Widget checkBoxOrActionMoreDesktop(Peer peer, {required bool isTile}) { + Widget checkBoxOrActionMoreLandscape(Peer peer, {required bool isTile}) { final PeerTabModel peerTabModel = Provider.of(context); final selected = peerTabModel.isPeerSelected(peer.id); if (peerTabModel.multiSelectionMode) { @@ -1257,9 +1254,9 @@ void _rdpDialog(String id) async { ), ], ).marginOnly(bottom: isDesktop ? 8 : 0), - Row( + Obx(() => Row( children: [ - (isDesktop || isWebDesktop) + stateGlobal.isPortrait.isFalse ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 140), child: Text( @@ -1270,17 +1267,17 @@ void _rdpDialog(String id) async { Expanded( child: TextField( decoration: InputDecoration( - labelText: (isDesktop || isWebDesktop) + labelText: isDesktop ? null : translate('Username')), controller: userController, ), ), ], - ).marginOnly(bottom: (isDesktop || isWebDesktop) ? 8 : 0), - Row( + ).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)), + Obx(() => Row( children: [ - (isDesktop || isWebDesktop) + stateGlobal.isPortrait.isFalse ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 140), child: Text( @@ -1292,7 +1289,7 @@ void _rdpDialog(String id) async { child: Obx(() => TextField( obscureText: secure.value, decoration: InputDecoration( - labelText: (isDesktop || isWebDesktop) + labelText: isDesktop ? null : translate('Password'), suffixIcon: IconButton( @@ -1304,7 +1301,7 @@ void _rdpDialog(String id) async { )), ), ], - ) + )) ], ), ), diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 8fe73144999..c941a1b93d4 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -16,6 +16,7 @@ import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -114,26 +115,26 @@ class _PeerTabPageState extends State textBaseline: TextBaseline.ideographic, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 32, - child: Container( - padding: (isDesktop || isWebDesktop) - ? null - : EdgeInsets.symmetric(horizontal: 2), - child: selectionWrap(Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: - visibleContextMenuListener(_createSwitchBar(context))), - if (isMobile) - ..._mobileRightActions(context) - else - ..._desktopRightActions(context) - ], - )), - ), - ).paddingOnly(right: (isDesktop || isWebDesktop) ? 12 : 0), + Obx(() => SizedBox( + height: 32, + child: Container( + padding: stateGlobal.isPortrait.isTrue + ? EdgeInsets.symmetric(horizontal: 2) + : null, + child: selectionWrap(Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: visibleContextMenuListener( + _createSwitchBar(context))), + if (stateGlobal.isPortrait.isTrue) + ..._portraitRightActions(context) + else + ..._landscapeRightActions(context) + ], + )), + ), + ).paddingOnly(right: stateGlobal.isPortrait.isTrue ? 0 : 12)), _createPeersView(), ], ); @@ -299,7 +300,7 @@ class _PeerTabPageState extends State } Widget visibleContextMenuListener(Widget child) { - if (isMobile) { + if (!(isDesktop || isWebDesktop)) { return GestureDetector( onLongPressDown: (e) { final x = e.globalPosition.dx; @@ -456,7 +457,7 @@ class _PeerTabPageState extends State showToast(translate('Successful')); }, child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]), - ).marginOnly(left: isMobile ? 11 : 6), + ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), ); } @@ -477,7 +478,7 @@ class _PeerTabPageState extends State model.setMultiSelectionMode(false); }, child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]), - ).marginOnly(left: isMobile ? 11 : 6), + ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), ); } @@ -500,7 +501,7 @@ class _PeerTabPageState extends State }); }, child: Icon(Icons.tag)) - .marginOnly(left: isMobile ? 11 : 6), + .marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), ); } @@ -556,10 +557,10 @@ class _PeerTabPageState extends State }); } - List _desktopRightActions(BuildContext context) { + List _landscapeRightActions(BuildContext context) { final model = Provider.of(context); return [ - const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13), + const PeerSearchBar().marginOnly(right: 13), _createRefresh( index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading), _createRefresh( @@ -580,7 +581,7 @@ class _PeerTabPageState extends State ]; } - List _mobileRightActions(BuildContext context) { + List _portraitRightActions(BuildContext context) { final model = Provider.of(context); final screenWidth = MediaQuery.of(context).size.width; final leftIconSize = Theme.of(context).iconTheme.size ?? 24; @@ -701,13 +702,13 @@ class _PeerSearchBarState extends State { baseOffset: 0, extentOffset: peerSearchTextController.value.text.length); }); - return Container( - width: isMobile ? 120 : 140, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6), - ), - child: Obx(() => Row( + return Obx(() => Container( + width: stateGlobal.isPortrait.isTrue ? 120 : 140, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(6), + ), + child: Row( children: [ Expanded( child: Row( @@ -768,8 +769,8 @@ class _PeerSearchBarState extends State { ), ) ], - )), - ); + ), + )); } } diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index ef9647eaa93..befc73338ee 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -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/state_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:visibility_detector/visibility_detector.dart'; @@ -128,7 +129,7 @@ class _PeersViewState extends State<_PeersView> @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); - if (isDesktop) return; + if (isDesktop || isWebDesktop) return; if (state == AppLifecycleState.resumed) { _isActive = true; _queryCount = 0; @@ -194,7 +195,7 @@ class _PeersViewState extends State<_PeersView> var peers = snapshot.data!; if (peers.length > 1000) peers = peers.sublist(0, 1000); gFFI.peerTabModel.setCurrentTabCachedPeers(peers); - buildOnePeer(Peer peer) { + buildOnePeer(Peer peer, bool isPortrait) { final visibilityChild = VisibilityDetector( key: ValueKey(_cardId(peer.id)), onVisibilityChanged: onVisibilityChanged, @@ -206,7 +207,7 @@ class _PeersViewState extends State<_PeersView> // No need to listen the currentTab change event. // Because the currentTab change event will trigger the peers change event, // and the peers change event will trigger _buildPeersView(). - return (isDesktop || isWebDesktop) + return !isPortrait ? Obx(() => peerCardUiType.value == PeerUiType.list ? Container(height: 45, child: visibilityChild) : peerCardUiType.value == PeerUiType.grid @@ -217,44 +218,41 @@ class _PeersViewState extends State<_PeersView> : Container(child: visibilityChild); } - final Widget child; - if (isMobile) { - child = ListView.builder( - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index]).marginOnly( - top: index == 0 ? 0 : space / 2, bottom: space / 2); - }, - ); - } else { - child = Obx(() => peerCardUiType.value == PeerUiType.list - ? DesktopScrollWrapper( - scrollController: _scrollController, - child: ListView.builder( - controller: _scrollController, - physics: DraggableNeverScrollableScrollPhysics(), - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index]).marginOnly( - right: space, - top: index == 0 ? 0 : space / 2, - bottom: space / 2); - }), - ) - : DesktopScrollWrapper( - scrollController: _scrollController, - child: DynamicGridView.builder( - controller: _scrollController, - physics: DraggableNeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithWrapping( - mainAxisSpacing: space / 2, - crossAxisSpacing: space), - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index]); - }), - )); - } + final Widget child = Obx(() => stateGlobal.isPortrait.isTrue + ? ListView.builder( + itemCount: peers.length, + itemBuilder: (BuildContext context, int index) { + return buildOnePeer(peers[index], true).marginOnly( + top: index == 0 ? 0 : space / 2, bottom: space / 2); + }, + ) + : peerCardUiType.value == PeerUiType.list + ? DesktopScrollWrapper( + scrollController: _scrollController, + child: ListView.builder( + controller: _scrollController, + physics: DraggableNeverScrollableScrollPhysics(), + itemCount: peers.length, + itemBuilder: (BuildContext context, int index) { + return buildOnePeer(peers[index], false).marginOnly( + right: space, + top: index == 0 ? 0 : space / 2, + bottom: space / 2); + }), + ) + : DesktopScrollWrapper( + scrollController: _scrollController, + child: DynamicGridView.builder( + controller: _scrollController, + physics: DraggableNeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithWrapping( + mainAxisSpacing: space / 2, + crossAxisSpacing: space), + itemCount: peers.length, + itemBuilder: (BuildContext context, int index) { + return buildOnePeer(peers[index], false); + }), + )); if (updateEvent == UpdateEvent.load) { _curPeers.clear(); diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart index fb19c4c2345..a4d3caf2990 100644 --- a/flutter/lib/common/widgets/remote_input.dart +++ b/flutter/lib/common/widgets/remote_input.dart @@ -243,7 +243,7 @@ class _RawTouchGestureDetectorRegionState if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) { return; } - if (isDesktop) { + if (isDesktop || isWebDesktop) { ffi.cursorModel.trySetRemoteWindowCoords(); } // Workaround for the issue that the first pan event is sent a long time after the start event. @@ -285,7 +285,7 @@ class _RawTouchGestureDetectorRegionState if (lastDeviceKind != PointerDeviceKind.touch) { return; } - if (isDesktop) { + if (isDesktop || isWebDesktop) { ffi.cursorModel.clearRemoteWindowCoords(); } inputModel.sendMouse('up', MouseButtons.left); diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index c2009bcae13..dc02ac81fda 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -372,7 +372,7 @@ class App extends StatefulWidget { State createState() => _AppState(); } -class _AppState extends State { +class _AppState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); @@ -396,6 +396,34 @@ class _AppState extends State { bind.mainChangeTheme(dark: to.toShortString()); } }; + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) => _updateOrientation()); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + _updateOrientation(); + } + + void _updateOrientation() { + if (isDesktop) return; + + // Don't use `MediaQuery.of(context).orientation` in `didChangeMetrics()`, + // my test (Flutter 3.19.6, Android 14) is always the reverse value. + // https://github.com/flutter/flutter/issues/60899 + // stateGlobal.isPortrait.value = + // MediaQuery.of(context).orientation == Orientation.portrait; + + final orientation = View.of(context).physicalSize.aspectRatio > 1 + ? Orientation.landscape + : Orientation.portrait; + stateGlobal.isPortrait.value = orientation == Orientation.portrait; } @override diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index 7c4d3cfd059..e18874785cf 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -20,6 +20,8 @@ class StateGlobal { final svcStatus = SvcStatus.notReady.obs; final RxBool isFocused = false.obs; + final isPortrait = false.obs; + String _inputSource = ''; // Use for desktop -> remote toolbar -> resolution From ec28567362e1a0f30cb20df42f66f6f6b221c95b Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 3 Sep 2024 20:55:45 +0800 Subject: [PATCH 036/210] fix: win, file clipboard (#9243) 1. Return the result of `wait_response_event()` in `cliprdr_send_format_list()` 2. Add recv flags to avoid waiting a long time. Signed-off-by: fufesou --- libs/clipboard/src/lib.rs | 19 +++++--- libs/clipboard/src/platform/windows.rs | 63 +++++++++++++++++-------- libs/clipboard/src/windows/wf_cliprdr.c | 41 +++++++++++----- src/client/io_loop.rs | 1 + 4 files changed, 86 insertions(+), 38 deletions(-) diff --git a/libs/clipboard/src/lib.rs b/libs/clipboard/src/lib.rs index 1a9a047578f..a5da25512d2 100644 --- a/libs/clipboard/src/lib.rs +++ b/libs/clipboard/src/lib.rs @@ -5,7 +5,7 @@ use std::{ }; #[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))] -use hbb_common::{allow_err, log}; +use hbb_common::{allow_err, bail}; use hbb_common::{ lazy_static, tokio::sync::{ @@ -25,6 +25,8 @@ pub use context_send::*; const ERR_CODE_SERVER_FUNCTION_NONE: u32 = 0x00000001; #[cfg(target_os = "windows")] const ERR_CODE_INVALID_PARAMETER: u32 = 0x00000002; +#[cfg(target_os = "windows")] +const ERR_CODE_SEND_MSG: u32 = 0x00000003; pub(crate) use platform::create_cliprdr_context; @@ -198,7 +200,7 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc ResultType<()> { #[cfg(target_os = "windows")] return send_data_to_channel(conn_id, data); #[cfg(not(target_os = "windows"))] @@ -210,25 +212,28 @@ fn send_data(conn_id: i32, data: ClipboardFile) { } #[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))] #[inline] -fn send_data_to_channel(conn_id: i32, data: ClipboardFile) { - // no need to handle result here +fn send_data_to_channel(conn_id: i32, data: ClipboardFile) -> ResultType<()> { if let Some(msg_channel) = VEC_MSG_CHANNEL .read() .unwrap() .iter() .find(|x| x.conn_id == conn_id) { - allow_err!(msg_channel.sender.send(data)); + msg_channel.sender.send(data)?; + Ok(()) + } else { + bail!("conn_id not found"); } } #[cfg(feature = "unix-file-copy-paste")] #[inline] -fn send_data_to_all(data: ClipboardFile) { - // no need to handle result here +fn send_data_to_all(data: ClipboardFile) -> ResultType<()> { + // Need more tests to see if it's necessary to handle the error. for msg_channel in VEC_MSG_CHANNEL.read().unwrap().iter() { allow_err!(msg_channel.sender.send(data.clone())); } + Ok(()) } #[cfg(test)] diff --git a/libs/clipboard/src/platform/windows.rs b/libs/clipboard/src/platform/windows.rs index 8fc917c6fee..5d1aa086ddb 100644 --- a/libs/clipboard/src/platform/windows.rs +++ b/libs/clipboard/src/platform/windows.rs @@ -7,7 +7,7 @@ use crate::{ allow_err, send_data, ClipboardFile, CliprdrError, CliprdrServiceContext, ResultType, - ERR_CODE_INVALID_PARAMETER, ERR_CODE_SERVER_FUNCTION_NONE, VEC_MSG_CHANNEL, + ERR_CODE_INVALID_PARAMETER, ERR_CODE_SEND_MSG, ERR_CODE_SERVER_FUNCTION_NONE, VEC_MSG_CHANNEL, }; use hbb_common::log; use std::{ @@ -998,7 +998,7 @@ extern "C" fn notify_callback(conn_id: UINT32, msg: *const NOTIFICATION_MESSAGE) } }; // no need to handle result here - send_data(conn_id as _, data); + allow_err!(send_data(conn_id as _, data)); 0 } @@ -1045,7 +1045,13 @@ extern "C" fn client_format_list( .iter() .for_each(|msg_channel| allow_err!(msg_channel.sender.send(data.clone()))); } else { - send_data(conn_id, data); + match send_data(conn_id, data) { + Ok(_) => {} + Err(e) => { + log::error!("failed to send format list: {:?}", e); + return ERR_CODE_SEND_MSG; + } + } } 0 @@ -1067,9 +1073,13 @@ extern "C" fn client_format_list_response( msg_flags ); let data = ClipboardFile::FormatListResponse { msg_flags }; - send_data(conn_id, data); - - 0 + match send_data(conn_id, data) { + Ok(_) => 0, + Err(e) => { + log::error!("failed to send format list response: {:?}", e); + ERR_CODE_SEND_MSG + } + } } extern "C" fn client_format_data_request( @@ -1090,10 +1100,13 @@ extern "C" fn client_format_data_request( conn_id, requested_format_id ); - // no need to handle result here - send_data(conn_id, data); - - 0 + match send_data(conn_id, data) { + Ok(_) => 0, + Err(e) => { + log::error!("failed to send format data request: {:?}", e); + ERR_CODE_SEND_MSG + } + } } extern "C" fn client_format_data_response( @@ -1125,9 +1138,13 @@ extern "C" fn client_format_data_response( msg_flags, format_data, }; - send_data(conn_id, data); - - 0 + match send_data(conn_id, data) { + Ok(_) => 0, + Err(e) => { + log::error!("failed to send format data response: {:?}", e); + ERR_CODE_SEND_MSG + } + } } extern "C" fn client_file_contents_request( @@ -1175,9 +1192,13 @@ extern "C" fn client_file_contents_request( clip_data_id, }; log::debug!("client_file_contents_request called, data: {:?}", &data); - send_data(conn_id, data); - - 0 + match send_data(conn_id, data) { + Ok(_) => 0, + Err(e) => { + log::error!("failed to send file contents request: {:?}", e); + ERR_CODE_SEND_MSG + } + } } extern "C" fn client_file_contents_response( @@ -1213,7 +1234,11 @@ extern "C" fn client_file_contents_response( msg_flags, stream_id ); - send_data(conn_id, data); - - 0 + match send_data(conn_id, data) { + Ok(_) => 0, + Err(e) => { + log::error!("failed to send file contents response: {:?}", e); + ERR_CODE_SEND_MSG + } + } } diff --git a/libs/clipboard/src/windows/wf_cliprdr.c b/libs/clipboard/src/windows/wf_cliprdr.c index c8f2038a1d2..5bca5405274 100644 --- a/libs/clipboard/src/windows/wf_cliprdr.c +++ b/libs/clipboard/src/windows/wf_cliprdr.c @@ -220,7 +220,8 @@ struct wf_clipboard HWND hwnd; HANDLE hmem; HANDLE thread; - HANDLE response_data_event; + HANDLE formatDataRespEvent; + BOOL formatDataRespReceived; LPDATAOBJECT data_obj; HANDLE data_obj_mutex; @@ -228,6 +229,7 @@ struct wf_clipboard ULONG req_fsize; char *req_fdata; HANDLE req_fevent; + BOOL req_f_received; size_t nFiles; size_t file_array_size; @@ -1444,7 +1446,7 @@ static UINT cliprdr_send_format_list(wfClipboard *clipboard, UINT32 connID) return rc; } -UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, void **data) +UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, BOOL* recvedFlag, void **data) { UINT rc = ERROR_SUCCESS; clipboard->context->IsStopped = FALSE; @@ -1456,7 +1458,14 @@ UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, vo DWORD waitRes = WaitForSingleObject(event, waitOnceTimeoutMillis); if (waitRes == WAIT_TIMEOUT && clipboard->context->IsStopped == FALSE) { - continue; + if ((*recvedFlag) == TRUE) { + // The data has been received, but the event is still not signaled. + // We just skip the rest of the waiting and reset the flag. + *recvedFlag = FALSE; + } else { + // The data has not been received yet, we should continue to wait. + continue; + } } if (clipboard->context->IsStopped == TRUE) @@ -1524,13 +1533,14 @@ static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UIN formatDataRequest.connID = connID; formatDataRequest.requestedFormatId = remoteFormatId; clipboard->requestedFormatId = formatId; + clipboard->formatDataRespReceived = FALSE; rc = clipboard->context->ClientFormatDataRequest(clipboard->context, &formatDataRequest); if (rc != ERROR_SUCCESS) { return rc; } - wait_response_event(connID, clipboard, clipboard->response_data_event, &clipboard->hmem); + return wait_response_event(connID, clipboard, clipboard->formatDataRespEvent, &clipboard->formatDataRespReceived, &clipboard->hmem); } UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, const void *streamid, ULONG index, @@ -1552,13 +1562,14 @@ UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, co fileContentsRequest.cbRequested = nreq; fileContentsRequest.clipDataId = 0; fileContentsRequest.msgFlags = 0; + clipboard->req_f_received = FALSE; rc = clipboard->context->ClientFileContentsRequest(clipboard->context, &fileContentsRequest); if (rc != ERROR_SUCCESS) { return rc; } - return wait_response_event(connID, clipboard, clipboard->req_fevent, (void **)&clipboard->req_fdata); + return wait_response_event(connID, clipboard, clipboard->req_fevent, &clipboard->req_f_received, (void **)&clipboard->req_fdata); } static UINT cliprdr_send_response_filecontents( @@ -2545,7 +2556,7 @@ wf_cliprdr_server_format_data_request(CliprdrClientContext *context, response.requestedFormatData = (BYTE *)buff; if (ERROR_SUCCESS != clipboard->context->ClientFormatDataResponse(clipboard->context, &response)) { - // CAUTION: if failed to send, server will wait a long time + // CAUTION: if failed to send, server will wait a long time, default 30 seconds. } if (buff) @@ -2621,9 +2632,11 @@ wf_cliprdr_server_format_data_response(CliprdrClientContext *context, rc = CHANNEL_RC_OK; } while (0); - if (!SetEvent(clipboard->response_data_event)) + if (!SetEvent(clipboard->formatDataRespEvent)) { - // CAUTION: critical error here, process will hang up until wait timeout default 3min. + // If failed to set event, set flag to indicate the event is received. + DEBUG_CLIPRDR("wf_cliprdr_server_format_data_response(), SetEvent failed with 0x%x", GetLastError()); + clipboard->formatDataRespReceived = TRUE; rc = ERROR_INTERNAL_ERROR; } return rc; @@ -2899,7 +2912,9 @@ wf_cliprdr_server_file_contents_response(CliprdrClientContext *context, if (!SetEvent(clipboard->req_fevent)) { - // CAUTION: critical error here, process will hang up until wait timeout default 3min. + // If failed to set event, set flag to indicate the event is received. + DEBUG_CLIPRDR("wf_cliprdr_server_file_contents_response(), SetEvent failed with 0x%x", GetLastError()); + clipboard->req_f_received = TRUE; } return rc; } @@ -2934,14 +2949,16 @@ BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr) (formatMapping *)calloc(clipboard->map_capacity, sizeof(formatMapping)))) goto error; - if (!(clipboard->response_data_event = CreateEvent(NULL, TRUE, FALSE, NULL))) + if (!(clipboard->formatDataRespEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) goto error; + clipboard->formatDataRespReceived = FALSE; if (!(clipboard->data_obj_mutex = CreateMutex(NULL, FALSE, "data_obj_mutex"))) goto error; if (!(clipboard->req_fevent = CreateEvent(NULL, TRUE, FALSE, NULL))) goto error; + clipboard->req_f_received = FALSE; if (!(clipboard->thread = CreateThread(NULL, 0, cliprdr_thread_func, clipboard, 0, NULL))) goto error; @@ -3002,8 +3019,8 @@ BOOL wf_cliprdr_uninit(wfClipboard *clipboard, CliprdrClientContext *cliprdr) clipboard->data_obj = NULL; } - if (clipboard->response_data_event) - CloseHandle(clipboard->response_data_event); + if (clipboard->formatDataRespEvent) + CloseHandle(clipboard->formatDataRespEvent); if (clipboard->data_obj_mutex) CloseHandle(clipboard->data_obj_mutex); diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index b222e411815..1a209ca0a53 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -353,6 +353,7 @@ impl Remote { } else { if let Err(e) = ContextSend::make_sure_enabled() { log::error!("failed to restart clipboard context: {}", e); + // to-do: Show msgbox with "Don't show again" option }; log::debug!("Send system clipboard message to remote"); let msg = crate::clipboard_file::clip_2_msg(clip); From 04c0f66ca983e80acc0c330246208e32e3fc35ea Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:15:35 +0800 Subject: [PATCH 037/210] fix: set to OK if recv flag is TRUE (#9244) Signed-off-by: fufesou --- libs/clipboard/src/windows/wf_cliprdr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/clipboard/src/windows/wf_cliprdr.c b/libs/clipboard/src/windows/wf_cliprdr.c index 5bca5405274..1f65b1913b2 100644 --- a/libs/clipboard/src/windows/wf_cliprdr.c +++ b/libs/clipboard/src/windows/wf_cliprdr.c @@ -1462,6 +1462,8 @@ UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, BO // The data has been received, but the event is still not signaled. // We just skip the rest of the waiting and reset the flag. *recvedFlag = FALSE; + // Explicitly set the waitRes to WAIT_OBJECT_0, because we have received the data. + waitRes = WAIT_OBJECT_0; } else { // The data has not been received yet, we should continue to wait. continue; From 29e12b84a9f24d57bb6a197774a299686b0e1625 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 4 Sep 2024 11:31:13 +0800 Subject: [PATCH 038/210] password max length prompt (#9248) Signed-off-by: 21pages --- flutter/lib/common/widgets/dialog.dart | 9 +++++++ flutter/lib/common/widgets/peer_card.dart | 2 ++ .../lib/desktop/pages/desktop_home_page.dart | 3 +++ .../desktop/pages/desktop_setting_page.dart | 1 + libs/hbb_common/src/config.rs | 2 +- libs/hbb_common/src/password_security.rs | 10 +++---- src/flutter_ffi.rs | 4 +++ src/ipc.rs | 27 ++++++++++++------- src/ui_interface.rs | 5 ++++ 9 files changed, 47 insertions(+), 16 deletions(-) diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 5a72f5dc217..cc3e0613105 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -381,6 +381,7 @@ class DialogTextField extends StatelessWidget { final FocusNode? focusNode; final TextInputType? keyboardType; final List? inputFormatters; + final int? maxLength; static const kUsernameTitle = 'Username'; static const kUsernameIcon = Icon(Icons.account_circle_outlined); @@ -398,6 +399,7 @@ class DialogTextField extends StatelessWidget { this.hintText, this.keyboardType, this.inputFormatters, + this.maxLength, required this.title, required this.controller}) : super(key: key); @@ -424,6 +426,7 @@ class DialogTextField extends StatelessWidget { obscureText: obscureText, keyboardType: keyboardType, inputFormatters: inputFormatters, + maxLength: maxLength, ), ), ], @@ -681,6 +684,7 @@ class PasswordWidget extends StatefulWidget { this.hintText, this.errorText, this.title, + this.maxLength, }) : super(key: key); final TextEditingController controller; @@ -689,6 +693,7 @@ class PasswordWidget extends StatefulWidget { final String? hintText; final String? errorText; final String? title; + final int? maxLength; @override State createState() => _PasswordWidgetState(); @@ -751,6 +756,7 @@ class _PasswordWidgetState extends State { obscureText: !_passwordVisible, errorText: widget.errorText, focusNode: _focusNode, + maxLength: widget.maxLength, ); } } @@ -2245,6 +2251,7 @@ void changeUnlockPinDialog(String oldPin, Function() callback) { final confirmController = TextEditingController(text: oldPin); String? pinErrorText; String? confirmationErrorText; + final maxLength = bind.mainMaxEncryptLen(); gFFI.dialogManager.show((setState, close, context) { submit() async { pinErrorText = null; @@ -2278,12 +2285,14 @@ void changeUnlockPinDialog(String oldPin, Function() callback) { controller: pinController, obscureText: true, errorText: pinErrorText, + maxLength: maxLength, ), DialogTextField( title: translate('Confirmation'), controller: confirmController, obscureText: true, errorText: confirmationErrorText, + maxLength: maxLength, ) ], ).marginOnly(bottom: 12), diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 7760f7a03ae..15ca8932d1f 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -1200,6 +1200,7 @@ class MyGroupPeerCard extends BasePeerCard { } void _rdpDialog(String id) async { + final maxLength = bind.mainMaxEncryptLen(); final port = await bind.mainGetPeerOption(id: id, key: 'rdp_port'); final username = await bind.mainGetPeerOption(id: id, key: 'rdp_username'); final portController = TextEditingController(text: port); @@ -1288,6 +1289,7 @@ void _rdpDialog(String id) async { Expanded( child: Obx(() => TextField( obscureText: secure.value, + maxLength: maxLength, decoration: InputDecoration( labelText: isDesktop ? null diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 31a8e1374ff..dee990af4c2 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -857,6 +857,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async { // SpecialCharacterValidationRule(), MinCharactersValidationRule(8), ]; + final maxLength = bind.mainMaxEncryptLen(); gFFI.dialogManager.show((setState, close, context) { submit() { @@ -915,6 +916,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async { errMsg0 = ''; }); }, + maxLength: maxLength, ), ), ], @@ -941,6 +943,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async { errMsg1 = ''; }); }, + maxLength: maxLength, ), ), ], diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 0e5fac4caf5..041b5569ed5 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -2512,6 +2512,7 @@ void changeSocks5Proxy() async { : Icons.visibility))), controller: pwdController, enabled: !isOptFixed, + maxLength: bind.mainMaxEncryptLen(), )), ), ], diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index d0b908b551c..c118070dd85 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -39,7 +39,7 @@ pub const REG_INTERVAL: i64 = 15_000; pub const COMPRESS_LEVEL: i32 = 3; const SERIAL: i32 = 3; const PASSWORD_ENC_VERSION: &str = "00"; -const ENCRYPT_MAX_LEN: usize = 128; +pub const ENCRYPT_MAX_LEN: usize = 128; // used for password, pin, etc, not for all #[cfg(target_os = "macos")] lazy_static::lazy_static! { diff --git a/libs/hbb_common/src/password_security.rs b/libs/hbb_common/src/password_security.rs index 49a2d4d9498..5c04cc97b92 100644 --- a/libs/hbb_common/src/password_security.rs +++ b/libs/hbb_common/src/password_security.rs @@ -89,11 +89,11 @@ pub fn encrypt_str_or_original(s: &str, version: &str, max_len: usize) -> String log::error!("Duplicate encryption!"); return s.to_owned(); } - if s.bytes().len() > max_len { + if s.chars().count() > max_len { return String::default(); } if version == "00" { - if let Ok(s) = encrypt(s.as_bytes(), max_len) { + if let Ok(s) = encrypt(s.as_bytes()) { return version.to_owned() + &s; } } @@ -130,7 +130,7 @@ pub fn encrypt_vec_or_original(v: &[u8], version: &str, max_len: usize) -> Vec (Vec, boo (v.to_owned(), false, !v.is_empty()) } -fn encrypt(v: &[u8], max_len: usize) -> Result { - if !v.is_empty() && v.len() <= max_len { +fn encrypt(v: &[u8]) -> Result { + if !v.is_empty() { symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original)) } else { Err(()) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 8cc47531614..72b0e5b37b4 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -2282,6 +2282,10 @@ pub fn main_clear_trusted_devices() { clear_trusted_devices() } +pub fn main_max_encrypt_len() -> SyncReturn { + SyncReturn(max_encrypt_len()) +} + pub fn session_request_new_display_init_msgs(session_id: SessionID, display: usize) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.request_init_msgs(display); diff --git a/src/ipc.rs b/src/ipc.rs index 3f093c758e2..6d5e247d42c 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -928,16 +928,23 @@ pub fn set_permanent_password(v: String) -> ResultType<()> { pub fn set_unlock_pin(v: String, translate: bool) -> ResultType<()> { let v = v.trim().to_owned(); let min_len = 4; - if !v.is_empty() && v.len() < min_len { - let err = if translate { - crate::lang::translate( - "Requires at least {".to_string() + &format!("{min_len}") + "} characters", - ) - } else { - // Sometimes, translated can't show normally in command line - format!("Requires at least {} characters", min_len) - }; - bail!(err); + let max_len = crate::ui_interface::max_encrypt_len(); + let len = v.chars().count(); + if !v.is_empty() { + if len < min_len { + let err = if translate { + crate::lang::translate( + "Requires at least {".to_string() + &format!("{min_len}") + "} characters", + ) + } else { + // Sometimes, translated can't show normally in command line + format!("Requires at least {} characters", min_len) + }; + bail!(err); + } + if len > max_len { + bail!("No more than {max_len} characters"); + } } Config::set_unlock_pin(&v); set_config("unlock-pin", v) diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 8489179ba67..aad968bb4a5 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1495,3 +1495,8 @@ pub fn clear_trusted_devices() { #[cfg(not(any(target_os = "android", target_os = "ios")))] ipc::clear_trusted_devices(); } + +#[cfg(feature = "flutter")] +pub fn max_encrypt_len() -> usize { + hbb_common::config::ENCRYPT_MAX_LEN +} From dbbbd08934bc52cdc455ca7edbfe512359218454 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:44:36 +0800 Subject: [PATCH 039/210] fix: clipboard, support excel xml spreadsheet (#9252) Signed-off-by: fufesou --- libs/hbb_common/protos/message.proto | 3 +++ src/clipboard.rs | 34 +++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 4554617a7d6..21f9e7aea0d 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -326,6 +326,7 @@ enum ClipboardFormat { ImageRgba = 21; ImagePng = 22; ImageSvg = 23; + Special = 31; } message Clipboard { @@ -334,6 +335,8 @@ message Clipboard { int32 width = 3; int32 height = 4; ClipboardFormat format = 5; + // Special format name, only used when format is Special. + string special_name = 6; } message MultiClipboards { repeated Clipboard clipboards = 1; } diff --git a/src/clipboard.rs b/src/clipboard.rs index 0510eca6a2d..4e6295db9f2 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -12,6 +12,9 @@ pub const CLIPBOARD_INTERVAL: u64 = 333; // This format is used to store the flag in the clipboard. const RUSTDESK_CLIPBOARD_OWNER_FORMAT: &'static str = "dyn.com.rustdesk.owner"; +// Add special format for Excel XML Spreadsheet +const CLIPBOARD_FORMAT_EXCEL_XML_SPREADSHEET: &'static str = "XML Spreadsheet"; + lazy_static::lazy_static! { static ref ARBOARD_MTX: Arc> = Arc::new(Mutex::new(())); // cache the clipboard msg @@ -30,6 +33,7 @@ const SUPPORTED_FORMATS: &[ClipboardFormat] = &[ ClipboardFormat::ImageRgba, ClipboardFormat::ImagePng, ClipboardFormat::ImageSvg, + ClipboardFormat::Special(CLIPBOARD_FORMAT_EXCEL_XML_SPREADSHEET), ClipboardFormat::Special(RUSTDESK_CLIPBOARD_OWNER_FORMAT), ]; @@ -267,8 +271,8 @@ impl ClipboardContext { } if !force { for c in data.iter() { - if let ClipboardData::Special((_, d)) = c { - if side.is_owner(d) { + if let ClipboardData::Special((s, d)) = c { + if s == RUSTDESK_CLIPBOARD_OWNER_FORMAT && side.is_owner(d) { return Ok(vec![]); } } @@ -276,7 +280,10 @@ impl ClipboardContext { } Ok(data .into_iter() - .filter(|c| !matches!(c, ClipboardData::Special(_))) + .filter(|c| match c { + ClipboardData::Special((s, _)) => s != RUSTDESK_CLIPBOARD_OWNER_FORMAT, + _ => true, + }) .collect()) } @@ -454,12 +461,30 @@ mod proto { } } + fn special_to_proto(d: Vec, s: String) -> Clipboard { + let compressed = compress_func(&d); + let compress = compressed.len() < d.len(); + let content = if compress { + compressed + } else { + s.bytes().collect::>() + }; + Clipboard { + compress, + content: content.into(), + format: ClipboardFormat::Special.into(), + special_name: s, + ..Default::default() + } + } + fn clipboard_data_to_proto(data: ClipboardData) -> Option { let d = match data { ClipboardData::Text(s) => plain_to_proto(s, ClipboardFormat::Text), ClipboardData::Rtf(s) => plain_to_proto(s, ClipboardFormat::Rtf), ClipboardData::Html(s) => plain_to_proto(s, ClipboardFormat::Html), ClipboardData::Image(a) => image_to_proto(a), + ClipboardData::Special((s, d)) => special_to_proto(d, s), _ => return None, }; Some(d) @@ -496,6 +521,9 @@ mod proto { Ok(ClipboardFormat::ImageSvg) => Some(ClipboardData::Image(arboard::ImageData::svg( std::str::from_utf8(&data).unwrap_or_default(), ))), + Ok(ClipboardFormat::Special) => { + Some(ClipboardData::Special((clipboard.special_name, data))) + } _ => None, } } From e40243b55dc44ac7a0462dfbd748dc7540b237a1 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:04:48 +0800 Subject: [PATCH 040/210] Fix/wf cliprdr c bugs (#9253) * fix: ResetEvent() after WaitForSingleObject() Signed-off-by: fufesou * fix: check and free mem Signed-off-by: fufesou --------- Signed-off-by: fufesou --- libs/clipboard/src/windows/wf_cliprdr.c | 241 +++++++++++++++++++----- 1 file changed, 190 insertions(+), 51 deletions(-) diff --git a/libs/clipboard/src/windows/wf_cliprdr.c b/libs/clipboard/src/windows/wf_cliprdr.c index 1f65b1913b2..c2b7556a46a 100644 --- a/libs/clipboard/src/windows/wf_cliprdr.c +++ b/libs/clipboard/src/windows/wf_cliprdr.c @@ -289,6 +289,9 @@ static BOOL try_open_clipboard(HWND hwnd) static HRESULT STDMETHODCALLTYPE CliprdrStream_QueryInterface(IStream *This, REFIID riid, void **ppvObject) { + if (ppvObject == NULL) + return E_INVALIDARG; + if (IsEqualIID(riid, &IID_IStream) || IsEqualIID(riid, &IID_IUnknown)) { IStream_AddRef(This); @@ -364,6 +367,13 @@ static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream *This, void *pv, ULO } *pcbRead = clipboard->req_fsize; + // Check overflow, can not be a real case + if ((instance->m_lOffset.QuadPart + clipboard->req_fsize) < instance->m_lOffset.QuadPart) { + // It's better to crash to release the explorer.exe + // This is a critical error, because the explorer is waiting for the data + // and the m_lOffset is wrong(overflowed) + return S_FALSE; + } instance->m_lOffset.QuadPart += clipboard->req_fsize; if (clipboard->req_fsize < cb) @@ -519,11 +529,17 @@ static HRESULT STDMETHODCALLTYPE CliprdrStream_Clone(IStream *This, IStream **pp static CliprdrStream *CliprdrStream_New(UINT32 connID, ULONG index, void *pData, const FILEDESCRIPTORW *dsc) { - IStream *iStream; + IStream *iStream = NULL; BOOL success = FALSE; BOOL isDir = FALSE; - CliprdrStream *instance; + CliprdrStream *instance = NULL; wfClipboard *clipboard = (wfClipboard *)pData; + + if (!(pData && dsc)) + { + return NULL; + } + instance = (CliprdrStream *)calloc(1, sizeof(CliprdrStream)); if (instance) @@ -876,14 +892,18 @@ static HRESULT STDMETHODCALLTYPE CliprdrDataObject_EnumDAdvise(IDataObject *This static CliprdrDataObject *CliprdrDataObject_New(UINT32 connID, FORMATETC *fmtetc, STGMEDIUM *stgmed, ULONG count, void *data) { - CliprdrDataObject *instance; - IDataObject *iDataObject; + CliprdrDataObject *instance = NULL; + IDataObject *iDataObject = NULL; instance = (CliprdrDataObject *)calloc(1, sizeof(CliprdrDataObject)); if (!instance) goto error; + instance->m_pFormatEtc = NULL; + instance->m_pStgMedium = NULL; + iDataObject = &instance->iDataObject; + iDataObject->lpVtbl = NULL; iDataObject->lpVtbl = (IDataObjectVtbl *)calloc(1, sizeof(IDataObjectVtbl)); if (!iDataObject->lpVtbl) @@ -931,7 +951,24 @@ static CliprdrDataObject *CliprdrDataObject_New(UINT32 connID, FORMATETC *fmtetc return instance; error: - CliprdrDataObject_Delete(instance); + if (iDataObject && iDataObject->lpVtbl) + { + free(iDataObject->lpVtbl); + } + if (instance) + { + if (instance->m_pFormatEtc) + { + free(instance->m_pFormatEtc); + } + + if (instance->m_pStgMedium) + { + free(instance->m_pStgMedium); + } + + CliprdrDataObject_Delete(instance); + } return NULL; } @@ -1012,6 +1049,8 @@ static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_QueryInterface(IEnumFORMAT REFIID riid, void **ppvObject) { (void)This; + if (!ppvObject) + return E_INVALIDARG; if (IsEqualIID(riid, &IID_IEnumFORMATETC) || IsEqualIID(riid, &IID_IUnknown)) { @@ -1200,6 +1239,7 @@ static UINT32 get_local_format_id_by_name(wfClipboard *clipboard, const TCHAR *f WCHAR *unicode_name; #if !defined(UNICODE) size_t size; + int towchar_count; #endif if (!clipboard || !format_name) @@ -1207,6 +1247,8 @@ static UINT32 get_local_format_id_by_name(wfClipboard *clipboard, const TCHAR *f #if defined(UNICODE) unicode_name = _wcsdup(format_name); + if (!unicode_name) + return 0; #else size = _tcslen(format_name); unicode_name = calloc(size + 1, sizeof(WCHAR)); @@ -1214,11 +1256,13 @@ static UINT32 get_local_format_id_by_name(wfClipboard *clipboard, const TCHAR *f if (!unicode_name) return 0; - MultiByteToWideChar(CP_OEMCP, 0, format_name, strlen(format_name), unicode_name, size); -#endif - - if (!unicode_name) + towchar_count = MultiByteToWideChar(CP_OEMCP, 0, format_name, strlen(format_name), NULL, 0); + if (towchar_count <= 0 || towchar_count > size) + return 0; + towchar_count = MultiByteToWideChar(CP_OEMCP, 0, format_name, strlen(format_name), unicode_name, size); + if (towchar_count <= 0) return 0; +#endif for (i = 0; i < clipboard->map_size; i++) { @@ -1314,6 +1358,9 @@ static UINT cliprdr_send_tempdir(wfClipboard *clipboard) if (!clipboard) return -1; + // to-do: + // Directly use the environment variable `TEMP` is not safe. + // But this function is not used for now. if (GetEnvironmentVariableA("TEMP", tempDirectory.szTempDir, sizeof(tempDirectory.szTempDir)) == 0) return -1; @@ -1446,6 +1493,36 @@ static UINT cliprdr_send_format_list(wfClipboard *clipboard, UINT32 connID) return rc; } +// Ensure the event is not signaled, and reset it if it is. +UINT try_reset_event(HANDLE event) +{ + if (!event) + { + return ERROR_INTERNAL_ERROR; + } + + DWORD result = WaitForSingleObject(event, 0); + if (result == WAIT_OBJECT_0) + { + if (!ResetEvent(event)) + { + return GetLastError(); + } + else + { + return ERROR_SUCCESS; + } + } + else if (result == WAIT_TIMEOUT) + { + return ERROR_SUCCESS; + } + else + { + return ERROR_INTERNAL_ERROR; + } +} + UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, BOOL* recvedFlag, void **data) { UINT rc = ERROR_SUCCESS; @@ -1470,6 +1547,11 @@ UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, BO } } + if (!ResetEvent(event)) + { + // NOTE: critical error here, crash may be better + } + if (clipboard->context->IsStopped == TRUE) { wf_do_empty_cliprdr(clipboard); @@ -1481,12 +1563,6 @@ UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, BO return ERROR_INTERNAL_ERROR; } - if (!ResetEvent(event)) - { - // NOTE: critical error here, crash may be better - rc = ERROR_INTERNAL_ERROR; - } - if ((*data) == NULL) { rc = ERROR_INTERNAL_ERROR; @@ -1530,12 +1606,18 @@ static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UIN if (!clipboard || !clipboard->context || !clipboard->context->ClientFormatDataRequest) return ERROR_INTERNAL_ERROR; + rc = try_reset_event(clipboard->formatDataRespEvent); + if (rc != ERROR_SUCCESS) + { + return rc; + } + clipboard->formatDataRespReceived = FALSE; + remoteFormatId = get_remote_format_id(clipboard, formatId); formatDataRequest.connID = connID; formatDataRequest.requestedFormatId = remoteFormatId; clipboard->requestedFormatId = formatId; - clipboard->formatDataRespReceived = FALSE; rc = clipboard->context->ClientFormatDataRequest(clipboard->context, &formatDataRequest); if (rc != ERROR_SUCCESS) { @@ -1555,7 +1637,17 @@ UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, co if (!clipboard || !clipboard->context || !clipboard->context->ClientFileContentsRequest) return ERROR_INTERNAL_ERROR; + rc = try_reset_event(clipboard->req_fevent); + if (rc != ERROR_SUCCESS) + { + return rc; + } + clipboard->req_f_received = FALSE; + fileContentsRequest.connID = connID; + // streamId is `IStream*` pointer, though it is not very good on a 64-bit system. + // But it is OK, because it is only used to check if the stream is the same in + // `wf_cliprdr_server_file_contents_request()` function. fileContentsRequest.streamId = (UINT32)(ULONG_PTR)streamid; fileContentsRequest.listIndex = index; fileContentsRequest.dwFlags = flag; @@ -1564,7 +1656,6 @@ UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, co fileContentsRequest.cbRequested = nreq; fileContentsRequest.clipDataId = 0; fileContentsRequest.msgFlags = 0; - clipboard->req_f_received = FALSE; rc = clipboard->context->ClientFileContentsRequest(clipboard->context, &fileContentsRequest); if (rc != ERROR_SUCCESS) { @@ -1801,6 +1892,7 @@ static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM break; case WM_DESTROYCLIPBOARD: + // to-do: clear clipboard data? case WM_ASKCBFORMATNAME: case WM_HSCROLLCLIPBOARD: case WM_PAINTCLIPBOARD: @@ -1917,7 +2009,7 @@ static BOOL wf_cliprdr_get_file_contents(WCHAR *file_name, BYTE *buffer, LONG po LONG positionHigh, DWORD nRequested, DWORD *puSize) { BOOL res = FALSE; - HANDLE hFile; + HANDLE hFile = NULL; DWORD nGet, rc; if (!file_name || !buffer || !puSize) @@ -1945,9 +2037,11 @@ static BOOL wf_cliprdr_get_file_contents(WCHAR *file_name, BYTE *buffer, LONG po res = TRUE; error: - - if (!CloseHandle(hFile)) - res = FALSE; + if (hFile) + { + if (!CloseHandle(hFile)) + res = FALSE; + } if (res) *puSize = nGet; @@ -1958,8 +2052,8 @@ static BOOL wf_cliprdr_get_file_contents(WCHAR *file_name, BYTE *buffer, LONG po /* path_name has a '\' at the end. e.g. c:\newfolder\, file_name is c:\newfolder\new.txt */ static FILEDESCRIPTORW *wf_cliprdr_get_file_descriptor(WCHAR *file_name, size_t pathLen) { - HANDLE hFile; - FILEDESCRIPTORW *fd; + HANDLE hFile = NULL; + FILEDESCRIPTORW *fd = NULL; fd = (FILEDESCRIPTORW *)calloc(1, sizeof(FILEDESCRIPTORW)); if (!fd) @@ -1988,7 +2082,16 @@ static FILEDESCRIPTORW *wf_cliprdr_get_file_descriptor(WCHAR *file_name, size_t } fd->nFileSizeLow = GetFileSize(hFile, &fd->nFileSizeHigh); - wcscpy_s(fd->cFileName, sizeof(fd->cFileName) / 2, file_name + pathLen); + if ((wcslen(file_name + pathLen) + 1) > sizeof(fd->cFileName) / sizeof(fd->cFileName[0])) + { + // The file name is too long, which is not a normal case. + // So we just return NULL. + CloseHandle(hFile); + free(fd); + return NULL; + } + + wcsncpy_s(fd->cFileName, sizeof(fd->cFileName) / sizeof(fd->cFileName[0]), file_name + pathLen, wcslen(file_name + pathLen) + 1); CloseHandle(hFile); return fd; @@ -2037,7 +2140,12 @@ static BOOL wf_cliprdr_add_to_file_arrays(wfClipboard *clipboard, WCHAR *full_fi if (!clipboard->file_names[clipboard->nFiles]) return FALSE; - wcscpy_s(clipboard->file_names[clipboard->nFiles], MAX_PATH, full_file_name); + // `MAX_PATH` is long enough for the file name. + // So we just return FALSE if the file name is too long, which is not a normal case. + if ((wcslen(full_file_name) + 1) > MAX_PATH) + return FALSE; + + wcsncpy_s(clipboard->file_names[clipboard->nFiles], MAX_PATH, full_file_name, wcslen(full_file_name) + 1); /* add to descriptor array */ clipboard->fileDescriptor[clipboard->nFiles] = wf_cliprdr_get_file_descriptor(full_file_name, pathLen); @@ -2061,8 +2169,8 @@ static BOOL wf_cliprdr_traverse_directory(wfClipboard *clipboard, WCHAR *Dir, si if (!clipboard || !Dir) return FALSE; - // StringCchCopy(DirSpec, MAX_PATH, Dir); - // StringCchCat(DirSpec, MAX_PATH, TEXT("\\*")); + if (wcslen(Dir) + 3 > MAX_PATH) + return FALSE; StringCchCopyW(DirSpec, MAX_PATH, Dir); StringCchCatW(DirSpec, MAX_PATH, L"\\*"); @@ -2091,9 +2199,8 @@ static BOOL wf_cliprdr_traverse_directory(wfClipboard *clipboard, WCHAR *Dir, si if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { WCHAR DirAdd[MAX_PATH]; - // StringCchCopy(DirAdd, MAX_PATH, Dir); - // StringCchCat(DirAdd, MAX_PATH, _T("\\")); - // StringCchCat(DirAdd, MAX_PATH, FindFileData.cFileName); + if (wcslen(Dir) + wcslen(FindFileData.cFileName) + 2 > MAX_PATH) + return FALSE; StringCchCopyW(DirAdd, MAX_PATH, Dir); StringCchCatW(DirAdd, MAX_PATH, L"\\"); StringCchCatW(DirAdd, MAX_PATH, FindFileData.cFileName); @@ -2107,10 +2214,8 @@ static BOOL wf_cliprdr_traverse_directory(wfClipboard *clipboard, WCHAR *Dir, si else { WCHAR fileName[MAX_PATH]; - // StringCchCopy(fileName, MAX_PATH, Dir); - // StringCchCat(fileName, MAX_PATH, _T("\\")); - // StringCchCat(fileName, MAX_PATH, FindFileData.cFileName); - + if (wcslen(Dir) + wcslen(FindFileData.cFileName) + 2 > MAX_PATH) + return FALSE; StringCchCopyW(fileName, MAX_PATH, Dir); StringCchCatW(fileName, MAX_PATH, L"\\"); StringCchCatW(fileName, MAX_PATH, FindFileData.cFileName); @@ -2255,9 +2360,11 @@ static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context, if (context->EnableFiles) { UINT32 *p_conn_id = (UINT32 *)calloc(1, sizeof(UINT32)); - *p_conn_id = formatList->connID; - if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, OLE_SETCLIPBOARD, p_conn_id)) - rc = CHANNEL_RC_OK; + if (p_conn_id) { + *p_conn_id = formatList->connID; + if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, OLE_SETCLIPBOARD, p_conn_id)) + rc = CHANNEL_RC_OK; + } } else { @@ -2278,16 +2385,30 @@ static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context, // SetClipboardData(clipboard->format_mappings[i].local_format_id, NULL); FORMAT_IDS *format_ids = (FORMAT_IDS *)calloc(1, sizeof(FORMAT_IDS)); - format_ids->connID = formatList->connID; - format_ids->size = (UINT32)clipboard->map_size; - format_ids->formats = (UINT32 *)calloc(format_ids->size, sizeof(UINT32)); - for (i = 0; i < format_ids->size; ++i) + if (format_ids) { - format_ids->formats[i] = clipboard->format_mappings[i].local_format_id; - } - if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, DELAYED_RENDERING, format_ids)) - { - rc = CHANNEL_RC_OK; + format_ids->connID = formatList->connID; + format_ids->size = (UINT32)clipboard->map_size; + format_ids->formats = (UINT32 *)calloc(format_ids->size, sizeof(UINT32)); + if (format_ids->formats) + { + for (i = 0; i < format_ids->size; ++i) + { + format_ids->formats[i] = clipboard->format_mappings[i].local_format_id; + } + if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, DELAYED_RENDERING, format_ids)) + { + rc = CHANNEL_RC_OK; + } + else + { + rc = ERROR_INTERNAL_ERROR; + } + } + else + { + rc = ERROR_INTERNAL_ERROR; + } } else { @@ -2482,17 +2603,28 @@ wf_cliprdr_server_format_data_request(CliprdrClientContext *context, p += len + 1, clipboard->nFiles++) { int cchWideChar; - WCHAR *wFileName; cchWideChar = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, NULL, 0); wFileName = (LPWSTR)calloc(cchWideChar, sizeof(WCHAR)); - MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, wFileName, cchWideChar); - wf_cliprdr_process_filename(clipboard, wFileName, cchWideChar); + if (wFileName) + { + MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, wFileName, cchWideChar); + wf_cliprdr_process_filename(clipboard, wFileName, cchWideChar); + free(wFileName); + } + else + { + rc = ERROR_INTERNAL_ERROR; + GlobalUnlock(stg_medium.hGlobal); + ReleaseStgMedium(&stg_medium); + goto exit; + } } } GlobalUnlock(stg_medium.hGlobal); ReleaseStgMedium(&stg_medium); resp: + // size will not overflow, because size type is size_t (unsigned __int64) size = 4 + clipboard->nFiles * sizeof(FILEDESCRIPTORW); groupDsc = (FILEGROUPDESCRIPTORW *)malloc(size); @@ -2532,10 +2664,17 @@ wf_cliprdr_server_format_data_request(CliprdrClientContext *context, globlemem = (char *)GlobalLock(hClipdata); size = (int)GlobalSize(hClipdata); buff = malloc(size); - CopyMemory(buff, globlemem, size); + if (buff) + { + CopyMemory(buff, globlemem, size); + rc = ERROR_SUCCESS; + } + else + { + rc = ERROR_INTERNAL_ERROR; + } GlobalUnlock(hClipdata); CloseClipboard(); - rc = ERROR_SUCCESS; } } else From 5f2901686175f76f6849eef2e76e51e36eee6a1c Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:27:52 +0800 Subject: [PATCH 041/210] fix: build web (#9259) 1. Web, build. 2. Web and mobile, `onSubmitted` for ID text field. 3. Web, remove unused key 'toggle_option'. Signed-off-by: fufesou --- flutter/lib/mobile/pages/connection_page.dart | 3 +++ flutter/lib/mobile/pages/home_page.dart | 3 ++- flutter/lib/web/bridge.dart | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 9fcef8e3f14..c6e81238926 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -249,6 +249,9 @@ class _ConnectionPageState extends State { ), ), inputFormatters: [IDTextInputFormatter()], + onSubmitted: (_) { + onConnect(); + }, ); }, onSelected: (option) { diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index 0db7a2b91e3..d26d9168542 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -46,10 +46,11 @@ class HomePageState extends State { void initPages() { _pages.clear(); - if (!bind.isIncomingOnly()) + if (!bind.isIncomingOnly()) { _pages.add(ConnectionPage( appBarActions: [], )); + } if (isAndroid && !bind.isOutgoingOnly()) { _chatPageTabIndex = _pages.length; _pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]); diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 8805831bc53..d1b777dd1e4 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -187,7 +187,7 @@ class RustdeskImpl { Future sessionToggleOption( {required UuidValue sessionId, required String value, dynamic hint}) { return Future( - () => js.context.callMethod('setByName', ['toggle_option', value])); + () => js.context.callMethod('setByName', ['option:toggle', value])); } Future sessionTogglePrivacyMode( @@ -196,7 +196,7 @@ class RustdeskImpl { required bool on, dynamic hint}) { return Future(() => js.context.callMethod('setByName', [ - 'toggle_option', + 'option:toggle', jsonEncode({implKey, on}) ])); } @@ -1704,6 +1704,10 @@ class RustdeskImpl { throw UnimplementedError(); } + int mainMaxEncryptLen({dynamic hint}) { + throw UnimplementedError(); + } + sessionRenameFile( {required UuidValue sessionId, required int actId, From 3bd34bf0b9a9073105db084b01d4b0a108affeab Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 5 Sep 2024 18:34:48 +0800 Subject: [PATCH 042/210] increase interval for restart linux ui, try fix loop start (#9264) Signed-off-by: 21pages --- src/ipc.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ipc.rs b/src/ipc.rs index 6d5e247d42c..2b591d10711 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -401,7 +401,8 @@ async fn handle(data: Data, stream: &mut Connection) { std::fs::remove_file(&Config::ipc_path("")).ok(); #[cfg(target_os = "linux")] { - hbb_common::sleep((crate::platform::SERVICE_INTERVAL * 2) as f32 / 1000.0) + // https://github.com/rustdesk/rustdesk/discussions/9254, slow on some machines + hbb_common::sleep((crate::platform::SERVICE_INTERVAL * 2) as f32 / 1000.0 + 1.2) .await; crate::run_me::<&str>(vec![]).ok(); } From 7a1157f1b0bb524f5d9595830e1595ec24dffb52 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:37:14 +0800 Subject: [PATCH 043/210] refact: quality status event (#9268) Signed-off-by: fufesou --- flutter/lib/common/widgets/overlay.dart | 3 ++- flutter/lib/models/model.dart | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index 9b20136e150..a1620b106e3 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -595,7 +595,8 @@ class QualityMonitor extends StatelessWidget { "${qualityMonitorModel.data.targetBitrate ?? '-'}kb"), _row( "Codec", qualityMonitorModel.data.codecFormat ?? '-'), - _row("Chroma", qualityMonitorModel.data.chroma ?? '-'), + if (!isWeb) + _row("Chroma", qualityMonitorModel.data.chroma ?? '-'), ], ), ) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 6f2a9eb2e2d..c622d9f10d8 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -2230,8 +2230,10 @@ class QualityMonitorModel with ChangeNotifier { updateQualityStatus(Map evt) { try { - if ((evt['speed'] as String).isNotEmpty) _data.speed = evt['speed']; - if ((evt['fps'] as String).isNotEmpty) { + if (evt.containsKey('speed') && (evt['speed'] as String).isNotEmpty) { + _data.speed = evt['speed']; + } + if (evt.containsKey('fps') && (evt['fps'] as String).isNotEmpty) { final fps = jsonDecode(evt['fps']) as Map; final pi = parent.target?.ffiModel.pi; if (pi != null) { @@ -2252,14 +2254,18 @@ class QualityMonitorModel with ChangeNotifier { _data.fps = null; } } - if ((evt['delay'] as String).isNotEmpty) _data.delay = evt['delay']; - if ((evt['target_bitrate'] as String).isNotEmpty) { + if (evt.containsKey('delay') && (evt['delay'] as String).isNotEmpty) { + _data.delay = evt['delay']; + } + if (evt.containsKey('target_bitrate') && + (evt['target_bitrate'] as String).isNotEmpty) { _data.targetBitrate = evt['target_bitrate']; } - if ((evt['codec_format'] as String).isNotEmpty) { + if (evt.containsKey('codec_format') && + (evt['codec_format'] as String).isNotEmpty) { _data.codecFormat = evt['codec_format']; } - if ((evt['chroma'] as String).isNotEmpty) { + if (evt.containsKey('chroma') && (evt['chroma'] as String).isNotEmpty) { _data.chroma = evt['chroma']; } notifyListeners(); From 415003658912927564ff16fcb27156410b336586 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 5 Sep 2024 22:48:20 +0800 Subject: [PATCH 044/210] remove first frame fallback if repeat (#9267) Signed-off-by: 21pages --- src/server/video_service.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/video_service.rs b/src/server/video_service.rs index e091966034b..aeff1911e01 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -947,7 +947,9 @@ fn handle_one_frame( } else { 3 }; - if first || *encode_fail_counter >= max_fail_times { + let repeat = !encoder.latency_free(); + // repeat encoders can reach max_fail_times on the first frame + if (first && !repeat) || *encode_fail_counter >= max_fail_times { *encode_fail_counter = 0; if encoder.is_hardware() { encoder.disable(); From 26ebd0deb92e630511fe4ee65a66b1c4e77b20fe Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 5 Sep 2024 23:39:07 +0800 Subject: [PATCH 045/210] fix: clipboard, cmd ipc (#9270) 1. Send raw contents if `content_len` > 1024*3. 2. Send raw contents if it is not empty. 3. Try read clipboard again if no data from cm. Signed-off-by: fufesou --- src/server/clipboard_service.rs | 43 ++++++++++++++++++--------------- src/ui_cm_interface.rs | 8 +++--- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index 3040a8f88f3..e7097425857 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -95,25 +95,30 @@ impl Handler { log::error!("Failed to read clipboard from cm: {}", e); } Ok(data) => { - let mut msg = Message::new(); - let multi_clipboards = MultiClipboards { - clipboards: data - .into_iter() - .map(|c| Clipboard { - compress: c.compress, - content: c.content, - width: c.width, - height: c.height, - format: ClipboardFormat::from_i32(c.format) - .unwrap_or(ClipboardFormat::Text) - .into(), - ..Default::default() - }) - .collect(), - ..Default::default() - }; - msg.set_multi_clipboards(multi_clipboards); - return Some(msg); + // Skip sending empty clipboard data. + // Maybe there's something wrong reading the clipboard data in cm, but no error msg is returned. + // The clipboard data should not be empty, the last line will try again to get the clipboard data. + if !data.is_empty() { + let mut msg = Message::new(); + let multi_clipboards = MultiClipboards { + clipboards: data + .into_iter() + .map(|c| Clipboard { + compress: c.compress, + content: c.content, + width: c.width, + height: c.height, + format: ClipboardFormat::from_i32(c.format) + .unwrap_or(ClipboardFormat::Text) + .into(), + ..Default::default() + }) + .collect(), + ..Default::default() + }; + msg.set_multi_clipboards(multi_clipboards); + return Some(msg); + } } } } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 89e9ceabbea..f1748112ea6 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -498,10 +498,10 @@ impl IpcTaskRunner { let (content, next_raw) = { // TODO: find out a better threshold if content_len > 1024 * 3 { - (c.content, false) - } else { raw_contents.extend(c.content); (bytes::Bytes::new(), true) + } else { + (c.content, false) } }; main_data.push(ClipboardNonFile { @@ -515,7 +515,9 @@ impl IpcTaskRunner { }); } allow_err!(self.stream.send(&Data::ClipboardNonFile(Some(("".to_owned(), main_data)))).await); - allow_err!(self.stream.send_raw(raw_contents.into()).await); + if !raw_contents.is_empty() { + allow_err!(self.stream.send_raw(raw_contents.into()).await); + } } Err(e) => { allow_err!(self.stream.send(&Data::ClipboardNonFile(Some((format!("{}", e), vec![])))).await); From aa3402b44a9acff175ad18b8a898d265fe8990a1 Mon Sep 17 00:00:00 2001 From: Xp96 <38923106+Xp96@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:15:07 -0300 Subject: [PATCH 046/210] Update ptbr.rs (#9271) --- src/lang/ptbr.rs | 190 +++++++++++++++++++++++------------------------ 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 83ee1e0d233..a5ba6de7b0f 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -498,131 +498,131 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("resolution_original_tip", "Resolução original"), ("resolution_fit_local_tip", "Adequar a resolução local"), ("resolution_custom_tip", "Customizar resolução"), - ("Collapse toolbar", ""), - ("Accept and Elevate", ""), - ("accept_and_elevate_btn_tooltip", ""), - ("clipboard_wait_response_timeout_tip", ""), - ("Incoming connection", ""), - ("Outgoing connection", ""), + ("Collapse toolbar", "Ocultar barra de ferramentas"), + ("Accept and Elevate", "Aceitar e elevar"), + ("accept_and_elevate_btn_tooltip", "Aceitar a conexão e elevar os privilégios do UAC."), + ("clipboard_wait_response_timeout_tip", "Tempo de espera para a resposta da área de transferência expirado."), + ("Incoming connection", "Conexão de entrada"), + ("Outgoing connection", "Conexão de saída"), ("Exit", "Sair"), ("Open", "Abrir"), - ("logout_tip", ""), + ("logout_tip", "Tem certeza que deseja sair?"), ("Service", "Serviço"), ("Start", "Iniciar"), ("Stop", "Parar"), - ("exceed_max_devices", ""), + ("exceed_max_devices", "Você atingiu o número máximo de dispositivos gerenciados."), ("Sync with recent sessions", "Sincronizar com sessões recentes"), ("Sort tags", "Classificar tags"), ("Open connection in new tab", "Abrir conexão em uma nova aba"), ("Move tab to new window", "Mover aba para uma nova janela"), ("Can not be empty", "Não pode estar vazio"), - ("Already exists", ""), + ("Already exists", "Já existe"), ("Change Password", "Alterar senha"), ("Refresh Password", "Atualizar senha"), - ("ID", ""), - ("Grid View", ""), - ("List View", ""), - ("Select", ""), - ("Toggle Tags", ""), - ("pull_ab_failed_tip", ""), - ("push_ab_failed_tip", ""), - ("synced_peer_readded_tip", ""), + ("ID", "ID"), + ("Grid View", "Visualização em grade"), + ("List View", "Visualização em lista"), + ("Select", "Selecionar"), + ("Toggle Tags", "Alternar etiquetas"), + ("pull_ab_failed_tip", "Não foi possível atualizar o diretório"), + ("push_ab_failed_tip", "Não foi possível sincronizar o diretório com o servidor"), + ("synced_peer_readded_tip", "Os dispositivos presentes em sessões recentes serão sincronizados com o diretório."), ("Change Color", "Alterar cor"), ("Primary Color", "Cor principal"), - ("HSV Color", ""), - ("Installation Successful!", ""), - ("Installation failed!", ""), - ("Reverse mouse wheel", ""), + ("HSV Color", "Cor HSV"), + ("Installation Successful!", "Instalação bem-sucedida!"), + ("Installation failed!", "A instalação falhou!"), + ("Reverse mouse wheel", "Inverter rolagem do mouse"), ("{} sessions", ""), - ("scam_title", ""), - ("scam_text1", ""), - ("scam_text2", ""), - ("Don't show again", ""), + ("scam_title", "Você pode estar sendo ENGANADO!"), + ("scam_text1", "Se você estiver ao telefone com alguém que NÃO conhece e em quem NÃO confia e essa pessoa pedir para você usar o RustDesk e iniciar o serviço, NÃO faça isso !! e desligue imediatamente."), + ("scam_text2", "Provavelmente são golpistas tentando roubar seu dinheiro ou informações privadas."), + ("Don't show again", "Não mostrar novamente"), ("I Agree", "Eu concordo"), - ("Decline", ""), - ("Timeout in minutes", ""), - ("auto_disconnect_option_tip", ""), - ("Connection failed due to inactivity", ""), - ("Check for software update on startup", ""), - ("upgrade_rustdesk_server_pro_to_{}_tip", ""), - ("pull_group_failed_tip", ""), + ("Decline", "Recusar"), + ("Timeout in minutes", "Tempo limite em minutos"), + ("auto_disconnect_option_tip", "Encerrar sessões entrantes automaticamente por inatividade do usuário."), + ("Connection failed due to inactivity", "Conexão encerrada automaticamente por inatividade."), + ("Check for software update on startup", "Verificar atualizações do software ao iniciar"), + ("upgrade_rustdesk_server_pro_to_{}_tip", "Atualize o RustDesk Server Pro para a versão {} ou superior."), + ("pull_group_failed_tip", "Não foi possível atualizar o grupo."), ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", "Remover papel de parede durante sessão remota"), ("Test", "Teste"), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), - ("Use all my displays for the remote session", ""), - ("selinux_tip", ""), + ("display_is_plugged_out_msg", "A tela está desconectada. Mudando para a principal."), + ("No displays", "Nenhum display encontrado"), + ("Open in new window", "Abrir em uma nova janela"), + ("Show displays as individual windows", "Mostrar as telas como janelas individuais"), + ("Use all my displays for the remote session", "Usar todas as minhas telas para a sessão remota"), + ("selinux_tip", "O SELinux está ativado em seu dispositivo, o que pode impedir que o RustDesk funcione corretamente como dispositivo controlado."), ("Change view", "Alterar visualização"), - ("Big tiles", ""), - ("Small tiles", ""), + ("Big tiles", "Ícones grandes"), + ("Small tiles", "Ícones pequenos"), ("List", "Lista"), ("Virtual display", "Display Virtual"), - ("Plug out all", ""), - ("True color (4:4:4)", ""), - ("Enable blocking user input", ""), - ("id_input_tip", ""), + ("Plug out all", "Desconectar tudo"), + ("True color (4:4:4)", "Cor verdadeira (4:4:4)"), + ("Enable blocking user input", "Habilitar bloqueio da entrada do usuário"), + ("id_input_tip", "Você pode inserir um ID, um IP direto ou um domínio com uma porta (:).\nPara acessar um dispositivo em outro servidor, adicione o IP do servidor (@?key=), por exemplo,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nPara acessar um dispositivo em um servidor público, insira \"@public\", a chave não é necessária para um servidor público."), ("privacy_mode_impl_mag_tip", ""), ("privacy_mode_impl_virtual_display_tip", ""), ("Enter privacy mode", "Entrar no modo privado"), ("Exit privacy mode", "Sair do modo privado"), - ("idd_not_support_under_win10_2004_tip", ""), - ("input_source_1_tip", ""), - ("input_source_2_tip", ""), - ("Swap control-command key", ""), - ("swap-left-right-mouse", ""), + ("idd_not_support_under_win10_2004_tip", "O driver de tela indireto não é suportado. É necessário o Windows 10, versão 2004 ou superior."), + ("input_source_1_tip", "Fonte de entrada 1"), + ("input_source_2_tip", "Fonte de entrada 2"), + ("Swap control-command key", "Trocar teclas Control e Comando"), + ("swap-left-right-mouse", "Trocar botões esquerdo e direito do mouse"), ("2FA code", "Código 2FA"), - ("More", ""), - ("enable-2fa-title", ""), - ("enable-2fa-desc", ""), - ("wrong-2fa-code", ""), - ("enter-2fa-title", ""), - ("Email verification code must be 6 characters.", ""), - ("2FA code must be 6 digits.", ""), - ("Multiple Windows sessions found", ""), + ("More", "Mais"), + ("enable-2fa-title", "Habilitar autenticação em duas etapas"), + ("enable-2fa-desc", "Configure seu autenticador agora. Você pode usar um aplicativo de autenticação como Authy, Microsoft ou Google Authenticator em seu telefone ou computador. Escaneie o código QR com seu aplicativo e insira o código mostrado para habilitar a autenticação em duas etapas."), + ("wrong-2fa-code", "Código inválido. Verifique se o código e as configurações de horário estão corretas."), + ("enter-2fa-title", "Autenticação em duas etapas"), + ("Email verification code must be 6 characters.", "O código de verificação por e-mail deve ter 6 caracteres."), + ("2FA code must be 6 digits.", "O código 2FA deve ter 6 dígitos."), + ("Multiple Windows sessions found", "Múltiplas sessões de janela encontradas"), ("Please select the session you want to connect to", "Por favor, selecione a sessão que você deseja se conectar"), - ("powered_by_me", ""), - ("outgoing_only_desk_tip", ""), - ("preset_password_warning", ""), - ("Security Alert", ""), - ("My address book", ""), + ("powered_by_me", "Desenvolvido por RustDesk"), + ("outgoing_only_desk_tip", "Esta é uma edição personalizada.\nVocê pode se conectar a outros dispositivos, mas eles não podem se conectar ao seu."), + ("preset_password_warning", "Atenção: esta edição personalizada vem com uma senha predefinida. Qualquer pessoa que a conhecer poderá controlar totalmente seu dispositivo. Se isso não for o que você deseja, desinstale o software imediatamente."), + ("Security Alert", "Alerta de Segurança"), + ("My address book", "Minha lista de contatos"), ("Personal", ""), - ("Owner", ""), + ("Owner", "Proprietário"), ("Set shared password", "Definir senha compartilhada"), ("Exist in", ""), ("Read-only", "Apenas leitura"), ("Read/Write", "Leitura/escrita"), ("Full Control", "Controle total"), - ("share_warning_tip", ""), + ("share_warning_tip", "Os campos mostrados acima são compartilhados e visíveis por outras pessoas."), ("Everyone", "Todos"), - ("ab_web_console_tip", ""), - ("allow-only-conn-window-open-tip", ""), - ("no_need_privacy_mode_no_physical_displays_tip", ""), - ("Follow remote cursor", ""), - ("Follow remote window focus", ""), + ("ab_web_console_tip", "Mais opções no console web"), + ("allow-only-conn-window-open-tip", "Permitir conexões apenas quando a janela do RustDesk estiver aberta"), + ("no_need_privacy_mode_no_physical_displays_tip", "Sem telas físicas, o modo privado não é necessário"), + ("Follow remote cursor", "Seguir cursor remoto"), + ("Follow remote window focus", "Seguir janela remota ativa"), ("default_proxy_tip", ""), - ("no_audio_input_device_tip", ""), + ("no_audio_input_device_tip", "Nenhum dispositivo de entrada de áudio encontrado"), ("Incoming", ""), ("Outgoing", ""), - ("Clear Wayland screen selection", ""), - ("clear_Wayland_screen_selection_tip", ""), - ("confirm_clear_Wayland_screen_selection_tip", ""), + ("Clear Wayland screen selection", "Limpar seleção de tela do Wayland"), + ("clear_Wayland_screen_selection_tip", "Depois de limpar a seleção de tela, você pode selecioná-la novamente para compartilhar."), + ("confirm_clear_Wayland_screen_selection_tip", "Tem certeza que deseja limpar a seleção da tela do Wayland?"), ("android_new_voice_call_tip", ""), - ("texture_render_tip", ""), - ("Use texture rendering", ""), - ("Floating window", ""), - ("floating_window_tip", ""), - ("Keep screen on", ""), - ("Never", ""), - ("During controlled", ""), - ("During service is on", ""), - ("Capture screen using DirectX", ""), + ("texture_render_tip", "Use renderização de textura para tornar as imagens mais suaves"), + ("Use texture rendering", "Usar renderização de textura"), + ("Floating window", "Janela flutuante"), + ("floating_window_tip", "Ajuda a manter o serviço RustDesk em segundo plano"), + ("Keep screen on", "Manter tela ligada"), + ("Never", "Nunca"), + ("During controlled", "Durante controle"), + ("During service is on", "Enquanto o serviço estiver ativo"), + ("Capture screen using DirectX", "Capturar tela usando DirectX"), ("Back", "Voltar"), - ("Apps", ""), - ("Volume up", ""), - ("Volume down", ""), + ("Apps", "Apps"), + ("Volume up", "Aumentar volume"), + ("Volume down", "Diminuir volume"), ("Power", ""), ("Telegram bot", "Bot Telegram"), ("enable-bot-tip", "Se você ativar este recurso, poderá receber o código 2FA do seu bot. Ele também pode funcionar como uma notificação de conexão."), @@ -630,19 +630,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("cancel-2fa-confirm-tip", "Tem certeza de que deseja cancelar a 2FA?"), ("cancel-bot-confirm-tip", "Tem certeza de que deseja cancelar o bot do Telegram?"), ("About RustDesk", "Sobre RustDesk"), - ("Send clipboard keystrokes", ""), + ("Send clipboard keystrokes", "Colar área de transferência"), ("network_error_tip", "Por favor, verifique sua conexão de rede e clique em tentar novamente."), ("Unlock with PIN", "Desbloquear com PIN"), - ("Requires at least {} characters", ""), + ("Requires at least {} characters", "São necessários pelo menos {} caracteres"), ("Wrong PIN", "PIN Errado"), ("Set PIN", "Definir PIN"), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("Enable trusted devices", "Habilitar dispositivos confiáveis"), + ("Manage trusted devices", "Gerenciar dispositivos confiáveis"), + ("Platform", "Plataforma"), + ("Days remaining", "Dias restantes"), + ("enable-trusted-devices-tip", "Ignore a verificação de dois fatores em dispositivos confiáveis"), + ("Parent directory", "Diretório pai"), + ("Resume", "Continuar"), + ("Invalid file name", "Nome de arquivo inválido"), ].iter().cloned().collect(); } From f0ca4b9fee666ec25c903e0c222894c591f09f63 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 6 Sep 2024 14:43:38 +0800 Subject: [PATCH 047/210] --no-server parameter to avoid server ipc occupied by ui (#9272) Signed-off-by: 21pages --- src/core_main.rs | 11 +++++++---- src/ipc.rs | 6 +++--- src/main.rs | 2 +- src/platform/linux.rs | 1 + src/server.rs | 31 +++++++++++++++++++++---------- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/core_main.rs b/src/core_main.rs index 3aa69f8f367..375b1dbc573 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -39,6 +39,7 @@ pub fn core_main() -> Option> { let mut _is_run_as_system = false; let mut _is_quick_support = false; let mut _is_flutter_invoke_new_connection = false; + let mut no_server = false; let mut arg_exe = Default::default(); for arg in std::env::args() { if i == 0 { @@ -62,6 +63,8 @@ pub fn core_main() -> Option> { _is_run_as_system = true; } else if arg == "--quick_support" { _is_quick_support = true; + } else if arg == "--no-server" { + no_server = true; } else { args.push(arg); } @@ -134,6 +137,7 @@ pub fn core_main() -> Option> { } } hbb_common::init_log(false, &log_name); + log::info!("main start args: {:?}, env: {:?}", args, std::env::args()); // linux uni (url) go here. #[cfg(all(target_os = "linux", feature = "flutter"))] @@ -161,9 +165,8 @@ pub fn core_main() -> Option> { #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] init_plugins(&args); - log::info!("main start args:{:?}", args); if args.is_empty() || crate::common::is_empty_uni_link(&args[0]) { - std::thread::spawn(move || crate::start_server(false)); + std::thread::spawn(move || crate::start_server(false, no_server)); } else { #[cfg(windows)] { @@ -279,11 +282,11 @@ pub fn core_main() -> Option> { crate::privacy_mode::restore_reg_connectivity(true); #[cfg(any(target_os = "linux", target_os = "windows"))] { - crate::start_server(true); + crate::start_server(true, false); } #[cfg(target_os = "macos")] { - let handler = std::thread::spawn(move || crate::start_server(true)); + let handler = std::thread::spawn(move || crate::start_server(true, false)); crate::tray::start_tray(); // prevent server exit when encountering errors from tray hbb_common::allow_err!(handler.join()); diff --git a/src/ipc.rs b/src/ipc.rs index 2b591d10711..9815fdb748b 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -401,10 +401,10 @@ async fn handle(data: Data, stream: &mut Connection) { std::fs::remove_file(&Config::ipc_path("")).ok(); #[cfg(target_os = "linux")] { - // https://github.com/rustdesk/rustdesk/discussions/9254, slow on some machines - hbb_common::sleep((crate::platform::SERVICE_INTERVAL * 2) as f32 / 1000.0 + 1.2) + hbb_common::sleep((crate::platform::SERVICE_INTERVAL * 2) as f32 / 1000.0) .await; - crate::run_me::<&str>(vec![]).ok(); + // https://github.com/rustdesk/rustdesk/discussions/9254 + crate::run_me::<&str>(vec!["--no-server"]).ok(); } #[cfg(target_os = "macos")] { diff --git a/src/main.rs b/src/main.rs index bc41365e3fb..44ace8a76e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,7 +102,7 @@ fn main() { cli::connect_test(p, key, token); } else if let Some(p) = matches.value_of("server") { log::info!("id={}", hbb_common::config::Config::get_id()); - crate::start_server(true); + crate::start_server(true, false); } common::global_clean(); } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 90e2f52ca09..4bb666fb9c2 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1378,6 +1378,7 @@ pub fn uninstall_service(show_new_window: bool, _: bool) -> bool { Config::set_option("stop-service".into(), "".into()); return true; } + // systemctl stop will kill child processes, below may not be executed. if show_new_window { run_me_with(2); } diff --git a/src/server.rs b/src/server.rs index 547886a5c98..a973ba6aef9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -456,16 +456,21 @@ pub async fn start_server(_is_server: bool) { /// * `is_server` - Whether the current client is definitely the server. /// If true, the server will be started. /// Otherwise, client will check if there's already a server and start one if not. +/// * `no_server` - If `is_server` is false, whether to start a server if not found. #[cfg(not(any(target_os = "android", target_os = "ios")))] #[tokio::main] -pub async fn start_server(is_server: bool) { - #[cfg(target_os = "linux")] - { - log::info!("DISPLAY={:?}", std::env::var("DISPLAY")); - log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY")); - } - #[cfg(windows)] - hbb_common::platform::windows::start_cpu_performance_monitor(); +pub async fn start_server(is_server: bool, no_server: bool) { + use std::sync::Once; + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + #[cfg(target_os = "linux")] + { + log::info!("DISPLAY={:?}", std::env::var("DISPLAY")); + log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY")); + } + #[cfg(windows)] + hbb_common::platform::windows::start_cpu_performance_monitor(); + }); if is_server { crate::common::set_server_running(true); @@ -516,8 +521,14 @@ pub async fn start_server(is_server: bool) { crate::ipc::client_get_hwcodec_config_thread(0); } Err(err) => { - log::info!("server not started (will try to start): {}", err); - std::thread::spawn(|| start_server(true)); + log::info!("server not started: {err:?}, no_server: {no_server}"); + if no_server { + hbb_common::sleep(1.0).await; + std::thread::spawn(|| start_server(false, true)); + } else { + log::info!("try start server"); + std::thread::spawn(|| start_server(true, false)); + } } } } From a4cd64f0d5a0a3745d416cf85c4aac945c03c0db Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 7 Sep 2024 10:20:52 +0800 Subject: [PATCH 048/210] fix qsv memory leak by updating ffmpeg (#9266) * fix qsv memory leak by updating ffmpeg * Memory leaks occur when destroying FFmpeg QSV VRAM encoders. This issue is resolved with FFmpeg version 7. * FFmpeg requires ffnvcodec version 12.1.14.0 or higher, and an NVIDIA driver version greater than 530. For more details, https://github.com/FFmpeg/nv-codec-headers/tree/n12.1.14.0. * The code of NVIDIA VRAM encoder is not changed, still use Video Codec SDK version 11, which is unaffected by FFmpeg. Drivers newer than 470 can support this, but we may consider an update later, as the support check by sdk code may not be accurate for FFmpeg RAM encoders. * The issue is related to FFmpeg, not libmfx. FFmpeg version 7 recommends using libvpl, but vcpkg currently lacks ports for libvpl. We can add these in the future. * D3D11 Texture Rendering: The "Shared GPU Memory" in the task manager continue increasing when using D3D11 texture render, which can exceed the GPU memory limit (e.g., reaching up to 100GB). I don't know what it is and will try to find it out. * Roughly tests on Windows, Linux, macOS, and Android for quick fix. Further testing will be performed, and I will share the results in this pr. Signed-off-by: 21pages * update flutter_gpu_texture_render, fix shared gpu memory leak while rendering Signed-off-by: 21pages --------- Signed-off-by: 21pages --- Cargo.lock | 2 +- flutter/pubspec.lock | 4 +- flutter/pubspec.yaml | 2 +- ...-release-7.0-s-qsvenc-update_bitrate.patch | 95 ------------------- ...1-android-mediacodec-encode-align-64.patch | 40 -------- ...dd-query_timeout-option-for-h264-hev.patch | 40 ++++---- ...-amfenc-reconfig-when-bitrate-change.patch | 30 +++--- .../0003-amf-colorspace.patch} | 42 ++++---- res/vcpkg/ffmpeg/portfile.cmake | 49 ++++------ res/vcpkg/ffmpeg/vcpkg.json | 2 +- vcpkg.json | 2 +- 11 files changed, 82 insertions(+), 226 deletions(-) delete mode 100644 res/vcpkg/ffmpeg/5.1/0003-use-release-7.0-s-qsvenc-update_bitrate.patch delete mode 100644 res/vcpkg/ffmpeg/7.0/0001-android-mediacodec-encode-align-64.patch rename res/vcpkg/ffmpeg/{5.1 => patch}/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch (62%) rename res/vcpkg/ffmpeg/{5.1 => patch}/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch (71%) rename res/vcpkg/ffmpeg/{5.1/0004-amf-colorspace.patch => patch/0003-amf-colorspace.patch} (88%) diff --git a/Cargo.lock b/Cargo.lock index a6839b9eb12..bd0e449e754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3045,7 +3045,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hwcodec" version = "0.7.0" -source = "git+https://github.com/rustdesk-org/hwcodec#b78a69c81631dd9ccfed9df68709808193082242" +source = "git+https://github.com/rustdesk-org/hwcodec#9e8b6efd8e5d904b5325597a271ebe78f5a74f3b" dependencies = [ "bindgen 0.59.2", "cc", diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 61d57bcba4d..62f9283a000 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -525,8 +525,8 @@ packages: dependency: "direct main" description: path: "." - ref: "38951317afe79d953ab25733667bd96e172a80d3" - resolved-ref: "38951317afe79d953ab25733667bd96e172a80d3" + ref: "2ded7f146437a761ffe6981e2f742038f85ca68d" + resolved-ref: "2ded7f146437a761ffe6981e2f742038f85ca68d" url: "https://github.com/rustdesk-org/flutter_gpu_texture_renderer" source: git version: "0.0.1" diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index db15c74cc0a..5d49249d49c 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -93,7 +93,7 @@ dependencies: flutter_gpu_texture_renderer: git: url: https://github.com/rustdesk-org/flutter_gpu_texture_renderer - ref: 38951317afe79d953ab25733667bd96e172a80d3 + ref: 2ded7f146437a761ffe6981e2f742038f85ca68d uuid: ^3.0.7 auto_size_text_field: ^2.2.1 flex_color_picker: ^3.3.0 diff --git a/res/vcpkg/ffmpeg/5.1/0003-use-release-7.0-s-qsvenc-update_bitrate.patch b/res/vcpkg/ffmpeg/5.1/0003-use-release-7.0-s-qsvenc-update_bitrate.patch deleted file mode 100644 index 475fb627f3e..00000000000 --- a/res/vcpkg/ffmpeg/5.1/0003-use-release-7.0-s-qsvenc-update_bitrate.patch +++ /dev/null @@ -1,95 +0,0 @@ -From afe89a70f6bc7ebd0a6a0a31101801b88cbd60ee Mon Sep 17 00:00:00 2001 -From: 21pages -Date: Sun, 5 May 2024 12:45:23 +0800 -Subject: [PATCH] use release/7.0's update_bitrate - -Signed-off-by: 21pages ---- - libavcodec/qsvenc.c | 39 +++++++++++++++++++++++++++++++++++++++ - libavcodec/qsvenc.h | 6 ++++++ - 2 files changed, 45 insertions(+) - -diff --git a/libavcodec/qsvenc.c b/libavcodec/qsvenc.c -index 2382c2f5f7..9b34f37eb3 100644 ---- a/libavcodec/qsvenc.c -+++ b/libavcodec/qsvenc.c -@@ -714,6 +714,11 @@ static int init_video_param(AVCodecContext *avctx, QSVEncContext *q) - brc_param_multiplier = (FFMAX(FFMAX3(target_bitrate_kbps, max_bitrate_kbps, buffer_size_in_kilobytes), - initial_delay_in_kilobytes) + 0x10000) / 0x10000; - -+ q->old_rc_buffer_size = avctx->rc_buffer_size; -+ q->old_rc_initial_buffer_occupancy = avctx->rc_initial_buffer_occupancy; -+ q->old_bit_rate = avctx->bit_rate; -+ q->old_rc_max_rate = avctx->rc_max_rate; -+ - switch (q->param.mfx.RateControlMethod) { - case MFX_RATECONTROL_CBR: - case MFX_RATECONTROL_VBR: -@@ -1657,6 +1662,39 @@ static int update_qp(AVCodecContext *avctx, QSVEncContext *q, - return updated; - } - -+static int update_bitrate(AVCodecContext *avctx, QSVEncContext *q) -+{ -+ int updated = 0; -+ int target_bitrate_kbps, max_bitrate_kbps, brc_param_multiplier; -+ int buffer_size_in_kilobytes, initial_delay_in_kilobytes; -+ -+ UPDATE_PARAM(q->old_rc_buffer_size, avctx->rc_buffer_size); -+ UPDATE_PARAM(q->old_rc_initial_buffer_occupancy, avctx->rc_initial_buffer_occupancy); -+ UPDATE_PARAM(q->old_bit_rate, avctx->bit_rate); -+ UPDATE_PARAM(q->old_rc_max_rate, avctx->rc_max_rate); -+ if (!updated) -+ return 0; -+ -+ buffer_size_in_kilobytes = avctx->rc_buffer_size / 8000; -+ initial_delay_in_kilobytes = avctx->rc_initial_buffer_occupancy / 8000; -+ target_bitrate_kbps = avctx->bit_rate / 1000; -+ max_bitrate_kbps = avctx->rc_max_rate / 1000; -+ brc_param_multiplier = (FFMAX(FFMAX3(target_bitrate_kbps, max_bitrate_kbps, buffer_size_in_kilobytes), -+ initial_delay_in_kilobytes) + 0x10000) / 0x10000; -+ -+ q->param.mfx.BufferSizeInKB = buffer_size_in_kilobytes / brc_param_multiplier; -+ q->param.mfx.InitialDelayInKB = initial_delay_in_kilobytes / brc_param_multiplier; -+ q->param.mfx.TargetKbps = target_bitrate_kbps / brc_param_multiplier; -+ q->param.mfx.MaxKbps = max_bitrate_kbps / brc_param_multiplier; -+ q->param.mfx.BRCParamMultiplier = brc_param_multiplier; -+ av_log(avctx, AV_LOG_VERBOSE, -+ "Reset BufferSizeInKB: %d; InitialDelayInKB: %d; " -+ "TargetKbps: %d; MaxKbps: %d; BRCParamMultiplier: %d\n", -+ q->param.mfx.BufferSizeInKB, q->param.mfx.InitialDelayInKB, -+ q->param.mfx.TargetKbps, q->param.mfx.MaxKbps, q->param.mfx.BRCParamMultiplier); -+ return updated; -+} -+ - static int update_parameters(AVCodecContext *avctx, QSVEncContext *q, - const AVFrame *frame) - { -@@ -1666,6 +1704,7 @@ static int update_parameters(AVCodecContext *avctx, QSVEncContext *q, - return 0; - - needReset = update_qp(avctx, q, frame); -+ needReset |= update_bitrate(avctx, q); - if (!needReset) - return 0; - -diff --git a/libavcodec/qsvenc.h b/libavcodec/qsvenc.h -index b754ac4b56..5745533165 100644 ---- a/libavcodec/qsvenc.h -+++ b/libavcodec/qsvenc.h -@@ -224,6 +224,12 @@ typedef struct QSVEncContext { - int min_qp_p; - int max_qp_b; - int min_qp_b; -+ -+ // These are used for bitrate control reset -+ int old_bit_rate; -+ int old_rc_buffer_size; -+ int old_rc_initial_buffer_occupancy; -+ int old_rc_max_rate; - } QSVEncContext; - - int ff_qsv_enc_init(AVCodecContext *avctx, QSVEncContext *q); --- -2.43.0.windows.1 - diff --git a/res/vcpkg/ffmpeg/7.0/0001-android-mediacodec-encode-align-64.patch b/res/vcpkg/ffmpeg/7.0/0001-android-mediacodec-encode-align-64.patch deleted file mode 100644 index d46c54af6da..00000000000 --- a/res/vcpkg/ffmpeg/7.0/0001-android-mediacodec-encode-align-64.patch +++ /dev/null @@ -1,40 +0,0 @@ -From be3d9d8092720bbe4239212648d2e9c4ffd7f40c Mon Sep 17 00:00:00 2001 -From: 21pages -Date: Wed, 22 May 2024 17:09:28 +0800 -Subject: [PATCH] android mediacodec encode align 64 - -Signed-off-by: 21pages ---- - libavcodec/mediacodecenc.c | 11 ++++++----- - 1 file changed, 6 insertions(+), 5 deletions(-) - -diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c -index 984014f1b1..8dcd3dcd64 100644 ---- a/libavcodec/mediacodecenc.c -+++ b/libavcodec/mediacodecenc.c -@@ -200,16 +200,17 @@ static av_cold int mediacodec_init(AVCodecContext *avctx) - ff_AMediaFormat_setString(format, "mime", codec_mime); - // Workaround the alignment requirement of mediacodec. We can't do it - // silently for AV_PIX_FMT_MEDIACODEC. -+ const int align = 64; - if (avctx->pix_fmt != AV_PIX_FMT_MEDIACODEC) { -- s->width = FFALIGN(avctx->width, 16); -- s->height = FFALIGN(avctx->height, 16); -+ s->width = FFALIGN(avctx->width, align); -+ s->height = FFALIGN(avctx->height, align); - } else { - s->width = avctx->width; - s->height = avctx->height; -- if (s->width % 16 || s->height % 16) -+ if (s->width % align || s->height % align) - av_log(avctx, AV_LOG_WARNING, -- "Video size %dx%d isn't align to 16, it may have device compatibility issue\n", -- s->width, s->height); -+ "Video size %dx%d isn't align to %d, it may have device compatibility issue\n", -+ s->width, s->height, align); - } - ff_AMediaFormat_setInt32(format, "width", s->width); - ff_AMediaFormat_setInt32(format, "height", s->height); --- -2.34.1 - diff --git a/res/vcpkg/ffmpeg/5.1/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch b/res/vcpkg/ffmpeg/patch/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch similarity index 62% rename from res/vcpkg/ffmpeg/5.1/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch rename to res/vcpkg/ffmpeg/patch/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch index 245a470d39f..5431b3edd05 100644 --- a/res/vcpkg/ffmpeg/5.1/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch +++ b/res/vcpkg/ffmpeg/patch/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch @@ -1,9 +1,9 @@ -From f0b694749b38b2cfd94df4eed10e667342c234e5 Mon Sep 17 00:00:00 2001 -From: 21pages -Date: Sat, 24 Feb 2024 15:33:24 +0800 -Subject: [PATCH 1/2] avcodec/amfenc: add query_timeout option for h264/hevc +From f6988e5424e041ff6f6e241f4d8fa69a04c05e64 Mon Sep 17 00:00:00 2001 +From: 21pages +Date: Thu, 5 Sep 2024 16:26:20 +0800 +Subject: [PATCH 1/3] avcodec/amfenc: add query_timeout option for h264/hevc -Signed-off-by: 21pages +Signed-off-by: 21pages --- libavcodec/amfenc.h | 1 + libavcodec/amfenc_h264.c | 4 ++++ @@ -11,10 +11,10 @@ Signed-off-by: 21pages 3 files changed, 9 insertions(+) diff --git a/libavcodec/amfenc.h b/libavcodec/amfenc.h -index 1ab98d2f78..e92120ea39 100644 +index 2dbd378ef8..d636673a9d 100644 --- a/libavcodec/amfenc.h +++ b/libavcodec/amfenc.h -@@ -87,6 +87,7 @@ typedef struct AmfContext { +@@ -89,6 +89,7 @@ typedef struct AmfContext { int quality; int b_frame_delta_qp; int ref_b_frame_delta_qp; @@ -23,40 +23,40 @@ index 1ab98d2f78..e92120ea39 100644 // Dynamic options, can be set after Init() call diff --git a/libavcodec/amfenc_h264.c b/libavcodec/amfenc_h264.c -index efb04589f6..f55dbc80f0 100644 +index c1d5f4054e..415828f005 100644 --- a/libavcodec/amfenc_h264.c +++ b/libavcodec/amfenc_h264.c -@@ -121,6 +121,7 @@ static const AVOption options[] = { +@@ -135,6 +135,7 @@ static const AVOption options[] = { { "aud", "Inserts AU Delimiter NAL unit", OFFSET(aud) ,AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "log_to_dbg", "Enable AMF logging to debug output", OFFSET(log_to_dbg) , AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, + { "query_timeout", "Timeout for QueryOutput call in ms", OFFSET(query_timeout), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, 1000, VE }, - { NULL } - }; -@@ -155,6 +156,9 @@ static av_cold int amf_encode_init_h264(AVCodecContext *avctx) + //Pre Analysis options + { "preanalysis", "Enable preanalysis", OFFSET(preanalysis), AV_OPT_TYPE_BOOL, {.i64 = -1 }, -1, 1, VE }, +@@ -222,6 +223,9 @@ FF_ENABLE_DEPRECATION_WARNINGS AMF_ASSIGN_PROPERTY_RATE(res, ctx->encoder, AMF_VIDEO_ENCODER_FRAMERATE, framerate); + if (ctx->query_timeout >= 0) -+ AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_QUERY_TIMEOUT, ctx->query_timeout); ++ AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_QUERY_TIMEOUT, ctx->query_timeout); + switch (avctx->profile) { - case FF_PROFILE_H264_BASELINE: + case AV_PROFILE_H264_BASELINE: profile = AMF_VIDEO_ENCODER_PROFILE_BASELINE; diff --git a/libavcodec/amfenc_hevc.c b/libavcodec/amfenc_hevc.c -index 8ab9330730..7a40bcad31 100644 +index 33a167aa52..65259d7153 100644 --- a/libavcodec/amfenc_hevc.c +++ b/libavcodec/amfenc_hevc.c -@@ -89,6 +89,7 @@ static const AVOption options[] = { +@@ -98,6 +98,7 @@ static const AVOption options[] = { { "aud", "Inserts AU Delimiter NAL unit", OFFSET(aud) ,AV_OPT_TYPE_BOOL,{ .i64 = 0 }, 0, 1, VE }, { "log_to_dbg", "Enable AMF logging to debug output", OFFSET(log_to_dbg), AV_OPT_TYPE_BOOL,{ .i64 = 0 }, 0, 1, VE }, + { "query_timeout", "Timeout for QueryOutput call in ms", OFFSET(query_timeout), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, 1000, VE }, - { NULL } - }; -@@ -122,6 +123,9 @@ static av_cold int amf_encode_init_hevc(AVCodecContext *avctx) + //Pre Analysis options + { "preanalysis", "Enable preanalysis", OFFSET(preanalysis), AV_OPT_TYPE_BOOL, {.i64 = -1 }, -1, 1, VE }, +@@ -183,6 +184,9 @@ FF_ENABLE_DEPRECATION_WARNINGS AMF_ASSIGN_PROPERTY_RATE(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_FRAMERATE, framerate); @@ -64,7 +64,7 @@ index 8ab9330730..7a40bcad31 100644 + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_QUERY_TIMEOUT, ctx->query_timeout); + switch (avctx->profile) { - case FF_PROFILE_HEVC_MAIN: + case AV_PROFILE_HEVC_MAIN: profile = AMF_VIDEO_ENCODER_HEVC_PROFILE_MAIN; -- 2.43.0.windows.1 diff --git a/res/vcpkg/ffmpeg/5.1/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch b/res/vcpkg/ffmpeg/patch/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch similarity index 71% rename from res/vcpkg/ffmpeg/5.1/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch rename to res/vcpkg/ffmpeg/patch/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch index 13b055ef289..62b86d08bd6 100644 --- a/res/vcpkg/ffmpeg/5.1/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch +++ b/res/vcpkg/ffmpeg/patch/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch @@ -1,16 +1,16 @@ -From 4d0d20d96ad458cfec0444b9be0182ca6085ee0c Mon Sep 17 00:00:00 2001 -From: 21pages -Date: Sat, 24 Feb 2024 16:02:44 +0800 -Subject: [PATCH 2/2] libavcodec/amfenc: reconfig when bitrate change +From 6e76c57cf2c0e790228f19c88089eef110fd74aa Mon Sep 17 00:00:00 2001 +From: 21pages +Date: Thu, 5 Sep 2024 16:32:16 +0800 +Subject: [PATCH 2/3] libavcodec/amfenc: reconfig when bitrate change -Signed-off-by: 21pages +Signed-off-by: 21pages --- libavcodec/amfenc.c | 20 ++++++++++++++++++++ libavcodec/amfenc.h | 1 + 2 files changed, 21 insertions(+) diff --git a/libavcodec/amfenc.c b/libavcodec/amfenc.c -index a033e1220e..3eab01a903 100644 +index 061859f85c..97587fe66b 100644 --- a/libavcodec/amfenc.c +++ b/libavcodec/amfenc.c @@ -222,6 +222,7 @@ static int amf_init_context(AVCodecContext *avctx) @@ -21,7 +21,7 @@ index a033e1220e..3eab01a903 100644 // configure AMF logger // the return of these functions indicates old state and do not affect behaviour -@@ -575,6 +576,23 @@ static void amf_release_buffer_with_frame_ref(AMFBuffer *frame_ref_storage_buffe +@@ -583,6 +584,23 @@ static void amf_release_buffer_with_frame_ref(AMFBuffer *frame_ref_storage_buffe frame_ref_storage_buffer->pVtbl->Release(frame_ref_storage_buffer); } @@ -45,9 +45,9 @@ index a033e1220e..3eab01a903 100644 int ff_amf_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) { AmfContext *ctx = avctx->priv_data; -@@ -586,6 +604,8 @@ int ff_amf_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) - AVFrame *frame = ctx->delayed_frame; - int block_and_wait; +@@ -596,6 +614,8 @@ int ff_amf_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) + int query_output_data_flag = 0; + AMF_RESULT res_resubmit; + reconfig_encoder(avctx); + @@ -55,13 +55,13 @@ index a033e1220e..3eab01a903 100644 return AVERROR(EINVAL); diff --git a/libavcodec/amfenc.h b/libavcodec/amfenc.h -index e92120ea39..31172645f2 100644 +index d636673a9d..09506ee2e0 100644 --- a/libavcodec/amfenc.h +++ b/libavcodec/amfenc.h -@@ -107,6 +107,7 @@ typedef struct AmfContext { - int me_half_pel; - int me_quarter_pel; - int aud; +@@ -113,6 +113,7 @@ typedef struct AmfContext { + int max_b_frames; + int qvbr_quality_level; + int hw_high_motion_quality_boost; + int64_t av_bitrate; // HEVC - specific options diff --git a/res/vcpkg/ffmpeg/5.1/0004-amf-colorspace.patch b/res/vcpkg/ffmpeg/patch/0003-amf-colorspace.patch similarity index 88% rename from res/vcpkg/ffmpeg/5.1/0004-amf-colorspace.patch rename to res/vcpkg/ffmpeg/patch/0003-amf-colorspace.patch index 49aef694795..9bcb6e6926c 100644 --- a/res/vcpkg/ffmpeg/5.1/0004-amf-colorspace.patch +++ b/res/vcpkg/ffmpeg/patch/0003-amf-colorspace.patch @@ -1,32 +1,32 @@ -From 8fd62e4ecd058b09abf8847be5fbbf0eef44a90f Mon Sep 17 00:00:00 2001 +From 14b77216106eaaff9cf701528039ae4264eaf420 Mon Sep 17 00:00:00 2001 From: 21pages -Date: Tue, 16 Jul 2024 14:58:33 +0800 -Subject: [PATCH] amf colorspace +Date: Thu, 5 Sep 2024 16:41:59 +0800 +Subject: [PATCH 3/3] amf colorspace Signed-off-by: 21pages --- libavcodec/amfenc.h | 1 + - libavcodec/amfenc_h264.c | 39 +++++++++++++++++++++++++++++++++ + libavcodec/amfenc_h264.c | 40 ++++++++++++++++++++++++++++++++++ libavcodec/amfenc_hevc.c | 47 ++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 87 insertions(+) + 3 files changed, 88 insertions(+) diff --git a/libavcodec/amfenc.h b/libavcodec/amfenc.h -index 31172645f2..493e01603d 100644 +index 09506ee2e0..7f458b14f7 100644 --- a/libavcodec/amfenc.h +++ b/libavcodec/amfenc.h -@@ -23,6 +23,7 @@ - +@@ -24,6 +24,7 @@ #include #include + #include +#include #include "libavutil/fifo.h" diff --git a/libavcodec/amfenc_h264.c b/libavcodec/amfenc_h264.c -index f55dbc80f0..5a6b6e164f 100644 +index 415828f005..7da5a96c71 100644 --- a/libavcodec/amfenc_h264.c +++ b/libavcodec/amfenc_h264.c -@@ -139,6 +139,9 @@ static av_cold int amf_encode_init_h264(AVCodecContext *avctx) +@@ -200,6 +200,9 @@ static av_cold int amf_encode_init_h264(AVCodecContext *avctx) AMFRate framerate; AMFSize framesize = AMFConstructSize(avctx->width, avctx->height); int deblocking_filter = (avctx->flags & AV_CODEC_FLAG_LOOP_FILTER) ? 1 : 0; @@ -36,7 +36,7 @@ index f55dbc80f0..5a6b6e164f 100644 if (avctx->framerate.num > 0 && avctx->framerate.den > 0) { framerate = AMFConstructRate(avctx->framerate.num, avctx->framerate.den); -@@ -199,11 +202,47 @@ static av_cold int amf_encode_init_h264(AVCodecContext *avctx) +@@ -266,10 +269,47 @@ FF_ENABLE_DEPRECATION_WARNINGS AMF_ASSIGN_PROPERTY_RATIO(res, ctx->encoder, AMF_VIDEO_ENCODER_ASPECT_RATIO, ratio); } @@ -70,25 +70,25 @@ index f55dbc80f0..5a6b6e164f 100644 + color_profile = AMF_VIDEO_CONVERTER_COLOR_PROFILE_2020; + break; + } -+ } + } + pix_fmt = avctx->hw_frames_ctx ? ((AVHWFramesContext*)avctx->hw_frames_ctx->data)->sw_format : avctx->pix_fmt; + color_depth = AMF_COLOR_BIT_DEPTH_8; + if (pix_fmt == AV_PIX_FMT_P010) { + color_depth = AMF_COLOR_BIT_DEPTH_10; - } - ++ } ++ + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_COLOR_BIT_DEPTH, color_depth); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_COLOR_PROFILE, color_profile); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_TRANSFER_CHARACTERISTIC, (amf_int64)avctx->color_trc); + AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_OUTPUT_COLOR_PRIMARIES, (amf_int64)avctx->color_primaries); + // autodetect rate control method if (ctx->rate_control_mode == AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_UNKNOWN) { - if (ctx->qp_i != -1 || ctx->qp_p != -1 || ctx->qp_b != -1) { diff --git a/libavcodec/amfenc_hevc.c b/libavcodec/amfenc_hevc.c -index 7a40bcad31..0260f43c81 100644 +index 65259d7153..7c930d3ccc 100644 --- a/libavcodec/amfenc_hevc.c +++ b/libavcodec/amfenc_hevc.c -@@ -106,6 +106,9 @@ static av_cold int amf_encode_init_hevc(AVCodecContext *avctx) +@@ -161,6 +161,9 @@ static av_cold int amf_encode_init_hevc(AVCodecContext *avctx) AMFRate framerate; AMFSize framesize = AMFConstructSize(avctx->width, avctx->height); int deblocking_filter = (avctx->flags & AV_CODEC_FLAG_LOOP_FILTER) ? 1 : 0; @@ -98,17 +98,17 @@ index 7a40bcad31..0260f43c81 100644 if (avctx->framerate.num > 0 && avctx->framerate.den > 0) { framerate = AMFConstructRate(avctx->framerate.num, avctx->framerate.den); -@@ -130,6 +133,9 @@ static av_cold int amf_encode_init_hevc(AVCodecContext *avctx) - case FF_PROFILE_HEVC_MAIN: +@@ -191,6 +194,9 @@ FF_ENABLE_DEPRECATION_WARNINGS + case AV_PROFILE_HEVC_MAIN: profile = AMF_VIDEO_ENCODER_HEVC_PROFILE_MAIN; break; -+ case FF_PROFILE_HEVC_MAIN_10: ++ case AV_PROFILE_HEVC_MAIN_10: + profile = AMF_VIDEO_ENCODER_HEVC_PROFILE_MAIN_10; + break; default: break; } -@@ -158,6 +164,47 @@ static av_cold int amf_encode_init_hevc(AVCodecContext *avctx) +@@ -219,6 +225,47 @@ FF_ENABLE_DEPRECATION_WARNINGS AMF_ASSIGN_PROPERTY_RATIO(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_ASPECT_RATIO, ratio); } diff --git a/res/vcpkg/ffmpeg/portfile.cmake b/res/vcpkg/ffmpeg/portfile.cmake index dc35752ff8b..3d4c10906df 100644 --- a/res/vcpkg/ffmpeg/portfile.cmake +++ b/res/vcpkg/ffmpeg/portfile.cmake @@ -1,16 +1,8 @@ -if(VCPKG_TARGET_IS_WINDOWS OR VCPKG_TARGET_IS_LINUX) - set(FF_VERSION "n5.1.5") - set(FF_SHA512 "a933f18e53207ccc277b42c9a68db00f31cefec555e6d5d7c57db3409023b2c38fd93ebe2ccfcd17ba2397adb912e93f2388241ca970b7d8bd005ccfe86d5679") -else() - set(FF_VERSION "n7.0.1") - set(FF_SHA512 "1212ebcb78fdaa103b0304373d374e41bf1fe680e1fa4ce0f60624857491c26b4dda004c490c3ef32d4a0e10f42ae6b54546f9f318e2dcfbaa116117f687bc88") -endif() - vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO ffmpeg/ffmpeg - REF "${FF_VERSION}" - SHA512 "${FF_SHA512}" + REF "n${VERSION}" + SHA512 3ba02e8b979c80bf61d55f414bdac2c756578bb36498ed7486151755c6ccf8bd8ff2b8c7afa3c5d1acd862ce48314886a86a105613c05e36601984c334f8f6bf HEAD_REF master PATCHES 0002-fix-msvc-link.patch # upstreamed in future version @@ -18,25 +10,11 @@ vcpkg_from_github( 0005-fix-nasm.patch # upstreamed in future version 0012-Fix-ssl-110-detection.patch 0013-define-WINVER.patch + patch/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch + patch/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch + patch/0003-amf-colorspace.patch ) -if(VCPKG_TARGET_IS_WINDOWS OR VCPKG_TARGET_IS_LINUX) - vcpkg_apply_patches( - SOURCE_PATH ${SOURCE_PATH} - PATCHES - ${CMAKE_CURRENT_LIST_DIR}/5.1/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch - ${CMAKE_CURRENT_LIST_DIR}/5.1/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch - ${CMAKE_CURRENT_LIST_DIR}/5.1/0003-use-release-7.0-s-qsvenc-update_bitrate.patch - ${CMAKE_CURRENT_LIST_DIR}/5.1/0004-amf-colorspace.patch - ) -elseif(VCPKG_TARGET_IS_ANDROID) - vcpkg_apply_patches( - SOURCE_PATH ${SOURCE_PATH} - PATCHES - ${CMAKE_CURRENT_LIST_DIR}/7.0/0001-android-mediacodec-encode-align-64.patch - ) -endif() - if(SOURCE_PATH MATCHES " ") message(FATAL_ERROR "Error: ffmpeg will not build with spaces in the path. Please use a directory with no spaces") endif() @@ -130,6 +108,7 @@ elseif(VCPKG_TARGET_IS_WINDOWS) string(APPEND OPTIONS "\ --target-os=win32 \ --toolchain=msvc \ +--cc=cl \ --enable-gpl \ --enable-d3d11va \ --enable-cuda \ @@ -210,6 +189,10 @@ endif() string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -I \"${CURRENT_INSTALLED_DIR}/include\"") string(APPEND VCPKG_COMBINED_C_FLAGS_RELEASE " -I \"${CURRENT_INSTALLED_DIR}/include\"") +if(VCPKG_TARGET_IS_WINDOWS) + string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -I \"${CURRENT_INSTALLED_DIR}/include/mfx\"") + string(APPEND VCPKG_COMBINED_C_FLAGS_RELEASE " -I \"${CURRENT_INSTALLED_DIR}/include/mfx\"") +endif() # # Setup vcpkg toolchain set(prog_env "") @@ -219,8 +202,9 @@ if(VCPKG_DETECTED_CMAKE_C_COMPILER) get_filename_component(CC_filename "${VCPKG_DETECTED_CMAKE_C_COMPILER}" NAME) set(ENV{CC} "${CC_filename}") string(APPEND OPTIONS " --cc=${CC_filename}") - - # string(APPEND OPTIONS " --host_cc=${CC_filename}") ffmpeg not yet setup for cross builds? + if(VCPKG_HOST_IS_WINDOWS) + string(APPEND OPTIONS " --host_cc=${CC_filename}") + endif() list(APPEND prog_env "${CC_path}") endif() @@ -291,6 +275,13 @@ if(VCPKG_DETECTED_CMAKE_STRIP) list(APPEND prog_env "${STRIP_path}") endif() +if(VCPKG_HOST_IS_WINDOWS) + vcpkg_acquire_msys(MSYS_ROOT PACKAGES automake1.16) + set(SHELL "${MSYS_ROOT}/usr/bin/bash.exe") + list(APPEND prog_env "${MSYS_ROOT}/usr/bin" "${MSYS_ROOT}/usr/share/automake-1.16") +else() + # find_program(SHELL bash) +endif() list(REMOVE_DUPLICATES prog_env) vcpkg_add_to_path(PREPEND ${prog_env}) diff --git a/res/vcpkg/ffmpeg/vcpkg.json b/res/vcpkg/ffmpeg/vcpkg.json index 61ff2c8b549..f7612d9281c 100644 --- a/res/vcpkg/ffmpeg/vcpkg.json +++ b/res/vcpkg/ffmpeg/vcpkg.json @@ -1,6 +1,6 @@ { "name": "ffmpeg", - "version": "7.0.1", + "version": "7.0.2", "port-version": 0, "description": [ "a library to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created.", diff --git a/vcpkg.json b/vcpkg.json index f1d7036eb5f..81484772aea 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -87,7 +87,7 @@ ] }, "overrides": [ - { "name": "ffnvcodec", "version": "11.1.5.2" }, + { "name": "ffnvcodec", "version": "12.1.14.0" }, { "name": "amd-amf", "version": "1.4.29" }, { "name": "mfx-dispatch", "version": "1.35.1" } ] From c8cd564e6986b1aab9ed066f678984b1e852e96b Mon Sep 17 00:00:00 2001 From: Andrzej Rudnik Date: Sat, 7 Sep 2024 11:58:07 +0200 Subject: [PATCH 049/210] Update pl.rs (#9285) --- src/lang/pl.rs | 86 +++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 246de02f4a7..725d67996a6 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -349,7 +349,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable hardware codec", "Włącz akcelerację sprzętową kodeków"), ("Unlock Security Settings", "Odblokuj ustawienia zabezpieczeń"), ("Enable audio", "Włącz dźwięk"), - ("Unlock Network Settings", "Odblokuj ustawienia Sieciowe"), + ("Unlock Network Settings", "Odblokuj ustawienia sieciowe"), ("Server", "Serwer"), ("Direct IP Access", "Bezpośredni adres IP"), ("Proxy", "Proxy"), @@ -602,47 +602,47 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no_need_privacy_mode_no_physical_displays_tip", "Brak fizycznych wyświetlaczy, tryb prywatny nie jest potrzebny."), ("Follow remote cursor", "Podążaj za zdalnym kursorem"), ("Follow remote window focus", "Podążaj za aktywnością zdalnych okien"), - ("default_proxy_tip", ""), - ("no_audio_input_device_tip", ""), - ("Incoming", ""), - ("Outgoing", ""), - ("Clear Wayland screen selection", ""), - ("clear_Wayland_screen_selection_tip", ""), - ("confirm_clear_Wayland_screen_selection_tip", ""), - ("android_new_voice_call_tip", ""), - ("texture_render_tip", ""), - ("Use texture rendering", ""), - ("Floating window", ""), - ("floating_window_tip", ""), - ("Keep screen on", ""), - ("Never", ""), - ("During controlled", ""), - ("During service is on", ""), - ("Capture screen using DirectX", ""), - ("Back", ""), - ("Apps", ""), - ("Volume up", ""), - ("Volume down", ""), - ("Power", ""), - ("Telegram bot", ""), - ("enable-bot-tip", ""), - ("enable-bot-desc", ""), - ("cancel-2fa-confirm-tip", ""), - ("cancel-bot-confirm-tip", ""), - ("About RustDesk", ""), - ("Send clipboard keystrokes", ""), - ("network_error_tip", ""), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("default_proxy_tip", "Domyślny protokół i port to Socks5 i 1080"), + ("no_audio_input_device_tip", "Nie znaleziono urządzenia audio."), + ("Incoming", "Przychodzące"), + ("Outgoing", "Wychodzące"), + ("Clear Wayland screen selection", "Wyczyść wybór ekranu Wayland"), + ("clear_Wayland_screen_selection_tip", "Po wyczyszczeniu wyboru ekranu, możesz wybrać, który ekran chcesz udostępnić."), + ("confirm_clear_Wayland_screen_selection_tip", "Na pewno wyczyścić wybór ekranu Wayland?"), + ("android_new_voice_call_tip", "Otrzymano nowe żądanie połączenia głosowego. Jeżeli je zaakceptujesz, dźwięk przełączy się na komunikację głosową."), + ("texture_render_tip", "Użyj renderowania tekstur, aby wygładzić zdjęcia. Możesz spróbować wyłączyć tę opcję, jeżeli napotkasz problemy z renderowaniem."), + ("Use texture rendering", "Użyj renderowania tekstur"), + ("Floating window", "Okno pływające"), + ("floating_window_tip", "Pozwala zachować usługę RustDesk w tle"), + ("Keep screen on", "Pozostaw ekran włączony"), + ("Never", "Nigdy"), + ("During controlled", "Podczas sterowania"), + ("During service is on", "Gdy usługa jest uruchomiona"), + ("Capture screen using DirectX", "Przechwytuj ekran używając DirectX"), + ("Back", "Wstecz"), + ("Apps", "Aplikacje"), + ("Volume up", "Głośniej"), + ("Volume down", "Ciszej"), + ("Power", "Zasilanie"), + ("Telegram bot", "Bot Telegram"), + ("enable-bot-tip", "Jeżeli włączysz tę funkcję, możesz otrzymać kod 2FA od swojego bota. Może on również działać jako powiadomienie o połączeniu."), + ("enable-bot-desc", "1. Otwórz czat z @BotFather.\n2. Wyślij polecenie \"/newbot\". Otrzymasz token do po wykonaniu tego kroku.\n3. Rozpocznij czat z nowo utworzonym botem. Wyślij wiadomość zaczynającą się od ukośnika (\"/\"),np. \"/hello\", aby go aktywować.\n"), + ("cancel-2fa-confirm-tip", "Na pewno chcesz anulować 2FA?"), + ("cancel-bot-confirm-tip", "Na pewno chcesz anulować bot Telegram?"), + ("About RustDesk", "O programie"), + ("Send clipboard keystrokes", "Wysyła naciśnięcia klawiszy ze schowka"), + ("network_error_tip", "Sprawdź swoje połączenie sieciowe, następnie kliknij Ponów."), + ("Unlock with PIN", "Odblokuj za pomocą PIN"), + ("Requires at least {} characters", "Wymaga co najmniej {} znaków"), + ("Wrong PIN", "Niewłaściwy PIN"), + ("Set PIN", "Ustaw PIN"), + ("Enable trusted devices", "Włącz zaufane urządzenia"), + ("Manage trusted devices", "Zarządzaj zaufanymi urządzeniami"), + ("Platform", "Platforma"), + ("Days remaining", "Pozostało dni"), + ("enable-trusted-devices-tip", "Omiń weryfikację 2FA dla zaufanych urządzeń"), + ("Parent directory", "Folder nadrzędny"), + ("Resume", "Wznów"), + ("Invalid file name", "Nieprawidłowa nazwa pliku"), ].iter().cloned().collect(); } From 993862c1038e002a9526c0ae9f9f4786ed9f94b2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 8 Sep 2024 12:37:41 +0800 Subject: [PATCH 050/210] quit cm process if ipc connection to ipc server closed (#9292) Signed-off-by: 21pages --- src/common.rs | 6 ++++++ src/core_main.rs | 5 ++++- src/ui_cm_interface.rs | 11 +++++++++-- src/ui_interface.rs | 4 ++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/common.rs b/src/common.rs index 1ed9d6a8fb4..2e801e66d2f 100644 --- a/src/common.rs +++ b/src/common.rs @@ -84,6 +84,7 @@ lazy_static::lazy_static! { // Is server logic running. The server code can invoked to run by the main process if --server is not running. static ref SERVER_RUNNING: Arc> = Default::default(); static ref IS_MAIN: bool = std::env::args().nth(1).map_or(true, |arg| !arg.starts_with("--")); + static ref IS_CM: bool = std::env::args().nth(1) == Some("--cm".to_owned()) || std::env::args().nth(1) == Some("--cm-no-ui".to_owned()); } pub struct SimpleCallOnReturn { @@ -137,6 +138,11 @@ pub fn is_main() -> bool { *IS_MAIN } +#[inline] +pub fn is_cm() -> bool { + *IS_CM +} + // Is server logic running. #[inline] pub fn is_server_running() -> bool { diff --git a/src/core_main.rs b/src/core_main.rs index 375b1dbc573..6cf3b9d02c0 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -477,7 +477,10 @@ pub fn core_main() -> Option> { } else if args[0] == "--cm-no-ui" { #[cfg(feature = "flutter")] #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "windows")))] - crate::flutter::connection_manager::start_cm_no_ui(); + { + crate::ui_interface::start_option_status_sync(); + crate::flutter::connection_manager::start_cm_no_ui(); + } return None; } else { #[cfg(all(feature = "flutter", feature = "plugin_framework"))] diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index f1748112ea6..c9b46ff1fc1 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -625,7 +625,6 @@ pub async fn start_ipc(cm: ConnectionManager) { OPTION_ENABLE_FILE_TRANSFER, &Config::get_option(OPTION_ENABLE_FILE_TRANSFER), )); - match ipc::new_listener("_cm").await { Ok(mut incoming) => { while let Some(result) = incoming.next().await { @@ -647,7 +646,7 @@ pub async fn start_ipc(cm: ConnectionManager) { log::error!("Failed to start cm ipc server: {}", err); } } - crate::platform::quit_gui(); + quit_cm(); } #[cfg(target_os = "android")] @@ -1042,3 +1041,11 @@ pub fn close_voice_call(id: i32) { allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned()))); }; } + +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn quit_cm() { + // in case of std::process::exit not work + log::info!("quit cm"); + CLIENTS.write().unwrap().clear(); + crate::platform::quit_gui(); +} diff --git a/src/ui_interface.rs b/src/ui_interface.rs index aad968bb4a5..ad2db6d01db 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1138,6 +1138,7 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver { log::error!("ipc connection closed: {}", err); + if is_cm { + crate::ui_cm_interface::quit_cm(); + } break; } #[cfg(not(any(target_os = "android", target_os = "ios")))] From 1e6944b380d3674c0aed948e955b34b934304b0c Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 8 Sep 2024 12:54:27 +0800 Subject: [PATCH 051/210] apply --cm-no-ui to non-windows --- src/core_main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core_main.rs b/src/core_main.rs index 6cf3b9d02c0..5d137516ee4 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -476,7 +476,7 @@ pub fn core_main() -> Option> { crate::ui_interface::start_option_status_sync(); } else if args[0] == "--cm-no-ui" { #[cfg(feature = "flutter")] - #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "windows")))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] { crate::ui_interface::start_option_status_sync(); crate::flutter::connection_manager::start_cm_no_ui(); From 2922ebe22aa3a0edb52a183e82efac78b0a7d43a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 8 Sep 2024 21:13:05 +0800 Subject: [PATCH 052/210] Fix/clipboard retry if is occupied (#9293) * fix: clipboard, retry if is occupied Signed-off-by: fufesou * fix: clipboard service, hold runtime to cm ipc Signed-off-by: fufesou * update arboard Signed-off-by: fufesou * refact: log Signed-off-by: fufesou * fix: get formats, return only not Signed-off-by: fufesou --------- Signed-off-by: fufesou --- Cargo.lock | 2 +- src/clipboard.rs | 52 ++++++++++++++++++++++++++------- src/server/clipboard_service.rs | 34 ++++++++++++++++----- src/ui_cm_interface.rs | 1 + 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd0e449e754..b2bd08d436a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,7 +224,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arboard" version = "3.4.0" -source = "git+https://github.com/rustdesk-org/arboard#a04bdb1b368a99691822c33bf0f7ed497d6a7a35" +source = "git+https://github.com/rustdesk-org/arboard#747ab2d9b40a5c9c5102051cf3b0bb38b4845e60" dependencies = [ "clipboard-win", "core-graphics 0.23.2", diff --git a/src/clipboard.rs b/src/clipboard.rs index 4e6295db9f2..e4bd0039062 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -1,9 +1,10 @@ use arboard::{ClipboardData, ClipboardFormat}; use clipboard_master::{ClipboardHandler, Master, Shutdown}; -use hbb_common::{log, message_proto::*, ResultType}; +use hbb_common::{bail, log, message_proto::*, ResultType}; use std::{ sync::{mpsc::Sender, Arc, Mutex}, thread::JoinHandle, + time::Duration, }; pub const CLIPBOARD_NAME: &'static str = "clipboard"; @@ -26,6 +27,9 @@ lazy_static::lazy_static! { static ref CLIPBOARD_CTX: Arc>> = Arc::new(Mutex::new(None)); } +const CLIPBOARD_GET_MAX_RETRY: usize = 3; +const CLIPBOARD_GET_RETRY_INTERVAL_DUR: Duration = Duration::from_millis(33); + const SUPPORTED_FORMATS: &[ClipboardFormat] = &[ ClipboardFormat::Text, ClipboardFormat::Html, @@ -151,14 +155,18 @@ pub fn check_clipboard( *ctx = ClipboardContext::new().ok(); } let ctx2 = ctx.as_mut()?; - let content = ctx2.get(side, force); - if let Ok(content) = content { - if !content.is_empty() { - let mut msg = Message::new(); - let clipboards = proto::create_multi_clipboards(content); - msg.set_multi_clipboards(clipboards.clone()); - *LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards; - return Some(msg); + match ctx2.get(side, force) { + Ok(content) => { + if !content.is_empty() { + let mut msg = Message::new(); + let clipboards = proto::create_multi_clipboards(content); + msg.set_multi_clipboards(clipboards.clone()); + *LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards; + return Some(msg); + } + } + Err(e) => { + log::error!("Failed to get clipboard content. {}", e); } } None @@ -263,9 +271,33 @@ impl ClipboardContext { Ok(ClipboardContext { inner: board }) } + fn get_formats(&mut self, formats: &[ClipboardFormat]) -> ResultType> { + for i in 0..CLIPBOARD_GET_MAX_RETRY { + match self.inner.get_formats(SUPPORTED_FORMATS) { + Ok(data) => { + return Ok(data + .into_iter() + .filter(|c| !matches!(c, arboard::ClipboardData::None)) + .collect()) + } + Err(e) => match e { + arboard::Error::ClipboardOccupied => { + log::debug!("Failed to get clipboard formats, clipboard is occupied, retrying... {}", i + 1); + std::thread::sleep(CLIPBOARD_GET_RETRY_INTERVAL_DUR); + } + _ => { + log::error!("Failed to get clipboard formats, {}", e); + return Err(e.into()); + } + }, + } + } + bail!("Failed to get clipboard formats, clipboard is occupied, {CLIPBOARD_GET_MAX_RETRY} retries failed"); + } + pub fn get(&mut self, side: ClipboardSide, force: bool) -> ResultType> { let _lock = ARBOARD_MTX.lock().unwrap(); - let data = self.inner.get_formats(SUPPORTED_FORMATS)?; + let data = self.get_formats(SUPPORTED_FORMATS)?; if data.is_empty() { return Ok(data); } diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index e7097425857..a2a3b315381 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -11,6 +11,8 @@ use std::{ sync::mpsc::{channel, RecvTimeoutError, Sender}, time::Duration, }; +#[cfg(windows)] +use tokio::runtime::Runtime; struct Handler { sp: EmptyExtraFieldService, @@ -18,6 +20,8 @@ struct Handler { tx_cb_result: Sender, #[cfg(target_os = "windows")] stream: Option>, + #[cfg(target_os = "windows")] + rt: Option, } pub fn new() -> GenericService { @@ -34,6 +38,8 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> { tx_cb_result, #[cfg(target_os = "windows")] stream: None, + #[cfg(target_os = "windows")] + rt: None, }; let (tx_start_res, rx_start_res) = channel(); @@ -129,29 +135,41 @@ impl Handler { // 1. the clipboard is not used frequently. // 2. the clipboard handle is sync and will not block the main thread. #[cfg(windows)] - #[tokio::main(flavor = "current_thread")] - async fn read_clipboard_from_cm_ipc(&mut self) -> ResultType> { + fn read_clipboard_from_cm_ipc(&mut self) -> ResultType> { + if self.rt.is_none() { + self.rt = Some(Runtime::new()?); + } + let Some(rt) = &self.rt else { + // unreachable! + bail!("failed to get tokio runtime"); + }; let mut is_sent = false; if let Some(stream) = &mut self.stream { // If previous stream is still alive, reuse it. // If the previous stream is dead, `is_sent` will trigger reconnect. - is_sent = stream.send(&Data::ClipboardNonFile(None)).await.is_ok(); + is_sent = match rt.block_on(stream.send(&Data::ClipboardNonFile(None))) { + Ok(_) => true, + Err(e) => { + log::debug!("Failed to send to cm: {}", e); + false + } + }; } if !is_sent { - let mut stream = crate::ipc::connect(100, "_cm").await?; - stream.send(&Data::ClipboardNonFile(None)).await?; + let mut stream = rt.block_on(crate::ipc::connect(100, "_cm"))?; + rt.block_on(stream.send(&Data::ClipboardNonFile(None)))?; self.stream = Some(stream); } if let Some(stream) = &mut self.stream { loop { - match stream.next_timeout(800).await? { + match rt.block_on(stream.next_timeout(800))? { Some(Data::ClipboardNonFile(Some((err, mut contents)))) => { if !err.is_empty() { bail!("{}", err); } else { if contents.iter().any(|c| c.next_raw) { - match timeout(1000, stream.next_raw()).await { + match rt.block_on(timeout(1000, stream.next_raw())) { Ok(Ok(mut data)) => { for c in &mut contents { if c.next_raw { @@ -168,7 +186,7 @@ impl Handler { Err(e) => { // Reconnect to avoid the next raw data remaining in the buffer. self.stream = None; - log::debug!("failed to get raw clipboard data: {}", e); + log::debug!("Failed to get raw clipboard data: {}", e); } } } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index c9b46ff1fc1..49f91a9dab3 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -520,6 +520,7 @@ impl IpcTaskRunner { } } Err(e) => { + log::debug!("Failed to get clipboard content. {}", e); allow_err!(self.stream.send(&Data::ClipboardNonFile(Some((format!("{}", e), vec![])))).await); } } From a2792d1527f004216f2002232bc4736636a9b58a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:07:42 +0800 Subject: [PATCH 053/210] comments (#9297) * comments Signed-off-by: fufesou * comments Signed-off-by: fufesou --------- Signed-off-by: fufesou --- src/clipboard.rs | 9 +++++++++ src/server/clipboard_service.rs | 9 ++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/clipboard.rs b/src/clipboard.rs index e4bd0039062..329b392bba7 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -272,6 +272,15 @@ impl ClipboardContext { } fn get_formats(&mut self, formats: &[ClipboardFormat]) -> ResultType> { + // If there're multiple threads or processes trying to access the clipboard at the same time, + // the previous clipboard owner will fail to access the clipboard. + // `GetLastError()` will return `ERROR_CLIPBOARD_NOT_OPEN` (OSError(1418): Thread does not have a clipboard open) at this time. + // See https://github.com/rustdesk-org/arboard/blob/747ab2d9b40a5c9c5102051cf3b0bb38b4845e60/src/platform/windows.rs#L34 + // + // This is a common case on Windows, so we retry here. + // Related issues: + // https://github.com/rustdesk/rustdesk/issues/9263 + // https://github.com/rustdesk/rustdesk/issues/9222#issuecomment-2329233175 for i in 0..CLIPBOARD_GET_MAX_RETRY { match self.inner.get_formats(SUPPORTED_FORMATS) { Ok(data) => { diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index a2a3b315381..d6bea7520a6 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -131,9 +131,12 @@ impl Handler { check_clipboard(&mut self.ctx, ClipboardSide::Host, false) } - // It's ok to do async operation in the clipboard service because: - // 1. the clipboard is not used frequently. - // 2. the clipboard handle is sync and will not block the main thread. + // Read clipboard data from cm using ipc. + // + // We cannot use `#[tokio::main(flavor = "current_thread")]` here, + // because the auto-managed tokio runtime (async context) will be dropped after the call. + // The next call will create a new runtime, which will cause the previous stream to be unusable. + // So we need to manage the tokio runtime manually. #[cfg(windows)] fn read_clipboard_from_cm_ipc(&mut self) -> ResultType> { if self.rt.is_none() { From 260a82ee5c40765aff41f170183a9bd7f48b654d Mon Sep 17 00:00:00 2001 From: rustdesk Date: Mon, 9 Sep 2024 17:24:03 +0800 Subject: [PATCH 054/210] upgrade pub for flutter memory leak --- flutter/macos/Podfile.lock | 8 +- flutter/pubspec.lock | 276 ++++++++++++++++++------------------- 2 files changed, 138 insertions(+), 146 deletions(-) diff --git a/flutter/macos/Podfile.lock b/flutter/macos/Podfile.lock index a9f3c7388cf..a29674fece3 100644 --- a/flutter/macos/Podfile.lock +++ b/flutter/macos/Podfile.lock @@ -95,17 +95,17 @@ SPEC CHECKSUMS: desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f - file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 + file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2 flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec texture_rgba_renderer: cbed959a3c127122194a364e14b8577bd62dc8f2 uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026 - url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 - video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 62f9283a000..0a1f3ada79d 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" after_layout: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" animations: dependency: transitive description: @@ -37,18 +37,18 @@ packages: dependency: transitive description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.4.10" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: auto_size_text_field - sha256: d47c81ffa9b61d219f6c50492dc03ea28fa9346561b2ec33b46ccdc000ddb0aa + sha256: "41c90b2270e38edc6ce5c02e5a17737a863e65e246bdfc94565a38f3ec399144" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" back_button_interceptor: dependency: "direct main" description: @@ -141,10 +141,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.9" build_runner_core: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: built_value - sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.9.0" + version: "8.9.2" cached_network_image: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: cached_network_image_web - sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" characters: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+2" crypto: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" dash_chat_2: dependency: "direct main" description: @@ -351,10 +351,10 @@ packages: dependency: transitive description: name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" draggable_float_widget: dependency: "direct main" description: @@ -408,10 +408,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" ffigen: dependency: "direct dev" description: @@ -448,10 +448,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.4" file_selector_platform_interface: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" url: "https://pub.dev" source: hosted - version: "0.9.3+1" + version: "0.9.3+2" fixnum: dependency: transitive description: @@ -480,18 +480,18 @@ packages: dependency: "direct main" description: name: flex_color_picker - sha256: "0871edc170153cfc3de316d30625f40a85daecfa76ce541641f3cc0ec7757cbf" + sha256: "5c846437069fb7afdd7ade6bf37e628a71d2ab0787095ddcb1253bf9345d5f3a" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.1" flex_seed_scheme: dependency: transitive description: name: flex_seed_scheme - sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" + sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" flutter: dependency: "direct main" description: flutter @@ -620,10 +620,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.19" flutter_rust_bridge: dependency: "direct main" description: @@ -636,10 +636,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_web_plugins: dependency: transitive description: flutter @@ -649,26 +649,26 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5" + sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 url: "https://pub.dev" source: hosted - version: "2.4.7" + version: "2.5.2" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" get: dependency: "direct main" description: @@ -705,10 +705,10 @@ packages: dependency: "direct main" description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -737,10 +737,10 @@ packages: dependency: "direct main" description: name: image - sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.7" + version: "4.2.0" image_picker: dependency: "direct main" description: @@ -753,10 +753,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1" + sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1" url: "https://pub.dev" source: hosted - version: "0.8.9+3" + version: "0.8.12+1" image_picker_for_web: dependency: transitive description: @@ -769,10 +769,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3 + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" url: "https://pub.dev" source: hosted - version: "0.8.9+1" + version: "0.8.12" image_picker_linux: dependency: transitive description: @@ -793,10 +793,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" url: "https://pub.dev" source: hosted - version: "2.9.3" + version: "2.10.0" image_picker_windows: dependency: transitive description: @@ -833,10 +833,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" lints: dependency: transitive description: @@ -881,10 +881,10 @@ packages: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" nested: dependency: transitive description: @@ -897,10 +897,10 @@ packages: dependency: transitive description: name: octo_image - sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" package_config: dependency: transitive description: @@ -953,26 +953,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -993,10 +993,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" pedantic: dependency: transitive description: @@ -1025,10 +1025,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -1037,14 +1037,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" - url: "https://pub.dev" - source: hosted - version: "3.7.4" pool: dependency: transitive description: @@ -1057,10 +1049,10 @@ packages: dependency: "direct main" description: name: provider - sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.2" pub_semver: dependency: transitive description: @@ -1073,26 +1065,26 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" pull_down_button: dependency: "direct main" description: name: pull_down_button - sha256: "235b302701ce029fd9e9470975069376a6700935bb47a5f1b3ec8a5efba07e6f" + sha256: "48b928203afdeafa4a8be5dc96980523bc8a2ddbd04569f766071a722be22379" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.9.4" puppeteer: dependency: transitive description: name: puppeteer - sha256: eedeaae6ec5d2e54f9ae22ab4d6b3dda2e8791c356cc783046d06c287ffe11d8 + sha256: a6752d4f09b510ae41911bfd0997f957e723d38facf320dd9ee0e5661108744a url: "https://pub.dev" source: hosted - version: "3.6.0" + version: "3.13.0" qr: dependency: transitive description: @@ -1121,10 +1113,10 @@ packages: dependency: transitive description: name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" rxdart: dependency: transitive description: @@ -1169,10 +1161,10 @@ packages: dependency: transitive description: name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" shelf_web_socket: dependency: transitive description: @@ -1214,18 +1206,18 @@ packages: dependency: transitive description: name: sqflite - sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3+1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" stack_trace: dependency: transitive description: @@ -1254,10 +1246,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" synchronized: dependency: transitive description: @@ -1302,10 +1294,10 @@ packages: dependency: "direct main" description: name: toggle_switch - sha256: "9e6af1f0c5a97d9de41109dc7b9e1b3bbe73417f89b10e0e44dc834fb493d4cb" + sha256: dca04512d7c23ed320d6c5ede1211a404f177d54d353bf785b07d15546a86ce5 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.0" tuple: dependency: "direct main" description: @@ -1366,66 +1358,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" uuid: dependency: "direct main" description: @@ -1438,26 +1430,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33 + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1470,26 +1462,26 @@ packages: dependency: transitive description: name: video_player - sha256: fbf28ce8bcfe709ad91b5789166c832cb7a684d14f571a81891858fefb5bb1c2 + sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "2.9.1" video_player_android: dependency: transitive description: name: video_player_android - sha256: "7f8f25d7ad56819a82b2948357f3c3af071f6a678db33833b26ec36bbc221316" + sha256: "134e1ad410d67e18a19486ed9512c72dfc6d8ffb284d0e8f2e99e903d1ba8fa3" url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.14" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" + sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.6.1" video_player_platform_interface: dependency: transitive description: @@ -1502,10 +1494,10 @@ packages: dependency: transitive description: name: video_player_web - sha256: "34beb3a07d4331a24f7e7b2f75b8e2b103289038e07e65529699a671b6a6e2cb" + sha256: "6dcdd298136523eaf7dfc31abaf0dfba9aa8a8dbc96670e87e9d42b6f2caf774" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.2" visibility_detector: dependency: "direct main" description: @@ -1518,18 +1510,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d + sha256: "104d94837bb28c735894dcd592877e990149c380e6358b00c04398ca1426eed4" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.2.1" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" + sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" watcher: dependency: transitive description: @@ -1542,34 +1534,34 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.5" win32: dependency: "direct main" description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.5.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" + sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" window_manager: dependency: "direct main" description: @@ -1616,18 +1608,18 @@ packages: dependency: transitive description: name: yaml_edit - sha256: "1579d4a0340a83cf9e4d580ea51a16329c916973bffd5bd4b45e911b25d46bfd" + sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.1" zxing2: dependency: "direct main" description: name: zxing2 - sha256: a042961441bd400f59595f9125ef5fca4c888daf0ea59c17f41e0e151f8a12b5 + sha256: "6cf995abd3c86f01ba882968dedffa7bc130185e382f2300239d2e857fc7912c" url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.2.3" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.19.0" From 943f96ef8c937b07878ed019aea032f0debea9a5 Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 10 Sep 2024 10:57:01 +0800 Subject: [PATCH 055/210] downgrade url_launcher/uni_links for linux ci (#9306) Signed-off-by: 21pages --- flutter/pubspec.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 0a1f3ada79d..e48e74a53d3 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -1358,50 +1358,50 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.2.4" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.1" url_launcher_web: dependency: transitive description: @@ -1414,10 +1414,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.1" uuid: dependency: "direct main" description: From 13effe7f141f367e3988c02291b3970580764f5c Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:29:20 +0800 Subject: [PATCH 056/210] fix: web, switch display (#9307) Signed-off-by: fufesou --- flutter/lib/models/model.dart | 11 +++++++---- flutter/lib/web/bridge.dart | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index c622d9f10d8..853cf8ac073 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -497,10 +497,12 @@ class FfiModel with ChangeNotifier { newDisplay.width = int.tryParse(evt['width']) ?? newDisplay.width; newDisplay.height = int.tryParse(evt['height']) ?? newDisplay.height; newDisplay.cursorEmbedded = int.tryParse(evt['cursor_embedded']) == 1; - newDisplay.originalWidth = - int.tryParse(evt['original_width']) ?? kInvalidResolutionValue; - newDisplay.originalHeight = - int.tryParse(evt['original_height']) ?? kInvalidResolutionValue; + newDisplay.originalWidth = int.tryParse( + evt['original_width'] ?? kInvalidResolutionValue.toString()) ?? + kInvalidResolutionValue; + newDisplay.originalHeight = int.tryParse( + evt['original_height'] ?? kInvalidResolutionValue.toString()) ?? + kInvalidResolutionValue; newDisplay._scale = _pi.scaleOfDisplay(display); _pi.displays[display] = newDisplay; @@ -2509,6 +2511,7 @@ class FFI { onEvent2UIRgba(); imageModel.onRgba(display, data); }); + this.id = id; return; } diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index d1b777dd1e4..97cac31c934 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -391,9 +391,9 @@ class RustdeskImpl { return Future(() => js.context.callMethod('setByName', [ 'switch_display', jsonEncode({ - isDesktop: isDesktop, - sessionId: sessionId.toString(), - value: value + 'isDesktop': isDesktop, + 'sessionId': sessionId.toString(), + 'value': value }) ])); } From 51055a7e5b6a8587ea255a386c1ebff1a69aa304 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:39:22 +0800 Subject: [PATCH 057/210] fix: tokio, call future in context of runtime (#9310) Signed-off-by: fufesou --- src/server/clipboard_service.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index d6bea7520a6..55ebbc2f2ec 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -133,7 +133,7 @@ impl Handler { // Read clipboard data from cm using ipc. // - // We cannot use `#[tokio::main(flavor = "current_thread")]` here, + // We cannot use `#[tokio::main(flavor = "current_thread")]` here, // because the auto-managed tokio runtime (async context) will be dropped after the call. // The next call will create a new runtime, which will cause the previous stream to be unusable. // So we need to manage the tokio runtime manually. @@ -172,7 +172,13 @@ impl Handler { bail!("{}", err); } else { if contents.iter().any(|c| c.next_raw) { - match rt.block_on(timeout(1000, stream.next_raw())) { + // Wrap the future with a `Timeout` in an async block to avoid panic. + // We cannot use `rt.block_on(timeout(1000, stream.next_raw()))` here, because it causes panic: + // thread '' panicked at D:\Projects\rust\rustdesk\libs\hbb_common\src\lib.rs:98:5: + // there is no reactor running, must be called from the context of a Tokio 1.x runtime + // note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + match rt.block_on(async { timeout(1000, stream.next_raw()).await }) + { Ok(Ok(mut data)) => { for c in &mut contents { if c.next_raw { From 1f2a75fbd89fa362e0adc333000446ce8731929d Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 10 Sep 2024 21:28:07 +0800 Subject: [PATCH 058/210] revert back pub lock because it does not fix leak --- flutter/pubspec.lock | 248 ++++++++++++++++++++++--------------------- 1 file changed, 128 insertions(+), 120 deletions(-) diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index e48e74a53d3..62f9283a000 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "64.0.0" after_layout: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.2.0" animations: dependency: transitive description: @@ -37,18 +37,18 @@ packages: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "3.4.10" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.4.2" async: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: auto_size_text_field - sha256: "41c90b2270e38edc6ce5c02e5a17737a863e65e246bdfc94565a38f3ec399144" + sha256: d47c81ffa9b61d219f6c50492dc03ea28fa9346561b2ec33b46ccdc000ddb0aa url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.2" back_button_interceptor: dependency: "direct main" description: @@ -141,10 +141,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.8" build_runner_core: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.0" cached_network_image: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: cached_network_image_web - sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.1.1" characters: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e url: "https://pub.dev" source: hosted - version: "0.3.4+2" + version: "0.3.3+8" crypto: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.4" dash_chat_2: dependency: "direct main" description: @@ -351,10 +351,10 @@ packages: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.0" draggable_float_widget: dependency: "direct main" description: @@ -408,10 +408,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.0" ffigen: dependency: "direct dev" description: @@ -448,10 +448,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.3+3" file_selector_platform_interface: dependency: transitive description: @@ -464,10 +464,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -480,18 +480,18 @@ packages: dependency: "direct main" description: name: flex_color_picker - sha256: "5c846437069fb7afdd7ade6bf37e628a71d2ab0787095ddcb1253bf9345d5f3a" + sha256: "0871edc170153cfc3de316d30625f40a85daecfa76ce541641f3cc0ec7757cbf" url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.3.1" flex_seed_scheme: dependency: transitive description: name: flex_seed_scheme - sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01" + sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.4.0" flutter: dependency: "direct main" description: flutter @@ -620,10 +620,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.17" flutter_rust_bridge: dependency: "direct main" description: @@ -636,10 +636,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.9" flutter_web_plugins: dependency: transitive description: flutter @@ -649,26 +649,26 @@ packages: dependency: "direct dev" description: name: freezed - sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.4.7" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.1" frontend_server_client: dependency: transitive description: name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "3.2.0" get: dependency: "direct main" description: @@ -705,10 +705,10 @@ packages: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.0" http_multi_server: dependency: transitive description: @@ -737,10 +737,10 @@ packages: dependency: "direct main" description: name: image - sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.1.7" image_picker: dependency: "direct main" description: @@ -753,10 +753,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1" + sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1" url: "https://pub.dev" source: hosted - version: "0.8.12+1" + version: "0.8.9+3" image_picker_for_web: dependency: transitive description: @@ -769,10 +769,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3 url: "https://pub.dev" source: hosted - version: "0.8.12" + version: "0.8.9+1" image_picker_linux: dependency: transitive description: @@ -793,10 +793,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.9.3" image_picker_windows: dependency: transitive description: @@ -833,10 +833,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.8.1" lints: dependency: transitive description: @@ -881,10 +881,10 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.5" nested: dependency: transitive description: @@ -897,10 +897,10 @@ packages: dependency: transitive description: name: octo_image - sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.0.0" package_config: dependency: transitive description: @@ -953,26 +953,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.2" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -993,10 +993,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.1" pedantic: dependency: transitive description: @@ -1025,10 +1025,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: @@ -1037,6 +1037,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + url: "https://pub.dev" + source: hosted + version: "3.7.4" pool: dependency: transitive description: @@ -1049,10 +1057,10 @@ packages: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.1" pub_semver: dependency: transitive description: @@ -1065,26 +1073,26 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.3" pull_down_button: dependency: "direct main" description: name: pull_down_button - sha256: "48b928203afdeafa4a8be5dc96980523bc8a2ddbd04569f766071a722be22379" + sha256: "235b302701ce029fd9e9470975069376a6700935bb47a5f1b3ec8a5efba07e6f" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.3" puppeteer: dependency: transitive description: name: puppeteer - sha256: a6752d4f09b510ae41911bfd0997f957e723d38facf320dd9ee0e5661108744a + sha256: eedeaae6ec5d2e54f9ae22ab4d6b3dda2e8791c356cc783046d06c287ffe11d8 url: "https://pub.dev" source: hosted - version: "3.13.0" + version: "3.6.0" qr: dependency: transitive description: @@ -1113,10 +1121,10 @@ packages: dependency: transitive description: name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.1" rxdart: dependency: transitive description: @@ -1161,10 +1169,10 @@ packages: dependency: transitive description: name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.2" shelf_web_socket: dependency: transitive description: @@ -1206,18 +1214,18 @@ packages: dependency: transitive description: name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 url: "https://pub.dev" source: hosted - version: "2.3.3+1" + version: "2.3.2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.3" stack_trace: dependency: transitive description: @@ -1246,10 +1254,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" synchronized: dependency: transitive description: @@ -1294,10 +1302,10 @@ packages: dependency: "direct main" description: name: toggle_switch - sha256: dca04512d7c23ed320d6c5ede1211a404f177d54d353bf785b07d15546a86ce5 + sha256: "9e6af1f0c5a97d9de41109dc7b9e1b3bbe73417f89b10e0e44dc834fb493d4cb" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.1.0" tuple: dependency: "direct main" description: @@ -1406,10 +1414,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.2.3" url_launcher_windows: dependency: transitive description: @@ -1430,26 +1438,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.10+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33 url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.10+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.10+1" vector_math: dependency: transitive description: @@ -1462,26 +1470,26 @@ packages: dependency: transitive description: name: video_player - sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d + sha256: fbf28ce8bcfe709ad91b5789166c832cb7a684d14f571a81891858fefb5bb1c2 url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.8.2" video_player_android: dependency: transitive description: name: video_player_android - sha256: "134e1ad410d67e18a19486ed9512c72dfc6d8ffb284d0e8f2e99e903d1ba8fa3" + sha256: "7f8f25d7ad56819a82b2948357f3c3af071f6a678db33833b26ec36bbc221316" url: "https://pub.dev" source: hosted - version: "2.4.14" + version: "2.4.11" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c + sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.5.6" video_player_platform_interface: dependency: transitive description: @@ -1494,10 +1502,10 @@ packages: dependency: transitive description: name: video_player_web - sha256: "6dcdd298136523eaf7dfc31abaf0dfba9aa8a8dbc96670e87e9d42b6f2caf774" + sha256: "34beb3a07d4331a24f7e7b2f75b8e2b103289038e07e65529699a671b6a6e2cb" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.1.3" visibility_detector: dependency: "direct main" description: @@ -1510,18 +1518,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: "104d94837bb28c735894dcd592877e990149c380e6358b00c04398ca1426eed4" + sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.1.4" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.1.0" watcher: dependency: transitive description: @@ -1534,34 +1542,34 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.4.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.0" win32: dependency: "direct main" description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.2.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.2" window_manager: dependency: "direct main" description: @@ -1608,18 +1616,18 @@ packages: dependency: transitive description: name: yaml_edit - sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f + sha256: "1579d4a0340a83cf9e4d580ea51a16329c916973bffd5bd4b45e911b25d46bfd" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.1.1" zxing2: dependency: "direct main" description: name: zxing2 - sha256: "6cf995abd3c86f01ba882968dedffa7bc130185e382f2300239d2e857fc7912c" + sha256: a042961441bd400f59595f9125ef5fca4c888daf0ea59c17f41e0e151f8a12b5 url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.1" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.19.0" From 519539ed0a35c9692192d8f2f50709f6c18c0c33 Mon Sep 17 00:00:00 2001 From: Gheorghi Date: Tue, 10 Sep 2024 18:22:14 +0300 Subject: [PATCH 059/210] Update bg.rs with more translations (#9317) --- src/lang/bg.rs | 682 ++++++++++++++++++++++++------------------------- 1 file changed, 341 insertions(+), 341 deletions(-) diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 9a5b320a85d..7efdd0dfa74 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -1,19 +1,19 @@ lazy_static::lazy_static! { pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ - ("Status", "Статус"), - ("Your Desktop", "Твоят Работен Плот"), - ("desk_tip", "Вашият работен плот може да бъде достъпен с този идентификационен код и парола."), + ("Status", "Положение"), + ("Your Desktop", "Вашата работна среда"), + ("desk_tip", "Вашата работна среда не може да бъде достъпена с този потребителски код и парола."), ("Password", "Парола"), ("Ready", "Готово"), ("Established", "Установен"), ("connecting_status", "Свързване с RustDesk мрежата..."), - ("Enable service", "Пусни услуга"), + ("Enable service", "Разреши услуга"), ("Start service", "Стартирай услуга"), ("Service is running", "Услугата работи"), ("Service is not running", "Услугата не работи"), ("not_ready_status", "Не е в готовност. Моля проверете мрежова връзка"), - ("Control Remote Desktop", "Контролирайте отдалечения работен плот"), + ("Control Remote Desktop", "Отдалечено управление на работна среда"), ("Transfer file", "Прехвърляне на файл"), ("Connect", "Свързване"), ("Recent sessions", "Последни сесии"), @@ -23,27 +23,27 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Remove", "Премахване"), ("Refresh random password", "Опресняване на произволна парола"), ("Set your own password", "Задайте собствена парола"), - ("Enable keyboard/mouse", "Разрешение на клавиатура/мишка"), - ("Enable clipboard", "Разрешение на клипборда"), - ("Enable file transfer", "Разрешение прехвърлянето на файлове"), - ("Enable TCP tunneling", "Разрешение за TCP тунел"), - ("IP Whitelisting", "IP беял списък"), - ("ID/Relay Server", "ID/Релейн сървър"), - ("Import server config", "Експортиране конфигурацията на сървъра"), - ("Export Server Config", "Експортиране на конфигурация на сървъра"), - ("Import server configuration successfully", "Импортирането конфигурацията на сървъра успешно"), - ("Export server configuration successfully", "Експортирането конфигурацията на сървъра успешно"), - ("Invalid server configuration", "Невалидна конфигурация на сървъра"), + ("Enable keyboard/mouse", "Позволяване на клавиатура/мишка"), + ("Enable clipboard", "Позволяване достъп до клипборда"), + ("Enable file transfer", "Позволяване прехвърляне на файлове"), + ("Enable TCP tunneling", "Позволяване на TCP тунели"), + ("IP Whitelisting", "Определяне на позволени IP по списък"), + ("ID/Relay Server", "ID/Препредаващ сървър"), + ("Import server config", "Внасяне сървър настройки за "), + ("Export Server Config", "Изнасяне настройки на сървър"), + ("Import server configuration successfully", "Успешно внасяне на сървърни настройки"), + ("Export server configuration successfully", "Успешно изнасяне на сървърни настройки"), + ("Invalid server configuration", "Недопустими сървърни настройки"), ("Clipboard is empty", "Клипбордът е празен"), - ("Stop service", "Спрете услугата"), - ("Change ID", "Промяна на ID"), - ("Your new ID", "Вашето ново ID"), + ("Stop service", "Спираане на услуга"), + ("Change ID", "Промяна определител (ID)"), + ("Your new ID", "Вашият нов определител (ID)"), ("length %min% to %max%", "дължина %min% до %max%"), ("starts with a letter", "започва с буква"), ("allowed characters", "разрешени знаци"), - ("id_change_tip", "Само a-z, A-Z, 0-9 и _ (долна черта) символи са позволени. Първата буква трябва да е a-z, A-Z. С дължина мержу 6 и 16."), + ("id_change_tip", "Само a-z, A-Z, 0-9 и _ (долна черта) са сред позволени. Първа буква следва да е a-z, A-Z. С дължина мержу 6 и 16."), ("Website", "Уебсайт"), - ("About", "Относно програмата"), + ("About", "Относно"), ("Slogan_tip", "Направено от сърце в този хаотичен свят!"), ("Privacy Statement", "Декларация за поверителност"), ("Mute", "Без звук"), @@ -53,23 +53,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input", "Аудио вход"), ("Enhancements", "Подобрения"), ("Hardware Codec", "Хардуерен кодек"), - ("Adaptive bitrate", "Адаптивен битрейт"), + ("Adaptive bitrate", "Приспособяваще се скорост на предаване наданни"), ("ID Server", "ID сървър"), - ("Relay Server", "Релейн сървър"), + ("Relay Server", "Препращащ сървър"), ("API Server", "API сървър"), ("invalid_http", "трябва да започва с http:// или https://"), - ("Invalid IP", "Невалиден IP"), - ("Invalid format", "Невалиден формат"), + ("Invalid IP", "Недопустим IP"), + ("Invalid format", "Недопустим формат"), ("server_not_support", "Все още не се поддържа от сървъра"), ("Not available", "Не е наличен"), ("Too frequent", "Твърде често"), ("Cancel", "Отказ"), ("Skip", "Пропускане"), - ("Close", "Затвори"), - ("Retry", "Опитайте отново"), + ("Close", "Затваряне"), + ("Retry", "Преповтори"), ("OK", "Добре"), ("Password Required", "Изисква се парола"), - ("Please enter your password", "Моля въведете паролата си"), + ("Please enter your password", "Моля въведете парола"), ("Remember password", "Запомни паролата"), ("Wrong Password", "Грешна парола"), ("Do you want to enter again?", "Искате ли да въведете отново?"), @@ -99,73 +99,73 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Properties", "Свойства"), ("Multi Select", "Множествен избор"), ("Select All", "Избери всички"), - ("Unselect All", "Деселектирай всички"), - ("Empty Directory", "Празна директория"), - ("Not an empty directory", "Не е празна директория"), + ("Unselect All", "Избери никой"), + ("Empty Directory", "Празна папка"), + ("Not an empty directory", "Не е празна папка"), ("Are you sure you want to delete this file?", "Сигурни ли сте, че искате да изтриете този файл?"), - ("Are you sure you want to delete this empty directory?", "Сигурни ли сте, че искате да изтриете тази празна директория?"), - ("Are you sure you want to delete the file of this directory?", "Сигурни ли сте, че искате да изтриете файла от тази директория?"), - ("Do this for all conflicts", "Направете това за всички конфликти"), - ("This is irreversible!", ""), + ("Are you sure you want to delete this empty directory?", "Сигурни ли сте, че искате да изтриете тази празна папка?"), + ("Are you sure you want to delete the file of this directory?", "Сигурни ли сте, че искате да изтриете файла от тази папка?"), + ("Do this for all conflicts", "Разреши така всички конфликти"), + ("This is irreversible!", "Това е необратимо!"), ("Deleting", "Изтриване"), ("files", "файлове"), - ("Waiting", ""), - ("Finished", "Готово"), + ("Waiting", "Изчакване"), + ("Finished", "Завършено"), ("Speed", "Скорост"), - ("Custom Image Quality", "Персонализирано качество на изображението"), + ("Custom Image Quality", "Качество на изображението по свой избор"), ("Privacy mode", "Режим на поверителност"), - ("Block user input", "Блокиране на потребителско въвеждане"), - ("Unblock user input", "Отблокиране на потребителско въвеждане"), - ("Adjust Window", "Регулирай прозореца"), + ("Block user input", "Забрана за потребителски вход"), + ("Unblock user input", "Разрешаване на потребителски въвеждане"), + ("Adjust Window", "Нагласи прозореца"), ("Original", "Оригинално"), ("Shrink", "Свиване"), ("Stretch", "Разтегнат"), ("Scrollbar", "Плъзгач"), - ("ScrollAuto", "Автоматичен плъзгач"), + ("ScrollAuto", "Автоматичено приплъзване"), ("Good image quality", "Добро качество на изображението"), - ("Balanced", "Балансиран"), - ("Optimize reaction time", "Оптимизирайте времето за реакция"), - ("Custom", "Персонализиран"), - ("Show remote cursor", "Показване на дистанционния курсор"), - ("Show quality monitor", "Показване на прозорец за качество"), - ("Disable clipboard", "Деактивиране на клипборда"), - ("Lock after session end", "Заключване след края на сесията"), + ("Balanced", "Уравновесен"), + ("Optimize reaction time", "С оглед времето на реакция"), + ("Custom", "По собствено желание"), + ("Show remote cursor", "Показвай отдалечения курсор"), + ("Show quality monitor", "Показвай прозорец за качество"), + ("Disable clipboard", "Забрана за достъп до клипборд"), + ("Lock after session end", "Заключване след край на ползване"), ("Insert", "Поставяне"), ("Insert Lock", "Заявка за заключване"), - ("Refresh", "Обнови"), - ("ID does not exist", "ID-то не съществува"), - ("Failed to connect to rendezvous server", "Неуспешно свързване със сървъра за рандеву"), + ("Refresh", "Обновяване"), + ("ID does not exist", "Несъществуващ определител (ID)"), + ("Failed to connect to rendezvous server", "Неуспешно свързване към сървъра за среща (rendezvous)"), ("Please try later", "Моля опитайте по-късно"), - ("Remote desktop is offline", "Отдалеченият работен плот е офлайн"), + ("Remote desktop is offline", "Отдалечената работна среда не е налична"), ("Key mismatch", "Ключово несъответствие"), - ("Timeout", ""), - ("Failed to connect to relay server", ""), - ("Failed to connect via rendezvous server", ""), - ("Failed to connect via relay server", ""), - ("Failed to make direct connection to remote desktop", ""), - ("Set Password", "Задайте парола"), + ("Timeout", "Изтичане на времето"), + ("Failed to connect to relay server", "Провал при свързване към препредаващ сървър"), + ("Failed to connect via rendezvous server", "Провал при свързване към сървър за срещи (rendezvous)"), + ("Failed to connect via relay server", "Провал при свързване чрез препредаващ сървър"), + ("Failed to make direct connection to remote desktop", "Провал при установяване на пряка връзка с отдалечена работна среда"), + ("Set Password", "Задаване на парола"), ("OS Password", "Парола на Операционната система"), - ("install_tip", "Поради UAC, RustDesk в някои случай не може да работи правилно като отдалечена достъп. За да заобиколите UAC, моля, щракнете върху бутона по-долу, за да инсталирате RustDesk в системата."), - ("Click to upgrade", "Кликнете, за да надстроите"), - ("Click to download", "Кликнете, за да изтеглите"), - ("Click to update", "Кликнете, за да актуализирате"), - ("Configure", "Конфигуриране"), - ("config_acc", "За да управлявате вашия работен плот дистанционно, трябва да предоставите на RustDesk разрешения \"Достъпност\"."), - ("config_screen", "In order to access your Desktop remotely, you need to grant RustDesk \"Screen Recording\" permissions."), - ("Installing ...", "Инсталиране..."), - ("Install", "Инсталирай"), - ("Installation", "Инсталация"), - ("Installation Path", "Инсталационен път"), - ("Create start menu shortcuts", "Създайте преки пътища в менюто 'Старт'."), + ("install_tip", "Поради UAC, RustDesk в някои случай не може да работи правилно за отдалечена достъп. За да заобиколите UAC, моля, натиснете копчето по-долу, за да поставите RustDesk като системна услуга."), + ("Click to upgrade", "Натиснете, за да надстроите"), + ("Click to download", "Натиснете, за да изтеглите"), + ("Click to update", "Натиснете, за да обновите"), + ("Configure", "Настройване"), + ("config_acc", "За да управлявате вашия работна среда отдалечено, трябва да предоставите на RustDesk права от раздел \"Достъпност\"."), + ("config_screen", "За да управлявате вашия работна среда отдалечено, трябва да предоставите на RustDesk права от раздел \"Запис на екрана\"."), + ("Installing ...", "Поставяне..."), + ("Install", "Постави"), + ("Installation", "Поставяне"), + ("Installation Path", "Път към място за поставяне"), + ("Create start menu shortcuts", "Бърз достъп от меню 'Старт'."), ("Create desktop icon", "Създайте икона на работния плот"), - ("agreement_tip", "Стартирайки инсталацията, вие приемате лицензионното споразумение."), - ("Accept and Install", "Приемете и инсталирайте"), - ("End-user license agreement", ""), - ("Generating ...", "Генериране..."), + ("agreement_tip", "Започвайки поставянето, вие приемате лицензионното споразумение."), + ("Accept and Install", "Приемете и поставяте"), + ("End-user license agreement", "Споразумение с потребителя"), + ("Generating ...", "Пораждане..."), ("Your installation is lower version.", "Вашата инсталация е по-ниска версия."), ("not_close_tcp_tip", "Не затваряйте този прозорец, докато използвате тунела"), ("Listening ...", "Слушане..."), - ("Remote Host", "Отдалечен хост"), + ("Remote Host", "Отдалечен сървър"), ("Remote Port", "Отдалечен порт"), ("Action", "Действие"), ("Add", "Добави"), @@ -173,154 +173,154 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Local Address", "Локален адрес"), ("Change Local Port", "Промяна на локалният порт"), ("setup_server_tip", "За по-бърза връзка, моля направете свой собствен сървър"), - ("Too short, at least 6 characters.", ""), - ("The confirmation is not identical.", ""), + ("Too short, at least 6 characters.", "Прекалено кратко, поне 6 знака"), + ("The confirmation is not identical.", "Потвърждението не съвпада"), ("Permissions", "Разрешения"), ("Accept", "Приеми"), ("Dismiss", "Отхвърляне"), - ("Disconnect", "Прекъснете връзката"), - ("Enable file copy and paste", ""), + ("Disconnect", "Прекъсване"), + ("Enable file copy and paste", "Разрешаване прехвърляне на файлове"), ("Connected", "Свързан"), - ("Direct and encrypted connection", "Директна и криптирана връзка"), - ("Relayed and encrypted connection", "Препредадена и криптирана връзка"), - ("Direct and unencrypted connection", "Директна и некриптирана връзка"), - ("Relayed and unencrypted connection", "Препредадена и некриптирана връзка"), - ("Enter Remote ID", "Въведете дистанционно ID"), - ("Enter your password", "Въведете паролата си"), - ("Logging in...", ""), - ("Enable RDP session sharing", "Активирайте споделянето на RDP сесия"), + ("Direct and encrypted connection", "Пряка защитена връзка"), + ("Relayed and encrypted connection", "Препредадена защитена връзка"), + ("Direct and unencrypted connection", "Пряка незащитена връзка"), + ("Relayed and unencrypted connection", "Препредадена незащитена връзка"), + ("Enter Remote ID", "Въведете отдалеченото ID"), + ("Enter your password", "Въведете парола"), + ("Logging in...", "Вписване..."), + ("Enable RDP session sharing", "Позволяване споделянето на RDP сесия"), ("Auto Login", "Автоматично вписване (Валидно само ако зададете \"Заключване след края на сесията\")"), - ("Enable direct IP access", "Разрешете директен IP достъп"), + ("Enable direct IP access", "Разрешаване пряк IP достъп"), ("Rename", "Преименуване"), ("Space", "Пространство"), ("Create desktop shortcut", "Създайте пряк път на работния плот"), ("Change Path", "Промяна на пътя"), ("Create Folder", "Създай папка"), - ("Please enter the folder name", "Моля, въведете името на папката"), + ("Please enter the folder name", "Моля, въведете име на папката"), ("Fix it", "Оправи го"), ("Warning", "Внимание"), - ("Login screen using Wayland is not supported", "Екранът за влизане с помощта на Wayland не се поддържа"), - ("Reboot required", "Изисква се рестартиране"), - ("Unsupported display server", "Неподдържан сървър за дисплея"), - ("x11 expected", ""), + ("Login screen using Wayland is not supported", "Екран за влизане чрез Wayland не се поддържа"), + ("Reboot required", "Нужно е презареждане на ОС"), + ("Unsupported display server", "Неподдържан екранен сървър"), + ("x11 expected", "Очаква се x11"), ("Port", "Порт"), ("Settings", "Настройки"), ("Username", "Потребителско име"), - ("Invalid port", "Невалиден порт"), - ("Closed manually by the peer", "Затворено ръчно от партньора"), + ("Invalid port", "Недопустим порт"), + ("Closed manually by the peer", "Затворено ръчно от другата страна"), ("Enable remote configuration modification", "Разрешаване на отдалечена промяна на конфигурацията"), ("Run without install", "Стартирайте без инсталиране"), - ("Connect via relay", "Свържете чрез реле"), - ("Always connect via relay", "Винаги свързвайте чрез реле"), + ("Connect via relay", "Свързване чрез препращане"), + ("Always connect via relay", "Винаги чрез препращане"), ("whitelist_tip", "Само IP адресите от белия списък имат достъп до мен"), ("Login", "Влизане"), ("Verify", "Потвърди"), ("Remember me", "Запомни ме"), - ("Trust this device", "Доверете се на това устройство"), + ("Trust this device", "Доверяване на това устройство"), ("Verification code", "Код за потвърждение"), - ("verification_tip", "На регистрирания имейл адрес е изпратен код за потвърждение, въведете кода за потвърждение, за да продължите да влизате."), - ("Logout", "Излез от профила си"), - ("Tags", "Етикети"), - ("Search ID", "Търсене на ID"), - ("whitelist_sep", "Разделени със запетая, точка и запетая, интервали или нов ред"), + ("verification_tip", "На посочения имейл е изпратен код за потвърждение. Моля въведете го, за да продължите с влизането."), + ("Logout", "Отписване (Изход)"), + ("Tags", "Белези"), + ("Search ID", "Търси ID"), + ("whitelist_sep", "Разделени със запетая, точка и запетая, празни символи или нов ред"), ("Add ID", "Добави ID"), ("Add Tag", "Добави етикет"), - ("Unselect all tags", "Премахнете избора на всички етикети"), + ("Unselect all tags", "Премахнете избора на всички белези (tags)"), ("Network error", "Мрежова грешка"), - ("Username missed", "Пропуснато потребителско име"), - ("Password missed", "Пропусната парола"), - ("Wrong credentials", "Wrong username or password"), - ("The verification code is incorrect or has expired", ""), - ("Edit Tag", "Edit tag"), + ("Username missed", "Липсващо потребителско име"), + ("Password missed", "Липсваща парола"), + ("Wrong credentials", "Грешни пълномощия"), + ("The verification code is incorrect or has expired", "Кодът за проверка е неправилен или с изтекла давност."), + ("Edit Tag", "Промени белег"), ("Forget Password", "Забравена парола"), - ("Favorites", ""), + ("Favorites", "Любими"), ("Add to Favorites", "Добави към любими"), ("Remove from Favorites", "Премахване от любими"), ("Empty", "Празно"), - ("Invalid folder name", ""), - ("Socks5 Proxy", "Socks5 прокси"), - ("Socks5/Http(s) Proxy", "Socks5/Http(s) прокси"), - ("Discovered", ""), - ("install_daemon_tip", "За стартиране с компютъра трябва да инсталирате системна услуга."), - ("Remote ID", "Дистанционно ID"), + ("Invalid folder name", "Непозволено име на папка"), + ("Socks5 Proxy", "Socks5 посредник"), + ("Socks5/Http(s) Proxy", "Socks5/Http(s) посредник"), + ("Discovered", "Открит"), + ("install_daemon_tip", "За зареждане при стартиране на ОС следва да поставите RustDesk като системна услуга."), + ("Remote ID", "Отдалечено ID"), ("Paste", "Постави"), ("Paste here?", "Постави тук?"), ("Are you sure to close the connection?", "Сигурни ли сте, че искате да затворите връзката?"), - ("Download new version", ""), - ("Touch mode", "Режим тъч (сензорен)"), + ("Download new version", "Изтегляне на нова версия"), + ("Touch mode", "Режим сензорен (touch)"), ("Mouse mode", "Режим мишка"), - ("One-Finger Tap", "Докосване с един пръст"), + ("One-Finger Tap", "Допир с един пръст"), ("Left Mouse", "Ляв бутон на мишката"), - ("One-Long Tap", "Едно дълго докосване"), - ("Two-Finger Tap", "Докосване с два пръста"), + ("One-Long Tap", "Дълъг допир"), + ("Two-Finger Tap", "Допир с два пръста"), ("Right Mouse", "Десен бутон на мишката"), ("One-Finger Move", "Преместване с един пръст"), - ("Double Tap & Move", "Докоснете два пъти и преместете"), - ("Mouse Drag", "Плъзгане с мишката"), + ("Double Tap & Move", "Двоен допир и преместване"), + ("Mouse Drag", "Провличане с мишката"), ("Three-Finger vertically", "Три пръста вертикално"), ("Mouse Wheel", "Колело на мишката"), ("Two-Finger Move", "Движение с два пръста"), ("Canvas Move", "Преместване на платното"), ("Pinch to Zoom", "Щипнете, за да увеличите"), ("Canvas Zoom", "Увеличение на платното"), - ("Reset canvas", ""), - ("No permission of file transfer", ""), - ("Note", ""), - ("Connection", ""), + ("Reset canvas", "Нулиране на платното"), + ("No permission of file transfer", "Няма разрешение за прехвърляне на файлове"), + ("Note", "Бележка"), + ("Connection", "Връзка"), ("Share Screen", "Сподели екран"), - ("Chat", "Чат"), - ("Total", "Обшо"), - ("items", "елементи"), + ("Chat", "Говор"), + ("Total", "Общо"), + ("items", "неща"), ("Selected", "Избрано"), - ("Screen Capture", "Заснемане на екрана"), - ("Input Control", "Контрол на въвеждане"), - ("Audio Capture", "Аудио записване"), + ("Screen Capture", "Снемане на екрана"), + ("Input Control", "Управление на вход"), + ("Audio Capture", "Аудиозапис"), ("File Connection", "Файлова връзка"), - ("Screen Connection", "Свързване на екрана"), + ("Screen Connection", "Екранна връзка"), ("Do you accept?", "Приемате ли?"), - ("Open System Setting", "Отворете системната настройка"), - ("How to get Android input permission?", ""), - ("android_input_permission_tip1", "За да може отдалечено устройство да управлява вашето Android устройство чрез мишка или докосване, трябва да разрешите на RustDesk да използва услугата \"Достъпност\"."), + ("Open System Setting", "Отворете системните настройки"), + ("How to get Android input permission?", "Как да получим право за въвеждане под Андрид?"), + ("android_input_permission_tip1", "За да може отдалечено устройство да управлява вашето Android устройство чрез мишка или допир, трябва да разрешите на RustDesk да използва услугата \"Достъпност\"."), ("android_input_permission_tip2", "Моля, отидете на следващата страница с системни настройки, намерете и въведете [Installed Services], включете услугата [RustDesk Input]."), - ("android_new_connection_tip", "Получена е нова заявка за контрол, която иска да контролира вашето текущо устройство."), - ("android_service_will_start_tip", "Включването на \"Заснемане на екрана\" автоматично ще стартира услугата, позволявайки на други устройства да поискат връзка с вашето устройство."), + ("android_new_connection_tip", "Получена е нова заявка за отдалечено управление на вашето текущо устройство."), + ("android_service_will_start_tip", "Включването на \"Снемане на екрана\" автоматично ще стартира услугата, позволявайки на други устройства да поискат връзка с вашето устройство."), ("android_stop_service_tip", "Затварянето на услугата автоматично ще затвори всички установени връзки."), - ("android_version_audio_tip", "Текущата версия на Android не поддържа аудио заснемане, моля, актуализирайте устройството с Android 10 или по-нова версия."), - ("android_start_service_tip", "Докоснете [Start service] или активирайте разрешение [Screen Capture], за да стартирате услугата за споделяне на екрана."), - ("android_permission_may_not_change_tip", "Разрешенията за установени връзки може да не се променят незабавно, докато не се свържете отново."), - ("Account", "Акаунт"), + ("android_version_audio_tip", "Текущата версия на Android не поддържа аудиозапис. Моля, актуализирайте устройството с Android 10 или по-нов."), + ("android_start_service_tip", "Докоснете [Start service] или позволете [Screen Capture], за да започне услугата по споделяне на екрана."), + ("android_permission_may_not_change_tip", "Разрешенията за установени връзки може да не се променят незабавно, а ще изискват да се свържете отново."), + ("Account", "Сметка"), ("Overwrite", "Презаписване"), - ("This file exists, skip or overwrite this file?", ""), - ("Quit", "Излез"), + ("This file exists, skip or overwrite this file?", "Този файл съществува вече. Пропускане или презаписване?"), + ("Quit", "Изход"), ("Help", "Помощ"), ("Failed", "Неуспешно"), ("Succeeded", "Успешно"), - ("Someone turns on privacy mode, exit", "Някой включва режим на поверителност, излезте"), - ("Unsupported", "Не се поддържа"), - ("Peer denied", ""), - ("Please install plugins", ""), - ("Peer exit", ""), - ("Failed to turn off", ""), - ("Turned off", ""), + ("Someone turns on privacy mode, exit", "Някой включва режим на поверителност, изход"), + ("Unsupported", "Неподдържан"), + ("Peer denied", "Отказ от другата страна"), + ("Please install plugins", "Моля поставете приставки"), + ("Peer exit", "Изход от другата страна"), + ("Failed to turn off", "Провал при опит за изключване"), + ("Turned off", "Изкключен"), ("Language", "Език"), - ("Keep RustDesk background service", ""), + ("Keep RustDesk background service", "Запази работеща фонова услуга с RustDesk"), ("Ignore Battery Optimizations", "Игнорирай оптимизациите на батерията"), ("android_open_battery_optimizations_tip", "Ако искате да деактивирате тази функция, моля, отидете на следващата страница с настройки на приложението RustDesk, намерете и въведете [Battery], премахнете отметката от [Unrestricted]"), ("Start on boot", "Стартирайте при зареждане"), ("Start the screen sharing service on boot, requires special permissions", ""), - ("Connection not allowed", ""), - ("Legacy mode", ""), - ("Map mode", ""), - ("Translate mode", "Режим на превод"), - ("Use permanent password", "Използвайте постоянна парола"), - ("Use both passwords", "Използвайте и двете пароли"), - ("Set permanent password", "Задайте постоянна парола"), - ("Enable remote restart", "Разрешете отдалечено рестартиране"), - ("Restart remote device", "Рестартирайте отдалеченото устройство"), + ("Connection not allowed", "Връзката непозволена"), + ("Legacy mode", "По остарял начин"), + ("Map mode", "По начин със съответствие (map)"), + ("Translate mode", "По нчаин с превод"), + ("Use permanent password", "Използване на постоянна парола"), + ("Use both passwords", "Използване и на двете пароли"), + ("Set permanent password", "Задаване постоянна парола"), + ("Enable remote restart", "Разрешаване на отдалечен рестарт"), + ("Restart remote device", "Рестартиране на отдалечено устройство"), ("Are you sure you want to restart", "Сигурни ли сте, че искате да рестартирате"), - ("Restarting remote device", "Рестартира се отдалечено устройство"), + ("Restarting remote device", "Рестартиране на отдалечено устройство"), ("remote_restarting_tip", "Отдалеченото устройство се рестартира, моля, затворете това съобщение и се свържете отново с постоянна парола след известно време"), - ("Copied", "Копирано"), + ("Copied", "Преписано"), ("Exit Fullscreen", "Изход от цял екран"), ("Fullscreen", "Цял екран"), ("Mobile Actions", "Мобилни действия"), @@ -334,10 +334,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Hide Toolbar", "Скриване на лентата с инструменти"), ("Direct Connection", "Директна връзка"), ("Relay Connection", "Релейна връзка"), - ("Secure Connection", "Защитена връзка"), - ("Insecure Connection", "Незащитена връзка"), + ("Secure Connection", "Сигурна връзка"), + ("Insecure Connection", "Несигурна връзка"), ("Scale original", "Оригинален мащаб"), - ("Scale adaptive", "Адаптивно мащабиране"), + ("Scale adaptive", "Приспособимо мащабиране"), ("General", "Основен"), ("Security", "Сигурност"), ("Theme", "Тема"), @@ -345,128 +345,128 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Light Theme", "Светла тема"), ("Dark", "Тъмна"), ("Light", "Светла"), - ("Follow System", "Следвай системата"), - ("Enable hardware codec", "Активиране на хардуерен кодек"), + ("Follow System", "Следвай система"), + ("Enable hardware codec", "Позволяване хардуерен кодек"), ("Unlock Security Settings", "Отключи настройките за сигурност"), ("Enable audio", "Разрешете аудиото"), ("Unlock Network Settings", "Отключи мрежовите настройки"), ("Server", "Сървър"), - ("Direct IP Access", "Директен IP достъп"), - ("Proxy", "Прокси"), - ("Apply", "Приложи"), - ("Disconnect all devices?", ""), - ("Clear", "Изчисти"), + ("Direct IP Access", "Пряк IP достъп"), + ("Proxy", "Посредник (Proxy)"), + ("Apply", "Прилагане"), + ("Disconnect all devices?", "Разкачване на всички устройства"), + ("Clear", "Изчистване"), ("Audio Input Device", "Аудио входно устройство"), - ("Use IP Whitelisting", "Използвайте бял списък с IP адреси"), + ("Use IP Whitelisting", "Използване бял списък с IP адреси"), ("Network", "Мрежа"), - ("Pin Toolbar", "Фиксиране на лентата с инструменти"), - ("Unpin Toolbar", "Откачване на лентата с инструменти"), + ("Pin Toolbar", "Закачане лента с инструменти"), + ("Unpin Toolbar", "Откачюане лента с инструменти"), ("Recording", "Записване"), ("Directory", "Директория"), - ("Automatically record incoming sessions", ""), - ("Change", "Промени"), - ("Start session recording", ""), - ("Stop session recording", ""), - ("Enable recording session", ""), - ("Enable LAN discovery", "Активирайте откриване в LAN"), - ("Deny LAN discovery", "Забранете откриване в LAN"), + ("Automatically record incoming sessions", "Автоматичен запис на входящи сесии"), + ("Change", "Промяна"), + ("Start session recording", "Започванена запис"), + ("Stop session recording", "Край на запис"), + ("Enable recording session", "Позволяване запис"), + ("Enable LAN discovery", "Позволяване откриване във вътрешна мрежа"), + ("Deny LAN discovery", "Забрана за откриване във вътрешна мрежа"), ("Write a message", "Напишете съобщение"), ("Prompt", "Подкана"), - ("Please wait for confirmation of UAC...", ""), + ("Please wait for confirmation of UAC...", "Моля изчакайте за потвърждение от UAC..."), ("elevated_foreground_window_tip", "Текущият прозорец на отдалечения работен плот изисква по-високи привилегии за работа, така че временно не може да използва мишката и клавиатурата. Можете да поискате от отдалечения потребител да минимизира текущия прозорец или да щракнете върху бутона за повдигане в прозореца за управление на връзката. За да избегнете този проблем, се препоръчва да инсталирате софтуера на отдалеченото устройство."), ("Disconnected", "Прекъсната връзка"), ("Other", "Други"), - ("Confirm before closing multiple tabs", ""), + ("Confirm before closing multiple tabs", "Потвърждение преди затваряне на няколко раздела"), ("Keyboard Settings", "Настройки на клавиатурата"), ("Full Access", "Пълен достъп"), ("Screen Share", "Споделяне на екрана"), - ("Wayland requires Ubuntu 21.04 or higher version.", ""), - ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", ""), - ("JumpLink", "Преглед"), - ("Please Select the screen to be shared(Operate on the peer side).", "Моля, изберете екрана, който да бъде споделен (Работете от страна на партньора)."), + ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland изисква Ubuntu 21.04 или по-нов"), + ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland изисква по-нов Linux. Моля, опитайте с X11 или сменете операционната система."), + ("JumpLink", "Препратка"), + ("Please Select the screen to be shared(Operate on the peer side).", "Моля, изберете екрана, който да бъде споделен (спрямо отдалечената страна)."), ("Show RustDesk", "Покажи RustDesk"), ("This PC", "Този компютър"), ("or", "или"), ("Continue with", "Продължи с"), ("Elevate", "Повишаване"), - ("Zoom cursor", "Мащабиране на Курсор"), - ("Accept sessions via password", "Приемайте сесии чрез парола"), - ("Accept sessions via click", "Приемане на сесии чрез щракване"), - ("Accept sessions via both", "Приемайте сесии и през двете"), - ("Please wait for the remote side to accept your session request...", ""), + ("Zoom cursor", "Уголемяване курсор"), + ("Accept sessions via password", "Приемане сесии чрез парола"), + ("Accept sessions via click", "Приемане сесии чрез цъкване"), + ("Accept sessions via both", "Приемане сесии и по двата начина"), + ("Please wait for the remote side to accept your session request...", "Моля, изчакайте докато другата страна приеме заявката за отдалечен достъп..."), ("One-time Password", "Еднократна парола"), - ("Use one-time password", ""), - ("One-time password length", ""), - ("Request access to your device", ""), - ("Hide connection management window", ""), - ("hide_cm_tip", "Разрешете скриването само ако приемате сесии чрез парола и използвате постоянна парола"), - ("wayland_experiment_tip", "Wayland support is in experimental stage, please use X11 if you require unattended access."), - ("Right click to select tabs", ""), + ("Use one-time password", "Ползване на еднократна парола"), + ("One-time password length", "Дължина на еднократна парола"), + ("Request access to your device", "Искане за достъп до ваше устройство"), + ("Hide connection management window", "Скриване на прозореца за управление на свързване"), + ("hide_cm_tip", "Разрешаване скриване само ако се приемат сесии чрез постоянна парола"), + ("wayland_experiment_tip", "Поддръжката на Wayland е в експериментален стадий, моля, използвайте X11, ако се нуждаете от безконтролен достъп.."), + ("Right click to select tabs", "Десен бутон за избор на раздел"), ("Skipped", "Пропуснато"), - ("Add to address book", ""), + ("Add to address book", "Добавяне към познати адреси"), ("Group", "Група"), ("Search", "Търсене"), - ("Closed manually by web console", ""), - ("Local keyboard type", ""), - ("Select local keyboard type", ""), + ("Closed manually by web console", "Затворен ръчно от уеб конзола"), + ("Local keyboard type", "Тип на тукашната клавиатура"), + ("Select local keyboard type", "Избор на тип на тукашната клавиатура"), ("software_render_tip", "Ако използвате графична карта Nvidia под Linux и отдалеченият прозорец се затваря веднага след свързване, превключването към драйвера Nouveau с отворен код и изборът да използвате софтуерно изобразяване може да помогне. Изисква се рестартиране на софтуера."), - ("Always use software rendering", ""), - ("config_input", "За да контролирате отдалечен работен плот с клавиатура, трябва да предоставите на RustDesk разрешения \"Input Monitoring\"."), - ("config_microphone", "За да говорите дистанционно, трябва да предоставите на RustDesk разрешения \"Запис на звук\"."), - ("request_elevation_tip", "Можете също така да поискате повишаване на привилегии, ако има някой от отдалечената страна."), - ("Wait", "Изчакайте"), - ("Elevation Error", "Грешка при повишаване на привилегии"), - ("Ask the remote user for authentication", ""), - ("Choose this if the remote account is administrator", ""), - ("Transmit the username and password of administrator", ""), - ("still_click_uac_tip", "Все още изисква отдалеченият потребител да щракне върху OK в прозореца на UAC при стартиранят RustDesk."), - ("Request Elevation", "Поискайте повишаване на привилегии"), + ("Always use software rendering", "Винаги ползвай софтуерно изграждане на картината"), + ("config_input", "За да управлявате отдалечена среда с клавиатура, трябва да предоставите на RustDesk право за \"Input Monitoring\"."), + ("config_microphone", "За да говорите отдалечено, трябва да предоставите на RustDesk право за \"Запис на звук\"."), + ("request_elevation_tip", "Можете също така да поискате разширени права, ако има някой от отдалечената страна."), + ("Wait", "Изчакване"), + ("Elevation Error", "Грешка при добвиане на разширени права"), + ("Ask the remote user for authentication", "Попитайте отдалечения потребител за удостоверяване"), + ("Choose this if the remote account is administrator", "Изберете това, ако отдалеченият потребител е администратор."), + ("Transmit the username and password of administrator", "Предаване на потребителското име и паролата на администратора"), + ("still_click_uac_tip", "Все още изисква отдалеченият потребител да щракне върху OK в прозореца на UAC при стартиран RustDesk."), + ("Request Elevation", "Поискайте разширени права"), ("wait_accept_uac_tip", "Моля, изчакайте отдалеченият потребител да приеме диалоговия прозорец на UAC."), - ("Elevate successfully", ""), - ("uppercase", ""), - ("lowercase", ""), - ("digit", ""), - ("special character", ""), - ("length>=8", ""), - ("Weak", ""), - ("Medium", ""), - ("Strong", ""), - ("Switch Sides", "Сменете страните"), - ("Please confirm if you want to share your desktop?", ""), - ("Display", ""), + ("Elevate successfully", "Успешно получаване на разширени права"), + ("uppercase", "големи букви"), + ("lowercase", "малки букви"), + ("digit", "цифра"), + ("special character", "специален знак"), + ("length>=8", "дължина>=8"), + ("Weak", "Слаба"), + ("Medium", "Средна"), + ("Strong", "Силна"), + ("Switch Sides", "Размяна на страните"), + ("Please confirm if you want to share your desktop?", "Моля, потвърдете дали искате да споделите работното си пространство"), + ("Display", "Екран"), ("Default View Style", "Стил на изглед по подразбиране"), ("Default Scroll Style", "Стил на превъртане по подразбиране"), ("Default Image Quality", "Качество на изображението по подразбиране"), ("Default Codec", "Кодек по подразбиране"), - ("Bitrate", "Битрейт"), + ("Bitrate", "Скорост на предаване на данни (bitrate)"), ("FPS", "Кадри в секунда"), ("Auto", "Автоматично"), ("Other Default Options", "Други опции по подразбиране"), - ("Voice call", ""), - ("Text chat", ""), - ("Stop voice call", ""), + ("Voice call", "Гласови обаждания"), + ("Text chat", "Текстов разговор"), + ("Stop voice call", "Прекратяване гласово обаждане"), ("relay_hint_tip", "Може да не е възможно да се свържете директно; можете да опитате да се свържете чрез реле. Освен това, ако искате да използвате реле при първия си опит, добавете наставка \"/r\" към идентификатора или да изберете опцията \"Винаги свързване чрез реле\" в картата на последните сесии, ако съществува."), - ("Reconnect", "Свържете се отново"), + ("Reconnect", "Повторно свързане"), ("Codec", "Кодек"), - ("Resolution", "Резолюция"), - ("No transfers in progress", "Не се извършват трансфери"), - ("Set one-time password length", ""), + ("Resolution", "Разделителна способност"), + ("No transfers in progress", "Няма текущи прехвърляния"), + ("Set one-time password length", "Задаване дължаина на еднократна парола"), ("RDP Settings", "RDP настройки"), - ("Sort by", "Сортирай по"), + ("Sort by", "Подредба по"), ("New Connection", "Ново свързване"), - ("Restore", ""), - ("Minimize", ""), - ("Maximize", ""), + ("Restore", "Възстановяване"), + ("Minimize", "Смаляване"), + ("Maximize", "Уголемяване"), ("Your Device", "Вашето устройство"), ("empty_recent_tip", "Ами сега, няма скорошни сесии!\nВреме е да планирате нова."), ("empty_favorite_tip", "Все още нямате любими връстници?\nНека намерим някой, с когото да се свържете, и да го добавим към вашите любими!"), ("empty_lan_tip", "О, не, изглежда, че все още не сме открили връстници."), ("empty_address_book_tip", "Изглежда, че в момента няма изброени връстници във вашата адресна книга."), - ("eg: admin", ""), + ("eg: admin", "напр. admin"), ("Empty Username", "Празно потребителско име"), ("Empty Password", "Празна парола"), - ("Me", "Аз"), - ("identical_file_tip", "Този файл е идентичен с този на партньора."), + ("Me", "Мен"), + ("identical_file_tip", "Файлът съвпада с този от другата страна."), ("show_monitors_tip", "Показване на мониторите в лентата с инструменти"), ("View Mode", "Режим на преглед"), ("login_linux_tip", "Трябва да влезете в отдалечен Linux акаунт, за да активирате X сесия на работния плот"), @@ -482,47 +482,47 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no_desktop_text_tip", "Моля, инсталирайте работен плот GNOME"), ("No need to elevate", ""), ("System Sound", "Системен звук"), - ("Default", ""), - ("New RDP", ""), - ("Fingerprint", ""), + ("Default", "По подразбиране"), + ("New RDP", "Нов RDP"), + ("Fingerprint", "Пръстов отпечатък"), ("Copy Fingerprint", "Копиране на пръстов отпечатък"), ("no fingerprints", "Няма пръстови отпечатъци"), - ("Select a peer", ""), - ("Select peers", ""), - ("Plugins", ""), - ("Uninstall", ""), - ("Update", ""), - ("Enable", ""), - ("Disable", ""), + ("Select a peer", "Избери отдалечена страна"), + ("Select peers", "Избери отдалечени страни"), + ("Plugins", "Приставки"), + ("Uninstall", "Премахни"), + ("Update", "Обновяване"), + ("Enable", "Позволяване"), + ("Disable", "Забрана"), ("Options", "Настроики"), - ("resolution_original_tip", "Оригинална резолюция"), - ("resolution_fit_local_tip", "Напасване към локална разделителна способност"), - ("resolution_custom_tip", "Персонализирана разделителна способност"), + ("resolution_original_tip", "Оригинална разделителна способност"), + ("resolution_fit_local_tip", "Приспособяване към тукашната разделителна способност"), + ("resolution_custom_tip", "Разделителна способност по свой избор"), ("Collapse toolbar", "Свиване на лентата с инструменти"), - ("Accept and Elevate", "Приемете и повишаване на привилегии"), - ("accept_and_elevate_btn_tooltip", "Приемете връзката и повишете UAC разрешенията."), - ("clipboard_wait_response_timeout_tip", "Времето за изчакване на отговор за копиране изтече."), - ("Incoming connection", ""), - ("Outgoing connection", ""), - ("Exit", "Излез"), - ("Open", "Отвори"), + ("Accept and Elevate", "Приемане и предоставяне на допълнителни права"), + ("accept_and_elevate_btn_tooltip", "Приемане на връзката предоставяне на UAC разрешения."), + ("clipboard_wait_response_timeout_tip", "Времето за изчакване на отговор за препис изтече."), + ("Incoming connection", "Входяща връзка"), + ("Outgoing connection", "Изходяща връзка"), + ("Exit", "Изход"), + ("Open", "Отваряне"), ("logout_tip", "Сигурни ли сте, че искате да излезете?"), ("Service", "Услуга"), ("Start", "Стартиране"), ("Stop", "Спиране"), ("exceed_max_devices", "Достигнахте максималния брой управлявани устройства."), - ("Sync with recent sessions", ""), - ("Sort tags", ""), - ("Open connection in new tab", ""), - ("Move tab to new window", ""), - ("Can not be empty", ""), - ("Already exists", ""), - ("Change Password", "Промяна на паролата"), - ("Refresh Password", "Обнови паролата"), - ("ID", ""), + ("Sync with recent sessions", "Синхронизиране с последните сесии"), + ("Sort tags", "Подреди белези"), + ("Open connection in new tab", "Разкриване на връзка в нов раздел"), + ("Move tab to new window", "Отделяне на раздела в нов прозорец"), + ("Can not be empty", "Не може да е празно"), + ("Already exists", "Вече съществува"), + ("Change Password", "Промяна на парола"), + ("Refresh Password", "Обновяване парола"), + ("ID", "Определител (ID)"), ("Grid View", "Мрежов изглед"), ("List View", "Списъчен изглед"), - ("Select", ""), + ("Select", "Избиране"), ("Toggle Tags", "Превключване на етикети"), ("pull_ab_failed_tip", "Неуспешно опресняване на адресната книга"), ("push_ab_failed_tip", "Неуспешно синхронизиране на адресната книга със сървъра"), @@ -530,119 +530,119 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Change Color", "Промяна на цвета"), ("Primary Color", "Основен цвят"), ("HSV Color", "HSV цвят"), - ("Installation Successful!", "Успешна инсталация!"), - ("Installation failed!", ""), - ("Reverse mouse wheel", ""), - ("{} sessions", ""), + ("Installation Successful!", "Успешно поставяне!"), + ("Installation failed!", "Провал при поставяне"), + ("Reverse mouse wheel", "Обърнато колелото на мишката"), + ("{} sessions", "{} сесии"), ("scam_title", "Възможно е да сте ИЗМАМЕНИ!"), ("scam_text1", "Ако разговаряте по телефона с някой, когото НЕ ПОЗНАВАТЕ и НЯМАТЕ ДОВЕРИЕ, който ви е помолил да използвате RustDesk и да стартирате услугата, не продължавайте и затворете незабавно."), ("scam_text2", "Те вероятно са измамник, който се опитва да открадне вашите пари или друга лична информация."), ("Don't show again", "Не показвай отново"), - ("I Agree", ""), - ("Decline", ""), - ("Timeout in minutes", ""), + ("I Agree", "Съгласен"), + ("Decline", "Отказвам"), + ("Timeout in minutes", "Време за отговор в минути"), ("auto_disconnect_option_tip", "Автоматично затваряне на входящите сесии при неактивност на потребителя"), ("Connection failed due to inactivity", "Автоматично прекъсване на връзката поради неактивност"), ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", "Моля обновете RustDesk Server Pro на версия {} или по-нова!"), ("pull_group_failed_tip", "Неуспешно опресняване на групата"), - ("Filter by intersection", ""), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), + ("Filter by intersection", "Отсяване по пресичане"), + ("Remove wallpaper during incoming sessions", "Спри фоновото изображение по време на входящи сесии"), + ("Test", "Проверка"), ("display_is_plugged_out_msg", "Дисплеят е изключен, превключете на първия монитор."), - ("No displays", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), - ("Use all my displays for the remote session", ""), + ("No displays", "Няма екрани"), + ("Open in new window", "Отваряне в нов прозорец"), + ("Show displays as individual windows", "Показване на екраните в отделни прозорци"), + ("Use all my displays for the remote session", "Използване на всички тукашни екрани за отдалечена работа"), ("selinux_tip", "SELinux е активиран на вашето устройство, което може да попречи на RustDesk да работи правилно като контролирана страна."), - ("Change view", ""), - ("Big tiles", ""), - ("Small tiles", ""), - ("List", ""), - ("Virtual display", ""), - ("Plug out all", ""), + ("Change view", "Промяна изглед"), + ("Big tiles", "Големи заглавия"), + ("Small tiles", "Малки заглавия"), + ("List", "Списък"), + ("Virtual display", "Виртуален екран"), + ("Plug out all", "Изтръгване на всички"), ("True color (4:4:4)", ""), ("Enable blocking user input", "Разрешаване на блокиране на потребителско въвеждане"), ("id_input_tip", "Можете да въведете ID, директен IP адрес или домейн с порт (:).\nАко искате да получите достъп до устройство на друг сървър, моля, добавете адреса на сървъра (@?key=), например\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nАко искате да получите достъп до устройство на обществен сървър, моля, въведете \"@public\" , ключът не е необходим за публичен сървър"), ("privacy_mode_impl_mag_tip", "Режим 1"), ("privacy_mode_impl_virtual_display_tip", "Режим 2"), - ("Enter privacy mode", ""), - ("Exit privacy mode", ""), + ("Enter privacy mode", "Влизане в поверителен режим"), + ("Exit privacy mode", "Изход от поверителен режим"), ("idd_not_support_under_win10_2004_tip", "Индиректен драйвер за дисплей не се поддържа. Изисква се Windows 10, версия 2004 или по-нова."), ("input_source_1_tip", "Входен източник 1"), ("input_source_2_tip", "Входен източник 2"), ("Swap control-command key", ""), - ("swap-left-right-mouse", "Разменете левия и десния бутон на мишката"), - ("2FA code", "Код за Двуфакторна удостоверяване"), + ("swap-left-right-mouse", "Размяна на копчетата на мишката"), + ("2FA code", "Код за Двуфакторно удостоверяване"), ("More", "Повече"), - ("enable-2fa-title", "Активиране на двуфакторно удостоверяване"), + ("enable-2fa-title", "Позволяване на двуфакторно удостоверяване"), ("enable-2fa-desc", "Моля, настройте вашия удостоверител сега. Можете да използвате приложение за удостоверяване като Authy, Microsoft или Google Authenticator на вашия телефон или настолен компютър.\n\nСканирайте QR кода с вашето приложение и въведете кода, който приложението ви показва, за да активирате двуфакторно удостоверяване."), ("wrong-2fa-code", "е може да се потвърди кодът. Проверете дали настройките за код и локалното време са правилни"), ("enter-2fa-title", "Двуфакторно удостоверяване"), - ("Email verification code must be 6 characters.", ""), - ("2FA code must be 6 digits.", ""), - ("Multiple Windows sessions found", ""), - ("Please select the session you want to connect to", ""), + ("Email verification code must be 6 characters.", "Кодът за проверка следва да е с дължина 6 знака."), + ("2FA code must be 6 digits.", "Кодът за 2FA (двуфакторно удостоверяване) трябва да е 6-цифрен"), + ("Multiple Windows sessions found", "Установени са няколко Windwos сесии"), + ("Please select the session you want to connect to", "Моля определете сесия към която искате да се свърженете"), ("powered_by_me", ""), ("outgoing_only_desk_tip", ""), ("preset_password_warning", ""), - ("Security Alert", ""), - ("My address book", ""), - ("Personal", ""), - ("Owner", ""), - ("Set shared password", ""), - ("Exist in", ""), - ("Read-only", ""), - ("Read/Write", ""), - ("Full Control", ""), + ("Security Alert", "Предупреждение за сигурност"), + ("My address book", "Моята адресна книга"), + ("Personal", "Личен"), + ("Owner", "Собственик"), + ("Set shared password", "Определяне споделена парола"), + ("Exist in", "Съществува в"), + ("Read-only", "Само четене"), + ("Read/Write", "Писане/четене"), + ("Full Control", "Пълен контрол"), ("share_warning_tip", ""), - ("Everyone", ""), + ("Everyone", "Всички"), ("ab_web_console_tip", ""), ("allow-only-conn-window-open-tip", ""), ("no_need_privacy_mode_no_physical_displays_tip", ""), - ("Follow remote cursor", ""), - ("Follow remote window focus", ""), + ("Follow remote cursor", "Следвай отдалечения курсор"), + ("Follow remote window focus", "Следвай фокуса на отдалечените прозорци"), ("default_proxy_tip", ""), ("no_audio_input_device_tip", ""), - ("Incoming", ""), - ("Outgoing", ""), - ("Clear Wayland screen selection", ""), + ("Incoming", "Входящ"), + ("Outgoing", "Изходящ"), + ("Clear Wayland screen selection", "Изчистване избор на Wayland екран"), ("clear_Wayland_screen_selection_tip", ""), ("confirm_clear_Wayland_screen_selection_tip", ""), ("android_new_voice_call_tip", ""), ("texture_render_tip", ""), - ("Use texture rendering", ""), - ("Floating window", ""), + ("Use texture rendering", "Използвай текстово изграждане"), + ("Floating window", "Плаващ прозорец"), ("floating_window_tip", ""), - ("Keep screen on", ""), - ("Never", ""), - ("During controlled", ""), - ("During service is on", ""), - ("Capture screen using DirectX", ""), + ("Keep screen on", "Запази екранът включен"), + ("Never", "Никога"), + ("During controlled", "Докато е обект на управление"), + ("During service is on", "Докато услугата е включена"), + ("Capture screen using DirectX", "Снемай екрана ползвайки DirectX"), ("Back", "Назад"), - ("Apps", ""), - ("Volume up", ""), - ("Volume down", ""), - ("Power", ""), - ("Telegram bot", ""), + ("Apps", "Приложения"), + ("Volume up", "Усилване звук"), + ("Volume down", "Намаляне звук"), + ("Power", "Мощност"), + ("Telegram bot", "Телеграм бот"), ("enable-bot-tip", ""), ("enable-bot-desc", ""), ("cancel-2fa-confirm-tip", ""), ("cancel-bot-confirm-tip", ""), - ("About RustDesk", ""), + ("About RustDesk", "Относно RustDesk"), ("Send clipboard keystrokes", ""), ("network_error_tip", ""), - ("Unlock with PIN", ""), + ("Unlock with PIN", "Отключване с PIN"), ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), + ("Wrong PIN", "Грешен PIN"), + ("Set PIN", "Избор PIN"), + ("Enable trusted devices", "Позволяване доверени устройства"), + ("Manage trusted devices", "Управление доверени устройства"), + ("Platform", "Платформа"), + ("Days remaining", "Оставащи дни"), ("enable-trusted-devices-tip", ""), ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("Resume", "Възобновяване"), + ("Invalid file name", "Невалидно име за файл"), ].iter().cloned().collect(); } From 9380f33d7c08b3c3db8659df5915feda22ab192a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:54:59 +0800 Subject: [PATCH 060/210] Refact/options (#9318) * refact options Signed-off-by: fufesou * Remove unused msg Signed-off-by: fufesou * web, toggle virtual display Signed-off-by: fufesou --------- Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 4 +++- flutter/lib/models/model.dart | 2 +- flutter/lib/web/bridge.dart | 10 +++++---- libs/scrap/src/common/mod.rs | 2 +- src/client.rs | 22 ------------------- src/client/io_loop.rs | 17 -------------- src/flutter.rs | 6 ++--- 7 files changed, 14 insertions(+), 49 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index f881bc84007..2d20d5931c9 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1718,7 +1718,9 @@ class _KeyboardMenu extends StatelessWidget { if (value == null) return; await bind.sessionToggleOption( sessionId: ffi.sessionId, value: kOptionToggleViewOnly); - ffiModel.setViewOnly(id, value); + final viewOnly = await bind.sessionGetToggleOption( + sessionId: ffi.sessionId, arg: kOptionToggleViewOnly); + ffiModel.setViewOnly(id, viewOnly ?? value); } : null, ffi: ffi, diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 853cf8ac073..084ac1b43a6 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -795,7 +795,7 @@ class FfiModel with ChangeNotifier { isRefreshing = false; } Map features = json.decode(evt['features']); - _pi.features.privacyMode = features['privacy_mode'] == 1; + _pi.features.privacyMode = features['privacy_mode'] == true; if (!isCache) { handleResolutions(peerId, evt["resolutions"]); } diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 97cac31c934..26e94ff1c58 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -196,8 +196,8 @@ class RustdeskImpl { required bool on, dynamic hint}) { return Future(() => js.context.callMethod('setByName', [ - 'option:toggle', - jsonEncode({implKey, on}) + 'toggle_privacy_mode', + jsonEncode({'impl_key': implKey, 'on': on}) ])); } @@ -1204,8 +1204,10 @@ class RustdeskImpl { required int index, required bool on, dynamic hint}) { - // TODO - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'toggle_virtual_display', + jsonEncode({'index': index, 'on': on}) + ])); } Future mainSetHomeDir({required String home, dynamic hint}) { diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index 164f7157de3..d6060e1315f 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -316,7 +316,7 @@ impl ToString for CodecFormat { CodecFormat::AV1 => "AV1".into(), CodecFormat::H264 => "H264".into(), CodecFormat::H265 => "H265".into(), - CodecFormat::Unknown => "Unknow".into(), + CodecFormat::Unknown => "Unknown".into(), } } } diff --git a/src/client.rs b/src/client.rs index f1df8d20e48..62a3ca0a870 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1800,28 +1800,6 @@ impl LoginConfigHandler { ) } - pub fn get_option_message_after_login(&self) -> Option { - if self.conn_type.eq(&ConnType::FILE_TRANSFER) - || self.conn_type.eq(&ConnType::PORT_FORWARD) - || self.conn_type.eq(&ConnType::RDP) - { - return None; - } - let mut n = 0; - let mut msg = OptionMessage::new(); - if self.version < hbb_common::get_version_number("1.2.4") { - if self.get_toggle_option("privacy-mode") { - msg.privacy_mode = BoolOption::Yes.into(); - n += 1; - } - } - if n > 0 { - Some(msg) - } else { - None - } - } - /// Parse the image quality option. /// Return [`ImageQuality`] if the option is valid, otherwise return `None`. /// diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 1a209ca0a53..21a5af9a359 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -958,22 +958,6 @@ impl Remote { true } - async fn send_opts_after_login(&self, peer: &mut Stream) { - if let Some(opts) = self - .handler - .lc - .read() - .unwrap() - .get_option_message_after_login() - { - let mut misc = Misc::new(); - misc.set_option(opts); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - allow_err!(peer.send(&msg_out).await); - } - } - async fn send_toggle_virtual_display_msg(&self, peer: &mut Stream) { if !self.peer_info.is_support_virtual_display() { return; @@ -1135,7 +1119,6 @@ impl Remote { self.first_frame = true; self.handler.close_success(); self.handler.adapt_size(); - self.send_opts_after_login(peer).await; self.send_toggle_virtual_display_msg(peer).await; self.send_toggle_privacy_mode_msg(peer).await; } diff --git a/src/flutter.rs b/src/flutter.rs index 03b5a575030..717615753b8 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -802,13 +802,13 @@ impl InvokeUiSession for FlutterHandler { fn set_peer_info(&self, pi: &PeerInfo) { let displays = Self::make_displays_msg(&pi.displays); - let mut features: HashMap<&str, i32> = Default::default(); + let mut features: HashMap<&str, bool> = Default::default(); for ref f in pi.features.iter() { - features.insert("privacy_mode", if f.privacy_mode { 1 } else { 0 }); + features.insert("privacy_mode", f.privacy_mode); } // compatible with 1.1.9 if get_version_number(&pi.version) < get_version_number("1.2.0") { - features.insert("privacy_mode", 0); + features.insert("privacy_mode", false); } let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned()); let resolutions = serialize_resolutions(&pi.resolutions.resolutions); From cbca0eb34070d63c0e89f467e69c59c6a791f5fd Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:01:03 +0800 Subject: [PATCH 061/210] fix: keyboard, move tab to new window (#9322) Do not disable keyboard when moving tab to new window on dispose. Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 341025c5f3c..4ef8157da19 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -245,8 +245,10 @@ class _RemotePageState extends State super.dispose(); debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}"); _ffi.textureModel.onRemotePageDispose(closeSession); - // ensure we leave this session, this is a double check - _ffi.inputModel.enterOrLeave(false); + if (closeSession) { + // ensure we leave this session, this is a double check + _ffi.inputModel.enterOrLeave(false); + } DesktopMultiWindow.removeListener(this); _ffi.dialogManager.hideMobileActionsOverlay(); _ffi.imageModel.disposeImage(); From 2e81bcb4475c1143c52a9561d1d292f464fb49b2 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:17:32 +0800 Subject: [PATCH 062/210] fix: Query online, remove loop retry (#9326) Signed-off-by: fufesou --- src/client.rs | 72 +++++++++++++++++++------------------------------- src/flutter.rs | 35 ++++++++++++------------ 2 files changed, 44 insertions(+), 63 deletions(-) diff --git a/src/client.rs b/src/client.rs index 62a3ca0a870..9e49b84e2e2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3414,7 +3414,6 @@ pub mod peer_online { tcp::FramedStream, ResultType, }; - use std::time::Instant; pub async fn query_online_states, Vec)>(ids: Vec, f: F) { let test = false; @@ -3424,29 +3423,14 @@ pub mod peer_online { let offlines = onlines.drain((onlines.len() / 2)..).collect(); f(onlines, offlines) } else { - let query_begin = Instant::now(); let query_timeout = std::time::Duration::from_millis(3_000); - loop { - match query_online_states_(&ids, query_timeout).await { - Ok((onlines, offlines)) => { - f(onlines, offlines); - break; - } - Err(e) => { - log::debug!("{}", &e); - } + match query_online_states_(&ids, query_timeout).await { + Ok((onlines, offlines)) => { + f(onlines, offlines); } - - if query_begin.elapsed() > query_timeout { - log::debug!( - "query onlines timeout {:?} ({:?})", - query_begin.elapsed(), - query_timeout - ); - break; + Err(e) => { + log::debug!("query onlines, {}", &e); } - - sleep(1.5).await; } } } @@ -3470,8 +3454,6 @@ pub mod peer_online { ids: &Vec, timeout: std::time::Duration, ) -> ResultType<(Vec, Vec)> { - let query_begin = Instant::now(); - let mut msg_out = RendezvousMessage::new(); msg_out.set_online_request(OnlineRequest { id: Config::get_id(), @@ -3479,24 +3461,28 @@ pub mod peer_online { ..Default::default() }); - loop { - let mut socket = match create_online_stream().await { - Ok(s) => s, - Err(e) => { - log::debug!("Failed to create peers online stream, {e}"); - return Ok((vec![], ids.clone())); - } - }; - // TODO: Use long connections to avoid socket creation - // If we use a Arc>> to hold and reuse the previous socket, - // we may face the following error: - // An established connection was aborted by the software in your host machine. (os error 10053) - if let Err(e) = socket.send(&msg_out).await { - log::debug!("Failed to send peers online states query, {e}"); + let mut socket = match create_online_stream().await { + Ok(s) => s, + Err(e) => { + log::debug!("Failed to create peers online stream, {e}"); return Ok((vec![], ids.clone())); } - if let Some(msg_in) = - crate::common::get_next_nonkeyexchange_msg(&mut socket, None).await + }; + // TODO: Use long connections to avoid socket creation + // If we use a Arc>> to hold and reuse the previous socket, + // we may face the following error: + // An established connection was aborted by the software in your host machine. (os error 10053) + if let Err(e) = socket.send(&msg_out).await { + log::debug!("Failed to send peers online states query, {e}"); + return Ok((vec![], ids.clone())); + } + // Retry for 2 times to get the online response + for _ in 0..2 { + if let Some(msg_in) = crate::common::get_next_nonkeyexchange_msg( + &mut socket, + Some(timeout.as_millis() as _), + ) + .await { match msg_in.union { Some(rendezvous_message::Union::OnlineResponse(online_response)) => { @@ -3522,13 +3508,9 @@ pub mod peer_online { // TODO: Make sure socket closed? bail!("Online stream receives None"); } - - if query_begin.elapsed() > timeout { - bail!("Try query onlines timeout {:?}", &timeout); - } - - sleep(300.0).await; } + + bail!("Failed to query online states, no online response"); } #[cfg(test)] diff --git a/src/flutter.rs b/src/flutter.rs index 717615753b8..cbeb3e2c3d3 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -2057,18 +2057,18 @@ pub mod sessions { pub(super) mod async_tasks { use hbb_common::{ bail, - tokio::{ - self, select, - sync::mpsc::{unbounded_channel, UnboundedSender}, - }, + tokio::{self, select}, ResultType, }; use std::{ collections::HashMap, - sync::{Arc, Mutex}, + sync::{ + mpsc::{sync_channel, SyncSender}, + Arc, Mutex, + }, }; - type TxQueryOnlines = UnboundedSender>; + type TxQueryOnlines = SyncSender>; lazy_static::lazy_static! { static ref TX_QUERY_ONLINES: Arc>> = Default::default(); } @@ -2085,20 +2085,18 @@ pub(super) mod async_tasks { #[tokio::main(flavor = "current_thread")] async fn start_flutter_async_runner_() { - let (tx_onlines, mut rx_onlines) = unbounded_channel::>(); + // Only one task is allowed to run at the same time. + let (tx_onlines, rx_onlines) = sync_channel::>(1); TX_QUERY_ONLINES.lock().unwrap().replace(tx_onlines); loop { - select! { - ids = rx_onlines.recv() => { - match ids { - Some(_ids) => { - crate::client::peer_online::query_online_states(_ids, handle_query_onlines).await - } - None => { - break; - } - } + match rx_onlines.recv() { + Ok(ids) => { + crate::client::peer_online::query_online_states(ids, handle_query_onlines).await + } + _ => { + // unreachable! + break; } } } @@ -2106,7 +2104,8 @@ pub(super) mod async_tasks { pub fn query_onlines(ids: Vec) -> ResultType<()> { if let Some(tx) = TX_QUERY_ONLINES.lock().unwrap().as_ref() { - let _ = tx.send(ids)?; + // Ignore if the channel is full. + let _ = tx.try_send(ids)?; } else { bail!("No tx_query_onlines"); } From d2e98cc620dc7553c374372211ffe4c2dcca6001 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:26:01 +0800 Subject: [PATCH 063/210] fix: reduce rebuild for online state (#9331) * fix: reduce rebuild for online state Signed-off-by: fufesou * Query online, update on focus changed Signed-off-by: fufesou * Use to ensure is right Signed-off-by: fufesou * refact, window events on peer view Signed-off-by: fufesou * chore Signed-off-by: fufesou * Remove unused code Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/common/widgets/peers_view.dart | 38 +++++++++++++++++++--- flutter/lib/models/peer_model.dart | 17 +++++++--- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index befc73338ee..a73ef0f0bd2 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -89,6 +89,7 @@ class _PeersViewState extends State<_PeersView> var _lastChangeTime = DateTime.now(); var _lastQueryPeers = {}; var _lastQueryTime = DateTime.now(); + var _lastWindowRestoreTime = DateTime.now(); var _queryCount = 0; var _exit = false; bool _isActive = true; @@ -117,11 +118,37 @@ class _PeersViewState extends State<_PeersView> @override void onWindowFocus() { _queryCount = 0; + _isActive = true; } @override - void onWindowMinimize() { + void onWindowBlur() { + // We need this comparison because window restore (on Windows) also triggers `onWindowBlur()`. + // Maybe it's a bug of the window manager, but the source code seems to be correct. + // + // 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)) { + return; + } _queryCount = _maxQueryCount; + _isActive = false; + } + + @override + void onWindowRestore() { + // Window restore (on MacOS and Linux) also triggers `onWindowFocus()`. + // But on Windows, it triggers `onWindowBlur()`, mybe it's a bug of the window manager. + if (!isWindows) return; + _queryCount = 0; + _isActive = true; + _lastWindowRestoreTime = DateTime.now(); + } + + @override + void onWindowMinimize() { + // Window minimize also triggers `onWindowBlur()`. } // This function is required for mobile. @@ -234,10 +261,11 @@ class _PeersViewState extends State<_PeersView> physics: DraggableNeverScrollableScrollPhysics(), itemCount: peers.length, itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index], false).marginOnly( - right: space, - top: index == 0 ? 0 : space / 2, - bottom: space / 2); + return buildOnePeer(peers[index], false) + .marginOnly( + right: space, + top: index == 0 ? 0 : space / 2, + bottom: space / 2); }), ) : DesktopScrollWrapper( diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index 188dd4e0bdf..7ab5a2b803e 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -194,10 +194,14 @@ class Peers extends ChangeNotifier { } void _updateOnlineState(Map evt) { + int changedCount = 0; evt['onlines'].split(',').forEach((online) { for (var i = 0; i < peers.length; i++) { if (peers[i].id == online) { - peers[i].online = true; + if (!peers[i].online) { + changedCount += 1; + peers[i].online = true; + } } } }); @@ -205,13 +209,18 @@ class Peers extends ChangeNotifier { evt['offlines'].split(',').forEach((offline) { for (var i = 0; i < peers.length; i++) { if (peers[i].id == offline) { - peers[i].online = false; + if (peers[i].online) { + changedCount += 1; + peers[i].online = false; + } } } }); - event = UpdateEvent.online; - notifyListeners(); + if (changedCount > 0) { + event = UpdateEvent.online; + notifyListeners(); + } } void _updatePeers(Map evt) { From cacca7295ca073d134c1e25e99241035c35715c5 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Thu, 12 Sep 2024 14:25:46 +0800 Subject: [PATCH 064/210] fix memory leak on mac because of wrong use of objc, by wrapping autoreleasepool --- src/platform/macos.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 71a1022db44..b3c5546a6fc 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -24,6 +24,7 @@ use hbb_common::{ sysinfo::{Pid, Process, ProcessRefreshKind, System}, }; use include_dir::{include_dir, Dir}; +use objc::rc::autoreleasepool; use objc::{class, msg_send, sel, sel_impl}; use scrap::{libc::c_void, quartz::ffi::*}; use std::path::PathBuf; @@ -60,6 +61,10 @@ pub fn major_version() -> u32 { } pub fn is_process_trusted(prompt: bool) -> bool { + autoreleasepool(|| unsafe_is_process_trusted(prompt)) +} + +fn unsafe_is_process_trusted(prompt: bool) -> bool { unsafe { let value = if prompt { YES } else { NO }; let value: id = msg_send![class!(NSNumber), numberWithBool: value]; @@ -79,10 +84,14 @@ pub fn is_can_input_monitoring(prompt: bool) -> bool { } } +pub fn is_can_screen_recording(prompt: bool) -> bool { + autoreleasepool(|| unsafe_is_can_screen_recording(prompt)) +} + // macOS >= 10.15 // https://stackoverflow.com/questions/56597221/detecting-screen-recording-settings-on-macos-catalina/ // remove just one app from all the permissions: tccutil reset All com.carriez.rustdesk -pub fn is_can_screen_recording(prompt: bool) -> bool { +fn unsafe_is_can_screen_recording(prompt: bool) -> bool { // we got some report that we show no permission even after set it, so we try to use new api for screen recording check // the new api is only available on macOS >= 10.15, but on stackoverflow, some people said it works on >= 10.16 (crash on 10.15), // but also some said it has bug on 10.16, so we just use it on 11.0. @@ -297,6 +306,10 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> { } pub fn get_focused_display(displays: Vec) -> Option { + autoreleasepool(|| unsafe_get_focused_display(displays)) +} + +fn unsafe_get_focused_display(displays: Vec) -> Option { unsafe { let main_screen: id = msg_send![class!(NSScreen), mainScreen]; let screen: id = msg_send![main_screen, deviceDescription]; @@ -311,6 +324,10 @@ pub fn get_focused_display(displays: Vec) -> Option { } pub fn get_cursor() -> ResultType> { + autoreleasepool(|| unsafe_get_cursor()) +} + +fn unsafe_get_cursor() -> ResultType> { unsafe { let seed = CGSCurrentCursorSeed(); if seed == LATEST_SEED { @@ -375,8 +392,12 @@ fn get_cursor_id() -> ResultType<(id, u64)> { } } -// https://github.com/stweil/OSXvnc/blob/master/OSXvnc-server/mousecursor.c pub fn get_cursor_data(hcursor: u64) -> ResultType { + autoreleasepool(|| unsafe_get_cursor_data(hcursor)) +} + +// https://github.com/stweil/OSXvnc/blob/master/OSXvnc-server/mousecursor.c +fn unsafe_get_cursor_data(hcursor: u64) -> ResultType { unsafe { let (c, hcursor2) = get_cursor_id()?; if hcursor != hcursor2 { From c358399eca055bfdc80e34f554c1d930684c519c Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:44:40 +0800 Subject: [PATCH 065/210] refact: reduce try_get_displays() on login (#9333) * refact: reduce try_get_displays() on login Signed-off-by: fufesou * Function rename Signed-off-by: fufesou --------- Signed-off-by: fufesou --- src/server/connection.rs | 34 +++++++++++++--------------------- src/server/display_service.rs | 8 ++++++-- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index 3a2b0a22e91..7181478b3d0 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1279,29 +1279,9 @@ impl Connection { self.send(msg_out).await; } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - #[cfg(not(windows))] - let displays = display_service::try_get_displays(); - #[cfg(windows)] - let displays = display_service::try_get_displays_add_amyuni_headless(); - pi.resolutions = Some(SupportedResolutions { - resolutions: displays - .map(|displays| { - displays - .get(self.display_idx) - .map(|d| crate::platform::resolutions(&d.name())) - .unwrap_or(vec![]) - }) - .unwrap_or(vec![]), - ..Default::default() - }) - .into(); - } - try_activate_screen(); - match super::display_service::update_get_sync_displays().await { + match super::display_service::update_get_sync_displays_on_login().await { Err(err) => { res.set_error(format!("{}", err)); } @@ -1314,6 +1294,18 @@ impl Connection { } pi.displays = displays; pi.current_display = self.display_idx as _; + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + pi.resolutions = Some(SupportedResolutions { + resolutions: pi + .displays + .get(self.display_idx) + .map(|d| crate::platform::resolutions(&d.name)) + .unwrap_or(vec![]), + ..Default::default() + }) + .into(); + } res.set_peer_info(pi); sub_service = true; diff --git a/src/server/display_service.rs b/src/server/display_service.rs index e099e25a096..98b42a5face 100644 --- a/src/server/display_service.rs +++ b/src/server/display_service.rs @@ -344,14 +344,18 @@ pub fn is_inited_msg() -> Option { None } -pub async fn update_get_sync_displays() -> ResultType> { +pub async fn update_get_sync_displays_on_login() -> ResultType> { #[cfg(target_os = "linux")] { if !is_x11() { return super::wayland::get_displays().await; } } - check_update_displays(&try_get_displays()?); + #[cfg(not(windows))] + let displays = display_service::try_get_displays(); + #[cfg(windows)] + let displays = display_service::try_get_displays_add_amyuni_headless(); + check_update_displays(&displays?); Ok(SYNC_DISPLAYS.lock().unwrap().displays.clone()) } From 0b3e7bf33e5651c075870309ad7d3bb1816a5163 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 12 Sep 2024 17:34:54 +0800 Subject: [PATCH 066/210] update hwcodec, fix linux ci (#9335) Signed-off-by: 21pages --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b2bd08d436a..3e793ddb791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3045,7 +3045,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hwcodec" version = "0.7.0" -source = "git+https://github.com/rustdesk-org/hwcodec#9e8b6efd8e5d904b5325597a271ebe78f5a74f3b" +source = "git+https://github.com/rustdesk-org/hwcodec#f74410edec91435252b8394c38f8eeca87ad2a26" dependencies = [ "bindgen 0.59.2", "cc", From a8f1a66043cd98e44bb8df34dcb1eb6d77b2844b Mon Sep 17 00:00:00 2001 From: m-hume Date: Fri, 13 Sep 2024 01:06:40 +0100 Subject: [PATCH 067/210] Trim whitespace from Import server config (#9341) --- flutter/lib/common/widgets/setting_widgets.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/common/widgets/setting_widgets.dart b/flutter/lib/common/widgets/setting_widgets.dart index 5bcb73a4c5e..b76bd3f55c0 100644 --- a/flutter/lib/common/widgets/setting_widgets.dart +++ b/flutter/lib/common/widgets/setting_widgets.dart @@ -184,7 +184,7 @@ List ServerConfigImportExportWidgets( ) { import() { Clipboard.getData(Clipboard.kTextPlain).then((value) { - importConfig(controllers, errMsgs, value?.text); + importConfig(controllers, errMsgs, value?.text.trim()); }); } From 9f9a22ec63c22e0d82418415c91a614f2f7c0235 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 13 Sep 2024 08:46:21 +0800 Subject: [PATCH 068/210] uppercase for all --- flutter/lib/common/widgets/custom_password.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flutter/lib/common/widgets/custom_password.dart b/flutter/lib/common/widgets/custom_password.dart index 99ece2434bf..1fa6e3645d9 100644 --- a/flutter/lib/common/widgets/custom_password.dart +++ b/flutter/lib/common/widgets/custom_password.dart @@ -14,7 +14,11 @@ class UppercaseValidationRule extends ValidationRule { String get name => translate('uppercase'); @override bool validate(String value) { - return value.contains(RegExp(r'[A-Z]')); + return value.runes.any((int rune) { + var character = String.fromCharCode(rune); + return character.toUpperCase() == character && + character.toLowerCase() != character; + }); } } From d65d3b7326b6d428a1e8fc6be2b6d0dd9a883151 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 13 Sep 2024 09:21:50 +0800 Subject: [PATCH 069/210] fix ci --- flutter/lib/common.dart | 1 + flutter/lib/common/widgets/setting_widgets.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index efcff4f83f4..7b6ec4ae5fc 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -3154,6 +3154,7 @@ class _ReconnectCountDownButtonState extends State<_ReconnectCountDownButton> { importConfig(List? controllers, List? errMsgs, String? text) { + text = text?.trim(); if (text != null && text.isNotEmpty) { try { final sc = ServerConfig.decode(text); diff --git a/flutter/lib/common/widgets/setting_widgets.dart b/flutter/lib/common/widgets/setting_widgets.dart index b76bd3f55c0..5bcb73a4c5e 100644 --- a/flutter/lib/common/widgets/setting_widgets.dart +++ b/flutter/lib/common/widgets/setting_widgets.dart @@ -184,7 +184,7 @@ List ServerConfigImportExportWidgets( ) { import() { Clipboard.getData(Clipboard.kTextPlain).then((value) { - importConfig(controllers, errMsgs, value?.text.trim()); + importConfig(controllers, errMsgs, value?.text); }); } From ab246fdcbf877dc84456af921680b9925cbd3ff1 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 13 Sep 2024 09:29:00 +0800 Subject: [PATCH 070/210] password lowercase check like uppercase (#9343) Signed-off-by: 21pages --- flutter/lib/common/widgets/custom_password.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flutter/lib/common/widgets/custom_password.dart b/flutter/lib/common/widgets/custom_password.dart index 1fa6e3645d9..dafc23b448b 100644 --- a/flutter/lib/common/widgets/custom_password.dart +++ b/flutter/lib/common/widgets/custom_password.dart @@ -28,7 +28,11 @@ class LowercaseValidationRule extends ValidationRule { @override bool validate(String value) { - return value.contains(RegExp(r'[a-z]')); + return value.runes.any((int rune) { + var character = String.fromCharCode(rune); + return character.toLowerCase() == character && + character.toUpperCase() != character; + }); } } From 179b562472a1edf9d68e97a2ed11926c8779b881 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 13 Sep 2024 15:41:29 +0800 Subject: [PATCH 071/210] another leak --- src/platform/windows.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/windows.cc b/src/platform/windows.cc index 7ed76d2e4be..9d284aea2d4 100644 --- a/src/platform/windows.cc +++ b/src/platform/windows.cc @@ -549,6 +549,7 @@ extern "C" continue; if (!stricmp(info.pWinStationName, "console")) { + WTSFreeMemory(pInfos); return info.SessionId; } if (!strnicmp(info.pWinStationName, rdp, nrdp)) From 2e7bd26e4c30ccb04eb13575af429dc11ac30302 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 13 Sep 2024 15:42:51 +0800 Subject: [PATCH 072/210] fix leak fix --- src/platform/windows.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/windows.cc b/src/platform/windows.cc index 9d284aea2d4..9ee3c1f5c9a 100644 --- a/src/platform/windows.cc +++ b/src/platform/windows.cc @@ -549,8 +549,9 @@ extern "C" continue; if (!stricmp(info.pWinStationName, "console")) { + auto id = info.SessionId; WTSFreeMemory(pInfos); - return info.SessionId; + return id; } if (!strnicmp(info.pWinStationName, rdp, nrdp)) { From 81fc22a1568b35b5c1f355f57face8a96af6c6d8 Mon Sep 17 00:00:00 2001 From: Alex Rijckaert Date: Fri, 13 Sep 2024 15:04:04 +0200 Subject: [PATCH 073/210] Update nl.rs (#9344) * Update nl.rs * Update nl.rs file updated after adjusting (as test) @FastAct to RijckAlex (same person, new account) --- src/lang/nl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 9ae7d0839f9..78b36b2e33b 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -31,8 +31,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("ID/Relay Server", "ID/Relay Server"), ("Import server config", "Importeer Serverconfiguratie"), ("Export Server Config", "Exporteer Serverconfiguratie"), - ("Import server configuration successfully", "Importeren serverconfiguratie succesvol"), - ("Export server configuration successfully", "Exporteren serverconfiguratie succesvol"), + ("Import server configuration successfully", "Importeren serverconfiguratie is geslaagd"), + ("Export server configuration successfully", "Exporteren serverconfiguratie is geslaagd"), ("Invalid server configuration", "Ongeldige Serverconfiguratie"), ("Clipboard is empty", "Klembord is leeg"), ("Stop service", "Stop service"), @@ -80,7 +80,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Connection in progress. Please wait.", "Verbinding in uitvoering. Even geduld a.u.b."), ("Please try 1 minute later", "Probeer 1 minuut later"), ("Login Error", "Login Fout"), - ("Successful", "Succesvol"), + ("Successful", "Geslaagd"), ("Connected, waiting for image...", "Verbonden, wacht op beeld..."), ("Name", "Naam"), ("Type", "Type"), From 40af9dc78beb26ea9444859de4676ff974b34dab Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 14 Sep 2024 09:59:14 +0800 Subject: [PATCH 074/210] not run window focus service on wayland (#9354) Signed-off-by: 21pages --- libs/scrap/src/x11/server.rs | 14 ++++++++++---- src/server.rs | 5 ++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/libs/scrap/src/x11/server.rs b/libs/scrap/src/x11/server.rs index e2ffdc74b4f..f9983f7cf2a 100644 --- a/libs/scrap/src/x11/server.rs +++ b/libs/scrap/src/x11/server.rs @@ -1,3 +1,4 @@ +use hbb_common::libc; use std::ptr; use std::rc::Rc; @@ -99,11 +100,16 @@ unsafe fn check_x11_shm_available(c: *mut xcb_connection_t) -> Result<(), Error> if reply.is_null() { // TODO: Should seperate SHM disabled from SHM not supported? return Err(Error::UnsupportedExtension); - } else if e.is_null() { - return Ok(()); } else { - // TODO: Does "This request does never generate any errors" in manual means `e` is never set, so we would never reach here? - return Err(Error::Generic); + // https://github.com/FFmpeg/FFmpeg/blob/6229e4ac425b4566446edefb67d5c225eb397b58/libavdevice/xcbgrab.c#L229 + libc::free(reply as *mut _); + if e.is_null() { + return Ok(()); + } else { + libc::free(e as *mut _); + // TODO: Does "This request does never generate any errors" in manual means `e` is never set, so we would never reach here? + return Err(Error::Generic); + } } } diff --git a/src/server.rs b/src/server.rs index a973ba6aef9..74bda41ce05 100644 --- a/src/server.rs +++ b/src/server.rs @@ -106,7 +106,10 @@ pub fn new() -> ServerPtr { if !display_service::capture_cursor_embedded() { server.add_service(Box::new(input_service::new_cursor())); server.add_service(Box::new(input_service::new_pos())); - server.add_service(Box::new(input_service::new_window_focus())); + if scrap::is_x11() { + // wayland does not support multiple displays currently + server.add_service(Box::new(input_service::new_window_focus())); + } } } Arc::new(RwLock::new(server)) From d9ea717056c14cd0b37239d36b1102840d70d8f2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 14 Sep 2024 10:03:50 +0800 Subject: [PATCH 075/210] fix last commit (#9355) Signed-off-by: 21pages --- src/server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server.rs b/src/server.rs index 74bda41ce05..02522db9684 100644 --- a/src/server.rs +++ b/src/server.rs @@ -106,6 +106,7 @@ pub fn new() -> ServerPtr { if !display_service::capture_cursor_embedded() { server.add_service(Box::new(input_service::new_cursor())); server.add_service(Box::new(input_service::new_pos())); + #[cfg(target_os = "linux")] if scrap::is_x11() { // wayland does not support multiple displays currently server.add_service(Box::new(input_service::new_window_focus())); From f4c038ea933dc4394c71dbe1014ae999f71d0641 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 15 Sep 2024 14:33:59 +0800 Subject: [PATCH 076/210] update appindicator and recommends install it (#9364) Signed-off-by: 21pages --- .github/workflows/flutter-build.yml | 8 ++++---- .github/workflows/playground.yml | 2 +- build.py | 3 ++- res/rpm-flutter-suse.spec | 3 ++- res/rpm-flutter.spec | 3 ++- res/rpm-suse.spec | 3 ++- res/rpm.spec | 3 ++- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index d7a36b4005a..b5f8707874e 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -884,7 +884,7 @@ jobs: git \ g++ \ g++-multilib \ - libappindicator3-dev \ + libayatana-appindicator3-dev \ libasound2-dev \ libc6-dev \ libclang-10-dev \ @@ -1147,7 +1147,7 @@ jobs: git \ g++ \ g++-multilib \ - libappindicator3-dev \ + libayatana-appindicator3-dev \ libasound2-dev \ libc6-dev \ libclang-10-dev \ @@ -1424,7 +1424,7 @@ jobs: gcc \ git \ g++ \ - libappindicator3-dev \ + libayatana-appindicator3-dev \ libasound2-dev \ libclang-10-dev \ libgstreamer1.0-dev \ @@ -1681,7 +1681,7 @@ jobs: gcc \ git \ g++ \ - libappindicator3-dev \ + libayatana-appindicator3-dev \ libasound2-dev \ libclang-dev \ libdbus-1-dev \ diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index fb7b8961450..205ce8f1ed5 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -262,7 +262,7 @@ jobs: git \ g++ \ g++-multilib \ - libappindicator3-dev \ + libayatana-appindicator3-dev\ libasound2-dev \ libc6-dev \ libclang-10-dev \ diff --git a/build.py b/build.py index 389ee33b6cd..be13207ffea 100755 --- a/build.py +++ b/build.py @@ -287,7 +287,8 @@ def generate_control_file(version): Architecture: %s Maintainer: rustdesk Homepage: https://rustdesk.com -Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g, libappindicator3-1, gstreamer1.0-pipewire%s +Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s +Recommends: libayatana-appindicator3-1 Description: A remote control software. """ % (version, get_deb_arch(), get_deb_extra_depends()) diff --git a/res/rpm-flutter-suse.spec b/res/rpm-flutter-suse.spec index 2995c54d435..c2c5be1f188 100644 --- a/res/rpm-flutter-suse.spec +++ b/res/rpm-flutter-suse.spec @@ -3,7 +3,8 @@ Version: 1.3.1 Release: 0 Summary: RPM package License: GPL-3.0 -Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libappindicator-gtk3 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire +Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire +Recommends: libayatana-appindicator3-1 Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit) %description diff --git a/res/rpm-flutter.spec b/res/rpm-flutter.spec index 48a7e2ac000..33a1314ccca 100644 --- a/res/rpm-flutter.spec +++ b/res/rpm-flutter.spec @@ -3,7 +3,8 @@ Version: 1.3.1 Release: 0 Summary: RPM package License: GPL-3.0 -Requires: gtk3 libxcb libxdo libXfixes alsa-lib libappindicator-gtk3 libvdpau libva pam gstreamer1-plugins-base +Requires: gtk3 libxcb libxdo libXfixes alsa-lib libvdpau libva pam gstreamer1-plugins-base +Recommends: libayatana-appindicator-gtk3 Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit) %description diff --git a/res/rpm-suse.spec b/res/rpm-suse.spec index d84e1481238..1d6a94b131b 100644 --- a/res/rpm-suse.spec +++ b/res/rpm-suse.spec @@ -3,7 +3,8 @@ Version: 1.1.9 Release: 0 Summary: RPM package License: GPL-3.0 -Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libayatana-appindicator3-1 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire +Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire +Recommends: libayatana-appindicator3-1 %description The best open-source remote desktop client software, written in Rust. diff --git a/res/rpm.spec b/res/rpm.spec index 8c99f9bb0b4..11dccc84209 100644 --- a/res/rpm.spec +++ b/res/rpm.spec @@ -3,7 +3,8 @@ Version: 1.3.1 Release: 0 Summary: RPM package License: GPL-3.0 -Requires: gtk3 libxcb libxdo libXfixes alsa-lib libappindicator libvdpau1 libva2 pam gstreamer1-plugins-base +Requires: gtk3 libxcb libxdo libXfixes alsa-lib libvdpau1 libva2 pam gstreamer1-plugins-base +Recommends: libayatana-appindicator-gtk3 %description The best open-source remote desktop client software, written in Rust. From c5038b1a78fe6d0ef6f4ef46aae180896859fe7f Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:57:40 +0800 Subject: [PATCH 077/210] Fix/virtual display do not plug out if not plugged in (#9372) * fix: win VD, do not plug out if not plugged in Signed-off-by: fufesou * Forcibly virtual display on clicking button "-" Signed-off-by: fufesou --------- Signed-off-by: fufesou --- src/privacy_mode/win_virtual_display.rs | 11 ++-- src/server/connection.rs | 2 +- src/virtual_display_manager.rs | 86 ++++++++++++++++++++----- 3 files changed, 78 insertions(+), 21 deletions(-) diff --git a/src/privacy_mode/win_virtual_display.rs b/src/privacy_mode/win_virtual_display.rs index 25997f03671..782d7ed75a8 100644 --- a/src/privacy_mode/win_virtual_display.rs +++ b/src/privacy_mode/win_virtual_display.rs @@ -150,8 +150,11 @@ impl PrivacyModeImpl { } fn restore_plug_out_monitor(&mut self) { - let _ = - virtual_display_manager::plug_out_monitor_indices(&self.virtual_displays_added, true); + let _ = virtual_display_manager::plug_out_monitor_indices( + &self.virtual_displays_added, + true, + false, + ); self.virtual_displays_added.clear(); } @@ -312,7 +315,7 @@ impl PrivacyModeImpl { // No physical displays, no need to use the privacy mode. if self.displays.is_empty() { - virtual_display_manager::plug_out_monitor_indices(&displays, false)?; + virtual_display_manager::plug_out_monitor_indices(&displays, false, false)?; bail!(NO_PHYSICAL_DISPLAYS); } @@ -509,7 +512,7 @@ pub fn restore_reg_connectivity(plug_out_monitors: bool) { return; } if plug_out_monitors { - let _ = virtual_display_manager::plug_out_monitor(-1, true); + let _ = virtual_display_manager::plug_out_monitor(-1, true, false); } if let Ok(reg_recovery) = serde_json::from_str::(&config_recovery_value) diff --git a/src/server/connection.rs b/src/server/connection.rs index 7181478b3d0..e351b0d5006 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -2713,7 +2713,7 @@ impl Connection { } } } else { - if let Err(e) = virtual_display_manager::plug_out_monitor(t.display, false) { + if let Err(e) = virtual_display_manager::plug_out_monitor(t.display, false, true) { log::error!("Failed to plug out virtual display {}: {}", t.display, e); self.send(make_msg(format!( "Failed to plug out virtual displays: {}", diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs index 138087c75e8..41e5b3fc83c 100644 --- a/src/virtual_display_manager.rs +++ b/src/virtual_display_manager.rs @@ -8,6 +8,7 @@ pub const AMYUNI_IDD_DEVICE_STRING: &'static str = "USB Mobile Monitor Virtual D const IDD_IMPL: &str = IDD_IMPL_AMYUNI; const IDD_IMPL_RUSTDESK: &str = "rustdesk_idd"; const IDD_IMPL_AMYUNI: &str = "amyuni_idd"; +const IDD_PLUG_OUT_ALL_INDEX: i32 = -1; pub fn is_amyuni_idd() -> bool { IDD_IMPL == IDD_IMPL_AMYUNI @@ -76,17 +77,17 @@ pub fn plug_in_monitor(idx: u32, modes: Vec) -> Re } } -pub fn plug_out_monitor(index: i32, force_all: bool) -> ResultType<()> { +pub fn plug_out_monitor(index: i32, force_all: bool, force_one: bool) -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => { - let indices = if index == -1 { + let indices = if index == IDD_PLUG_OUT_ALL_INDEX { rustdesk_idd::get_virtual_displays() } else { vec![index as _] }; rustdesk_idd::plug_out_peer_request(&indices) } - IDD_IMPL_AMYUNI => amyuni_idd::plug_out_monitor(index, force_all), + IDD_IMPL_AMYUNI => amyuni_idd::plug_out_monitor(index, force_all, force_one), _ => bail!("Unsupported virtual display implementation."), } } @@ -102,12 +103,16 @@ pub fn plug_in_peer_request(modes: Vec>) -> Re } } -pub fn plug_out_monitor_indices(indices: &[u32], force_all: bool) -> ResultType<()> { +pub fn plug_out_monitor_indices( + indices: &[u32], + force_all: bool, + force_one: bool, +) -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::plug_out_peer_request(indices), IDD_IMPL_AMYUNI => { for _idx in indices.iter() { - amyuni_idd::plug_out_monitor(0, force_all)?; + amyuni_idd::plug_out_monitor(0, force_all, force_one)?; } Ok(()) } @@ -382,7 +387,7 @@ pub mod amyuni_idd { use hbb_common::{bail, lazy_static, log, tokio::time::Instant, ResultType}; use std::{ ptr::null_mut, - sync::{Arc, Mutex}, + sync::{atomic, Arc, Mutex}, time::Duration, }; use winapi::{ @@ -405,6 +410,14 @@ pub mod amyuni_idd { static ref LOCK: Arc> = Default::default(); static ref LAST_PLUG_IN_HEADLESS_TIME: Arc>> = Arc::new(Mutex::new(None)); } + const VIRTUAL_DISPLAY_MAX_COUNT: usize = 4; + // The count of virtual displays plugged in. + // This count is not accurate, because: + // 1. The virtual display driver may also be controlled by other processes. + // 2. RustDesk may crash and restart, but the virtual displays are kept. + // + // to-do: Maybe a better way is to add an option asking the user if plug out all virtual displays on disconnect. + static VIRTUAL_DISPLAY_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0); fn get_deviceinstaller64_work_dir() -> ResultType>> { let cur_exe = std::env::current_exe()?; @@ -510,7 +523,7 @@ pub mod amyuni_idd { pub fn reset_all() -> ResultType<()> { let _ = crate::privacy_mode::turn_off_privacy(0, None); - let _ = plug_out_monitor(-1, true); + let _ = plug_out_monitor(super::IDD_PLUG_OUT_ALL_INDEX, true, false); *LAST_PLUG_IN_HEADLESS_TIME.lock().unwrap() = None; Ok(()) } @@ -522,6 +535,18 @@ pub mod amyuni_idd { unsafe { win_device::device_io_control(&INTERFACE_GUID, PLUG_MONITOR_IO_CONTROL_CDOE, &cmd, 0)?; } + // No need to consider concurrency here. + if add { + // If the monitor is plugged in, increase the count. + // Though there's already a check of `VIRTUAL_DISPLAY_MAX_COUNT`, it's still better to check here for double ensure. + if VIRTUAL_DISPLAY_COUNT.load(atomic::Ordering::SeqCst) < VIRTUAL_DISPLAY_MAX_COUNT { + VIRTUAL_DISPLAY_COUNT.fetch_add(1, atomic::Ordering::SeqCst); + } + } else { + if VIRTUAL_DISPLAY_COUNT.load(atomic::Ordering::SeqCst) > 0 { + VIRTUAL_DISPLAY_COUNT.fetch_sub(1, atomic::Ordering::SeqCst); + } + } Ok(()) } @@ -607,44 +632,73 @@ pub mod amyuni_idd { bail!("Failed to install driver."); } - if get_monitor_count() == 4 { - bail!("There are already 4 monitors plugged in."); + if get_monitor_count() == VIRTUAL_DISPLAY_MAX_COUNT { + bail!("There are already {VIRTUAL_DISPLAY_MAX_COUNT} monitors plugged in."); } plug_in_monitor_(true, is_async) } - pub fn plug_out_monitor(index: i32, force_all: bool) -> ResultType<()> { - let all_count = windows::get_device_names(None).len(); + // `index` the display index to plug out. -1 means plug out all. + // `force_all` is used to forcibly plug out all virtual displays. + // `force_one` is used to forcibly plug out one virtual display managed by other processes + // if there're no virtual displays managed by RustDesk. + pub fn plug_out_monitor(index: i32, force_all: bool, force_one: bool) -> ResultType<()> { + let plug_out_all = index == super::IDD_PLUG_OUT_ALL_INDEX; + // If `plug_out_all and force_all` is true, forcibly plug out all virtual displays. + // Though the driver may be controlled by other processes, + // we still forcibly plug out all virtual displays. + // + // 1. RustDesk plug in 2 virtual displays. (RustDesk) + // 2. Other process plug out all virtual displays. (User mannually) + // 3. Other process plug in 1 virtual display. (User mannually) + // 4. RustDesk plug out all virtual displays in this call. (RustDesk disconnect) + // + // This is not a normal scenario, RustDesk will plug out virtual display unexpectedly. + let mut plug_in_count = VIRTUAL_DISPLAY_COUNT.load(atomic::Ordering::Relaxed); let amyuni_count = get_monitor_count(); + if !plug_out_all { + if plug_in_count == 0 && amyuni_count > 0 { + if force_one { + plug_in_count = 1; + } else { + bail!("The virtual display is managed by other processes."); + } + } + } else { + // Ignore the message if trying to plug out all virtual displays. + } + + let all_count = windows::get_device_names(None).len(); let mut to_plug_out_count = match all_count { 0 => return Ok(()), 1 => { - if amyuni_count == 0 { + if plug_in_count == 0 { bail!("No virtual displays to plug out.") } else { if force_all { 1 } else { - bail!("This only virtual display cannot be pulled out.") + bail!("This only virtual display cannot be plugged out.") } } } _ => { - if all_count == amyuni_count { + if all_count == plug_in_count { if force_all { all_count } else { all_count - 1 } } else { - amyuni_count + plug_in_count } } }; - if to_plug_out_count != 0 && index != -1 { + if to_plug_out_count != 0 && !plug_out_all { to_plug_out_count = 1; } + for _i in 0..to_plug_out_count { let _ = plug_monitor_(false); } From 8a8f708c3e7bce90860fb68a12e72c422eecab39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E9=A4=85=E3=81=AECreeeper?= <56744841+creeper-0910@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:32:32 +0900 Subject: [PATCH 078/210] update ja.rs (#9376) --- src/lang/ja.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lang/ja.rs b/src/lang/ja.rs index ffb93e379f7..46a7e9271e5 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -630,19 +630,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("cancel-2fa-confirm-tip", "本当に二要素認証をキャンセルしますか?"), ("cancel-bot-confirm-tip", "本当にTelegram Botをキャンセルしますか?"), ("About RustDesk", "RustDeskについて"), - ("Send clipboard keystrokes", ""), - ("network_error_tip", ""), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), + ("Send clipboard keystrokes", "クリップボードの内容をキー入力として送信する"), + ("network_error_tip", "ネットワーク接続を確認し、再度お試しください。"), + ("Unlock with PIN", "PINでロック解除"), + ("Requires at least {} characters", "最低でも{}文字必要です"), + ("Wrong PIN", "PINが間違っています"), + ("Set PIN", "PINを設定"), + ("Enable trusted devices", "承認済デバイスを有効化"), + ("Manage trusted devices", "承認済デバイスの管理"), + ("Platform", "プラットフォーム"), + ("Days remaining", "残り日数"), + ("enable-trusted-devices-tip", "承認済デバイスで2FAチェックをスキップします。"), + ("Parent directory", "親ディレクトリ"), + ("Resume", "再開"), + ("Invalid file name", "無効なファイル名"), ].iter().cloned().collect(); } From 29c3b29bda5b6c6c2d682d25ae36e943c32dfed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E9=A4=85=E3=81=AECreeeper?= <56744841+creeper-0910@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:26:49 +0900 Subject: [PATCH 079/210] Fix ja.rs typo (#9378) --- src/lang/ja.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 46a7e9271e5..9d0f6ea0511 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -19,7 +19,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recent sessions", "最近のセッション"), ("Address book", "アドレス帳"), ("Confirmation", "確認"), - ("TCP tunneling", "TXPトンネリング"), + ("TCP tunneling", "TCPトンネリング"), ("Remove", "削除"), ("Refresh random password", "ランダムパスワードを再生成"), ("Set your own password", "パスワードを設定"), From 49ce4edb8a44ca40d5d89d757ed4c7b42073dccd Mon Sep 17 00:00:00 2001 From: Lumiphare <58658634+Lumiphare@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:39:26 +0800 Subject: [PATCH 080/210] Chinese versions of CONTRIBUTING.md and CODE_OF_CONDUCT-ZH.md (#9386) * Update CONTRIBUTING.md links to point to the Chinese version * translated with AI assistance and manual refinement * Adapted from the official Chinese translation of the Contributor Covenant --------- Co-authored-by: sea --- docs/CODE_OF_CONDUCT-ZH.md | 87 ++++++++++++++++++++++++++++++++++++++ docs/CONTRIBUTING-ZH.md | 32 ++++++++++++++ docs/README-ZH.md | 2 +- 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 docs/CODE_OF_CONDUCT-ZH.md create mode 100644 docs/CONTRIBUTING-ZH.md diff --git a/docs/CODE_OF_CONDUCT-ZH.md b/docs/CODE_OF_CONDUCT-ZH.md new file mode 100644 index 00000000000..0877ab20f24 --- /dev/null +++ b/docs/CODE_OF_CONDUCT-ZH.md @@ -0,0 +1,87 @@ + +# 贡献者公约行为准则 + +## 我们的承诺 + +身为社区成员、贡献者和领袖,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。 + +我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。 + +## 我们的标准 + +有助于为我们的社区创造积极环境的行为例子包括但不限于: + +* 表现出对他人的同情和善意 +* 尊重不同的主张、观点和感受 +* 提出和大方接受建设性意见 +* 承担责任并向受我们错误影响的人道歉 +* 注重社区共同诉求,而非个人得失 + +不当行为例子包括: + +* 使用情色化的语言或图像,及性引诱或挑逗 +* 嘲弄、侮辱或诋毁性评论,以及人身或政治攻击 +* 公开或私下的骚扰行为 +* 未经他人明确许可,公布他人的私人信息,如物理或电子邮件地址 +* 其他有理由认定为违反职业操守的不当行为 + +## 责任和权力 + +社区领袖有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。 + +社区领导有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论(comment)、提交(commits)、代码、维基(wiki)编辑、议题(issues)或其他贡献,并在适当时机知采取措施的理由。 + +## 适用范围 + +本行为准则适用于所有社区场合,也适用于在公共场所代表社区时的个人。 + +代表社区的情形包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在线上或线下活动中担任指定代表。 + +## 监督 + +辱骂、骚扰或其他不可接受的行为可通过[info@rustdesk.com](mailto:info@rustdesk.com)向负责监督的社区领袖报告。 所有投诉都将得到及时和公平的审查和调查。 + +所有社区领袖都有义务尊重任何事件报告者的隐私和安全。 + +## 处理方针 + +社区领袖将遵循下列社区处理方针来明确他们所认定违反本行为准则的行为的处理方式: + +### 1. 纠正 + +**社区影响**: 使用不恰当的语言或其他在社区中被认定为不符合职业道德或不受欢迎的行为。 + +**处理意见**: 由社区领袖发出非公开的书面警告,明确说明违规行为的性质,并解释举止如何不妥。或将要求公开道歉。 + +### 2. 警告 + +**社区影响**: 单个或一系列违规行为。 + +**处理意见**: 警告并对连续性行为进行处理。在指定时间内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区场所和外部渠道中的互动。违反这些条款可能会导致临时或永久封禁。 + +### 3. 临时封禁 + +**社区影响**: 严重违反社区准则,包括持续的不当行为。 + +**处理意见**: 在指定时间内,暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与行为准则执行者互动。违反这些条款可能会导致永久封禁。 + +### 4. 永久封禁 + +**社区影响**: 行为模式表现出违反社区准则,包括持续的不当行为、骚扰个人或攻击或贬低某个类别的个体。 + +**处理意见**: 永久禁止在社区内进行任何形式的公开互动。 + +## 参见 + +本行为准则改编自[参与者公约][homepage]2.0 版, 参见 +[https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html][v2.0]. + +指导方针借鉴自[Mozilla纪检分级][Mozilla CoC]. + +有关本行为准则的常见问题的答案,参见 [https://www.contributor-covenant.org/faq][FAQ]。 其他语言翻译参见[https://www.contributor-covenant.org/translations][translations]。 + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/docs/CONTRIBUTING-ZH.md b/docs/CONTRIBUTING-ZH.md new file mode 100644 index 00000000000..718cdac69bb --- /dev/null +++ b/docs/CONTRIBUTING-ZH.md @@ -0,0 +1,32 @@ +# 为RustDesk做贡献 + +Rust欢迎每一位贡献者,如果您有意向为我们做出贡献,请遵循以下指南: + +## 贡献方式 + +对 RustDesk 或其依赖项的贡献需要通过 GitHub 的 Pull Request (PR) 的形式提交。每个 PR 都会由核心贡献者(即有权限合并代码的人)进行审核,审核通过后代码会合并到主分支,或者您会收到需要修改的反馈。所有贡献者,包括核心贡献者,提交的代码都应遵循此流程。 + +如果您希望处理某个问题,请先在对应的 GitHub issue 下发表评论,声明您将处理该问题,以避免该问题被多位贡献者重复处理。 + +## PR 注意事项 + +- 从 master 分支创建一个新的分支,并在提交PR之前,如果需要,将您的分支 变基(rebase) 到最新的 master 分支。如果您的分支无法顺利合并到 master 分支,您可能会被要求更新您的代码。 + +- 每次提交的改动应该尽可能少,并且要保证每次提交的代码都是正确的(即每个 commit 都应能成功编译并通过测试)。 + +- 每个提交都应附有开发者证书签名(http://developercertificate.org), 表明您(以及您的雇主,若适用)同意遵守项目[许可证条款](../LICENCE)。在使用 git 提交代码时,可以通过在 `git commit` 时使用 `-s` 选项加入签名 + +- 如果您的 PR 未被及时审核,或需要指定的人员进行审核,您可以通过在 PR 或评论中 @ 提到相关审核者,以及发送[电子邮件](mailto:info@rustdesk.com)的方式请求审核。 + +- 请为修复的 bug 或新增的功能添加相应的测试用例。 + +有关具体的 git 使用说明,请参考[GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). + +## 行为准则 + +请遵守项目的[贡献者公约行为准则](./CODE_OF_CONDUCT-ZH.md)。 + + +## 沟通渠道 + +RustDesk 的贡献者主要通过 [Discord](https://discord.gg/nDceKgxnkV) 进行交流。 diff --git a/docs/README-ZH.md b/docs/README-ZH.md index 54b9c29a141..0460384ab42 100644 --- a/docs/README-ZH.md +++ b/docs/README-ZH.md @@ -18,7 +18,7 @@ Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https: ![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) -RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING.md](CONTRIBUTING.md). +RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING-ZH.md](CONTRIBUTING-ZH.md). [**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) From cc288272d3334d133e47a0c1696cf42de6d2bf25 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 18 Sep 2024 12:18:26 +0800 Subject: [PATCH 081/210] OPTION_ONE_WAY_CLIPBOARD_REDIRECTION, OPTION_ENABLE_CLIPBOARD_INIT_SYNC, OPTION_ALLOW_LOGON_SCREEN_PASSWORD, OPTION_ONE_WAY_FILE_TRANSFER, --- libs/hbb_common/src/config.rs | 8 +++++++ libs/hbb_common/src/platform/mod.rs | 6 +++++ src/client/io_loop.rs | 23 +++++++++++------- src/common.rs | 10 ++++++++ src/server/connection.rs | 37 +++++++++++++++++++++++------ src/ui_cm_interface.rs | 2 +- src/ui_interface.rs | 7 +----- 7 files changed, 70 insertions(+), 23 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index c118070dd85..0e91ecf42c3 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -2218,6 +2218,10 @@ pub mod keys { pub const OPTION_HIDE_HELP_CARDS: &str = "hide-help-cards"; pub const OPTION_DEFAULT_CONNECT_PASSWORD: &str = "default-connect-password"; pub const OPTION_HIDE_TRAY: &str = "hide-tray"; + pub const OPTION_ONE_WAY_CLIPBOARD_REDIRECTION: &str = "one-way-clipboard-redirection"; + pub const OPTION_ENABLE_CLIPBOARD_INIT_SYNC: &str = "enable-clipboard-init-sync"; + pub const OPTION_ALLOW_LOGON_SCREEN_PASSWORD: &str = "allow-logon-screen-password"; + pub const OPTION_ONE_WAY_FILE_TRANSFER: &str = "one-way-file-transfer"; // flutter local options pub const OPTION_FLUTTER_REMOTE_MENUBAR_STATE: &str = "remoteMenubarState"; @@ -2362,6 +2366,10 @@ pub mod keys { OPTION_HIDE_HELP_CARDS, OPTION_DEFAULT_CONNECT_PASSWORD, OPTION_HIDE_TRAY, + OPTION_ONE_WAY_CLIPBOARD_REDIRECTION, + OPTION_ENABLE_CLIPBOARD_INIT_SYNC, + OPTION_ALLOW_LOGON_SCREEN_PASSWORD, + OPTION_ONE_WAY_FILE_TRANSFER, ]; } diff --git a/libs/hbb_common/src/platform/mod.rs b/libs/hbb_common/src/platform/mod.rs index 5dc004a81b7..d01333558a6 100644 --- a/libs/hbb_common/src/platform/mod.rs +++ b/libs/hbb_common/src/platform/mod.rs @@ -79,3 +79,9 @@ where libc::signal(libc::SIGSEGV, breakdown_signal_handler as _); } } + +#[cfg(any(target_os = "android", target_os = "ios"))] +#[inline] +fn is_prelogin() -> bool { + false +} diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 21a5af9a359..46eb6f54613 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -26,7 +26,7 @@ use crossbeam_queue::ArrayQueue; use hbb_common::tokio::sync::mpsc::error::TryRecvError; use hbb_common::{ allow_err, - config::{PeerConfig, TransferSerde}, + config::{self, PeerConfig, TransferSerde}, fs::{ self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, RemoveJobMeta, @@ -1201,13 +1201,18 @@ impl Remote { &peer_platform, crate::clipboard::ClipboardSide::Client, ) { - let sender = self.sender.clone(); - let permission_config = self.handler.get_permission_config(); - tokio::spawn(async move { - if permission_config.is_text_clipboard_required() { - sender.send(Data::Message(msg_out)).ok(); - } - }); + if crate::get_builtin_option( + config::keys::OPTION_ENABLE_CLIPBOARD_INIT_SYNC, + ) != "N" + { + let sender = self.sender.clone(); + let permission_config = self.handler.get_permission_config(); + tokio::spawn(async move { + if permission_config.is_text_clipboard_required() { + sender.send(Data::Message(msg_out)).ok(); + } + }); + } } // on connection established client @@ -1618,7 +1623,7 @@ impl Remote { }, Some(message::Union::MessageBox(msgbox)) => { let mut link = msgbox.link; - if let Some(v) = hbb_common::config::HELPER_URL.get(&link as &str) { + if let Some(v) = config::HELPER_URL.get(&link as &str) { link = v.to_string(); } else { log::warn!("Message box ignore link {} for security", &link); diff --git a/src/common.rs b/src/common.rs index 2e801e66d2f..9950850708f 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1650,3 +1650,13 @@ mod tests { ); } } + +#[inline] +pub fn get_builtin_option(key: &str) -> String { + config::BUILTIN_SETTINGS + .read() + .unwrap() + .get(key) + .cloned() + .unwrap_or_default() +} diff --git a/src/server/connection.rs b/src/server/connection.rs index e351b0d5006..2863c2365db 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -27,7 +27,7 @@ use hbb_common::platform::linux::run_cmds; #[cfg(target_os = "android")] use hbb_common::protobuf::EnumOrUnknown; use hbb_common::{ - config::{self, Config, TrustedDevice}, + config::{self, keys, Config, TrustedDevice}, fs::{self, can_enable_overwrite_detection}, futures::{SinkExt, StreamExt}, get_time, get_version_number, @@ -335,7 +335,7 @@ impl Connection { clipboard: Connection::permission("enable-clipboard"), audio: Connection::permission("enable-audio"), // to-do: make sure is the option correct here - file: Connection::permission(config::keys::OPTION_ENABLE_FILE_TRANSFER), + file: Connection::permission(keys::OPTION_ENABLE_FILE_TRANSFER), restart: Connection::permission("enable-remote-restart"), recording: Connection::permission("enable-record-session"), block_input: Connection::permission("enable-block-input"), @@ -1359,7 +1359,10 @@ impl Connection { if !self.follow_remote_window { noperms.push(NAME_WINDOW_FOCUS); } - if !self.clipboard_enabled() || !self.peer_keyboard_enabled() { + if !self.clipboard_enabled() + || !self.peer_keyboard_enabled() + || crate::get_builtin_option(keys::OPTION_ONE_WAY_CLIPBOARD_REDIRECTION) == "Y" + { noperms.push(super::clipboard_service::NAME); } if !self.audio_enabled() { @@ -1621,8 +1624,8 @@ impl Connection { #[inline] fn enable_trusted_devices() -> bool { config::option2bool( - config::keys::OPTION_ENABLE_TRUSTED_DEVICES, - &Config::get_option(config::keys::OPTION_ENABLE_TRUSTED_DEVICES), + keys::OPTION_ENABLE_TRUSTED_DEVICES, + &Config::get_option(keys::OPTION_ENABLE_TRUSTED_DEVICES), ) } @@ -1689,7 +1692,7 @@ impl Connection { } match lr.union { Some(login_request::Union::FileTransfer(ft)) => { - if !Connection::permission(config::keys::OPTION_ENABLE_FILE_TRANSFER) { + if !Connection::permission(keys::OPTION_ENABLE_FILE_TRANSFER) { self.send_login_error("No permission of file transfer") .await; sleep(1.).await; @@ -1762,7 +1765,9 @@ impl Connection { self.send_login_error(crate::client::LOGIN_MSG_OFFLINE) .await; return false; - } else if password::approve_mode() == ApproveMode::Click + } else if (password::approve_mode() == ApproveMode::Click + && !(crate::platform::is_prelogin() + && crate::get_builtin_option(keys::OPTION_ALLOW_LOGON_SCREEN_PASSWORD) == "Y")) || password::approve_mode() == ApproveMode::Both && !password::has_valid_password() { self.try_start_cm(lr.my_id, lr.my_name, false); @@ -2133,6 +2138,24 @@ impl Connection { } return true; } + if crate::get_builtin_option(keys::OPTION_ONE_WAY_FILE_TRANSFER) == "Y" { + match fa.union { + Some(file_action::Union::Send(_)) + | Some(file_action::Union::RemoveFile(_)) + | Some(file_action::Union::Rename(_)) + | Some(file_action::Union::Create(_)) + | Some(file_action::Union::RemoveDir(_)) => { + self.send(fs::new_error( + 0, + "One-way file transfer is enabled on controlled side", + 0, + )) + .await; + return true; + } + _ => {} + } + } match fa.union { Some(file_action::Union::ReadDir(rd)) => { self.read_dir(&rd.path, rd.include_hidden); diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 49f91a9dab3..e75683d8a30 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -565,7 +565,7 @@ impl IpcTaskRunner { log::debug!( "Process clipboard message from clip, stop: {}, is_stopping_allowed: {}, is_clipboard_enabled: {}, file_transfer_enabled: {}, file_transfer_enabled_peer: {}", stop, is_stopping_allowed, is_clipboard_enabled, file_transfer_enabled, file_transfer_enabled_peer); - if stop { + if stop || crate::get_builtin_option(OPTION_ONE_WAY_FILE_TRANSFER) == "Y"{ ContextSend::set_is_stopped(); } else { allow_err!(self.tx.send(Data::ClipboardFile(_clip))); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index ad2db6d01db..e9f2875be5d 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -207,12 +207,7 @@ pub fn get_hard_option(key: String) -> String { #[inline] pub fn get_builtin_option(key: &str) -> String { - config::BUILTIN_SETTINGS - .read() - .unwrap() - .get(key) - .cloned() - .unwrap_or_default() + crate::get_builtin_option(key) } #[inline] From e1a6ccc10053c4c1914893a0d689a712d8891cae Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 18 Sep 2024 12:37:26 +0800 Subject: [PATCH 082/210] fix ci --- libs/hbb_common/src/platform/mod.rs | 6 ------ src/platform/mod.rs | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/hbb_common/src/platform/mod.rs b/libs/hbb_common/src/platform/mod.rs index d01333558a6..5dc004a81b7 100644 --- a/libs/hbb_common/src/platform/mod.rs +++ b/libs/hbb_common/src/platform/mod.rs @@ -79,9 +79,3 @@ where libc::signal(libc::SIGSEGV, breakdown_signal_handler as _); } } - -#[cfg(any(target_os = "android", target_os = "ios"))] -#[inline] -fn is_prelogin() -> bool { - false -} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 7bb503fdd5c..7b4f9c78e67 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -128,6 +128,12 @@ impl Drop for InstallingService { } } +#[cfg(any(target_os = "android", target_os = "ios"))] +#[inline] +fn is_prelogin() -> bool { + false +} + #[cfg(test)] mod tests { use super::*; From e20f5dd001f3e7fbc9e932ea227cdb8e94d80c15 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 18 Sep 2024 13:00:15 +0800 Subject: [PATCH 083/210] fix ci --- src/platform/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 7b4f9c78e67..169bdb199f4 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -130,7 +130,7 @@ impl Drop for InstallingService { #[cfg(any(target_os = "android", target_os = "ios"))] #[inline] -fn is_prelogin() -> bool { +pub fn is_prelogin() -> bool { false } From e5ec6957fe86fe83902e6745d30b0b321b38e190 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:22:12 +0800 Subject: [PATCH 084/210] fix: option OPTION_ONE_WAY_FILE_TRANSFER (#9387) Signed-off-by: fufesou --- libs/clipboard/src/lib.rs | 2 +- src/client/io_loop.rs | 2 +- src/ui_cm_interface.rs | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/libs/clipboard/src/lib.rs b/libs/clipboard/src/lib.rs index a5da25512d2..30055740ed8 100644 --- a/libs/clipboard/src/lib.rs +++ b/libs/clipboard/src/lib.rs @@ -132,7 +132,7 @@ impl ClipboardFile { ) } - pub fn is_stopping_allowed_from_peer(&self) -> bool { + pub fn is_beginning_message(&self) -> bool { matches!( self, ClipboardFile::MonitorReady | ClipboardFile::FormatList { .. } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 46eb6f54613..c23c967b580 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1895,7 +1895,7 @@ impl Remote { return; }; - let is_stopping_allowed = clip.is_stopping_allowed_from_peer(); + let is_stopping_allowed = clip.is_beginning_message(); let file_transfer_enabled = self.handler.lc.read().unwrap().enable_file_copy_paste.v; let stop = is_stopping_allowed && !file_transfer_enabled; log::debug!( diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index e75683d8a30..549798a5172 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -440,7 +440,7 @@ impl IpcTaskRunner { Data::ClipboardFile(_clip) => { #[cfg(any(target_os = "windows", target_os="linux", target_os = "macos"))] { - let is_stopping_allowed = _clip.is_stopping_allowed_from_peer(); + let is_stopping_allowed = _clip.is_beginning_message(); let is_clipboard_enabled = ContextSend::is_enabled(); let file_transfer_enabled = self.file_transfer_enabled; let stop = !is_stopping_allowed && !(is_clipboard_enabled && file_transfer_enabled); @@ -565,10 +565,15 @@ impl IpcTaskRunner { log::debug!( "Process clipboard message from clip, stop: {}, is_stopping_allowed: {}, is_clipboard_enabled: {}, file_transfer_enabled: {}, file_transfer_enabled_peer: {}", stop, is_stopping_allowed, is_clipboard_enabled, file_transfer_enabled, file_transfer_enabled_peer); - if stop || crate::get_builtin_option(OPTION_ONE_WAY_FILE_TRANSFER) == "Y"{ + if stop { ContextSend::set_is_stopped(); } else { - allow_err!(self.tx.send(Data::ClipboardFile(_clip))); + if _clip.is_beginning_message() && crate::get_builtin_option(OPTION_ONE_WAY_FILE_TRANSFER) == "Y" { + // If one way file transfer is enabled, don't send clipboard file to client + // Don't call `ContextSend::set_is_stopped()`, because it will stop bidirectional file copy&paste. + } else { + allow_err!(self.tx.send(Data::ClipboardFile(_clip))); + } } } } From d08c335fdf341b335419baf45b941aaccd73a805 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:29:35 +0800 Subject: [PATCH 085/210] fix: file transfer, show error, msgbox (#9389) * fix: file transfer, show error, msgbox Signed-off-by: fufesou * fix: translation Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/models/file_model.dart | 21 +++++++++++++++-- src/lang/ar.rs | 1 + src/lang/be.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/en.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/eu.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/uk.rs | 1 + src/lang/vn.rs | 1 + src/server/connection.rs | 38 ++++++++++++++++++++---------- 46 files changed, 88 insertions(+), 15 deletions(-) diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 68af293804e..a0d5bc0b558 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -34,6 +34,7 @@ class JobID { } typedef GetSessionID = SessionID Function(); +typedef GetDialogManager = OverlayDialogManager? Function(); class FileModel { final WeakReference parent; @@ -45,13 +46,15 @@ class FileModel { late final FileController remoteController; late final GetSessionID getSessionID; + late final GetDialogManager getDialogManager; SessionID get sessionId => getSessionID(); late final FileDialogEventLoop evtLoop; FileModel(this.parent) { getSessionID = () => parent.target!.sessionId; + getDialogManager = () => parent.target?.dialogManager; fileFetcher = FileFetcher(getSessionID); - jobController = JobController(getSessionID); + jobController = JobController(getSessionID, getDialogManager); localController = FileController( isLocal: true, getSessionID: getSessionID, @@ -736,14 +739,19 @@ class FileController { } } +const _kOneWayFileTransferError = 'one-way-file-transfer-tip'; + class JobController { static final JobID jobID = JobID(); final jobTable = List.empty(growable: true).obs; final jobResultListener = JobResultListener>(); final GetSessionID getSessionID; + final GetDialogManager getDialogManager; SessionID get sessionId => getSessionID(); + OverlayDialogManager? get alogManager => getDialogManager(); + int _lastTimeShowMsgbox = DateTime.now().millisecondsSinceEpoch; - JobController(this.getSessionID); + JobController(this.getSessionID, this.getDialogManager); int getJob(int id) { return jobTable.indexWhere((element) => element.id == id); @@ -882,6 +890,15 @@ class JobController { } jobTable.refresh(); } + if (err == _kOneWayFileTransferError) { + if (DateTime.now().millisecondsSinceEpoch - _lastTimeShowMsgbox > 3000) { + final dm = alogManager; + if (dm != null) { + _lastTimeShowMsgbox = DateTime.now().millisecondsSinceEpoch; + msgBox(sessionId, 'custom-nocancel', 'Error', err, '', dm); + } + } + } debugPrint("jobError $evt"); } diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 68c041481e5..a157d3d9d82 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index 136ccf9feec..4fbfff131c1 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 7efdd0dfa74..2992911a814 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", "Възобновяване"), ("Invalid file name", "Невалидно име за файл"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 0aa64ec28e6..260b0b4c6bf 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 62cb5452c60..dc986aaca8d 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "父目录"), ("Resume", "继续"), ("Invalid file name", "无效文件名"), + ("one-way-file-transfer-tip", "被控端启用了单项文件传输"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 8ff3d806918..c3a1b975877 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Rodičovský adresář"), ("Resume", "Pokračovat"), ("Invalid file name", "Nesprávný název souboru"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 03cf47d4bb8..be02a7360ac 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "mappe"), ("Resume", "Fortsæt"), ("Invalid file name", "Ugyldigt filnavn"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index e8e61543a48..3069ebe3c95 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Übergeordnetes Verzeichnis"), ("Resume", "Fortsetzen"), ("Invalid file name", "Ungültiger Dateiname"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index b3ce7dcaf80..e05aa9fb89e 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 917422d0dc4..55ef4470c55 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -234,5 +234,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("About RustDesk", ""), ("network_error_tip", "Please check your network connection, then click retry."), ("enable-trusted-devices-tip", "Skip 2FA verification on trusted devices"), + ("one-way-file-transfer-tip", "One-way file transfer is enabled on the controlled side."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 144bf7bc3c4..d8cb8ec3404 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 794c751c00f..c087b287ca4 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -644,5 +644,6 @@ 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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index cc3f3afc392..24901265f5c 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 412b4e74060..9213c9689eb 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index b6949aa5043..47988a95d92 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 31d78640888..688bcf25f7c 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 07d9aa977d1..7fc4758e149 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index 1dca1c7e0c0..4671a0d47f9 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index be089347aca..2ceb6bce4ce 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 42c36b5c744..168c78117f7 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 7c18b52d2a1..08c00ee1ffb 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -644,5 +644,6 @@ 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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 9d0f6ea0511..e58ff25f78a 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "親ディレクトリ"), ("Resume", "再開"), ("Invalid file name", "無効なファイル名"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 3e84ca262f5..daf85e63224 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index c47764c8190..85829e84a27 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index b1d0317f89d..7387d34e323 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index bb1090dca9c..0d8b193876b 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -644,5 +644,6 @@ 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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index d23191ef2b7..f3c4ed410d6 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 78b36b2e33b..c6d7bcbd361 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -644,5 +644,6 @@ 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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 725d67996a6..59e4cbad2a1 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Folder nadrzędny"), ("Resume", "Wznów"), ("Invalid file name", "Nieprawidłowa nazwa pliku"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 6afa4c528eb..d172cb4f911 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index a5ba6de7b0f..6a5300028f4 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Diretório pai"), ("Resume", "Continuar"), ("Invalid file name", "Nome de arquivo inválido"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 0f11e544953..7b3ba2b2881 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index b13c4a1b663..59a49959584 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Родительская директория"), ("Resume", "Продолжить"), ("Invalid file name", "Неверное имя файла"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 51d09032834..7b467842371 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Rodičovský adresár"), ("Resume", "Obnoviť"), ("Invalid file name", "Nesprávny názov súboru"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 7c8d9749486..637859cd401 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 0a73e021744..fe9a30c0cc7 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 4d3654ea998..906dc0d3f2a 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 9d7956545bc..e877aca0cde 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 76e491c91ce..7b08bf3a3bd 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index acd14c8f9c5..1b818f479e7 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index f926e94549a..26085f53931 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 9bbef24311d..690792dbb5f 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "父目錄"), ("Resume", "繼續"), ("Invalid file name", "無效文件名"), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 6a177059cfa..ccc73b7b049 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 88f70a8e2c4..408060a09c3 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -644,5 +644,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", ""), ("Resume", ""), ("Invalid file name", ""), + ("one-way-file-transfer-tip", ""), ].iter().cloned().collect(); } diff --git a/src/server/connection.rs b/src/server/connection.rs index 2863c2365db..dbe8b961431 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -2139,22 +2139,34 @@ impl Connection { return true; } if crate::get_builtin_option(keys::OPTION_ONE_WAY_FILE_TRANSFER) == "Y" { - match fa.union { - Some(file_action::Union::Send(_)) - | Some(file_action::Union::RemoveFile(_)) - | Some(file_action::Union::Rename(_)) - | Some(file_action::Union::Create(_)) - | Some(file_action::Union::RemoveDir(_)) => { - self.send(fs::new_error( - 0, - "One-way file transfer is enabled on controlled side", - 0, - )) - .await; - return true; + let mut job_id = None; + match &fa.union { + Some(file_action::Union::Send(s)) => { + job_id = Some(s.id); + } + Some(file_action::Union::RemoveFile(rf)) => { + job_id = Some(rf.id); + } + Some(file_action::Union::Rename(r)) => { + job_id = Some(r.id); + } + Some(file_action::Union::Create(c)) => { + job_id = Some(c.id); + } + Some(file_action::Union::RemoveDir(rd)) => { + job_id = Some(rd.id); } _ => {} } + if let Some(job_id) = job_id { + self.send(fs::new_error( + job_id, + "one-way-file-transfer-tip", + 0, + )) + .await; + return true; + } } match fa.union { Some(file_action::Union::ReadDir(rd)) => { From 88a99211f31e07c6fb1ff6a3fadb73d2aa421569 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 19 Sep 2024 18:47:37 +0800 Subject: [PATCH 086/210] replace pkexec with gtk sudo (#9383) * Fix https://github.com/rustdesk/rustdesk/issues/9286, replace pkexec with gtk sudo. Tested on gnome (ubuntu 22.04, debian 13), xfce (manjaro, suse), kde (kubuntu 23), lxqt (lubuntu 22), Cinnamon (mint 21.3), Mate (mint 21.2) * Fix incorrect config of the main window opened by the tray, replace xdg-open with run_me, replace with dbus + run_me * Fix `check_if_stop_service`, it causes the problem fixed in https://github.com/rustdesk/rustdesk/pull/8414, now revert that fix and fix itself. Signed-off-by: 21pages --- Cargo.lock | 23 +- Cargo.toml | 3 + build.py | 4 - res/com.rustdesk.RustDesk.policy | 23 - src/core_main.rs | 7 + src/lang/ar.rs | 2 + src/lang/be.rs | 2 + src/lang/bg.rs | 2 + src/lang/ca.rs | 2 + src/lang/cn.rs | 2 + src/lang/cs.rs | 2 + src/lang/da.rs | 2 + src/lang/de.rs | 2 + src/lang/el.rs | 2 + src/lang/eo.rs | 2 + src/lang/es.rs | 2 + src/lang/et.rs | 2 + src/lang/eu.rs | 2 + src/lang/fa.rs | 2 + src/lang/fr.rs | 2 + src/lang/he.rs | 2 + src/lang/hr.rs | 2 + src/lang/hu.rs | 2 + src/lang/id.rs | 2 + src/lang/it.rs | 2 + src/lang/ja.rs | 2 + src/lang/ko.rs | 2 + src/lang/kz.rs | 2 + src/lang/lt.rs | 2 + src/lang/lv.rs | 2 + src/lang/nb.rs | 2 + src/lang/nl.rs | 2 + src/lang/pl.rs | 2 + src/lang/pt_PT.rs | 2 + src/lang/ptbr.rs | 2 + src/lang/ro.rs | 2 + src/lang/ru.rs | 2 + src/lang/sk.rs | 2 + src/lang/sl.rs | 2 + src/lang/sq.rs | 2 + src/lang/sr.rs | 2 + src/lang/sv.rs | 2 + src/lang/template.rs | 2 + src/lang/th.rs | 2 + src/lang/tr.rs | 2 + src/lang/tw.rs | 2 + src/lang/uk.rs | 2 + src/lang/vn.rs | 2 + src/platform/gtk_sudo.rs | 774 +++++++++++++++++++++++++++++++ src/platform/linux.rs | 58 +-- src/platform/mod.rs | 3 + src/tray.rs | 9 +- 52 files changed, 916 insertions(+), 74 deletions(-) delete mode 100644 res/com.rustdesk.RustDesk.policy create mode 100644 src/platform/gtk_sudo.rs diff --git a/Cargo.lock b/Cargo.lock index 3e793ddb791..486b7a6f9be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -860,6 +860,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -3967,11 +3973,23 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", "memoffset 0.9.1", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "cfg_aliases 0.2.1", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -5494,6 +5512,7 @@ dependencies = [ "flutter_rust_bridge", "fon", "fruitbasket", + "gtk", "hbb_common", "hex", "hound", @@ -5508,6 +5527,7 @@ dependencies = [ "libpulse-simple-binding", "mac_address", "magnum-opus", + "nix 0.29.0", "num_cpus", "objc", "objc_id", @@ -5539,6 +5559,7 @@ dependencies = [ "system_shutdown", "tao", "tauri-winrt-notification", + "termios", "totp-rs", "tray-icon", "url", diff --git a/Cargo.toml b/Cargo.toml index 28c2c363d46..c71b2918bd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,6 +161,9 @@ x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/ x11rb = {version = "0.12", features = ["all-extensions"], optional = true} percent-encoding = {version = "2.3", optional = true} once_cell = {version = "1.18", optional = true} +nix = { version = "0.29", features = ["term", "process"]} +gtk = "0.18" +termios = "0.3" [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.13" diff --git a/build.py b/build.py index be13207ffea..3ad206ab189 100755 --- a/build.py +++ b/build.py @@ -331,8 +331,6 @@ def build_flutter_deb(version, features): 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') system2( 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') - system2( - 'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/') system2( 'cp ../res/startwm.sh tmpdeb/etc/rustdesk/') system2( @@ -376,8 +374,6 @@ def build_deb_from_folder(version, binary_folder): 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') system2( 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') - system2( - 'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/') system2( "echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") diff --git a/res/com.rustdesk.RustDesk.policy b/res/com.rustdesk.RustDesk.policy deleted file mode 100644 index 55f13629b7f..00000000000 --- a/res/com.rustdesk.RustDesk.policy +++ /dev/null @@ -1,23 +0,0 @@ - - - - RustDesk - https://rustdesk.com/ - rustdesk - - Change RustDesk options - Authentication is required to change RustDesk options - 要更改RustDesk选项, 需要您先通过身份验证 - 要變更RustDesk選項, 需要您先通過身份驗證 - Authentifizierung zum Ändern der RustDesk-Optionen - /usr/share/rustdesk/files/polkit - true - - auth_admin - auth_admin - auth_admin - - - diff --git a/src/core_main.rs b/src/core_main.rs index 5d137516ee4..23d7706d473 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -482,6 +482,13 @@ pub fn core_main() -> Option> { crate::flutter::connection_manager::start_cm_no_ui(); } return None; + } else if args[0] == "-gtk-sudo" { + // rustdesk service kill `rustdesk --` processes + #[cfg(target_os = "linux")] + if args.len() > 2 { + crate::platform::gtk_sudo::exec(); + } + return None; } else { #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] diff --git a/src/lang/ar.rs b/src/lang/ar.rs index a157d3d9d82..be1a6b7675a 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index 4fbfff131c1..fb8444becb5 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 2992911a814..b683a5293c4 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 260b0b4c6bf..a52da9735fd 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index dc986aaca8d..887a7297114 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -645,5 +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", "认证"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index c3a1b975877..67588bfb848 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Pokračovat"), ("Invalid file name", "Nesprávný název souboru"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index be02a7360ac..aea1514ae93 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Fortsæt"), ("Invalid file name", "Ugyldigt filnavn"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 3069ebe3c95..38720f53797 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Fortsetzen"), ("Invalid file name", "Ungültiger Dateiname"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index e05aa9fb89e..fecff28945f 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index d8cb8ec3404..eb512922ed3 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index c087b287ca4..e42a5abed84 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Continuar"), ("Invalid file name", "Nombre de archivo no válido"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 24901265f5c..2443faae9b0 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 9213c9689eb..7c953ebe46e 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 47988a95d92..a97566d99a1 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 688bcf25f7c..6a9ca8d8de2 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 7fc4758e149..5a42c4257c7 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index 4671a0d47f9..9a2b8e3a1a0 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 2ceb6bce4ce..fb748b6b80d 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 168c78117f7..9ed642459c1 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 08c00ee1ffb..613dcea94c3 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Riprendi"), ("Invalid file name", "Nome file non valido"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index e58ff25f78a..a63687c68f7 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index daf85e63224..417073d05e1 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 85829e84a27..a23d3444830 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 7387d34e323..b818cb7e76e 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 0d8b193876b..a89590bf4f1 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Atsākt"), ("Invalid file name", "Nederīgs faila nosaukums"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index f3c4ed410d6..eb2e21b683c 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index c6d7bcbd361..5b2eee79e92 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Hervatten"), ("Invalid file name", "Ongeldige bestandsnaam"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 59e4cbad2a1..99bf570848f 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Wznów"), ("Invalid file name", "Nieprawidłowa nazwa pliku"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index d172cb4f911..534efe12e68 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 6a5300028f4..4396dd40de3 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Continuar"), ("Invalid file name", "Nome de arquivo inválido"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 7b3ba2b2881..ea1d9888014 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 59a49959584..f7db94bd298 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 7b467842371..e7b17c6aea8 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Obnoviť"), ("Invalid file name", "Nesprávny názov súboru"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 637859cd401..acbaf2ea9db 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index fe9a30c0cc7..aca5b6a7ace 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 906dc0d3f2a..c12555c97d2 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index e877aca0cde..837f702f889 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 7b08bf3a3bd..43fbbcfa509 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 1b818f479e7..1c5b0933377 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 26085f53931..10fa46880f4 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 690792dbb5f..812d5f8616e 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index ccc73b7b049..aa34887be37 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 408060a09c3..4f7bcf7d164 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -645,5 +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", ""), ].iter().cloned().collect(); } diff --git a/src/platform/gtk_sudo.rs b/src/platform/gtk_sudo.rs new file mode 100644 index 00000000000..a3727134e35 --- /dev/null +++ b/src/platform/gtk_sudo.rs @@ -0,0 +1,774 @@ +// https://github.com/aarnt/qt-sudo +// Sometimes reboot is needed to refresh sudoers. + +use crate::lang::translate; +use gtk::{glib, prelude::*}; +use hbb_common::{ + anyhow::{bail, Error}, + log, ResultType, +}; +use nix::{ + libc::{fcntl, kill}, + pty::{forkpty, ForkptyResult}, + sys::{ + signal::Signal, + wait::{waitpid, WaitPidFlag}, + }, + unistd::{execvp, setsid, Pid}, +}; +use std::{ + ffi::CString, + fs::File, + io::{Read, Write}, + os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, Mutex, + }, +}; + +const EXIT_CODE: i32 = -1; + +enum Message { + PasswordPrompt(String), + Password((String, String)), + ErrorDialog(String), + Cancel, + Exit(i32), +} + +pub fn run(cmds: Vec<&str>) -> ResultType<()> { + // rustdesk service kill `rustdesk --` processes + let second_arg = std::env::args().nth(1).unwrap_or_default(); + let cmd_mode = + second_arg.starts_with("--") && second_arg != "--tray" && second_arg != "--no-server"; + let mod_arg = if cmd_mode { "cmd" } else { "gui" }; + let mut args = vec!["-gtk-sudo", mod_arg]; + args.append(&mut cmds.clone()); + let mut child = crate::run_me(args)?; + let exit_status = child.wait()?; + if exit_status.success() { + Ok(()) + } else { + bail!("child exited with status: {:?}", exit_status); + } +} + +pub fn exec() { + let mut args = vec![]; + for arg in std::env::args().skip(3) { + args.push(arg); + } + let cmd_mode = std::env::args().nth(2) == Some("cmd".to_string()); + if cmd_mode { + cmd(args); + } else { + ui(args); + } +} + +fn cmd(args: Vec) { + match unsafe { forkpty(None, None) } { + Ok(forkpty_result) => match forkpty_result { + ForkptyResult::Parent { child, master } => { + if let Err(e) = cmd_parent(child, master) { + log::error!("Parent error: {:?}", e); + kill_child(child); + std::process::exit(EXIT_CODE); + } + } + ForkptyResult::Child => { + if let Err(e) = child(None, args) { + log::error!("Child error: {:?}", e); + std::process::exit(EXIT_CODE); + } + } + }, + Err(err) => { + log::error!("forkpty error: {:?}", err); + std::process::exit(EXIT_CODE); + } + } +} + +fn ui(args: Vec) { + // https://docs.gtk.org/gtk4/ctor.Application.new.html + // https://docs.gtk.org/gio/type_func.Application.id_is_valid.html + let application = gtk::Application::new(None, Default::default()); + + let (tx_to_ui, rx_to_ui) = channel::(); + let (tx_from_ui, rx_from_ui) = channel::(); + + let rx_to_ui = Arc::new(Mutex::new(rx_to_ui)); + let tx_from_ui = Arc::new(Mutex::new(tx_from_ui)); + + let rx_to_ui_clone = rx_to_ui.clone(); + let tx_from_ui_clone = tx_from_ui.clone(); + + let username = Arc::new(Mutex::new(crate::platform::get_active_username())); + let username_clone = username.clone(); + let first_prompt = Arc::new(Mutex::new(true)); + + application.connect_activate(glib::clone!(@weak application =>move |_| { + let rx_to_ui = rx_to_ui_clone.clone(); + let tx_from_ui = tx_from_ui_clone.clone(); + let last_password = Arc::new(Mutex::new(String::new())); + let username = username_clone.clone(); + let first_prompt = first_prompt.clone(); + + glib::timeout_add_local(std::time::Duration::from_millis(50), move || { + if let Ok(msg) = rx_to_ui.lock().unwrap().try_recv() { + match msg { + Message::PasswordPrompt(err_msg) => { + let last_pwd = last_password.lock().unwrap().clone(); + let username = username.lock().unwrap().clone(); + let first = first_prompt.lock().unwrap().clone(); + *first_prompt.lock().unwrap() = false; + if let Some((username, password)) = password_prompt(&username, &last_pwd, &err_msg, first) { + *last_password.lock().unwrap() = password.clone(); + if let Err(e) = tx_from_ui + .lock() + .unwrap() + .send(Message::Password((username, password))) { + error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE); + } + } else { + if let Err(e) = tx_from_ui.lock().unwrap().send(Message::Cancel) { + error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE); + } + } + } + Message::ErrorDialog(err_msg) => { + error_dialog_and_exit(&err_msg, EXIT_CODE); + } + Message::Exit(code) => { + log::info!("Exit code: {}", code); + std::process::exit(code); + } + _ => {} + } + } + glib::ControlFlow::Continue + }); + })); + + let tx_to_ui_clone = tx_to_ui.clone(); + std::thread::spawn(move || { + let acitve_user = crate::platform::get_active_username(); + let mut initial_password = None; + if acitve_user != "root" { + if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt("".to_string())) { + log::error!("Channel error: {e:?}"); + std::process::exit(EXIT_CODE); + } + match rx_from_ui.recv() { + Ok(Message::Password((user, password))) => { + *username.lock().unwrap() = user; + initial_password = Some(password); + } + Ok(Message::Cancel) => { + log::info!("User canceled"); + std::process::exit(EXIT_CODE); + } + _ => { + log::error!("Unexpected message"); + std::process::exit(EXIT_CODE); + } + } + } + let username = username.lock().unwrap().clone(); + let su_user = if username == acitve_user { + None + } else { + Some(username) + }; + match unsafe { forkpty(None, None) } { + Ok(forkpty_result) => match forkpty_result { + ForkptyResult::Parent { child, master } => { + if let Err(e) = ui_parent( + child, + master, + tx_to_ui_clone, + rx_from_ui, + su_user.is_some(), + initial_password, + ) { + log::error!("Parent error: {:?}", e); + kill_child(child); + std::process::exit(EXIT_CODE); + } + } + ForkptyResult::Child => { + if let Err(e) = child(su_user, args) { + log::error!("Child error: {:?}", e); + std::process::exit(EXIT_CODE); + } + } + }, + Err(err) => { + log::error!("forkpty error: {:?}", err); + if let Err(e) = + tx_to_ui.send(Message::ErrorDialog(format!("Forkpty error: {:?}", err))) + { + log::error!("Channel error: {e:?}"); + std::process::exit(EXIT_CODE); + } + } + } + }); + + let _holder = application.hold(); + let args: Vec<&str> = vec![]; + application.run_with_args(&args); + log::debug!("exit from gtk::Application::run_with_args"); + std::process::exit(EXIT_CODE); +} + +fn cmd_parent(child: Pid, master: OwnedFd) -> ResultType<()> { + let raw_fd = master.as_raw_fd(); + if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 { + let errno = std::io::Error::last_os_error(); + bail!("fcntl error: {errno:?}"); + } + let mut file = unsafe { File::from_raw_fd(raw_fd) }; + let mut stdout = std::io::stdout(); + let stdin = std::io::stdin(); + let stdin_fd = stdin.as_raw_fd(); + let old_termios = termios::Termios::from_fd(stdin_fd)?; + turn_off_echo(stdin_fd).ok(); + shutdown_hooks::add_shutdown_hook(turn_on_echo_shutdown_hook); + let (tx, rx) = channel::>(); + std::thread::spawn(move || loop { + let mut line = String::default(); + match stdin.read_line(&mut line) { + Ok(0) => { + kill_child(child); + break; + } + Ok(_) => { + if let Err(e) = tx.send(line.as_bytes().to_vec()) { + log::error!("Channel error: {e:?}"); + kill_child(child); + break; + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {} + Err(e) => { + log::info!("Failed to read stdin: {e:?}"); + kill_child(child); + break; + } + }; + }); + loop { + let mut buf = [0; 1024]; + match file.read(&mut buf) { + Ok(0) => { + log::info!("read from child: EOF"); + break; + } + Ok(n) => { + let buf = String::from_utf8_lossy(&buf[..n]).to_string(); + print!("{}", buf); + if let Err(e) = stdout.flush() { + log::error!("flush failed: {e:?}"); + kill_child(child); + break; + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(std::time::Duration::from_millis(50)); + } + Err(e) => { + // Child process is dead + log::info!("Read child error: {:?}", e); + break; + } + } + match rx.try_recv() { + Ok(v) => { + if let Err(e) = file.write_all(&v) { + log::error!("write error: {e:?}"); + kill_child(child); + break; + } + } + Err(e) => match e { + std::sync::mpsc::TryRecvError::Empty => {} + std::sync::mpsc::TryRecvError::Disconnected => { + log::error!("receive error: {e:?}"); + kill_child(child); + break; + } + }, + } + } + + // Wait for child process + let status = waitpid(child, None); + log::info!("waitpid status: {:?}", status); + let mut code = EXIT_CODE; + match status { + Ok(s) => match s { + nix::sys::wait::WaitStatus::Exited(_pid, status) => { + code = status; + } + _ => {} + }, + Err(_) => {} + } + termios::tcsetattr(stdin_fd, termios::TCSANOW, &old_termios).ok(); + std::process::exit(code); +} + +fn ui_parent( + child: Pid, + master: OwnedFd, + tx_to_ui: Sender, + rx_from_ui: Receiver, + is_su: bool, + initial_password: Option, +) -> ResultType<()> { + let mut initial_password = initial_password; + let raw_fd = master.as_raw_fd(); + if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 { + let errno = std::io::Error::last_os_error(); + tx_to_ui.send(Message::ErrorDialog(format!("fcntl error: {errno:?}")))?; + bail!("fcntl error: {errno:?}"); + } + let mut file = unsafe { File::from_raw_fd(raw_fd) }; + + let mut first = initial_password.is_none(); + let mut su_password_sent = false; + let mut saved_output = String::default(); + loop { + let mut buf = [0; 1024]; + match file.read(&mut buf) { + Ok(0) => { + log::info!("read from child: EOF"); + break; + } + Ok(n) => { + saved_output = String::default(); + let buf = String::from_utf8_lossy(&buf[..n]).trim().to_string(); + let last_line = buf.lines().last().unwrap_or(&buf).trim().to_string(); + log::info!("read from child: {}", buf); + + if last_line.starts_with("sudo:") || last_line.starts_with("su:") { + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(last_line)) { + log::error!("Channel error: {e:?}"); + kill_child(child); + } + break; + } else if last_line.ends_with(":") { + match get_echo_turn_off(raw_fd) { + Ok(true) => { + log::debug!("get_echo_turn_off ok"); + if let Some(password) = initial_password.clone() { + let v = format!("{}\n", password); + if let Err(e) = file.write_all(v.as_bytes()) { + let e = format!("Failed to send password: {e:?}"); + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) { + log::error!("Channel error: {e:?}"); + } + kill_child(child); + break; + } + if is_su && !su_password_sent { + su_password_sent = true; + continue; + } + initial_password = None; + continue; + } + // In fact, su mode can only input password once + let err_msg = if first { "" } else { "Sorry, try again." }; + first = false; + if let Err(e) = + tx_to_ui.send(Message::PasswordPrompt(err_msg.to_string())) + { + log::error!("Channel error: {e:?}"); + kill_child(child); + break; + } + match rx_from_ui.recv() { + Ok(Message::Password((_, password))) => { + let v = format!("{}\n", password); + if let Err(e) = file.write_all(v.as_bytes()) { + let e = format!("Failed to send password: {e:?}"); + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) { + log::error!("Channel error: {e:?}"); + } + kill_child(child); + break; + } + } + Ok(Message::Cancel) => { + log::info!("User canceled"); + kill_child(child); + break; + } + _ => { + log::error!("Unexpected message"); + break; + } + } + } + Ok(false) => log::warn!("get_echo_turn_off timeout"), + Err(e) => log::error!("get_echo_turn_off error: {:?}", e), + } + } else { + saved_output = buf.clone(); + if !last_line.is_empty() && initial_password.is_some() { + log::error!("received not empty line: {last_line}, clear initial password"); + initial_password = None; + } + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(std::time::Duration::from_millis(50)); + } + Err(e) => { + // Child process is dead + log::debug!("Read error: {:?}", e); + break; + } + } + } + + // Wait for child process + let status = waitpid(child, None); + log::info!("waitpid status: {:?}", status); + let mut code = EXIT_CODE; + match status { + Ok(s) => match s { + nix::sys::wait::WaitStatus::Exited(_pid, status) => { + code = status; + } + _ => {} + }, + Err(_) => {} + } + + if code != 0 && !saved_output.is_empty() { + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(saved_output.clone())) { + log::error!("Channel error: {e:?}"); + std::process::exit(code); + } + return Ok(()); + } + if let Err(e) = tx_to_ui.send(Message::Exit(code)) { + log::error!("Channel error: {e:?}"); + std::process::exit(code); + } + Ok(()) +} + +fn child(su_user: Option, args: Vec) -> ResultType<()> { + // https://doc.rust-lang.org/std/env/consts/constant.OS.html + let os = std::env::consts::OS; + let bsd = os == "freebsd" || os == "dragonfly" || os == "netbsd" || os == "openbad"; + let mut params = vec!["sudo".to_string()]; + if su_user.is_some() { + params.push("-S".to_string()); + } + params.push("/bin/sh".to_string()); + params.push("-c".to_string()); + + let command = args + .iter() + .map(|s| { + if su_user.is_some() { + s.to_string() + } else { + quote_shell_arg(s, true) + } + }) + .collect::>() + .join(" "); + let mut command = if bsd { + let lc = match std::env::var("LC_ALL") { + Ok(lc_all) => { + if lc_all.contains('\'') { + eprintln!( + "sudo: Detected attempt to inject privileged command via LC_ALL env({lc_all}). Exiting!\n", + ); + std::process::exit(EXIT_CODE); + } + format!("LC_ALL='{lc_all}' ") + } + Err(_) => { + format!("unset LC_ALL;") + } + }; + format!("{}exec {}", lc, command) + } else { + command.to_string() + }; + if su_user.is_some() { + command = format!("'{}'", quote_shell_arg(&command, false)); + } + params.push(command); + std::env::set_var("LC_ALL", "C.UTF-8"); + + if let Some(user) = &su_user { + let su_subcommand = params + .iter() + .map(|p| p.to_string()) + .collect::>() + .join(" "); + params = vec![ + "su".to_string(), + "-".to_string(), + user.to_string(), + "-c".to_string(), + su_subcommand, + ]; + } + + // allow failure here + let _ = setsid(); + let mut cparams = vec![]; + for param in ¶ms { + cparams.push(CString::new(param.as_str())?); + } + let su_or_sudo = if su_user.is_some() { "su" } else { "sudo" }; + let res = execvp(CString::new(su_or_sudo)?.as_c_str(), &cparams); + eprintln!("sudo: execvp error: {:?}", res); + std::process::exit(EXIT_CODE); +} + +fn get_echo_turn_off(fd: RawFd) -> Result { + let tios = termios::Termios::from_fd(fd)?; + for _ in 0..10 { + if tios.c_lflag & termios::ECHO == 0 { + return Ok(true); + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + Ok(false) +} + +fn turn_off_echo(fd: RawFd) -> Result<(), Error> { + use termios::*; + let mut termios = Termios::from_fd(fd)?; + // termios.c_lflag &= !(ECHO | ECHONL | ICANON | IEXTEN); + termios.c_lflag &= !ECHO; + tcsetattr(fd, TCSANOW, &termios)?; + Ok(()) +} + +pub extern "C" fn turn_on_echo_shutdown_hook() { + let fd = std::io::stdin().as_raw_fd(); + if let Ok(mut termios) = termios::Termios::from_fd(fd) { + termios.c_lflag |= termios::ECHO; + termios::tcsetattr(fd, termios::TCSANOW, &termios).ok(); + } +} + +fn kill_child(child: Pid) { + unsafe { kill(child.as_raw(), Signal::SIGINT as _) }; + let mut res = 0; + + for _ in 0..10 { + match waitpid(child, Some(WaitPidFlag::WNOHANG)) { + Ok(_) => { + res = 1; + break; + } + Err(_) => (), + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + + if res == 0 { + log::info!("Force killing child process"); + unsafe { kill(child.as_raw(), Signal::SIGKILL as _) }; + } +} + +fn password_prompt( + username: &str, + last_password: &str, + err: &str, + show_edit: bool, +) -> Option<(String, String)> { + let dialog = gtk::Dialog::builder() + .title(crate::get_app_name()) + .modal(true) + .build(); + // https://docs.gtk.org/gtk4/method.Dialog.set_default_response.html + dialog.set_default_response(gtk::ResponseType::Ok); + let content_area = dialog.content_area(); + + let label = gtk::Label::builder() + .label(translate("Authentication Required".to_string())) + .margin_top(10) + .build(); + content_area.add(&label); + + let image = gtk::Image::from_icon_name(Some("avatar-default-symbolic"), gtk::IconSize::Dialog); + image.set_margin_top(10); + content_area.add(&image); + + let user_label = gtk::Label::new(Some(username)); + let edit_button = gtk::Button::new(); + edit_button.set_relief(gtk::ReliefStyle::None); + let edit_icon = + gtk::Image::from_icon_name(Some("document-edit-symbolic"), gtk::IconSize::Button.into()); + edit_button.set_image(Some(&edit_icon)); + edit_button.set_can_focus(false); + let user_entry = gtk::Entry::new(); + user_entry.set_alignment(0.5); + user_entry.set_width_request(100); + let user_box = gtk::Box::new(gtk::Orientation::Horizontal, 5); + user_box.add(&user_label); + user_box.add(&edit_button); + user_box.add(&user_entry); + user_box.set_halign(gtk::Align::Center); + user_box.set_valign(gtk::Align::Center); + content_area.add(&user_box); + + edit_button.connect_clicked( + glib::clone!(@weak user_label, @weak edit_button, @weak user_entry=> move |_| { + let username = user_label.text().to_string(); + user_entry.set_text(&username); + user_label.hide(); + edit_button.hide(); + user_entry.show(); + user_entry.grab_focus(); + }), + ); + + let password_input = gtk::Entry::builder() + .visibility(false) + .input_purpose(gtk::InputPurpose::Password) + .placeholder_text(translate("Password".to_string())) + .margin_top(20) + .margin_start(30) + .margin_end(30) + .activates_default(true) + .text(last_password) + .build(); + password_input.set_alignment(0.5); + // https://docs.gtk.org/gtk3/signal.Entry.activate.html + password_input.connect_activate(glib::clone!(@weak dialog => move |_| { + dialog.response(gtk::ResponseType::Ok); + })); + content_area.add(&password_input); + + user_entry.connect_focus_out_event( + glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => @default-return glib::Propagation::Proceed, move |_, _| { + let username = user_entry.text().to_string(); + user_label.set_text(&username); + user_entry.hide(); + user_label.show(); + edit_button.show(); + glib::Propagation::Proceed + }), + ); + user_entry.connect_activate( + glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => move |_| { + let username = user_entry.text().to_string(); + user_label.set_text(&username); + user_entry.hide(); + user_label.show(); + edit_button.show(); + password_input.grab_focus(); + }), + ); + + if !err.is_empty() { + let err_label = gtk::Label::new(None); + err_label.set_markup(&format!( + "{}", + err + )); + err_label.set_selectable(true); + content_area.add(&err_label); + } + + let cancel_button = gtk::Button::builder() + .label(translate("Cancel".to_string())) + .expand(true) + .build(); + cancel_button.connect_clicked(glib::clone!(@weak dialog => move |_| { + dialog.response(gtk::ResponseType::Cancel); + })); + let authenticate_button = gtk::Button::builder() + .label(translate("Authenticate".to_string())) + .expand(true) + .build(); + authenticate_button.connect_clicked(glib::clone!(@weak dialog => move |_| { + dialog.response(gtk::ResponseType::Ok); + })); + let button_box = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .expand(true) + .homogeneous(true) + .spacing(10) + .margin_top(10) + .build(); + button_box.add(&cancel_button); + button_box.add(&authenticate_button); + content_area.add(&button_box); + + content_area.set_spacing(10); + content_area.set_border_width(10); + + dialog.set_width_request(400); + dialog.show_all(); + dialog.set_position(gtk::WindowPosition::Center); + dialog.set_keep_above(true); + password_input.grab_focus(); + user_entry.hide(); + if !show_edit { + edit_button.hide(); + } + dialog.check_resize(); + let response = dialog.run(); + dialog.hide(); + + if response == gtk::ResponseType::Ok { + let username = if user_entry.get_visible() { + user_entry.text().to_string() + } else { + user_label.text().to_string() + }; + Some((username, password_input.text().to_string())) + } else { + None + } +} + +fn error_dialog_and_exit(err_msg: &str, exit_code: i32) { + log::error!("Error dialog: {err_msg}, exit code: {exit_code}"); + let dialog = gtk::MessageDialog::builder() + .message_type(gtk::MessageType::Error) + .title(crate::get_app_name()) + .text("Error") + .secondary_text(err_msg) + .modal(true) + .buttons(gtk::ButtonsType::Ok) + .build(); + dialog.set_position(gtk::WindowPosition::Center); + dialog.set_keep_above(true); + dialog.run(); + dialog.close(); + std::process::exit(exit_code); +} + +fn quote_shell_arg(arg: &str, add_splash_if_match: bool) -> String { + let mut rv = arg.to_string(); + let re = hbb_common::regex::Regex::new("(\\s|[][!\"#$&'()*,;<=>?\\^`{}|~])"); + let Ok(re) = re else { + return rv; + }; + if re.is_match(arg) { + rv = rv.replace("'", "'\\''"); + if add_splash_if_match { + rv = format!("'{}'", rv); + } + } + rv +} diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 4bb666fb9c2..9c549423045 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,4 +1,4 @@ -use super::{CursorData, ResultType}; +use super::{gtk_sudo, CursorData, ResultType}; use desktop::Desktop; use hbb_common::config::keys::OPTION_ALLOW_LINUX_HEADLESS; pub use hbb_common::platform::linux::*; @@ -15,8 +15,6 @@ use hbb_common::{ use std::{ cell::RefCell, ffi::OsStr, - fs::File, - io::{BufRead, BufReader, Write}, path::{Path, PathBuf}, process::{Child, Command}, string::String, @@ -766,30 +764,18 @@ pub fn quit_gui() { unsafe { gtk_main_quit() }; } +/* pub fn exec_privileged(args: &[&str]) -> ResultType { Ok(Command::new("pkexec").args(args).spawn()?) } +*/ pub fn check_super_user_permission() -> ResultType { - let file = format!( - "/usr/share/{}/files/polkit", - crate::get_app_name().to_lowercase() - ); - let arg; - if Path::new(&file).is_file() { - arg = file.as_str(); - } else { - arg = "echo"; - } - // https://github.com/rustdesk/rustdesk/issues/2756 - if let Ok(status) = Command::new("pkexec").arg(arg).status() { - // https://github.com/rustdesk/rustdesk/issues/5205#issuecomment-1658059657s - Ok(status.code() != Some(126) && status.code() != Some(127)) - } else { - Ok(true) - } + gtk_sudo::run(vec!["echo"])?; + Ok(true) } +/* pub fn elevate(args: Vec<&str>) -> ResultType { let cmd = std::env::current_exe()?; match cmd.to_str() { @@ -824,6 +810,7 @@ pub fn elevate(args: Vec<&str>) -> ResultType { } } } +*/ type GtkSettingsPtr = *mut c_void; type GObjectPtr = *mut c_void; @@ -1324,21 +1311,8 @@ fn has_cmd(cmd: &str) -> bool { .unwrap_or_default() } -pub fn run_cmds_pkexec(cmds: &str) -> bool { - const DONE: &str = "RUN_CMDS_PKEXEC_DONE"; - if let Ok(output) = std::process::Command::new("pkexec") - .arg("sh") - .arg("-c") - .arg(&format!("{cmds} echo {DONE}")) - .output() - { - let out = String::from_utf8_lossy(&output.stdout); - log::debug!("cmds: {cmds}"); - log::debug!("output: {out}"); - out.contains(DONE) - } else { - false - } +pub fn run_cmds_privileged(cmds: &str) -> bool { + crate::platform::gtk_sudo::run(vec![cmds]).is_ok() } pub fn run_me_with(secs: u32) { @@ -1367,13 +1341,15 @@ fn switch_service(stop: bool) -> String { pub fn uninstall_service(show_new_window: bool, _: bool) -> bool { if !has_cmd("systemctl") { + // Failed when installed + flutter run + started by `show_new_window`. return false; } log::info!("Uninstalling service..."); let cp = switch_service(true); let app_name = crate::get_app_name().to_lowercase(); - if !run_cmds_pkexec(&format!( - "systemctl disable {app_name}; systemctl stop {app_name}; {cp}" + // systemctl kill rustdesk --tray, execute cp first + if !run_cmds_privileged(&format!( + "{cp} systemctl disable {app_name}; systemctl stop {app_name};" )) { Config::set_option("stop-service".into(), "".into()); return true; @@ -1393,8 +1369,8 @@ pub fn install_service() -> bool { log::info!("Installing service..."); let cp = switch_service(false); let app_name = crate::get_app_name().to_lowercase(); - if !run_cmds_pkexec(&format!( - "{cp} systemctl enable {app_name}; systemctl stop {app_name}; systemctl start {app_name};" + if !run_cmds_privileged(&format!( + "{cp} systemctl enable {app_name}; systemctl start {app_name};" )) { Config::set_option("stop-service".into(), "Y".into()); } @@ -1404,9 +1380,9 @@ pub fn install_service() -> bool { fn check_if_stop_service() { if Config::get_option("stop-service".into()) == "Y" { let app_name = crate::get_app_name().to_lowercase(); - allow_err!(run_cmds( + allow_err!(run_cmds(&format!( "systemctl disable {app_name}; systemctl stop {app_name}" - )); + ))); } } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 169bdb199f4..d0ddd09bf70 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -23,6 +23,9 @@ pub mod linux; #[cfg(target_os = "linux")] pub mod linux_desktop_manager; +#[cfg(target_os = "linux")] +pub mod gtk_sudo; + #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::{message_proto::CursorData, ResultType}; use std::sync::{Arc, Mutex}; diff --git a/src/tray.rs b/src/tray.rs index 74c18bf7bd0..3a3ae92f37f 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -98,12 +98,11 @@ fn make_tray() -> hbb_common::ResultType<()> { crate::run_me::<&str>(vec![]).ok(); } #[cfg(target_os = "linux")] - if !std::process::Command::new("xdg-open") - .arg(&crate::get_uri_prefix()) - .spawn() - .is_ok() { - crate::run_me::<&str>(vec![]).ok(); + // Do not use "xdg-open", it won't read config + if crate::dbus::invoke_new_connection(crate::get_uri_prefix()).is_err() { + crate::run_me::<&str>(vec![]).ok(); + } } }; From c6e3f60a6b45160b5e8853a3bb9dde2e4cc523e7 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:48:01 +0800 Subject: [PATCH 087/210] refact: flutter, ChangeNotifier, reduce rebuild (#9392) Signed-off-by: fufesou --- flutter/lib/common/widgets/peer_card.dart | 3 +++ flutter/lib/common/widgets/peer_tab_page.dart | 10 ++++------ flutter/lib/common/widgets/peers_view.dart | 6 ++++++ flutter/lib/models/peer_tab_model.dart | 9 ++++++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 15ca8932d1f..7827298b6d0 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -250,6 +250,9 @@ class _PeerCardState extends State<_PeerCard> color: Colors.transparent, elevation: 0, margin: EdgeInsets.zero, + // to-do: memory leak here, more investigation needed. + // Continious rebuilds of `Obx()` will cause memory leak here. + // The simple demo does not have this issue. child: Obx( () => Container( foregroundDecoration: deco.value, diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index c941a1b93d4..35975078805 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -108,7 +108,7 @@ class _PeerTabPageState extends State Widget build(BuildContext context) { final model = Provider.of(context); Widget selectionWrap(Widget widget) { - return model.multiSelectionMode ? createMultiSelectionBar() : widget; + return model.multiSelectionMode ? createMultiSelectionBar(model) : widget; } return Column( @@ -362,8 +362,7 @@ class _PeerTabPageState extends State .toList()); } - Widget createMultiSelectionBar() { - final model = Provider.of(context); + Widget createMultiSelectionBar(PeerTabModel model) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -381,7 +380,7 @@ class _PeerTabPageState extends State Row( children: [ selectionCount(model.selectedPeers.length), - selectAll(), + selectAll(model), closeSelection(), ], ) @@ -512,8 +511,7 @@ class _PeerTabPageState extends State ); } - Widget selectAll() { - final model = Provider.of(context); + Widget selectAll(PeerTabModel model) { return Offstage( offstage: model.selectedPeers.length >= model.currentTabCachedPeers.length, diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index a73ef0f0bd2..b18de82d995 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -167,6 +167,9 @@ class _PeersViewState extends State<_PeersView> @override Widget build(BuildContext context) { + // 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( create: (context) => widget.peers, child: Consumer(builder: (context, peers, child) { @@ -245,6 +248,9 @@ class _PeersViewState extends State<_PeersView> : Container(child: visibilityChild); } + // We should avoid too many rebuilds. Win10(Some machines) on Flutter 3.19.6. + // Continious rebuilds of `ListView.builder` will cause memory leak. + // Simple demo can reproduce this issue. final Widget child = Obx(() => stateGlobal.isPortrait.isTrue ? ListView.builder( itemCount: peers.length, diff --git a/flutter/lib/models/peer_tab_model.dart b/flutter/lib/models/peer_tab_model.dart index 3c0fe636d68..7dab2574dcf 100644 --- a/flutter/lib/models/peer_tab_model.dart +++ b/flutter/lib/models/peer_tab_model.dart @@ -184,10 +184,17 @@ class PeerTabModel with ChangeNotifier { notifyListeners(); } + // `notifyListeners()` will cause many rebuilds. + // So, we need to reduce the calls to "notifyListeners()" only when necessary. + // A better way is to use a new model. setCurrentTabCachedPeers(List peers) { Future.delayed(Duration.zero, () { + final isPreEmpty = _currentTabCachedPeers.isEmpty; _currentTabCachedPeers = peers; - notifyListeners(); + final isNowEmpty = _currentTabCachedPeers.isEmpty; + if (isPreEmpty != isNowEmpty) { + notifyListeners(); + } }); } From 47139edd81a4add7be363d234b69b27b5177d491 Mon Sep 17 00:00:00 2001 From: solokot Date: Thu, 19 Sep 2024 13:48:54 +0300 Subject: [PATCH 088/210] Update Russian translation (#9391) Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com> --- src/lang/ru.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index f7db94bd298..9235384f43f 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -554,7 +554,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Open in new window", "Открыть в новом окне"), ("Show displays as individual windows", "Показывать дисплеи в отдельных окнах"), ("Use all my displays for the remote session", "Использовать все мои дисплеи для удалённого сеанса"), - ("selinux_tip", "На вашем устройстве включён SELinux, что может помешать правильной работе RustDesk на контролируемой стороне."), + ("selinux_tip", "На вашем устройстве включён SELinux, что может помешать правильной работе RustDesk на управляемой стороне."), ("Change view", "Вид"), ("Big tiles", "Большие значки"), ("Small tiles", "Маленькие значки"), @@ -641,10 +641,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Platform", "Платформа"), ("Days remaining", "Дней осталось"), ("enable-trusted-devices-tip", "Разрешить доверенным устройствам пропускать проверку подлинности 2FA"), - ("Parent directory", "Родительская директория"), + ("Parent directory", "Родительская папка"), ("Resume", "Продолжить"), - ("Invalid file name", "Неверное имя файла"), - ("one-way-file-transfer-tip", ""), + ("Invalid file name", "Неправильное имя файла"), + ("one-way-file-transfer-tip", "На управляемой стороне включена односторонняя передача файлов."), ("Authentication Required", ""), ("Authenticate", ""), ].iter().cloned().collect(); From ddd3401bd79248bc9bb4d777bdf93ee2477b53a6 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:52:19 +0800 Subject: [PATCH 089/210] fix: keyboard, translate mode (#9406) hotkey, linux -> win Signed-off-by: fufesou --- src/server/input_service.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 06ab61a25c0..1527009cad2 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -1448,17 +1448,27 @@ fn translate_keyboard_mode(evt: &KeyEvent) { en.key_sequence(seq); #[cfg(any(target_os = "linux", target_os = "windows"))] { - if get_modifier_state(Key::Shift, &mut en) { - simulate_(&EventType::KeyRelease(RdevKey::ShiftLeft)); - } - if get_modifier_state(Key::RightShift, &mut en) { - simulate_(&EventType::KeyRelease(RdevKey::ShiftRight)); + #[cfg(target_os = "windows")] + let simulate_win_hot_key = is_hot_key_modifiers_down(&mut en); + #[cfg(target_os = "linux")] + let simulate_win_hot_key = false; + if !simulate_win_hot_key { + if get_modifier_state(Key::Shift, &mut en) { + simulate_(&EventType::KeyRelease(RdevKey::ShiftLeft)); + } + if get_modifier_state(Key::RightShift, &mut en) { + simulate_(&EventType::KeyRelease(RdevKey::ShiftRight)); + } } for chr in seq.chars() { // char in rust is 4 bytes. // But for this case, char comes from keyboard. We only need 2 bytes. #[cfg(target_os = "windows")] - rdev::simulate_unicode(chr as _).ok(); + if simulate_win_hot_key { + rdev::simulate_char(chr, true).ok(); + } else { + rdev::simulate_unicode(chr as _).ok(); + } #[cfg(target_os = "linux")] en.key_click(Key::Layout(chr)); } @@ -1483,6 +1493,17 @@ fn translate_keyboard_mode(evt: &KeyEvent) { } } +#[inline] +#[cfg(target_os = "windows")] +fn is_hot_key_modifiers_down(en: &mut Enigo) -> bool { + en.get_key_state(Key::Control) + || en.get_key_state(Key::RightControl) + || en.get_key_state(Key::Alt) + || en.get_key_state(Key::RightAlt) + || en.get_key_state(Key::Meta) + || en.get_key_state(Key::RWin) +} + #[cfg(target_os = "windows")] fn simulate_win2win_hotkey(code: u32, down: bool) { let unicode: u16 = (code & 0x0000FFFF) as u16; From 216a72592dcba9a14a329acf605f91caf34646ec Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Fri, 20 Sep 2024 04:54:37 +0200 Subject: [PATCH 090/210] Update Italian language (#9403) --- src/lang/it.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 613dcea94c3..008fb3b3518 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -73,7 +73,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Remember password", "Ricorda password"), ("Wrong Password", "Password errata"), ("Do you want to enter again?", "Vuoi riprovare?"), - ("Connection Error", "Errore di connessione"), + ("Connection Error", "Errore connessione"), ("Error", "Errore"), ("Reset by the peer", "Reimpostata dal dispositivo remoto"), ("Connecting...", "Connessione..."), @@ -134,14 +134,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Insert Lock", "Blocco inserimento"), ("Refresh", "Aggiorna"), ("ID does not exist", "L'ID non esiste"), - ("Failed to connect to rendezvous server", "Errore di connessione al server rendezvous"), + ("Failed to connect to rendezvous server", "Errore connessione al server rendezvous"), ("Please try later", "Riprova più tardi"), ("Remote desktop is offline", "Il desktop remoto è offline"), ("Key mismatch", "La chiave non corrisponde"), ("Timeout", "Timeout"), - ("Failed to connect to relay server", "Errore di connessione al server relay"), - ("Failed to connect via rendezvous server", "Errore di connessione tramite il server rendezvous"), - ("Failed to connect via relay server", "Errore di connessione tramite il server relay"), + ("Failed to connect to relay server", "Errore connessione al server relay"), + ("Failed to connect via rendezvous server", "Errore connessione tramite il server rendezvous"), + ("Failed to connect via relay server", "Errore connessione tramite il server relay"), ("Failed to make direct connection to remote desktop", "Impossibile connettersi direttamente al desktop remoto"), ("Set Password", "Imposta password"), ("OS Password", "Password sistema operativo"), @@ -226,7 +226,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add ID", "Aggiungi ID"), ("Add Tag", "Aggiungi etichetta"), ("Unselect all tags", "Deseleziona tutte le etichette"), - ("Network error", "Errore di rete"), + ("Network error", "Errore rete"), ("Username missed", "Nome utente mancante"), ("Password missed", "Password mancante"), ("Wrong credentials", "Credenziali errate"), @@ -644,8 +644,8 @@ 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", ""), - ("Authentication Required", ""), - ("Authenticate", ""), + ("one-way-file-transfer-tip", "Il trasferimento file unidirezionale è abilitato sul lato controllato."), + ("Authentication Required", "Richiesta autenticazione"), + ("Authenticate", "Autentica"), ].iter().cloned().collect(); } From cfd801c5d66dafdbd3329aa96cd89cee748180c1 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Fri, 20 Sep 2024 04:54:50 +0200 Subject: [PATCH 091/210] Update de.rs (#9401) --- src/lang/de.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 38720f53797..d4f72c94b01 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -548,7 +548,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"), ("Filter by intersection", "Nach Schnittmenge filtern"), ("Remove wallpaper during incoming sessions", "Hintergrundbild bei eingehenden Sitzungen entfernen"), - ("Test", "Test"), + ("Test", "Testen"), ("display_is_plugged_out_msg", "Der Bildschirm ist nicht angeschlossen, schalten Sie auf den ersten Bildschirm um."), ("No displays", "Keine Bildschirme"), ("Open in new window", "In einem neuen Fenster öffnen"), @@ -644,8 +644,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Übergeordnetes Verzeichnis"), ("Resume", "Fortsetzen"), ("Invalid file name", "Ungültiger Dateiname"), - ("one-way-file-transfer-tip", ""), - ("Authentication Required", ""), - ("Authenticate", ""), + ("one-way-file-transfer-tip", "Die einseitige Dateiübertragung ist auf der kontrollierten Seite aktiviert."), + ("Authentication Required", "Authentifizierung erforderlich"), + ("Authenticate", "Authentifizieren"), ].iter().cloned().collect(); } From 3d5262c36f54a8648b4ae6d78293f44cd069968f Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 20 Sep 2024 11:06:56 +0800 Subject: [PATCH 092/210] opt gtk sudo ui, fix edit button show (#9399) Signed-off-by: 21pages --- src/platform/gtk_sudo.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/platform/gtk_sudo.rs b/src/platform/gtk_sudo.rs index a3727134e35..0105b335c31 100644 --- a/src/platform/gtk_sudo.rs +++ b/src/platform/gtk_sudo.rs @@ -30,7 +30,7 @@ use std::{ const EXIT_CODE: i32 = -1; enum Message { - PasswordPrompt(String), + PasswordPrompt((String, bool)), Password((String, String)), ErrorDialog(String), Cancel, @@ -107,24 +107,20 @@ fn ui(args: Vec) { let username = Arc::new(Mutex::new(crate::platform::get_active_username())); let username_clone = username.clone(); - let first_prompt = Arc::new(Mutex::new(true)); application.connect_activate(glib::clone!(@weak application =>move |_| { let rx_to_ui = rx_to_ui_clone.clone(); let tx_from_ui = tx_from_ui_clone.clone(); let last_password = Arc::new(Mutex::new(String::new())); let username = username_clone.clone(); - let first_prompt = first_prompt.clone(); glib::timeout_add_local(std::time::Duration::from_millis(50), move || { if let Ok(msg) = rx_to_ui.lock().unwrap().try_recv() { match msg { - Message::PasswordPrompt(err_msg) => { + Message::PasswordPrompt((err_msg, show_edit)) => { let last_pwd = last_password.lock().unwrap().clone(); let username = username.lock().unwrap().clone(); - let first = first_prompt.lock().unwrap().clone(); - *first_prompt.lock().unwrap() = false; - if let Some((username, password)) = password_prompt(&username, &last_pwd, &err_msg, first) { + if let Some((username, password)) = password_prompt(&username, &last_pwd, &err_msg, show_edit) { *last_password.lock().unwrap() = password.clone(); if let Err(e) = tx_from_ui .lock() @@ -157,7 +153,7 @@ fn ui(args: Vec) { let acitve_user = crate::platform::get_active_username(); let mut initial_password = None; if acitve_user != "root" { - if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt("".to_string())) { + if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt(("".to_string(), true))) { log::error!("Channel error: {e:?}"); std::process::exit(EXIT_CODE); } @@ -385,7 +381,7 @@ fn ui_parent( let err_msg = if first { "" } else { "Sorry, try again." }; first = false; if let Err(e) = - tx_to_ui.send(Message::PasswordPrompt(err_msg.to_string())) + tx_to_ui.send(Message::PasswordPrompt((err_msg.to_string(), false))) { log::error!("Channel error: {e:?}"); kill_child(child); @@ -627,6 +623,7 @@ fn password_prompt( user_box.add(&user_entry); user_box.set_halign(gtk::Align::Center); user_box.set_valign(gtk::Align::Center); + user_box.set_vexpand(true); content_area.add(&user_box); edit_button.connect_clicked( @@ -690,21 +687,21 @@ fn password_prompt( let cancel_button = gtk::Button::builder() .label(translate("Cancel".to_string())) - .expand(true) + .hexpand(true) .build(); cancel_button.connect_clicked(glib::clone!(@weak dialog => move |_| { dialog.response(gtk::ResponseType::Cancel); })); let authenticate_button = gtk::Button::builder() .label(translate("Authenticate".to_string())) - .expand(true) + .hexpand(true) .build(); authenticate_button.connect_clicked(glib::clone!(@weak dialog => move |_| { dialog.response(gtk::ResponseType::Ok); })); let button_box = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) - .expand(true) + .hexpand(true) .homogeneous(true) .spacing(10) .margin_top(10) From 21bcfd173d299d38fcf0e956aa0dc40afaee1931 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:15:19 +0800 Subject: [PATCH 093/210] fix: wayland, rdp input, mouse, scale (#9402) * fix: wayland, rdp input, mouse, scale Signed-off-by: fufesou * fix: rdp input, mouse, scale, check 0 Signed-off-by: fufesou --------- Signed-off-by: fufesou --- libs/hbb_common/src/platform/linux.rs | 12 ++++++ libs/scrap/src/wayland/pipewire.rs | 58 ++++++++++++++++++--------- src/server/input_service.rs | 22 +++++++--- src/server/rdp_input.rs | 53 ++++++++++++++++++++++-- 4 files changed, 117 insertions(+), 28 deletions(-) diff --git a/libs/hbb_common/src/platform/linux.rs b/libs/hbb_common/src/platform/linux.rs index 5e03b6816e4..60c8714d821 100644 --- a/libs/hbb_common/src/platform/linux.rs +++ b/libs/hbb_common/src/platform/linux.rs @@ -7,6 +7,9 @@ lazy_static::lazy_static! { pub const DISPLAY_SERVER_WAYLAND: &str = "wayland"; pub const DISPLAY_SERVER_X11: &str = "x11"; +pub const DISPLAY_DESKTOP_KDE: &str = "KDE"; + +pub const XDG_CURRENT_DESKTOP: &str = "XDG_CURRENT_DESKTOP"; pub struct Distro { pub name: String, @@ -29,6 +32,15 @@ impl Distro { } } +#[inline] +pub fn is_kde() -> bool { + if let Ok(env) = std::env::var(XDG_CURRENT_DESKTOP) { + env == DISPLAY_DESKTOP_KDE + } else { + false + } +} + #[inline] pub fn is_gdm_user(username: &str) -> bool { username == "gdm" diff --git a/libs/scrap/src/wayland/pipewire.rs b/libs/scrap/src/wayland/pipewire.rs index 640f37d0b83..2f1e2a85267 100644 --- a/libs/scrap/src/wayland/pipewire.rs +++ b/libs/scrap/src/wayland/pipewire.rs @@ -27,39 +27,40 @@ use super::screencast_portal::OrgFreedesktopPortalScreenCast as screencast_porta use lazy_static::lazy_static; lazy_static! { - pub static ref RDP_RESPONSE: Mutex> = Mutex::new(None); + pub static ref RDP_SESSION_INFO: Mutex> = Mutex::new(None); } #[inline] pub fn close_session() { - let _ = RDP_RESPONSE.lock().unwrap().take(); + let _ = RDP_SESSION_INFO.lock().unwrap().take(); } #[inline] pub fn is_rdp_session_hold() -> bool { - RDP_RESPONSE.lock().unwrap().is_some() + RDP_SESSION_INFO.lock().unwrap().is_some() } pub fn try_close_session() { - let mut rdp_res = RDP_RESPONSE.lock().unwrap(); + let mut rdp_info = RDP_SESSION_INFO.lock().unwrap(); let mut close = false; - if let Some(rdp_res) = &*rdp_res { + if let Some(rdp_info) = &*rdp_info { // If is server running and restore token is supported, there's no need to keep the session. - if is_server_running() && rdp_res.is_support_restore_token { + if is_server_running() && rdp_info.is_support_restore_token { close = true; } } if close { - *rdp_res = None; + *rdp_info = None; } } -pub struct RdpResponse { +pub struct RdpSessionInfo { pub conn: Arc, pub streams: Vec, pub fd: OwnedFd, pub session: dbus::Path<'static>, pub is_support_restore_token: bool, + pub resolution: Arc>>, } #[derive(Debug, Clone, Copy)] pub struct PwStreamInfo { @@ -69,6 +70,12 @@ pub struct PwStreamInfo { size: (usize, usize), } +impl PwStreamInfo { + pub fn get_size(&self) -> (usize, usize) { + self.size + } +} + #[derive(Debug)] pub struct DBusError(String); @@ -105,24 +112,31 @@ pub struct PipeWireCapturable { } impl PipeWireCapturable { - fn new(conn: Arc, fd: OwnedFd, stream: PwStreamInfo) -> Self { + fn new( + conn: Arc, + fd: OwnedFd, + resolution: Arc>>, + stream: PwStreamInfo, + ) -> Self { // alternative to get screen resolution as stream.size is not always correct ex: on fractional scaling // https://github.com/rustdesk/rustdesk/issues/6116#issuecomment-1817724244 - let res = get_res(Self { + let size = get_res(Self { dbus_conn: conn.clone(), fd: fd.clone(), path: stream.path, source_type: stream.source_type, position: stream.position, size: stream.size, - }); + }) + .unwrap_or(stream.size); + *resolution.lock().unwrap() = Some(size); Self { dbus_conn: conn, fd, path: stream.path, source_type: stream.source_type, position: stream.position, - size: res.unwrap_or(stream.size), + size, } } } @@ -813,7 +827,7 @@ fn on_start_response( } pub fn get_capturables() -> Result, Box> { - let mut rdp_connection = match RDP_RESPONSE.lock() { + let mut rdp_connection = match RDP_SESSION_INFO.lock() { Ok(conn) => conn, Err(err) => return Err(Box::new(err)), }; @@ -822,28 +836,36 @@ pub fn get_capturables() -> Result, Box> { let (conn, fd, streams, session, is_support_restore_token) = request_remote_desktop()?; let conn = Arc::new(conn); - let rdp_res = RdpResponse { + let rdp_info = RdpSessionInfo { conn, streams, fd, session, is_support_restore_token, + resolution: Arc::new(Mutex::new(None)), }; - *rdp_connection = Some(rdp_res); + *rdp_connection = Some(rdp_info); } - let rdp_res = match rdp_connection.as_ref() { + let rdp_info = match rdp_connection.as_ref() { Some(res) => res, None => { return Err(Box::new(DBusError("RDP response is None.".into()))); } }; - Ok(rdp_res + Ok(rdp_info .streams .clone() .into_iter() - .map(|s| PipeWireCapturable::new(rdp_res.conn.clone(), rdp_res.fd.clone(), s)) + .map(|s| { + PipeWireCapturable::new( + rdp_info.conn.clone(), + rdp_info.fd.clone(), + rdp_info.resolution.clone(), + s, + ) + }) .collect()) } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 1527009cad2..fd4291cc8f6 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -16,7 +16,7 @@ use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey}; #[cfg(target_os = "macos")] use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput}; #[cfg(target_os = "linux")] -use scrap::wayland::pipewire::RDP_RESPONSE; +use scrap::wayland::pipewire::RDP_SESSION_INFO; use std::{ convert::TryFrom, ops::{Deref, DerefMut, Sub}, @@ -521,15 +521,25 @@ pub async fn setup_uinput(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultT #[cfg(target_os = "linux")] pub async fn setup_rdp_input() -> ResultType<(), Box> { let mut en = ENIGO.lock()?; - let rdp_res_lock = RDP_RESPONSE.lock()?; - let rdp_res = rdp_res_lock.as_ref().ok_or("RDP response is None")?; + let rdp_info_lock = RDP_SESSION_INFO.lock()?; + let rdp_info = rdp_info_lock.as_ref().ok_or("RDP session is None")?; - let keyboard = RdpInputKeyboard::new(rdp_res.conn.clone(), rdp_res.session.clone())?; + let keyboard = RdpInputKeyboard::new(rdp_info.conn.clone(), rdp_info.session.clone())?; en.set_custom_keyboard(Box::new(keyboard)); log::info!("RdpInput keyboard created"); - if let Some(stream) = rdp_res.streams.clone().into_iter().next() { - let mouse = RdpInputMouse::new(rdp_res.conn.clone(), rdp_res.session.clone(), stream)?; + if let Some(stream) = rdp_info.streams.clone().into_iter().next() { + let resolution = rdp_info + .resolution + .lock() + .unwrap() + .unwrap_or(stream.get_size()); + let mouse = RdpInputMouse::new( + rdp_info.conn.clone(), + rdp_info.session.clone(), + stream, + resolution, + )?; en.set_custom_mouse(Box::new(mouse)); log::info!("RdpInput mouse created"); } diff --git a/src/server/rdp_input.rs b/src/server/rdp_input.rs index 1a0a64054b9..910a192761d 100644 --- a/src/server/rdp_input.rs +++ b/src/server/rdp_input.rs @@ -8,6 +8,8 @@ use std::collections::HashMap; use std::sync::Arc; pub mod client { + use hbb_common::platform::linux::is_kde; + use super::*; const EVDEV_MOUSE_LEFT: i32 = 272; @@ -67,6 +69,8 @@ pub mod client { conn: Arc, session: Path<'static>, stream: PwStreamInfo, + resolution: (usize, usize), + scale: Option, } impl RdpInputMouse { @@ -74,11 +78,32 @@ pub mod client { conn: Arc, session: Path<'static>, stream: PwStreamInfo, + resolution: (usize, usize), ) -> ResultType { + // https://github.com/rustdesk/rustdesk/pull/9019#issuecomment-2295252388 + // There may be a bug in Rdp input on Gnome util Ubuntu 24.04 (Gnome 46) + // + // eg. Resultion 800x600, Fractional scale: 200% (logic size: 400x300) + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.RemoteDesktop.html#:~:text=new%20pointer%20position-,in%20the%20streams%20logical%20coordinate%20space,-. + // Then (x,y) in `mouse_move_to()` and `mouse_move_relative()` should be scaled to the logic size(stream.get_size()), which is from (0,0) to (400,300). + // For Ubuntu 24.04(Gnome 46), (x,y) is restricted from (0,0) to (400,300), but the actual range in screen is: + // Logic coordinate from (0,0) to (200x150). + // Or physical coordinate from (0,0) to (400,300). + let scale = if is_kde() { + if resolution.0 == 0 || stream.get_size().0 == 0 { + Some(1.0f64) + } else { + Some(resolution.0 as f64 / stream.get_size().0 as f64) + } + } else { + None + }; Ok(Self { conn, session, stream, + resolution, + scale, }) } } @@ -93,24 +118,44 @@ pub mod client { } fn mouse_move_to(&mut self, x: i32, y: i32) { + let x = if let Some(s) = self.scale { + x as f64 / s + } else { + x as f64 + }; + let y = if let Some(s) = self.scale { + y as f64 / s + } else { + y as f64 + }; let portal = get_portal(&self.conn); let _ = remote_desktop_portal::notify_pointer_motion_absolute( &portal, &self.session, HashMap::new(), self.stream.path as u32, - x as f64, - y as f64, + x, + y, ); } fn mouse_move_relative(&mut self, x: i32, y: i32) { + let x = if let Some(s) = self.scale { + x as f64 / s + } else { + x as f64 + }; + let y = if let Some(s) = self.scale { + y as f64 / s + } else { + y as f64 + }; let portal = get_portal(&self.conn); let _ = remote_desktop_portal::notify_pointer_motion( &portal, &self.session, HashMap::new(), - x as f64, - y as f64, + x, + y, ); } fn mouse_down(&mut self, button: MouseButton) -> enigo::ResultType { From b93d4ce3fc2633351b7829db1b396d65fcf296a5 Mon Sep 17 00:00:00 2001 From: jkh0kr Date: Fri, 20 Sep 2024 16:16:20 +0900 Subject: [PATCH 094/210] Update ko.rs (#9411) Add Korean language --- src/lang/ko.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 417073d05e1..9d9c29f9af2 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -641,11 +641,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Platform", "플랫폼"), ("Days remaining", "일 남음"), ("enable-trusted-devices-tip", "신뢰할 수 있는 기기에서 2FA 검증 건너뛰기"), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), - ("one-way-file-transfer-tip", ""), - ("Authentication Required", ""), - ("Authenticate", ""), + ("Parent directory", "상위 디렉토리"), + ("Resume", "재개"), + ("Invalid file name", "잘못된 파일 이름"), + ("one-way-file-transfer-tip", "단방향 파일 전송은 제어되는 쪽에서 활성화됩니다."), + ("Authentication Required", "인증 필요함"), + ("Authenticate", "인증"), ].iter().cloned().collect(); } From 2e314bf032938fb57a82a8bed3285b490da5b9c9 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 20 Sep 2024 17:38:29 +0800 Subject: [PATCH 095/210] disable init clipboard sync by default --- libs/hbb_common/src/config.rs | 14 ++++++++++++-- src/client/io_loop.rs | 15 ++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 0e91ecf42c3..f0f7ec7317e 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -296,6 +296,8 @@ pub struct PeerConfig { pub keyboard_mode: String, #[serde(flatten)] pub view_only: ViewOnly, + #[serde(flatten)] + pub sync_init_clipboard: SyncInitClipboard, // Mouse wheel or touchpad scroll mode #[serde( default = "PeerConfig::default_reverse_mouse_wheel", @@ -373,6 +375,7 @@ impl Default for PeerConfig { ui_flutter: Default::default(), info: Default::default(), transfer: Default::default(), + sync_init_clipboard: Default::default(), } } } @@ -1462,6 +1465,13 @@ serde_field_bool!( "ViewOnly::default_view_only" ); +serde_field_bool!( + SyncInitClipboard, + "sync-init-clipboard", + default_sync_init_clipboard, + "SyncInitClipboard::default_sync_init_clipboard" +); + #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct LocalConfig { #[serde(default, deserialize_with = "deserialize_string")] @@ -2156,6 +2166,7 @@ pub mod keys { pub const OPTION_CUSTOM_IMAGE_QUALITY: &str = "custom_image_quality"; pub const OPTION_CUSTOM_FPS: &str = "custom-fps"; pub const OPTION_CODEC_PREFERENCE: &str = "codec-preference"; + pub const OPTION_SYNC_INIT_CLIPBOARD: &str = "sync-init-clipboard"; pub const OPTION_THEME: &str = "theme"; pub const OPTION_LANGUAGE: &str = "lang"; pub const OPTION_REMOTE_MENUBAR_DRAG_LEFT: &str = "remote-menubar-drag-left"; @@ -2219,7 +2230,6 @@ pub mod keys { pub const OPTION_DEFAULT_CONNECT_PASSWORD: &str = "default-connect-password"; pub const OPTION_HIDE_TRAY: &str = "hide-tray"; pub const OPTION_ONE_WAY_CLIPBOARD_REDIRECTION: &str = "one-way-clipboard-redirection"; - pub const OPTION_ENABLE_CLIPBOARD_INIT_SYNC: &str = "enable-clipboard-init-sync"; pub const OPTION_ALLOW_LOGON_SCREEN_PASSWORD: &str = "allow-logon-screen-password"; pub const OPTION_ONE_WAY_FILE_TRANSFER: &str = "one-way-file-transfer"; @@ -2280,6 +2290,7 @@ pub mod keys { OPTION_CUSTOM_IMAGE_QUALITY, OPTION_CUSTOM_FPS, OPTION_CODEC_PREFERENCE, + OPTION_SYNC_INIT_CLIPBOARD, ]; // DEFAULT_LOCAL_SETTINGS, OVERWRITE_LOCAL_SETTINGS pub const KEYS_LOCAL_SETTINGS: &[&str] = &[ @@ -2367,7 +2378,6 @@ pub mod keys { OPTION_DEFAULT_CONNECT_PASSWORD, OPTION_HIDE_TRAY, OPTION_ONE_WAY_CLIPBOARD_REDIRECTION, - OPTION_ENABLE_CLIPBOARD_INIT_SYNC, OPTION_ALLOW_LOGON_SCREEN_PASSWORD, OPTION_ONE_WAY_FILE_TRANSFER, ]; diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index c23c967b580..84d8a897cf5 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1196,15 +1196,12 @@ impl Remote { } #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Some(msg_out) = crate::clipboard::get_current_clipboard_msg( - &peer_version, - &peer_platform, - crate::clipboard::ClipboardSide::Client, - ) { - if crate::get_builtin_option( - config::keys::OPTION_ENABLE_CLIPBOARD_INIT_SYNC, - ) != "N" - { + if self.handler.lc.read().unwrap().sync_init_clipboard.v { + if let Some(msg_out) = crate::clipboard::get_current_clipboard_msg( + &peer_version, + &peer_platform, + crate::clipboard::ClipboardSide::Client, + ) { let sender = self.sender.clone(); let permission_config = self.handler.get_permission_config(); tokio::spawn(async move { From a516f01feb982202b95bc291e4cbd593eb5bd0be Mon Sep 17 00:00:00 2001 From: Dmytro <67293930+Nollasko@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:38:16 +0300 Subject: [PATCH 096/210] Update uk.rs (#9416) --- src/lang/uk.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lang/uk.rs b/src/lang/uk.rs index aa34887be37..41634facc39 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -246,7 +246,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Paste", "Вставити"), ("Paste here?", "Вставити сюди?"), ("Are you sure to close the connection?", "Ви впевнені, що хочете завершити підключення?"), - ("Download new version", "Завантажити нову версію"), + ("Download new version", "Завантажте нову версію"), ("Touch mode", "Сенсорний режим"), ("Mouse mode", "Режим миші"), ("One-Finger Tap", "Дотик одним пальцем"), @@ -636,16 +636,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Requires at least {} characters", "Потрібно щонайменше {} символів"), ("Wrong PIN", "Неправильний PIN-код"), ("Set PIN", "Встановити PIN-код"), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), - ("one-way-file-transfer-tip", ""), - ("Authentication Required", ""), - ("Authenticate", ""), + ("Enable trusted devices", "Увімкнути довірені пристрої"), + ("Manage trusted devices", "Керувати довіреними пристроями"), + ("Platform", "Платформа"), + ("Days remaining", "Залишилося днів"), + ("enable-trusted-devices-tip", "Дозволити довіреним пристроям пропускати двофакторну автентифікацію?"), + ("Parent directory", "Батьківський каталог"), + ("Resume", "Продовжити"), + ("Invalid file name", "Неправильне ім'я файлу"), + ("one-way-file-transfer-tip", "На керованій стороні ввімкнено одностороннє передавання файлів."), + ("Authentication Required", "Потрібна автентифікація"), + ("Authenticate", "Автентифікувати"), ].iter().cloned().collect(); } From 3db55a718c2b89f08a7629d98f6ee54fb726aad6 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sat, 21 Sep 2024 11:22:33 +0800 Subject: [PATCH 097/210] build 48 --- flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 5d49249d49c..899a8bbecf1 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -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.1+48 environment: sdk: '^3.1.0' From 1d799483d7460f86d01ec420f2bc7fb55686fdfa Mon Sep 17 00:00:00 2001 From: Alex Rijckaert Date: Sun, 22 Sep 2024 13:06:33 +0200 Subject: [PATCH 098/210] Update nl.rs (#9422) --- src/lang/nl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 5b2eee79e92..6a0fa3e702a 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -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(); } From 5f52ce2c1b780f11a3476e6d7332fe99db7f2ed2 Mon Sep 17 00:00:00 2001 From: solokot Date: Sun, 22 Sep 2024 14:06:45 +0300 Subject: [PATCH 099/210] Update ru.rs (#9421) --- src/lang/ru.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 9235384f43f..ba8d33f95db 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -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(); } From d98f9478243def9c959c6a244750535aa97f9208 Mon Sep 17 00:00:00 2001 From: XLion Date: Mon, 23 Sep 2024 09:17:13 +0800 Subject: [PATCH 100/210] Update translation (#9426) add translation for tw.rs and fix typo on cn.rs --- src/lang/cn.rs | 2 +- src/lang/tw.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 887a7297114..99b3284cbc6 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -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(); diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 812d5f8616e..0a3f291ac97 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -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(); } From 8fefd34c15a9b5647a10b9bddf2639e71de8b62a Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 23 Sep 2024 09:18:22 +0800 Subject: [PATCH 101/210] update web (#9427) Signed-off-by: 21pages --- flutter/lib/common/widgets/overlay.dart | 3 +-- flutter/lib/web/bridge.dart | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index a1620b106e3..9b20136e150 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -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 ?? '-'), ], ), ) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 26e94ff1c58..c952c55d27e 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -472,7 +472,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}) ])); From f3f3bb538f112268706c72b109ff7d8200baa702 Mon Sep 17 00:00:00 2001 From: Vasyl Gello Date: Mon, 23 Sep 2024 08:43:59 +0000 Subject: [PATCH 102/210] Fix F-Droid build and bump Android NDK (#9428) * Fix F-Droid build Signed-off-by: Vasyl Gello * Bump Android NDK to r27b Signed-off-by: Vasyl Gello --------- Signed-off-by: Vasyl Gello --- .github/workflows/flutter-build.yml | 2 +- flutter/build_fdroid.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index b5f8707874e..e3a791ea141 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -34,7 +34,7 @@ env: # vcpkg version: 2024.07.12 VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1" VERSION: "1.3.1" - NDK_VERSION: "r27" + NDK_VERSION: "r27b" #signing keys env variable checks ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}" diff --git a/flutter/build_fdroid.sh b/flutter/build_fdroid.sh index 2e0a20b6db6..7f3a9cc48f4 100755 --- a/flutter/build_fdroid.sh +++ b/flutter/build_fdroid.sh @@ -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 From f5354069628bfd756f70d8850a016242b09edd80 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:58:21 +0800 Subject: [PATCH 103/210] refact: web, keyboard, translate mode (#9432) Signed-off-by: fufesou --- flutter/lib/models/input_model.dart | 16 ++++++++++++---- flutter/lib/web/bridge.dart | 24 +++++++++++++++++------- flutter/lib/web/common.dart | 11 ++++++----- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index ac78e17309b..b57048a08e4 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -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. diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index c952c55d27e..d28e0c26abf 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -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 sessionInputKey( @@ -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 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 mainGetMyId({dynamic hint}) { @@ -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'] ]); } diff --git a/flutter/lib/web/common.dart b/flutter/lib/web/common.dart index 0f3a996816d..4d539d5d47c 100644 --- a/flutter/lib/web/common.dart +++ b/flutter/lib/web/common.dart @@ -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; From 75a14fea23307ed1f3d8df5d447bd47fcea03b45 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:31:37 +0800 Subject: [PATCH 104/210] fix: keyboard, change mode, await, on input source changed (#9434) Signed-off-by: fufesou --- flutter/lib/models/model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 084ac1b43a6..899affa62be 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -839,7 +839,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; } } From 49989e34e44fdda580fe3f453aa2eff7f6bcd6ca Mon Sep 17 00:00:00 2001 From: Kleofass <4000163+Kleofass@users.noreply.github.com> Date: Tue, 24 Sep 2024 04:51:58 +0300 Subject: [PATCH 105/210] Update lv.rs (#9437) --- src/lang/lv.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/lv.rs b/src/lang/lv.rs index a89590bf4f1..63a4a7dae4c 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -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(); } From e4f7e126e541cdc5cef4cb8e130783158fa574c4 Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 24 Sep 2024 11:37:30 +0800 Subject: [PATCH 106/210] fix check update (#9444) check_software_update runs in a new thread, won't return directly Signed-off-by: 21pages --- flutter/lib/consts.dart | 2 ++ flutter/lib/desktop/pages/desktop_home_page.dart | 16 ++++++++++++++-- flutter/lib/mobile/pages/connection_page.dart | 16 ++++++++++++++-- src/common.rs | 10 ++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index edc7f427853..17d5cec271b 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -570,3 +570,5 @@ enum WindowsTarget { extension WindowsTargetExt on int { WindowsTarget get windowsVersion => getWindowsTarget(this); } + +const kCheckSoftwareUpdateFinish = 'check_software_update_finish'; \ No newline at end of file diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index dee990af4c2..90fa67dedde 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -664,9 +664,17 @@ class _DesktopHomePageState extends State void initState() { super.initState(); if (!bind.isCustomClient()) { + platformFFI.registerEventHandler( + kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, + (Map 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 _uniLinksSubscription?.cancel(); Get.delete(tag: 'stop-service'); _updateTimer?.cancel(); + if (!bind.isCustomClient()) { + platformFFI.unregisterEventHandler( + kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish); + } super.dispose(); } diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index c6e81238926..181f36e58cc 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -70,9 +70,17 @@ class _ConnectionPageState extends State { } if (isAndroid) { if (!bind.isCustomClient()) { + platformFFI.registerEventHandler( + kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, + (Map 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 { if (Get.isRegistered()) { Get.delete(); } + if (!bind.isCustomClient()) { + platformFFI.unregisterEventHandler( + kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish); + } super.dispose(); } } diff --git a/src/common.rs b/src/common.rs index 9950850708f..c02aea45e8c 100644 --- a/src/common.rs +++ b/src/common.rs @@ -830,6 +830,16 @@ async fn check_software_update_() -> hbb_common::ResultType<()> { 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"); + let url = SOFTWARE_UPDATE_URL.lock().unwrap().clone(); + m.insert("url", url.as_str()); + if let Ok(data) = serde_json::to_string(&m) { + let _ = crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, data); + } + } Ok(()) } From 664a3e186eaf189ec3f49241552e2c71c11c6ac2 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 24 Sep 2024 12:00:37 +0800 Subject: [PATCH 107/210] clean SOFTWARE_UPDATE_URL --- flutter/lib/web/bridge.dart | 2 +- src/common.rs | 19 +++++++++---------- src/flutter_ffi.rs | 4 +--- src/main.rs | 2 -- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index d28e0c26abf..c99ef140da4 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1063,7 +1063,7 @@ class RustdeskImpl { () => js.context.callMethod('getByName', ['option', 'last_remote_id'])); } - Future mainGetSoftwareUpdateUrl({dynamic hint}) { + Future mainGetSoftwareUpdateUrl({dynamic hint}) { throw UnimplementedError(); } diff --git a/src/common.rs b/src/common.rs index c02aea45e8c..f53dd703fe6 100644 --- a/src/common.rs +++ b/src/common.rs @@ -828,17 +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"); - let url = SOFTWARE_UPDATE_URL.lock().unwrap().clone(); - m.insert("url", url.as_str()); - if let Ok(data) = serde_json::to_string(&m) { - let _ = crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, data); + #[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(()) } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 72b0e5b37b4..020bc98aa92 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -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 { diff --git a/src/main.rs b/src/main.rs index 44ace8a76e5..f295363aa90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(); } From e0095aebdaf7610b164886e505f965be67183ff8 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:53:17 +0800 Subject: [PATCH 108/210] refact: web elevation (#9445) Signed-off-by: fufesou --- flutter/lib/web/bridge.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index c99ef140da4..6e758dae438 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -608,7 +608,7 @@ class RustdeskImpl { Future sessionElevateDirect( {required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', ['elevate_direct'])); } Future sessionElevateWithLogon( @@ -618,7 +618,7 @@ class RustdeskImpl { dynamic hint}) { return Future(() => js.context.callMethod('setByName', [ 'elevate_with_logon', - jsonEncode({username, password}) + jsonEncode({'username': username, 'password': password}) ])); } From ba88bc9e8bdf14ea44e371315eb9cb557768f16c Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:33:28 +0200 Subject: [PATCH 109/210] Update Italian language (#9452) --- src/lang/it.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 008fb3b3518..a07df2da922 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -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(); From ce5151032ef8d3245b1fbbc2754d577f38434c17 Mon Sep 17 00:00:00 2001 From: Lumiphare <58658634+Lumiphare@users.noreply.github.com> Date: Wed, 25 Sep 2024 08:48:45 +0800 Subject: [PATCH 110/210] Update README-ZH.md (#9457) * Update CONTRIBUTING.md links to point to the Chinese version * translated with AI assistance and manual refinement * Adapted from the official Chinese translation of the Contributor Covenant * Improve README-ZH.md --------- Co-authored-by: sea Co-authored-by: Lumiphare --- docs/README-ZH.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/README-ZH.md b/docs/README-ZH.md index 0460384ab42..5a5f56b204e 100644 --- a/docs/README-ZH.md +++ b/docs/README-ZH.md @@ -8,7 +8,7 @@ [English] | [Українська] | [česky] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]

    -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代码 ## 截图 From 7c55e3266b1cf13a92e6973323aba3034651afb1 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 25 Sep 2024 15:11:11 +0800 Subject: [PATCH 111/210] fix peers view ChangeNotifierProvider update (#9459) Signed-off-by: 21pages --- flutter/lib/common/widgets/peers_view.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index b18de82d995..e80e5ffe343 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -128,8 +128,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 +171,9 @@ 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( - create: (context) => widget.peers, + return ChangeNotifierProvider.value( + // https://pub.dev/packages/provider: If you already have an object instance and want to expose it, it would be best to use the .value constructor of a provider. + value: widget.peers, child: Consumer(builder: (context, peers, child) { if (peers.peers.isEmpty) { gFFI.peerTabModel.setCurrentTabCachedPeers([]); @@ -186,7 +188,7 @@ class _PeersViewState extends State<_PeersView> ).paddingOnly(bottom: 10), Text( translate( - _emptyMessages[widget.peers.loadEvent] ?? 'Empty', + _emptyMessages[peers.loadEvent] ?? 'Empty', ), textAlign: TextAlign.center, style: TextStyle( From 1d6873f6223deb85aec48f3b34685fb9e8abe963 Mon Sep 17 00:00:00 2001 From: flusheDData <116861809+flusheDData@users.noreply.github.com> Date: Thu, 26 Sep 2024 03:29:49 +0200 Subject: [PATCH 112/210] New translation terms (#9463) * Update es.rs New terms added * Update es.rs New terms added --- src/lang/es.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index e42a5abed84..cb5e4a25b39 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -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(); } From 6d8b5b289f8642446b94dac11cd4db2d9b8d0304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ssio=20Oliveira?= <57467189+zkassimz@users.noreply.github.com> Date: Wed, 25 Sep 2024 23:34:12 -0300 Subject: [PATCH 113/210] Refactor ScanPage for better performance and memory management (#9464) - Added null checks in `reassemble` method to avoid potential null pointer exceptions when pausing/resuming the camera. - Refactored image picking and QR code decoding process to use async/await, avoiding UI blocking with synchronous file reads. - Improved exception handling by making it more specific to QR code reading errors. - Introduced `StreamSubscription` for the QR scan listener and ensured proper cancellation in `dispose` method to prevent memory leaks. - Separated button building logic (`_buildImagePickerButton`, `_buildFlashToggleButton`, `_buildCameraSwitchButton`) to enhance code readability and maintainability. --- flutter/lib/mobile/pages/scan_page.dart | 151 +++++++++++++----------- 1 file changed, 81 insertions(+), 70 deletions(-) diff --git a/flutter/lib/mobile/pages/scan_page.dart b/flutter/lib/mobile/pages/scan_page.dart index 527e7446ff2..e92400dba6d 100644 --- a/flutter/lib/mobile/pages/scan_page.dart +++ b/flutter/lib/mobile/pages/scan_page.dart @@ -19,95 +19,48 @@ class ScanPage extends StatefulWidget { class _ScanPageState extends State { 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 { 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 { } } + Future _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(); } From c74bdcdfdb2dbb5321ae7a260462bd2e025a90e6 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:20:37 +0800 Subject: [PATCH 114/210] Revert "fix peers view ChangeNotifierProvider update (#9459)" (#9471) This reverts commit 7c55e3266b1cf13a92e6973323aba3034651afb1. --- flutter/lib/common/widgets/peers_view.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index e80e5ffe343..b18de82d995 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -128,9 +128,8 @@ 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; @@ -171,9 +170,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.value( - // https://pub.dev/packages/provider: If you already have an object instance and want to expose it, it would be best to use the .value constructor of a provider. - value: widget.peers, + return ChangeNotifierProvider( + create: (context) => widget.peers, child: Consumer(builder: (context, peers, child) { if (peers.peers.isEmpty) { gFFI.peerTabModel.setCurrentTabCachedPeers([]); @@ -188,7 +186,7 @@ class _PeersViewState extends State<_PeersView> ).paddingOnly(bottom: 10), Text( translate( - _emptyMessages[peers.loadEvent] ?? 'Empty', + _emptyMessages[widget.peers.loadEvent] ?? 'Empty', ), textAlign: TextAlign.center, style: TextStyle( From ffc73f86a0d75cc3f3c0ac02a133ef8312c77c72 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 26 Sep 2024 22:08:32 +0800 Subject: [PATCH 115/210] fix ab peers view, all peer tab use global peers model (#9475) Use ChangeNotifierProvider.value, and each peer tab has a global unique `Peers` model, then `load peers` and `build peers` will always be the same one. Signed-off-by: 21pages --- flutter/lib/common/widgets/address_book.dart | 109 ++++++++++--------- flutter/lib/common/widgets/my_group.dart | 8 +- flutter/lib/common/widgets/peers_view.dart | 79 +++++++------- flutter/lib/models/ab_model.dart | 6 + flutter/lib/models/group_model.dart | 9 +- flutter/lib/models/model.dart | 15 +++ 6 files changed, 134 insertions(+), 92 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index a0a456807a8..78bd20ef033 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -241,14 +241,15 @@ class _AddressBookState extends State { 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 { alignment: Alignment.topLeft, child: AddressBookPeersView( menuPadding: widget.menuPadding, - getInitPeers: () => gFFI.abModel.currentAbPeers, )), ); } @@ -509,19 +509,19 @@ class _AddressBookState extends State { 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 { ], ), 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 { 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, diff --git a/flutter/lib/common/widgets/my_group.dart b/flutter/lib/common/widgets/my_group.dart index 2d26536eb8f..867d71dff2d 100644 --- a/flutter/lib/common/widgets/my_group.dart +++ b/flutter/lib/common/widgets/my_group.dart @@ -83,8 +83,8 @@ class _MyGroupState extends State { 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 { child: Align( alignment: Alignment.topLeft, child: MyGroupPeerView( - menuPadding: widget.menuPadding, - getInitPeers: () => gFFI.groupModel.peers)), + menuPadding: widget.menuPadding, + )), ) ], ); diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index b18de82d995..32db418f5bb 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -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( - create: (context) => widget.peers, + return ChangeNotifierProvider.value( + value: widget.peers, child: Consumer(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 selectedTags, List 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) { diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 6f3820e86bb..0da84e0f26c 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -66,10 +66,16 @@ class AbModel { var listInitialized = false; var _maxPeerOneAb = 0; + late final Peers peersModel; + WeakReference 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) { diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index 184c94bfff3..b14ccd46b0e 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -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 pull({force = true, quiet = false}) async { if (bind.isDisableGroupPanel()) return; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 899affa62be..3f2dcade9ae 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -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'; @@ -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 From d4184fd865be29ce955bd2c95d89b486cb175cba Mon Sep 17 00:00:00 2001 From: rustdesk Date: Thu, 26 Sep 2024 23:07:53 +0800 Subject: [PATCH 116/210] bump to 1.3.2 --- .github/workflows/flutter-build.yml | 2 +- .github/workflows/playground.yml | 2 +- Cargo.lock | 4 ++-- Cargo.toml | 2 +- appimage/AppImageBuilder-aarch64.yml | 2 +- appimage/AppImageBuilder-x86_64.yml | 2 +- flatpak/rustdesk.json | 2 +- flutter/pubspec.yaml | 2 +- libs/portable/Cargo.toml | 2 +- res/PKGBUILD | 2 +- res/rpm-flutter-suse.spec | 2 +- res/rpm-flutter.spec | 2 +- res/rpm.spec | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index e3a791ea141..dd21515ba54 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -33,7 +33,7 @@ env: VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" # vcpkg version: 2024.07.12 VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1" - VERSION: "1.3.1" + VERSION: "1.3.2" NDK_VERSION: "r27b" #signing keys env variable checks ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index 205ce8f1ed5..efd6974a991 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -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 }}" diff --git a/Cargo.lock b/Cargo.lock index 486b7a6f9be..e1474f2f3a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index c71b2918bd2..1ee749afb80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustdesk" -version = "1.3.1" +version = "1.3.2" authors = ["rustdesk "] edition = "2021" build= "build.rs" diff --git a/appimage/AppImageBuilder-aarch64.yml b/appimage/AppImageBuilder-aarch64.yml index e23cde7172d..5ff9fc2a7b2 100644 --- a/appimage/AppImageBuilder-aarch64.yml +++ b/appimage/AppImageBuilder-aarch64.yml @@ -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: diff --git a/appimage/AppImageBuilder-x86_64.yml b/appimage/AppImageBuilder-x86_64.yml index 83df32ec87b..d8f0991cf95 100644 --- a/appimage/AppImageBuilder-x86_64.yml +++ b/appimage/AppImageBuilder-x86_64.yml @@ -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: diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json index 6d7acb5b89c..6b205aa9992 100644 --- a/flatpak/rustdesk.json +++ b/flatpak/rustdesk.json @@ -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" } ] diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 899a8bbecf1..cc3a2e6c53f 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -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+48 +version: 1.3.2+51 environment: sdk: '^3.1.0' diff --git a/libs/portable/Cargo.toml b/libs/portable/Cargo.toml index 10d16605b2b..7e60f7d1fac 100644 --- a/libs/portable/Cargo.toml +++ b/libs/portable/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustdesk-portable-packer" -version = "1.3.1" +version = "1.3.2" edition = "2021" description = "RustDesk Remote Desktop" diff --git a/res/PKGBUILD b/res/PKGBUILD index a805997cd88..616682e8f69 100644 --- a/res/PKGBUILD +++ b/res/PKGBUILD @@ -1,5 +1,5 @@ pkgname=rustdesk -pkgver=1.3.1 +pkgver=1.3.2 pkgrel=0 epoch= pkgdesc="" diff --git a/res/rpm-flutter-suse.spec b/res/rpm-flutter-suse.spec index c2c5be1f188..768b04c28d2 100644 --- a/res/rpm-flutter-suse.spec +++ b/res/rpm-flutter-suse.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.3.1 +Version: 1.3.2 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm-flutter.spec b/res/rpm-flutter.spec index 33a1314ccca..b62c18b3b71 100644 --- a/res/rpm-flutter.spec +++ b/res/rpm-flutter.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.3.1 +Version: 1.3.2 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm.spec b/res/rpm.spec index 11dccc84209..033e95937d2 100644 --- a/res/rpm.spec +++ b/res/rpm.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.3.1 +Version: 1.3.2 Release: 0 Summary: RPM package License: GPL-3.0 From beb1084e877f0ae302945f912a3ccf5aaf59ea84 Mon Sep 17 00:00:00 2001 From: hashiguchi <118793426+hashiguchi736@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:14:46 +0900 Subject: [PATCH 117/210] Change the minimum value of the bitrate slider to 5 (#9480) Signed-off-by: hashiguchi --- src/ui/header.tis | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/header.tis b/src/ui/header.tis index e99d398aa73..36aa624b48f 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -446,7 +446,7 @@ function handle_custom_image_quality() { var extendedBitrate = bitrate > 100; var maxRate = extendedBitrate ? 2000 : 100; msgbox("custom-image-quality", "Custom Image Quality", "
    \ -
    x% Bitrate More
    \ +
    x% Bitrate More
    \
    ", "", function(res=null) { if (!res) return; if (res.id === "extended-slider") { From 4459406578b9d46bc64eef33a9af32fde34c64c8 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:19:25 +0800 Subject: [PATCH 118/210] fix: windows, window, restore from minimized state (#9482) Signed-off-by: fufesou --- flutter/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 62f9283a000..b25d395dca6 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -335,7 +335,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "80b063b9d4e015f62e17f42a5aa0b3d20a365926" + resolved-ref: af2e9a51f18effd9796bebb2fa75e0b7ef079613 url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window" source: git version: "0.1.0" From 3e6938bec616e2d42f71416a8f4a126b87859909 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:56:10 +0800 Subject: [PATCH 119/210] refact: web desktop, web_id_input_tip (#9490) * refact: web desktop, web_id_input_tip Signed-off-by: fufesou * Update en.rs * Update cn.rs * Update en.rs --------- Signed-off-by: fufesou Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com> --- .../common/widgets/connection_page_title.dart | 38 +++++++++++++++++++ .../lib/desktop/pages/connection_page.dart | 33 +--------------- flutter/lib/mobile/pages/connection_page.dart | 8 +++- src/lang/ar.rs | 1 + src/lang/be.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/en.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/eu.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/uk.rs | 1 + src/lang/vn.rs | 1 + 47 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 flutter/lib/common/widgets/connection_page_title.dart diff --git a/flutter/lib/common/widgets/connection_page_title.dart b/flutter/lib/common/widgets/connection_page_title.dart new file mode 100644 index 00000000000..ba03c265696 --- /dev/null +++ b/flutter/lib/common/widgets/connection_page_title.dart @@ -0,0 +1,38 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../common.dart'; + +Widget getConnectionPageTitle(BuildContext context, bool isWeb) { + return Row( + children: [ + Expanded( + child: Row( + children: [ + AutoSizeText( + translate('Control Remote Desktop'), + maxLines: 1, + style: Theme.of(context) + .textTheme + .titleLarge + ?.merge(TextStyle(height: 1)), + ).marginOnly(right: 4), + Tooltip( + waitDuration: Duration(milliseconds: 300), + message: translate(isWeb ? "web_id_input_tip" : "id_input_tip"), + child: Icon( + Icons.help_outline_outlined, + size: 16, + color: Theme.of(context) + .textTheme + .titleLarge + ?.color + ?.withOpacity(0.5), + ), + ), + ], + )), + ], + ); +} diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index b2073ae4a57..744c05f9c22 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'dart:convert'; -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common/widgets/connection_page_title.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:get/get.dart'; @@ -323,36 +323,7 @@ class _ConnectionPageState extends State child: Ink( child: Column( children: [ - Row( - children: [ - Expanded( - child: Row( - children: [ - AutoSizeText( - translate('Control Remote Desktop'), - maxLines: 1, - style: Theme.of(context) - .textTheme - .titleLarge - ?.merge(TextStyle(height: 1)), - ).marginOnly(right: 4), - Tooltip( - waitDuration: Duration(milliseconds: 300), - message: translate("id_input_tip"), - child: Icon( - Icons.help_outline_outlined, - size: 16, - color: Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5), - ), - ), - ], - )), - ], - ).marginOnly(bottom: 15), + getConnectionPageTitle(context, false).marginOnly(bottom: 15), Row( children: [ Expanded( diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 181f36e58cc..e71cc1c5647 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:auto_size_text_field/auto_size_text_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/formatter/id_formatter.dart'; +import 'package:flutter_hbb/common/widgets/connection_page_title.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -349,9 +350,14 @@ class _ConnectionPageState extends State { ), ), ); + final child = Column(children: [ + if (isWebDesktop) + getConnectionPageTitle(context, true).marginOnly(bottom: 10, top: 15, left: 12), + w + ]); return Align( alignment: Alignment.topCenter, - child: Container(constraints: kMobilePageConstraints, child: w)); + child: Container(constraints: kMobilePageConstraints, child: child)); } @override diff --git a/src/lang/ar.rs b/src/lang/ar.rs index be1a6b7675a..0990614e2f0 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index fb8444becb5..2b9167a9cc2 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index b683a5293c4..7e7e7a05c21 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index a52da9735fd..ef53608de82 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 99b3284cbc6..cca8b75031d 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "被控端启用了单向文件传输"), ("Authentication Required", "需要身份验证"), ("Authenticate", "认证"), + ("web_id_input_tip", "可以输入同一个服务器内的 ID, web 客户端不支持直接 IP 访问。\n要访问另一台服务器上的设备,请附加服务器地址(@<服务器地址>?key=<密钥>)。比如,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=。\n要访问公共服务器上的设备,请输入 \"@public\", 无需密钥。"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 67588bfb848..c93dd37b7f9 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index aea1514ae93..f59accfe1c7 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index d4f72c94b01..826f97b88d5 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Die einseitige Dateiübertragung ist auf der kontrollierten Seite aktiviert."), ("Authentication Required", "Authentifizierung erforderlich"), ("Authenticate", "Authentifizieren"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index fecff28945f..779e71d23ea 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 55ef4470c55..7ed83a8fe4f 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -235,5 +235,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("network_error_tip", "Please check your network connection, then click retry."), ("enable-trusted-devices-tip", "Skip 2FA verification on trusted devices"), ("one-way-file-transfer-tip", "One-way file transfer is enabled on the controlled side."), + ("web_id_input_tip", "You can input an ID in the same server, direct IP access is not supported in web client.\nIf you want to access a device on another server, please append the server address (@?key=), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"@public\", the key is not needed for public server."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index eb512922ed3..3a406e55871 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index cb5e4a25b39..369cd5b9515 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "La transferencia en un sentido está habilitada en el lado controlado."), ("Authentication Required", "Se requiere autenticación"), ("Authenticate", "Autenticar"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 2443faae9b0..db7970f16e4 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 7c953ebe46e..90976236e57 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index a97566d99a1..71bea4e7892 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 6a9ca8d8de2..97f8d651406 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 5a42c4257c7..526873de3d2 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index 9a2b8e3a1a0..e210e7566b6 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index fb748b6b80d..ccb2be1cb02 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 9ed642459c1..5d5d06a662f 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index a07df2da922..7f49e60452e 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Sul lato controllato è abilitato il trasferimento file unidirezionale."), ("Authentication Required", "Richiesta autenticazione"), ("Authenticate", "Autentica"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index a63687c68f7..eca28da863e 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 9d9c29f9af2..a3c24e98cd7 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "단방향 파일 전송은 제어되는 쪽에서 활성화됩니다."), ("Authentication Required", "인증 필요함"), ("Authenticate", "인증"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index a23d3444830..404e9b511af 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index b818cb7e76e..a93cef88c3d 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 63a4a7dae4c..0cddcb951b2 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("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"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index eb2e21b683c..7f08f83d10c 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 6a0fa3e702a..bd1dc8d83f3 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Eenzijdige bestandsoverdracht is ingeschakeld aan de gecontroleerde kant."), ("Authentication Required", "Verificatie vereist"), ("Authenticate", "Verificatie"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 99bf570848f..a7216e78041 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 534efe12e68..994d5cc63db 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 4396dd40de3..ba6fe051b81 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index ea1d9888014..429c2534ab7 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index ba8d33f95db..8ad59802486 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "На управляемой стороне включена односторонняя передача файлов."), ("Authentication Required", "Требуется аутентификация"), ("Authenticate", "Аутентификация"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index e7b17c6aea8..1e5a51f9f55 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index acbaf2ea9db..bba9499d75a 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index aca5b6a7ace..b3e85a351e1 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index c12555c97d2..ee84a68e1b8 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 837f702f889..9ba9bb5362a 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 43fbbcfa509..e6211984348 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 1c5b0933377..a2f8bc9103c 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 10fa46880f4..78b09dca47e 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 0a3f291ac97..7708de9ecd4 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "被控端啟用了單向文件傳輸"), ("Authentication Required", "需要身分驗證"), ("Authenticate", "認證"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 41634facc39..05031dcc930 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "На керованій стороні ввімкнено одностороннє передавання файлів."), ("Authentication Required", "Потрібна автентифікація"), ("Authenticate", "Автентифікувати"), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 4f7bcf7d164..bc6311fcd9c 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -647,5 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", ""), ("Authentication Required", ""), ("Authenticate", ""), + ("web_id_input_tip", ""), ].iter().cloned().collect(); } From 9959217cc35b05248c252a0dba5301689dd24ce2 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:10:51 +0800 Subject: [PATCH 120/210] chore (#9491) Signed-off-by: fufesou --- src/lang/cn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/cn.rs b/src/lang/cn.rs index cca8b75031d..db942f598f0 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Plug out all", "拔出所有"), ("True color (4:4:4)", "真彩模式(4:4:4)"), ("Enable blocking user input", "允许阻止用户输入"), - ("id_input_tip", "可以输入 ID、直连 IP,或域名和端口号(<域名>:<端口号>)。\n要访问另一台服务器上的设备,请附加服务器地址(@<服务器地址>?key=<密钥>)。比如,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=。\n要访问公共服务器上的设备,请输入 \"@public\", 无需密钥。\n\n如果您想要在首次连接时,强制走中继连接,请在 ID 的后面添加 \"/r\",例如,\"9123456234/r\"。"), + ("id_input_tip", "可以输入 ID、直连 IP,或域名和端口号(<域名>:<端口号>)。\n要访问另一台服务器上的设备,请附加服务器地址(@<服务器地址>?key=<密钥>)。比如,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=。\n要访问公共服务器上的设备,请输入 \"@public\",无需密钥。\n\n如果您想要在首次连接时,强制走中继连接,请在 ID 的后面添加 \"/r\",例如,\"9123456234/r\"。"), ("privacy_mode_impl_mag_tip", "模式 1"), ("privacy_mode_impl_virtual_display_tip", "模式 2"), ("Enter privacy mode", "进入隐私模式"), @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "被控端启用了单向文件传输"), ("Authentication Required", "需要身份验证"), ("Authenticate", "认证"), - ("web_id_input_tip", "可以输入同一个服务器内的 ID, web 客户端不支持直接 IP 访问。\n要访问另一台服务器上的设备,请附加服务器地址(@<服务器地址>?key=<密钥>)。比如,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=。\n要访问公共服务器上的设备,请输入 \"@public\", 无需密钥。"), + ("web_id_input_tip", "可以输入同一个服务器内的 ID,web 客户端不支持直接 IP 访问。\n要访问另一台服务器上的设备,请附加服务器地址(@<服务器地址>?key=<密钥>)。比如,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=。\n要访问公共服务器上的设备,请输入 \"@public\",无需密钥。"), ].iter().cloned().collect(); } From 81b999cfbea26bcc23391c93965d6293a267c87b Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Sat, 28 Sep 2024 03:25:35 +0200 Subject: [PATCH 121/210] Update de.rs (#9496) --- src/lang/de.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 826f97b88d5..4a7bb08dbf7 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Plug out all", "Alle ausschalten"), ("True color (4:4:4)", "True Color (4:4:4)"), ("Enable blocking user input", "Blockieren von Benutzereingaben aktivieren"), - ("id_input_tip", "Sie können eine ID, eine direkte IP oder eine Domäne mit einem Port (:) eingeben.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen möchten, fügen Sie bitte die Serveradresse (@?key=) hinzu, zum Beispiel\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nWenn Sie auf ein Gerät auf einem öffentlichen Server zugreifen wollen, geben Sie bitte \"@public\" ein. Der Schlüssel wird für öffentliche Server nicht benötigt.\n\nWenn Sie bei der ersten Verbindung die Verwendung einer Relay-Verbindung erzwingen wollen, fügen Sie \"/r\" am Ende der ID hinzu, zum Beispiel \"9123456234/r\"."), + ("id_input_tip", "Sie können eine ID, eine direkte IP oder eine Domäne mit einem Port (:) eingeben.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen wollen, fügen Sie bitte die Serveradresse (@?key=) hinzu, zum Beispiel\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nWenn Sie auf ein Gerät auf einem öffentlichen Server zugreifen wollen, geben Sie bitte \"@public\" ein. Der Schlüssel wird für öffentliche Server nicht benötigt.\n\nWenn Sie bei der ersten Verbindung die Verwendung einer Relay-Verbindung erzwingen wollen, fügen Sie \"/r\" am Ende der ID hinzu, zum Beispiel \"9123456234/r\"."), ("privacy_mode_impl_mag_tip", "Modus 1"), ("privacy_mode_impl_virtual_display_tip", "Modus 2"), ("Enter privacy mode", "Datenschutzmodus aktivieren"), @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Die einseitige Dateiübertragung ist auf der kontrollierten Seite aktiviert."), ("Authentication Required", "Authentifizierung erforderlich"), ("Authenticate", "Authentifizieren"), - ("web_id_input_tip", ""), + ("web_id_input_tip", "Sie können eine ID auf demselben Server eingeben, direkter IP-Zugriff wird im Web-Client nicht unterstützt.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen wollen, fügen Sie bitte die Serveradresse (@?key=) hinzu, zum Beispiel\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nWenn Sie auf ein Gerät auf einem öffentlichen Server zugreifen wollen, geben Sie bitte \"@public\" ein. Der Schlüssel wird für öffentliche Server nicht benötigt."), ].iter().cloned().collect(); } From 769bbf1e1c82f0bb5f5065ebbca6d5094e345f05 Mon Sep 17 00:00:00 2001 From: solokot Date: Sat, 28 Sep 2024 04:25:52 +0300 Subject: [PATCH 122/210] Update ru.rs (#9495) --- src/lang/ru.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 8ad59802486..3ab54b42a08 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "На управляемой стороне включена односторонняя передача файлов."), ("Authentication Required", "Требуется аутентификация"), ("Authenticate", "Аутентификация"), - ("web_id_input_tip", ""), + ("web_id_input_tip", "Можно ввести ID на том же сервере, прямой доступ по IP в веб-клиенте не поддерживается.\nЕсли вы хотите получить доступ к устройству на другом сервере, добавьте адрес сервера (@<адрес_сервера>?key=<ключ>), например,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли вы хотите получить доступ к устройству на публичном сервере, введите \"@public\", для публичного сервера ключ не нужен."), ].iter().cloned().collect(); } From 3365844defe413e58e626ae59aedfe7e3050a308 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Sat, 28 Sep 2024 03:26:05 +0200 Subject: [PATCH 123/210] Update Italian language (#9493) --- src/lang/it.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 7f49e60452e..4d9ead853c8 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -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", "Disabilita rilevamento LAN"), + ("Deny LAN discovery", "Non effettuare rilevamento LAN"), ("Write a message", "Scrivi un messaggio"), ("Prompt", "Richiedi"), ("Please wait for confirmation of UAC...", "Attendi la conferma dell'UAC..."), @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Sul lato controllato è abilitato il trasferimento file unidirezionale."), ("Authentication Required", "Richiesta autenticazione"), ("Authenticate", "Autentica"), - ("web_id_input_tip", ""), + ("web_id_input_tip", "È possibile inserire un ID nello stesso server, nel client web non è supportato l'accesso con IP diretto.\nSe vuoi accedere ad un dispositivo in un altro server, aggiungi l'indirizzo del server (@?key=), ad esempio,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSe vuoi accedere ad un dispositivo in un server pubblico, inserisci \"@public\", la chiave non è necessaria per il server pubblico."), ].iter().cloned().collect(); } From f6261883e81a6012c60d61f7c447af49540529f8 Mon Sep 17 00:00:00 2001 From: Alex Rijckaert Date: Sat, 28 Sep 2024 09:33:08 +0200 Subject: [PATCH 124/210] Update nl.rs (#9500) --- src/lang/nl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index bd1dc8d83f3..dc94f4235aa 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Eenzijdige bestandsoverdracht is ingeschakeld aan de gecontroleerde kant."), ("Authentication Required", "Verificatie vereist"), ("Authenticate", "Verificatie"), - ("web_id_input_tip", ""), + ("web_id_input_tip", "Het is mogelijk om een ID in te voeren in de server zelf, in de webclient wordt directe IP-toegang niet ondersteund.\nAls je toegang wilt krijgen tot een apparaat op een andere server, voeg dan bijvoorbeeld het serveradres toe (@?key=),\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \“@public\” in, de sleutel is niet nodig voor de publieke server. ].iter().cloned().collect(); } From 1e822fa13577f71538e29ae1ef50686c597c1cf0 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Sat, 28 Sep 2024 15:49:11 +0800 Subject: [PATCH 125/210] Update nl.rs (#9501) --- src/lang/nl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index dc94f4235aa..960aa6234ae 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Eenzijdige bestandsoverdracht is ingeschakeld aan de gecontroleerde kant."), ("Authentication Required", "Verificatie vereist"), ("Authenticate", "Verificatie"), - ("web_id_input_tip", "Het is mogelijk om een ID in te voeren in de server zelf, in de webclient wordt directe IP-toegang niet ondersteund.\nAls je toegang wilt krijgen tot een apparaat op een andere server, voeg dan bijvoorbeeld het serveradres toe (@?key=),\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \“@public\” in, de sleutel is niet nodig voor de publieke server. + ("web_id_input_tip", "Het is mogelijk om een ID in te voeren in de server zelf, in de webclient wordt directe IP-toegang niet ondersteund.\nAls je toegang wilt krijgen tot een apparaat op een andere server, voeg dan bijvoorbeeld het serveradres toe (@?key=),\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \“@public\” in, de sleutel is niet nodig voor de publieke server.“), ].iter().cloned().collect(); } From 30a7847100857db44ec31614f68c5b8bc204047e Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sat, 28 Sep 2024 15:53:39 +0800 Subject: [PATCH 126/210] fix ci --- src/lang/nl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 960aa6234ae..df1d6a1e7e4 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Eenzijdige bestandsoverdracht is ingeschakeld aan de gecontroleerde kant."), ("Authentication Required", "Verificatie vereist"), ("Authenticate", "Verificatie"), - ("web_id_input_tip", "Het is mogelijk om een ID in te voeren in de server zelf, in de webclient wordt directe IP-toegang niet ondersteund.\nAls je toegang wilt krijgen tot een apparaat op een andere server, voeg dan bijvoorbeeld het serveradres toe (@?key=),\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \“@public\” in, de sleutel is niet nodig voor de publieke server.“), + ("web_id_input_tip", "Het is mogelijk om een ID in te voeren in de server zelf, in de webclient wordt directe IP-toegang niet ondersteund.\nAls je toegang wilt krijgen tot een apparaat op een andere server, voeg dan bijvoorbeeld het serveradres toe (@?key=),\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \"@public\" in, de sleutel is niet nodig voor de publieke server."), ].iter().cloned().collect(); } From 60a0099ba0a6ecf7b3e2a920964912e66228853d Mon Sep 17 00:00:00 2001 From: Iraccib <56926862+Iraccib@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:04:31 +0200 Subject: [PATCH 127/210] Update it.rs (#9503) Changed "Scehrmo" to "Schermo" --- src/lang/it.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 4d9ead853c8..68a24265999 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -559,7 +559,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Big tiles", "Icone grandi"), ("Small tiles", "Icone piccole"), ("List", "Elenco"), - ("Virtual display", "Scehrmo virtuale"), + ("Virtual display", "Schermo virtuale"), ("Plug out all", "Scollega tutto"), ("True color (4:4:4)", "Colore reale (4:4:4)"), ("Enable blocking user input", "Abilita blocco input utente"), From 2f5f701dc70b3638075e0731e203de90f2e84aa2 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sat, 28 Sep 2024 19:43:28 +0800 Subject: [PATCH 128/210] fix: windows, subwindow, scale (#9506) Signed-off-by: fufesou --- flutter/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index b25d395dca6..5a4a74ed676 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -335,7 +335,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: af2e9a51f18effd9796bebb2fa75e0b7ef079613 + resolved-ref: ad889c35b896435eb7c828e200db2fb6f41d2c3b url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window" source: git version: "0.1.0" From 4a745d82f63abbd0718c68cce4fabf8cd6c892be Mon Sep 17 00:00:00 2001 From: Alex Rijckaert Date: Sat, 28 Sep 2024 14:07:55 +0200 Subject: [PATCH 129/210] Update nl.rs (#9507) --- src/lang/nl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index df1d6a1e7e4..1b8a96e540d 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -647,6 +647,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "Eenzijdige bestandsoverdracht is ingeschakeld aan de gecontroleerde kant."), ("Authentication Required", "Verificatie vereist"), ("Authenticate", "Verificatie"), - ("web_id_input_tip", "Het is mogelijk om een ID in te voeren in de server zelf, in de webclient wordt directe IP-toegang niet ondersteund.\nAls je toegang wilt krijgen tot een apparaat op een andere server, voeg dan bijvoorbeeld het serveradres toe (@?key=),\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \"@public\" in, de sleutel is niet nodig voor de publieke server."), + ("web_id_input_tip", "Je kunt een ID invoeren op dezelfde server, directe IP-toegang wordt niet ondersteund in de webclient.\nAls je toegang wilt tot een apparaat op een andere server, voeg je het serveradres toe (@?key=), bijvoorbeeld,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \"@public\" in, sleutel is niet nodig voor de publieke server."), ].iter().cloned().collect(); } From d563372a91a1b1dcb552f6571828ccf081ea3f42 Mon Sep 17 00:00:00 2001 From: hashiguchi <118793426+hashiguchi736@users.noreply.github.com> Date: Sat, 28 Sep 2024 22:52:05 +0900 Subject: [PATCH 130/210] Change the value of kMinQuality to 5 (#9508) * Change the minimum value of the bitrate slider to 5 Signed-off-by: hashiguchi * Change the value of kMinQuality to 5 Signed-off-by: hashiguchi --------- Signed-off-by: hashiguchi --- flutter/lib/consts.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 17d5cec271b..1be9c7712c7 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -201,7 +201,7 @@ const double kMinFps = 5; const double kDefaultFps = 30; const double kMaxFps = 120; -const double kMinQuality = 10; +const double kMinQuality = 5; const double kDefaultQuality = 50; const double kMaxQuality = 100; const double kMaxMoreQuality = 2000; @@ -571,4 +571,4 @@ extension WindowsTargetExt on int { WindowsTarget get windowsVersion => getWindowsTarget(this); } -const kCheckSoftwareUpdateFinish = 'check_software_update_finish'; \ No newline at end of file +const kCheckSoftwareUpdateFinish = 'check_software_update_finish'; From 1ebc726acdd46d902d3c6444be8ede3152e65598 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Mon, 30 Sep 2024 10:54:05 +0800 Subject: [PATCH 131/210] fix ci --- flatpak/rustdesk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json index 6b205aa9992..6d7acb5b89c 100644 --- a/flatpak/rustdesk.json +++ b/flatpak/rustdesk.json @@ -17,7 +17,7 @@ "sources": [ { "type": "archive", - "url": "https://github.com/linux-pam/linux-pam/releases/download/v1.3.2/Linux-PAM-1.3.2.tar.xz", + "url": "https://github.com/linux-pam/linux-pam/releases/download/v1.3.1/Linux-PAM-1.3.1.tar.xz", "sha256": "eff47a4ecd833fbf18de9686632a70ee8d0794b79aecb217ebd0ce11db4cd0db" } ] From 4eca8b944797e32a9bc7755ca7ad0635bdec995a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 1 Oct 2024 07:04:43 +0800 Subject: [PATCH 132/210] fix: web, forget password (#9524) Signed-off-by: fufesou --- flutter/lib/web/bridge.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 6e758dae438..5f699114ae0 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -936,7 +936,7 @@ class RustdeskImpl { } Future mainForgetPassword({required String id, dynamic hint}) { - return Future(() => js.context.callMethod('setByName', ['forget'])); + return mainSetPeerOption(id: id, key: 'password', value: ''); } Future mainPeerHasPassword({required String id, dynamic hint}) { From b5414ec002b605dda20d7a57772a426c3ab29bc5 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 1 Oct 2024 07:09:17 +0800 Subject: [PATCH 133/210] fix https://github.com/rustdesk/rustdesk/issues/9527 --- flutter/lib/desktop/pages/file_manager_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 4677744197e..b75a946c06b 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -1020,7 +1020,7 @@ class _FileManagerViewState extends State { if (!entry.isDrive && versionCmp(_ffi.ffiModel.pi.version, "1.3.0") >= 0) mod_menu.PopupMenuItem( - child: Text("Rename"), + child: Text(translate("Rename")), height: CustomPopupMenuTheme.height, onTap: () { controller.renameAction(entry, isLocal); From 334526026cad032376166fa4b69f97db4bc63806 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:25:59 +0800 Subject: [PATCH 134/210] fix: web/mobile, skip querying onlines, if not in main page (#9535) * fix: web, skip querying onlines, if not in main page Signed-off-by: fufesou * fix: web/mobile, skip querying onlines Signed-off-by: fufesou * Set isInMainPage to false after router is changed. Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/common.dart | 3 +++ flutter/lib/common/widgets/peers_view.dart | 7 ++++++- flutter/lib/mobile/pages/home_page.dart | 2 ++ flutter/lib/models/state_model.dart | 3 +++ flutter/lib/models/web_model.dart | 11 ++++++++++- flutter/lib/web/settings_page.dart | 1 - 6 files changed, 24 insertions(+), 3 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 7b6ec4ae5fc..99a24781b95 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -680,10 +680,12 @@ closeConnection({String? id}) { overlays: SystemUiOverlay.values); gFFI.chatModel.hideChatOverlay(); Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/")); + stateGlobal.isInMainPage = true; }(); } else { if (isWeb) { Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/")); + stateGlobal.isInMainPage = true; } else { final controller = Get.find(); controller.closeBy(id); @@ -2405,6 +2407,7 @@ connect(BuildContext context, String id, ); } } + stateGlobal.isInMainPage = false; } FocusScopeNode currentFocus = FocusScope.of(context); diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 32db418f5bb..7f16850219f 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -332,7 +332,12 @@ class _PeersViewState extends State<_PeersView> _queryOnlines(false); } } else { - if (_isActive && (_queryCount < _maxQueryCount || !p)) { + final skipIfIsWeb = + isWeb && !(stateGlobal.isWebVisible && stateGlobal.isInMainPage); + final skipIfMobile = + (isAndroid || isIOS) && !stateGlobal.isInMainPage; + final skipIfNotActive = skipIfIsWeb || skipIfMobile || !_isActive; + if (!skipIfNotActive && (_queryCount < _maxQueryCount || !p)) { if (now.difference(_lastQueryTime) >= _queryInterval) { if (_curPeers.isNotEmpty) { bind.queryOnlines(ids: _curPeers.toList(growable: false)); diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index d26d9168542..e329acdfe17 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -6,6 +6,7 @@ import 'package:get/get.dart'; import '../../common.dart'; import '../../common/widgets/chat_page.dart'; import '../../models/platform_model.dart'; +import '../../models/state_model.dart'; import 'connection_page.dart'; abstract class PageShape extends Widget { @@ -159,6 +160,7 @@ class WebHomePage extends StatelessWidget { @override Widget build(BuildContext context) { + stateGlobal.isInMainPage = true; return Scaffold( // backgroundColor: MyTheme.grayBg, appBar: AppBar( diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index e18874785cf..a1b5fc736e9 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -19,6 +19,9 @@ class StateGlobal { final RxBool showRemoteToolBar = false.obs; final svcStatus = SvcStatus.notReady.obs; final RxBool isFocused = false.obs; + // for mobile and web + bool isInMainPage = true; + bool isWebVisible = true; final isPortrait = false.obs; diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index 4896781a9cc..ff0d1b806da 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -7,6 +7,7 @@ import 'dart:html'; import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/web/bridge.dart'; import 'package:flutter_hbb/common.dart'; @@ -28,7 +29,15 @@ class PlatformFFI { context.callMethod('setByName', [name, value]); } - PlatformFFI._(); + PlatformFFI._() { + window.document.addEventListener( + 'visibilitychange', + (event) => { + stateGlobal.isWebVisible = + window.document.visibilityState == 'visible' + }); + } + static final PlatformFFI instance = PlatformFFI._(); static get localeName => window.navigator.language; diff --git a/flutter/lib/web/settings_page.dart b/flutter/lib/web/settings_page.dart index 13ba6cb2f27..cbd79a718ec 100644 --- a/flutter/lib/web/settings_page.dart +++ b/flutter/lib/web/settings_page.dart @@ -95,4 +95,3 @@ class WebSettingsPage extends StatelessWidget { }); } } - From b0edfb8f7027bf8ad7284c47095118a056c0f158 Mon Sep 17 00:00:00 2001 From: hms5232 <43672033+hms5232@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:01:53 +0800 Subject: [PATCH 135/210] fix wrong terms in tw lang (#9541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "port" is "通訊埠", "連接埠" or just "埠" in Taiwan, "端口" is used in China. --- src/lang/tw.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 7708de9ecd4..c935b5552ff 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Plug out all", "拔出所有"), ("True color (4:4:4)", "全彩模式(4:4:4)"), ("Enable blocking user input", "允許封鎖使用者輸入"), - ("id_input_tip", "您可以輸入 ID、IP、或網域名稱+端口號(<網域名稱>:<端口號>)。\n如果您要存取位於其他伺服器上的設備,請在ID之後添加伺服器地址(@<伺服器地址>?key=<金鑰>)\n例如:9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\n要存取公共伺服器上的設備,請輸入\"@public\",不需輸入金鑰。\n\n如果您想要在第一次連線時,強制使用中繼連接,請在 ID 的末尾添加 \"/r\",例如,\"9123456234/r\"。"), + ("id_input_tip", "您可以輸入 ID、IP、或網域名稱+通訊埠號(<網域名稱>:<通訊埠號>)。\n如果您要存取位於其他伺服器上的設備,請在ID之後添加伺服器地址(@<伺服器地址>?key=<金鑰>)\n例如:9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\n要存取公共伺服器上的設備,請輸入\"@public\",不需輸入金鑰。\n\n如果您想要在第一次連線時,強制使用中繼連接,請在 ID 的末尾添加 \"/r\",例如,\"9123456234/r\"。"), ("privacy_mode_impl_mag_tip", "模式 1"), ("privacy_mode_impl_virtual_display_tip", "模式 2"), ("Enter privacy mode", "進入隱私模式"), @@ -602,7 +602,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no_need_privacy_mode_no_physical_displays_tip", "沒有物理螢幕,沒必要使用隱私模式。"), ("Follow remote cursor", "跟隨遠端游標"), ("Follow remote window focus", "跟隨遠端視窗焦點"), - ("default_proxy_tip", "預設代理協定及端口為 Socks5 和 1080"), + ("default_proxy_tip", "預設代理協定及通訊埠為 Socks5 和 1080"), ("no_audio_input_device_tip", "未找到音訊輸入裝置"), ("Incoming", "連入"), ("Outgoing", "連出"), From fd62751cb830d75b1b1395bc923527104352d3e9 Mon Sep 17 00:00:00 2001 From: BennyBeat Date: Thu, 3 Oct 2024 07:02:24 +0000 Subject: [PATCH 136/210] Improved Catalan translation (#9546) * Improved Catalan translation Improved Catalan translation * Update ca.rs Improved Catalan translation --- src/lang/ca.rs | 926 ++++++++++++++++++++++++------------------------- 1 file changed, 463 insertions(+), 463 deletions(-) diff --git a/src/lang/ca.rs b/src/lang/ca.rs index ef53608de82..4331e16be6e 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -2,651 +2,651 @@ lazy_static::lazy_static! { pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ("Status", "Estat"), - ("Your Desktop", "El teu escriptori"), - ("desk_tip", "Pots accedir al teu escriptori amb aquest ID i contrasenya."), + ("Your Desktop", "Aquest ordinador"), + ("desk_tip", "Es pot accedir a aquest equip mitjançant les credencials:"), ("Password", "Contrasenya"), - ("Ready", "Llest"), - ("Established", "Establert"), - ("connecting_status", "Connexió a la xarxa RustDesk en progrés..."), - ("Enable service", "Habilita el servei"), - ("Start service", "Inicia el servei"), - ("Service is running", "El servei s'està executant"), - ("Service is not running", "El servei no s'està executant"), - ("not_ready_status", "No està llest. Comprova la teva connexió"), - ("Control Remote Desktop", "Controlar escriptori remot"), - ("Transfer file", "Transferir arxiu"), - ("Connect", "Connectar"), + ("Ready", "Preparat."), + ("Established", "S'ha establert."), + ("connecting_status", "S'està connectant a la xarxa de RustDesk..."), + ("Enable service", "Habilita el servei."), + ("Start service", "Inicia el servei."), + ("Service is running", "El servei s'està executant."), + ("Service is not running", "El servei no s'està executant."), + ("not_ready_status", "No disponible. Verifiqueu la connexió"), + ("Control Remote Desktop", "Dispositiu remot"), + ("Transfer file", "Transfereix fitxers"), + ("Connect", "Connecta"), ("Recent sessions", "Sessions recents"), - ("Address book", "Directori"), + ("Address book", "Llibreta d'adreces"), ("Confirmation", "Confirmació"), ("TCP tunneling", "Túnel TCP"), - ("Remove", "Eliminar"), + ("Remove", "Suprimeix"), ("Refresh random password", "Actualitza la contrasenya aleatòria"), - ("Set your own password", "Estableix la teva contrasenya"), + ("Set your own password", "Establiu la vostra contrasenya"), ("Enable keyboard/mouse", "Habilita el teclat/ratolí"), - ("Enable clipboard", "Habilita el portapapers"), - ("Enable file transfer", "Habilita la transferència d'arxius"), + ("Enable clipboard", "Habilita el porta-retalls"), + ("Enable file transfer", "Habilita la transferència de fitxers"), ("Enable TCP tunneling", "Habilita el túnel TCP"), ("IP Whitelisting", "Adreces IP admeses"), - ("ID/Relay Server", "Servidor ID/Relay"), + ("ID/Relay Server", "ID/Repetidor del Servidor"), ("Import server config", "Importa la configuració del servidor"), ("Export Server Config", "Exporta la configuració del servidor"), - ("Import server configuration successfully", "Configuració del servidor importada amb èxit"), - ("Export server configuration successfully", "Configuració del servidor exportada con èxit"), - ("Invalid server configuration", "Configuració del servidor incorrecta"), - ("Clipboard is empty", "El portapapers està buit"), + ("Import server configuration successfully", "S'ha importat la configuració del servidor correctament"), + ("Export server configuration successfully", "S'ha exportat la configuració del servidor correctament"), + ("Invalid server configuration", "Configuració del servidor no vàlida"), + ("Clipboard is empty", "El porta-retalls és buit"), ("Stop service", "Atura el servei"), - ("Change ID", "Canviar ID"), - ("Your new ID", "La teva nova ID"), - ("length %min% to %max%", "Ha de tenir entre %min% i %max% caràcters"), - ("starts with a letter", "comença amb una lletra"), - ("allowed characters", "caràcters permesos"), - ("id_change_tip", "Només pots utilitzar caràcters a-z, A-Z, 0-9 e _ (guionet baix). El primer caràcter ha de ser a-z o A-Z. La longitut ha d'estar entre 6 i 16 caràcters."), + ("Change ID", "Canvia la ID"), + ("Your new ID", "Identificador nou"), + ("length %min% to %max%", "Entre %min% i %max% caràcters"), + ("starts with a letter", "Comença amb una lletra"), + ("allowed characters", "Caràcters admesos"), + ("id_change_tip", "Els caràcters admesos són: a-z, A-Z, 0-9, _ (guió baix). El primer caràcter ha de ser a-z/A-Z, i una mida de 6 a 16 caràcters."), ("Website", "Lloc web"), - ("About", "Sobre"), - ("Slogan_tip", ""), - ("Privacy Statement", "Declaració de privacitat"), + ("About", "Quant al RustDesk"), + ("Slogan_tip", "Fet de tot cor dins d'aquest món caòtic!\nTraducció: Benet R. i Camps (BennyBeat)."), + ("Privacy Statement", "Declaració de privadesa"), ("Mute", "Silencia"), - ("Build Date", "Data de creació"), + ("Build Date", "Data de compilació"), ("Version", "Versió"), ("Home", "Inici"), ("Audio Input", "Entrada d'àudio"), ("Enhancements", "Millores"), - ("Hardware Codec", "Còdec de hardware"), - ("Adaptive bitrate", "Tasa de bits adaptativa"), - ("ID Server", "Servidor de IDs"), - ("Relay Server", "Servidor Relay"), - ("API Server", "Servidor API"), + ("Hardware Codec", "Codificació per maquinari"), + ("Adaptive bitrate", "Taxa de bits adaptativa"), + ("ID Server", "ID del servidor"), + ("Relay Server", "Repetidor del servidor"), + ("API Server", "Clau API del servidor"), ("invalid_http", "ha de començar amb http:// o https://"), - ("Invalid IP", "IP incorrecta"), - ("Invalid format", "Format incorrecte"), - ("server_not_support", "Encara no és compatible amb el servidor"), + ("Invalid IP", "IP no vàlida"), + ("Invalid format", "Format no vàlid"), + ("server_not_support", "Encara no suportat pel servidor"), ("Not available", "No disponible"), - ("Too frequent", "Massa comú"), + ("Too frequent", "Massa freqüent"), ("Cancel", "Cancel·la"), - ("Skip", "Salta"), - ("Close", "Tanca"), - ("Retry", "Reintenta"), + ("Skip", "Omet"), + ("Close", "Surt"), + ("Retry", "Torna a provar"), ("OK", "D'acord"), - ("Password Required", "Es necessita la contrasenya"), - ("Please enter your password", "Introdueix la teva contrasenya"), + ("Password Required", "Contrasenya requerida"), + ("Please enter your password", "Inseriu la contrasenya"), ("Remember password", "Recorda la contrasenya"), - ("Wrong Password", "Contrasenya incorrecta"), - ("Do you want to enter again?", "Vols tornar a entrar?"), + ("Wrong Password", "Contrasenya no vàlida"), + ("Do you want to enter again?", "Voleu tornar a provar?"), ("Connection Error", "Error de connexió"), ("Error", "Error"), - ("Reset by the peer", "Reestablert pel peer"), - ("Connecting...", "Connectant..."), - ("Connection in progress. Please wait.", "Connexió en procés. Espera."), - ("Please try 1 minute later", "Torna a provar-ho d'aquí un minut"), - ("Login Error", "Error d'inici de sessió"), - ("Successful", "Exitós"), - ("Connected, waiting for image...", "Connectant, esperant imatge..."), + ("Reset by the peer", "Restablert pel client"), + ("Connecting...", "S'està connectant..."), + ("Connection in progress. Please wait.", "S'està connectant. Espereu..."), + ("Please try 1 minute later", "Torneu a provar en 1 minut"), + ("Login Error", "Error d'accés"), + ("Successful", "Correcte"), + ("Connected, waiting for image...", "S'ha connectat; en espera de rebre la imatge..."), ("Name", "Nom"), ("Type", "Tipus"), ("Modified", "Modificat"), - ("Size", "Grandària"), - ("Show Hidden Files", "Mostra arxius ocults"), + ("Size", "Mida"), + ("Show Hidden Files", "Mostra els fitxers ocults"), ("Receive", "Rep"), ("Send", "Envia"), - ("Refresh File", "Actualitza el fitxer"), + ("Refresh File", "Actualitza"), ("Local", "Local"), ("Remote", "Remot"), - ("Remote Computer", "Ordinador remot"), - ("Local Computer", "Ordinador local"), - ("Confirm Delete", "Confirma l'eliminació"), - ("Delete", "Elimina"), + ("Remote Computer", "Dispositiu remot"), + ("Local Computer", "Aquest ordinador"), + ("Confirm Delete", "Confirmació de supressió"), + ("Delete", "Suprimeix"), ("Properties", "Propietats"), ("Multi Select", "Selecció múltiple"), - ("Select All", "Selecciona-ho tot"), - ("Unselect All", "Deselecciona-ho tot"), + ("Select All", "Seleciona-ho tot"), + ("Unselect All", "Desselecciona-ho tot"), ("Empty Directory", "Carpeta buida"), ("Not an empty directory", "No és una carpeta buida"), - ("Are you sure you want to delete this file?", "Segur que vols eliminar aquest fitxer?"), - ("Are you sure you want to delete this empty directory?", "Segur que vols eliminar aquesta carpeta buida?"), - ("Are you sure you want to delete the file of this directory?", "Segur que vols eliminar aquest fitxer d'aquesta car`peta?"), - ("Do this for all conflicts", "Fes això per a tots els conflictes"), - ("This is irreversible!", "Això és irreversible!"), - ("Deleting", "Eliminant"), + ("Are you sure you want to delete this file?", "Segur que voleu suprimir aquest fitxer?"), + ("Are you sure you want to delete this empty directory?", "Segur que voleu suprimir aquesta carpeta buida?"), + ("Are you sure you want to delete the file of this directory?", "Segur que voleu suprimir el fitxer d'aquesta carpeta?"), + ("Do this for all conflicts", "Aplica aquesta acció per a tots els conflictes"), + ("This is irreversible!", "Aquesta acció no es pot desfer!"), + ("Deleting", "S'està suprimint"), ("files", "fitxers"), - ("Waiting", "Esperant"), - ("Finished", "Acabat"), + ("Waiting", "En espera"), + ("Finished", "Ha finalitzat"), ("Speed", "Velocitat"), ("Custom Image Quality", "Qualitat d'imatge personalitzada"), ("Privacy mode", "Mode privat"), - ("Block user input", "Bloqueja l'entrada d'usuari"), - ("Unblock user input", "Desbloqueja l'entrada d'usuari"), + ("Block user input", "Bloca el control a l'usuari"), + ("Unblock user input", "Desbloca el control a l'usuari"), ("Adjust Window", "Ajusta la finestra"), ("Original", "Original"), - ("Shrink", "Reduir"), - ("Stretch", "Estirar"), + ("Shrink", "Encongida"), + ("Stretch", "Ampliada"), ("Scrollbar", "Barra de desplaçament"), ("ScrollAuto", "Desplaçament automàtic"), ("Good image quality", "Bona qualitat d'imatge"), - ("Balanced", "Equilibrat"), + ("Balanced", "Equilibrada"), ("Optimize reaction time", "Optimitza el temps de reacció"), - ("Custom", "Personalitzat"), + ("Custom", "Personalitzada"), ("Show remote cursor", "Mostra el cursor remot"), - ("Show quality monitor", "Mostra la qualitat del monitor"), - ("Disable clipboard", "Deshabilita el portapapers"), - ("Lock after session end", "Bloqueja després del final de la sessió"), + ("Show quality monitor", "Mostra la informació de flux"), + ("Disable clipboard", "Inhabilita el porta-retalls"), + ("Lock after session end", "Bloca en finalitzar la sessió"), ("Insert", "Insereix"), - ("Insert Lock", "Insereix bloqueig"), + ("Insert Lock", "Bloca"), ("Refresh", "Actualitza"), - ("ID does not exist", "L'ID no existeix"), - ("Failed to connect to rendezvous server", "No es pot connectar al servidor rendezvous"), - ("Please try later", "Prova-ho més tard"), - ("Remote desktop is offline", "L'escriptori remot està desconecctat"), + ("ID does not exist", "Aquesta ID no existeix"), + ("Failed to connect to rendezvous server", "Ha fallat en connectar al servidor assignat"), + ("Please try later", "Proveu més tard"), + ("Remote desktop is offline", "El dispositiu remot està desconnectat"), ("Key mismatch", "La clau no coincideix"), - ("Timeout", "Temps esgotat"), - ("Failed to connect to relay server", "No es pot connectar al servidor de relay"), - ("Failed to connect via rendezvous server", "No es pot connectar a través del servidor de rendezvous"), - ("Failed to connect via relay server", "No es pot connectar a través del servidor de relay"), - ("Failed to make direct connection to remote desktop", "No s'ha pogut establir una connexió directa amb l'escriptori remot"), - ("Set Password", "Configura la contrasenya"), - ("OS Password", "contrasenya del sistema operatiu"), - ("install_tip", ""), - ("Click to upgrade", "Clica per a actualitzar"), - ("Click to download", "Clica per a descarregar"), - ("Click to update", "Clica per a refrescar"), - ("Configure", "Configurr"), - ("config_acc", ""), - ("config_screen", "Configurar pantalla"), - ("Installing ...", "Instal·lant ..."), + ("Timeout", "S'ha exhaurit el temps"), + ("Failed to connect to relay server", "Ha fallat en connectar amb el repetidor del servidor"), + ("Failed to connect via rendezvous server", "Ha fallat en connectar mitjançant el servidor assignat"), + ("Failed to connect via relay server", "Ha fallat en connectar mitjançant el repetidor del servidor"), + ("Failed to make direct connection to remote desktop", "Ha fallat la connexió directa amb el dispositiu remot"), + ("Set Password", "Establiu una contrasenya"), + ("OS Password", "Contrasenya del sistema"), + ("install_tip", "En alguns casos és possible que el RustDesk no funcioni correctament per les restriccions UAC («User Account Control»; Control de comptes d'usuari). Per evitar aquest problema, instal·leu el RustDesk al vostre sistema."), + ("Click to upgrade", "Feu clic per a actualitzar"), + ("Click to download", "Feu clic per a baixar"), + ("Click to update", "Feu clic per a actualitzar"), + ("Configure", "Configura"), + ("config_acc", "Per a poder controlar el dispositiu remotament, faciliteu al RustDesk els permisos d'accessibilitat."), + ("config_screen", "Per a poder controlar el dispositiu remotament, faciliteu al RustDesk els permisos de gravació de pantalla."), + ("Installing ...", "S'està instal·lant..."), ("Install", "Instal·la"), ("Installation", "Instal·lació"), - ("Installation Path", "Ruta d'instal·lació"), - ("Create start menu shortcuts", "Crear accessos directes al menú d'inici"), - ("Create desktop icon", "Crear icona d'escriptori"), - ("agreement_tip", ""), - ("Accept and Install", "Acceptar i instal·la"), + ("Installation Path", "Ruta de la instal·lació"), + ("Create start menu shortcuts", "Crea una drecera al menú d'inici"), + ("Create desktop icon", "Crea una icona a l'escriptori"), + ("agreement_tip", "En iniciar la instal·lació, esteu acceptant l'acord de llicència d'usuari."), + ("Accept and Install", "Accepta i instal·la"), ("End-user license agreement", "Acord de llicència d'usuari final"), - ("Generating ...", "Generant ..."), - ("Your installation is lower version.", "La teva instal·lació és una versión inferior."), - ("not_close_tcp_tip", ""), - ("Listening ...", "Escoltant..."), - ("Remote Host", "Hoste remot"), + ("Generating ...", "S'està generant..."), + ("Your installation is lower version.", "La instal·lació actual és una versió inferior"), + ("not_close_tcp_tip", "No tanqueu aquesta finestra mentre utilitzeu el túnel"), + ("Listening ...", "S'està escoltant..."), + ("Remote Host", "Amfitrió remot"), ("Remote Port", "Port remot"), ("Action", "Acció"), ("Add", "Afegeix"), ("Local Port", "Port local"), - ("Local Address", "Adreça Local"), + ("Local Address", "Adreça local"), ("Change Local Port", "Canvia el port local"), - ("setup_server_tip", ""), - ("Too short, at least 6 characters.", "Massa curt, almenys 6 caràcters."), - ("The confirmation is not identical.", "La confirmació no coincideix."), + ("setup_server_tip", "Per a connexions més ràpides o privades, configureu el vostre servidor"), + ("Too short, at least 6 characters.", "Massa curt. Són necessaris almenys 6 caràcters."), + ("The confirmation is not identical.", "Les contrasenyes no coincideixen."), ("Permissions", "Permisos"), ("Accept", "Accepta"), - ("Dismiss", "Cancel·la"), + ("Dismiss", "Ignora"), ("Disconnect", "Desconnecta"), - ("Enable file copy and paste", "Permet copiar i enganxar fitxers"), + ("Enable file copy and paste", "Habilita la còpia i enganxament de fitxers"), ("Connected", "Connectat"), - ("Direct and encrypted connection", "Connexió directa i xifrada"), - ("Relayed and encrypted connection", "Connexió retransmesa i xifrada"), - ("Direct and unencrypted connection", "Connexió directa i sense xifrar"), - ("Relayed and unencrypted connection", "Connexió retransmesa i sense xifrar"), - ("Enter Remote ID", "Introdueix l'ID remot"), - ("Enter your password", "Introdueix la teva contrasenya"), - ("Logging in...", "Iniciant sessió..."), + ("Direct and encrypted connection", "Connexió xifrada directa"), + ("Relayed and encrypted connection", "Connexió xifrada per repetidor"), + ("Direct and unencrypted connection", "Connexió directa sense xifratge"), + ("Relayed and unencrypted connection", "Connexió per repetidor sense xifratge"), + ("Enter Remote ID", "Inseriu la ID remota"), + ("Enter your password", "Inseriu la contrasenya"), + ("Logging in...", "S'està iniciant..."), ("Enable RDP session sharing", "Habilita l'ús compartit de sessions RDP"), ("Auto Login", "Inici de sessió automàtic"), - ("Enable direct IP access", "Habilita accés IP directe"), - ("Rename", "Renombra"), + ("Enable direct IP access", "Habilita l'accés directe per IP"), + ("Rename", "Reanomena"), ("Space", "Espai"), - ("Create desktop shortcut", "Crea accés directe a l'escriptori"), + ("Create desktop shortcut", "Crea una drecera a l'escriptori"), ("Change Path", "Canvia la ruta"), - ("Create Folder", "Crea carpeta"), - ("Please enter the folder name", "Indica el nom de la carpeta"), - ("Fix it", "Soluciona-ho"), - ("Warning", "Avís"), - ("Login screen using Wayland is not supported", "La pantalla d'inici de sessió amb Wayland no és compatible"), + ("Create Folder", "Carpeta nova"), + ("Please enter the folder name", "Inseriu el nom de la carpeta"), + ("Fix it", "Repara"), + ("Warning", "Atenció"), + ("Login screen using Wayland is not supported", "L'inici de sessió amb Wayland encara no és compatible"), ("Reboot required", "Cal reiniciar"), ("Unsupported display server", "Servidor de visualització no compatible"), ("x11 expected", "x11 necessari"), ("Port", "Port"), - ("Settings", "Ajustaments"), - ("Username", " Nom d'usuari"), - ("Invalid port", "Port incorrecte"), - ("Closed manually by the peer", "Tancat manualment pel peer"), - ("Enable remote configuration modification", "Habilitar modificació remota de configuració"), - ("Run without install", "Executa sense instal·lar"), - ("Connect via relay", "Connecta per relay"), - ("Always connect via relay", "Connecta sempre a través de relay"), - ("whitelist_tip", ""), - ("Login", "Inicia sessió"), + ("Settings", "Configuració"), + ("Username", "Nom d'usuari"), + ("Invalid port", "Port no vàlid"), + ("Closed manually by the peer", "Tancat manualment pel client"), + ("Enable remote configuration modification", "Habilita la modificació remota de la configuració"), + ("Run without install", "Inicia sense instal·lar"), + ("Connect via relay", "Connecta mitjançant un repetidor"), + ("Always connect via relay", "Connecta sempre mitjançant un repetidor"), + ("whitelist_tip", "Només les IP admeses es podran connectar"), + ("Login", "Inicia la sessió"), ("Verify", "Verifica"), ("Remember me", "Recorda'm"), ("Trust this device", "Confia en aquest dispositiu"), ("Verification code", "Codi de verificació"), - ("verification_tip", ""), - ("Logout", "Sortir"), + ("verification_tip", "S'ha enviat un codi de verificació al correu-e registrat. Inseriu-lo per a continuar amb l'inici de sessió."), + ("Logout", "Tanca la sessió"), ("Tags", "Etiquetes"), - ("Search ID", "Cerca ID"), - ("whitelist_sep", ""), - ("Add ID", "Afegir ID"), - ("Add Tag", "Afegir tag"), - ("Unselect all tags", "Deseleccionar tots els tags"), - ("Network error", "Error de xarxa"), - ("Username missed", "Nom d'usuari oblidat"), - ("Password missed", "Contrasenya oblidada"), - ("Wrong credentials", "Credencials incorrectes"), - ("The verification code is incorrect or has expired", "El codi de verificació es incorrecte o ha expirat"), - ("Edit Tag", "Editar tag"), + ("Search ID", "Cerca per ID"), + ("whitelist_sep", "Separades per coma, punt i coma, espai o una adreça per línia"), + ("Add ID", "Afegeix una ID"), + ("Add Tag", "Afegeix una etiqueta"), + ("Unselect all tags", "Desselecciona totes les etiquetes"), + ("Network error", "Error de la xarxa"), + ("Username missed", "No s'ha indicat el nom d'usuari"), + ("Password missed", "No s'ha indicat la contrasenya"), + ("Wrong credentials", "Credencials errònies"), + ("The verification code is incorrect or has expired", "El codi de verificació no és vàlid o ha caducat"), + ("Edit Tag", "Edita l'etiqueta"), ("Forget Password", "Contrasenya oblidada"), ("Favorites", "Preferits"), - ("Add to Favorites", "Afegir a preferits"), - ("Remove from Favorites", "Treure de preferits"), - ("Empty", "Buit"), - ("Invalid folder name", "Nom de carpeta incorrecte"), - ("Socks5 Proxy", "Proxy Socks5"), - ("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"), + ("Add to Favorites", "Afegeix als preferits"), + ("Remove from Favorites", "Suprimeix dels preferits"), + ("Empty", "Buida"), + ("Invalid folder name", "Nom de carpeta no vàlid"), + ("Socks5 Proxy", "Servidor intermediari Socks5"), + ("Socks5/Http(s) Proxy", "Servidor intermediari Socks5/Http(s)"), ("Discovered", "Descobert"), - ("install_daemon_tip", ""), - ("Remote ID", "ID remot"), + ("install_daemon_tip", "Per a iniciar durant l'arrencada del sistema, heu d'instal·lar el servei."), + ("Remote ID", "ID remota"), ("Paste", "Enganxa"), - ("Paste here?", "Enganxa-ho aquí?"), - ("Are you sure to close the connection?", "Segur que vols tancar la connexió?"), - ("Download new version", "Descarrega una nova versió"), + ("Paste here?", "Voleu enganxar aquí?"), + ("Are you sure to close the connection?", "Segur que voleu finalitzar la connexió?"), + ("Download new version", "Baixa la versió nova"), ("Touch mode", "Mode tàctil"), ("Mouse mode", "Mode ratolí"), - ("One-Finger Tap", "Toca amb un dit"), - ("Left Mouse", "Ratolí esquerra"), - ("One-Long Tap", "Toc llarg"), - ("Two-Finger Tap", "Toqui amb dos dits"), + ("One-Finger Tap", "Toc amb un dit"), + ("Left Mouse", "Botó esquerre"), + ("One-Long Tap", "Toc prolongat"), + ("Two-Finger Tap", "Toc amb dos dits"), ("Right Mouse", "Botó dret"), - ("One-Finger Move", "Moviment amb un dir"), - ("Double Tap & Move", "Toca dos cops i mogui"), - ("Mouse Drag", "Arrossega amb el ratolí"), - ("Three-Finger vertically", "Tres dits verticalment"), + ("One-Finger Move", "Moviment amb un dit"), + ("Double Tap & Move", "Toc doble i moveu"), + ("Mouse Drag", "Arrossega el ratolí"), + ("Three-Finger vertically", "Tres dits en vertical"), ("Mouse Wheel", "Roda del ratolí"), ("Two-Finger Move", "Moviment amb dos dits"), ("Canvas Move", "Moviment del llenç"), - ("Pinch to Zoom", "Pessiga per fer zoom"), - ("Canvas Zoom", "Amplia el llenç"), - ("Reset canvas", "Reestableix el llenç"), - ("No permission of file transfer", "No tens permís de transferència de fitxers"), + ("Pinch to Zoom", "Pessic per escalar"), + ("Canvas Zoom", "escala del llenç"), + ("Reset canvas", "Reinici del llenç"), + ("No permission of file transfer", "Cap permís per a transferència de fitxers"), ("Note", "Nota"), ("Connection", "Connexió"), - ("Share Screen", "Comparteix la pantalla"), + ("Share Screen", "Compartició de pantalla"), ("Chat", "Xat"), ("Total", "Total"), - ("items", "ítems"), + ("items", "elements"), ("Selected", "Seleccionat"), ("Screen Capture", "Captura de pantalla"), ("Input Control", "Control d'entrada"), ("Audio Capture", "Captura d'àudio"), - ("File Connection", "Connexió d'arxius"), + ("File Connection", "Connexió de fitxer"), ("Screen Connection", "Connexió de pantalla"), - ("Do you accept?", "Acceptes?"), - ("Open System Setting", "Configuració del sistema obert"), - ("How to get Android input permission?", "Com obtenir el permís d'entrada d'Android?"), - ("android_input_permission_tip1", "Per a que un dispositiu remot controli el seu dispositiu Android amb el ratolí o tocs, cal permetre que RustDesk utilitzi el servei d' \"Accesibilitat\"."), - ("android_input_permission_tip2", "Vés a la pàgina de [Serveis instal·lats], activa el servei [RustDesk Input]."), - ("android_new_connection_tip", "S'ha rebut una nova sol·licitud de control per al dispositiu actual."), - ("android_service_will_start_tip", "Habilitar la captura de pantalla iniciarà el servei automàticament, i permetrà que altres dispositius sol·licitin una connexió des d'aquest dispositiu."), - ("android_stop_service_tip", "Tancar el servei tancarà totes les connexions establertes."), - ("android_version_audio_tip", "La versión actual de Android no admet la captura d'àudio, actualizi a Android 10 o superior."), - ("android_start_service_tip", ""), - ("android_permission_may_not_change_tip", ""), + ("Do you accept?", "Voleu acceptar?"), + ("Open System Setting", "Obre la configuració del sistema"), + ("How to get Android input permission?", "Com modificar els permisos a Android?"), + ("android_input_permission_tip1", "Per a controlar de forma remota el vostre dispositiu amb gestos o un ratolí, heu de permetre al RustDesk l'ús del servei «Accessibilitat»."), + ("android_input_permission_tip2", "A l'apartat Configuració del sistema de la pàgina següent, aneu a «Serveis baixats», i activeu el «RustDesk Input»."), + ("android_new_connection_tip", "S'ha rebut una petició nova per a controlar el vostre dispositiu."), + ("android_service_will_start_tip", "Activant «Gravació de pantalla» s'iniciarà automàticament el servei que permet a altres enviar sol·licituds de connexió cap al vostre dispositiu."), + ("android_stop_service_tip", "Tancant el servei finalitzaran automàticament les connexions en ús."), + ("android_version_audio_tip", "Aquesta versió d'Android no suporta la captura d'àudio. Actualitzeu a Android 10 o superior."), + ("android_start_service_tip", "Toqueu a «Inicia el servei» o activeu el permís «Captura de pantalla» per a iniciar el servei de compartició de pantalla."), + ("android_permission_may_not_change_tip", "Els permisos per a les connexions ja establertes poden no canviar, fins que no torneu a connectar."), ("Account", "Compte"), - ("Overwrite", "Sobreescriu"), - ("This file exists, skip or overwrite this file?", "Aquest fitxer ja existeix, ometre o sobreescriure l'arxiu?"), + ("Overwrite", "Reemplaça"), + ("This file exists, skip or overwrite this file?", "Aquest fitxer ja existeix. Voleu ometre o reemplaçar l'original?"), ("Quit", "Surt"), ("Help", "Ajuda"), ("Failed", "Ha fallat"), - ("Succeeded", "Aconseguit"), - ("Someone turns on privacy mode, exit", "Algú ha activat el mode de privacitat, surt"), + ("Succeeded", "Fet"), + ("Someone turns on privacy mode, exit", "S'ha activat el Mode privat; surt"), ("Unsupported", "No suportat"), - ("Peer denied", "Peer denegat"), - ("Please install plugins", "Instal·la els complements"), - ("Peer exit", "El peer ha sortit"), - ("Failed to turn off", "Error en apagar"), - ("Turned off", "Apagat"), + ("Peer denied", "Client denegat"), + ("Please install plugins", "Instal·leu els complements"), + ("Peer exit", "Finalitzat pel client"), + ("Failed to turn off", "Ha fallat en desactivar"), + ("Turned off", "Desactivat"), ("Language", "Idioma"), - ("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"), - ("Ignore Battery Optimizations", "Ignora optimizacions de la bateria"), - ("android_open_battery_optimizations_tip", ""), - ("Start on boot", "Engega en l'arrencada"), - ("Start the screen sharing service on boot, requires special permissions", "Engega el servei de captura de pantalla en l'arrencada, requereix permisos especials"), - ("Connection not allowed", "Connexió no disponible"), + ("Keep RustDesk background service", "Manté el servei del RustDesk en rerefons"), + ("Ignore Battery Optimizations", "Ignora les optimitzacions de bateria"), + ("android_open_battery_optimizations_tip", "Si voleu desactivar aquesta característica, feu-ho des de la pàgina següent de configuració del RustDesk, utilitzant l'opció relativa a «Bateria»"), + ("Start on boot", "Inicia durant l'arrencada"), + ("Start the screen sharing service on boot, requires special permissions", "Per iniciar la compartició de pantalla durant l'arrencada del sistema, calen permisos especials"), + ("Connection not allowed", "Connexió no permesa"), ("Legacy mode", "Mode heretat"), ("Map mode", "Mode mapa"), - ("Translate mode", "Mode traduit"), - ("Use permanent password", "Utilitza una contrasenya permament"), - ("Use both passwords", "Utilitza ambdues contrasenyas"), - ("Set permanent password", "Estableix una contrasenya permament"), - ("Enable remote restart", "Activa el reinici remot"), - ("Restart remote device", "Reinicia el dispositiu"), - ("Are you sure you want to restart", "Segur que vol reiniciar?"), - ("Restarting remote device", "Reiniciant el dispositiu remot"), - ("remote_restarting_tip", "Reiniciant el dispositiu remot, tanca aquest missatge i torna't a connectar amb la contrasenya."), - ("Copied", "Copiat"), + ("Translate mode", "Mode traduït"), + ("Use permanent password", "Utilitza la contrasenya permanent"), + ("Use both passwords", "Utilitza totes dues opcions"), + ("Set permanent password", "Estableix la contrasenya permanent"), + ("Enable remote restart", "Habilita el reinici remot"), + ("Restart remote device", "Reinicia el dispositiu remot"), + ("Are you sure you want to restart", "Segur que voleu reiniciar"), + ("Restarting remote device", "Reinici del dispositiu remot"), + ("remote_restarting_tip", "S'està reiniciant el dispositiu remot. Tanqueu aquest missatge i torneu a connectar amb ell mitjançant la contrasenya, un cop estigui en línia."), + ("Copied", "S'ha copiat"), ("Exit Fullscreen", "Surt de la pantalla completa"), ("Fullscreen", "Pantalla completa"), - ("Mobile Actions", "Accions mòbils"), - ("Select Monitor", "Selecciona el monitor"), - ("Control Actions", "Accions de control"), + ("Mobile Actions", "Funcions mòbils"), + ("Select Monitor", "Selecció de monitor"), + ("Control Actions", "Control de funcions"), ("Display Settings", "Configuració de pantalla"), ("Ratio", "Relació"), - ("Image Quality", "Qualitat d'imatge"), - ("Scroll Style", "Estil de desplaçament"), + ("Image Quality", "Qualitat de la imatge"), + ("Scroll Style", "Tipus de desplaçament"), ("Show Toolbar", "Mostra la barra d'eines"), ("Hide Toolbar", "Amaga la barra d'eines"), ("Direct Connection", "Connexió directa"), - ("Relay Connection", "Connexió Relay"), + ("Relay Connection", "Connexió amb repetidor"), ("Secure Connection", "Connexió segura"), - ("Insecure Connection", "Connexió insegura"), + ("Insecure Connection", "Connexió no segura"), ("Scale original", "Escala original"), ("Scale adaptive", "Escala adaptativa"), ("General", "General"), ("Security", "Seguretat"), ("Theme", "Tema"), - ("Dark Theme", "Tema Fosc"), + ("Dark Theme", "Tema fosc"), ("Light Theme", "Tema clar"), ("Dark", "Fosc"), ("Light", "Clar"), - ("Follow System", "Tema del sistema"), - ("Enable hardware codec", "Habilita el còdec per hardware"), - ("Unlock Security Settings", "Desbloqueja els ajustaments de seguretat"), + ("Follow System", "Utilitza la configuració del sistema"), + ("Enable hardware codec", "Habilita la codificació per maquinari"), + ("Unlock Security Settings", "Desbloca la configuració de seguretat"), ("Enable audio", "Habilita l'àudio"), - ("Unlock Network Settings", "Desbloqueja els ajustaments de xarxa"), + ("Unlock Network Settings", "Desbloca la configuració de la xarxa"), ("Server", "Servidor"), - ("Direct IP Access", "Accés IP Directe"), - ("Proxy", "Proxy"), + ("Direct IP Access", "Accés directe per IP"), + ("Proxy", "Servidor intermediari"), ("Apply", "Aplica"), - ("Disconnect all devices?", "Vols desconnectar tots els dispositius?"), - ("Clear", "Neteja"), + ("Disconnect all devices?", "Voleu desconnectar tots els dispositius?"), + ("Clear", "Buida"), ("Audio Input Device", "Dispositiu d'entrada d'àudio"), - ("Use IP Whitelisting", "Utilitza llista de IPs admeses"), + ("Use IP Whitelisting", "Utilitza un llistat d'IP admeses"), ("Network", "Xarxa"), - ("Pin Toolbar", "Fixa la barra d'eines"), - ("Unpin Toolbar", "Deixa de fixar la barra d'eines"), - ("Recording", "Gravant"), - ("Directory", "Directori"), - ("Automatically record incoming sessions", "Gravació automàtica de sessions entrants"), + ("Pin Toolbar", "Ancora a la barra d'eines"), + ("Unpin Toolbar", "Desancora de la barra d'eines"), + ("Recording", "Gravació"), + ("Directory", "Contactes"), + ("Automatically record incoming sessions", "Enregistrament automàtic de sessions entrants"), ("Change", "Canvia"), - ("Start session recording", "Comença la gravació de la sessió"), + ("Start session recording", "Inicia la gravació de la sessió"), ("Stop session recording", "Atura la gravació de la sessió"), ("Enable recording session", "Habilita la gravació de la sessió"), - ("Enable LAN discovery", "Habilita el descobriment de LAN"), - ("Deny LAN discovery", "Denega el descobriment de LAN"), - ("Write a message", "Escriu un missatge"), - ("Prompt", "Consulta"), - ("Please wait for confirmation of UAC...", "Espera per confirmar l'UAC..."), - ("elevated_foreground_window_tip", ""), + ("Enable LAN discovery", "Habilita el descobriment LAN"), + ("Deny LAN discovery", "Inhabilita el descobriment LAN"), + ("Write a message", "Escriviu un missatge"), + ("Prompt", "Sol·licitud"), + ("Please wait for confirmation of UAC...", "Espereu a la confirmació de l'UAC..."), + ("elevated_foreground_window_tip", "La finestra de connexió actual requereix permisos ampliats per a funcionar i, de forma temporal, no es pot utilitzar ni el teclat ni el ratolí. Demaneu a l'usuari remot que minimitzi la finestra actual, o bé que faci clic al botó Permisos ampliats de la finestra d'administració de la connexió. Per a evitar aquest problema en un futur, instal·leu el RustDesk al dispositiu remot."), ("Disconnected", "Desconnectat"), ("Other", "Altre"), - ("Confirm before closing multiple tabs", "Confirma abans de tancar múltiples pestanyes"), - ("Keyboard Settings", "Ajustaments de teclat"), - ("Full Access", "Acces complet"), - ("Screen Share", "Comparteix la pantalla"), - ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland requereix Ubuntu 21.04 o una versió superior."), - ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland requereix una versió superior de la distribución de Linux. Prova l'escriptori X11 o canvia el teu sistema operatiu."), - ("JumpLink", "Veure"), - ("Please Select the screen to be shared(Operate on the peer side).", "Selecciona la pantalla que es compartirà (Opera al costat del peer)."), - ("Show RustDesk", "Mostra RustDesk"), - ("This PC", "Aquest PC"), + ("Confirm before closing multiple tabs", "Confirma abans de tancar diverses pestanyes alhora"), + ("Keyboard Settings", "Configuració del teclat"), + ("Full Access", "Accés complet"), + ("Screen Share", "Compartició de pantalla"), + ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland requereix Ubuntu 21.04 o superior"), + ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland requereix una versió superior de sistema Linux per a funcionar. Proveu iniciant un entorn d'escriptori amb x11 o actualitzeu el vostre sistema operatiu."), + ("JumpLink", "Marcador"), + ("Please Select the screen to be shared(Operate on the peer side).", "Seleccioneu la pantalla que compartireu (quina serà visible al client)"), + ("Show RustDesk", "Mostra el RustDesk"), + ("This PC", "Aquest equip"), ("or", "o"), - ("Continue with", "Continur amb"), - ("Elevate", "Eleva"), - ("Zoom cursor", "Zoom del ratolí"), - ("Accept sessions via password", "Accepta sessions via contrasenya"), - ("Accept sessions via click", "Accepta sessions via clic"), - ("Accept sessions via both", "Accepta sessions via les dues opcions"), - ("Please wait for the remote side to accept your session request...", "Esperea que la part remota accepti la teva sol·licitud de sessió..."), + ("Continue with", "Continua amb"), + ("Elevate", "Permisos ampliats"), + ("Zoom cursor", "Escala del ratolí"), + ("Accept sessions via password", "Accepta les sessions mitjançant una contrasenya"), + ("Accept sessions via click", "Accepta les sessions expressament amb el ratolí"), + ("Accept sessions via both", "Accepta les sessions de totes dues formes"), + ("Please wait for the remote side to accept your session request...", "S'està esperant l'acceptació remota de la vostra connexió..."), ("One-time Password", "Contrasenya d'un sol ús"), - ("Use one-time password", "Fes servir una contrasenya d'un sol ús"), - ("One-time password length", "Caracters de la contrasenya d'un sol ús"), - ("Request access to your device", "Sol·licita l'acces al teu dispositiu"), - ("Hide connection management window", "Amaga la finestra de gestió de connexió"), - ("hide_cm_tip", ""), - ("wayland_experiment_tip", ""), - ("Right click to select tabs", "Clic dret per seleccionar les pestanyes"), - ("Skipped", "Saltat"), + ("Use one-time password", "Utilitza una contrasenya d'un sol ús"), + ("One-time password length", "Mida de la contrasenya d'un sol ús"), + ("Request access to your device", "Ha demanat connectar al vostre dispositiu"), + ("Hide connection management window", "Amaga la finestra d'administració de la connexió"), + ("hide_cm_tip", "Permet amagar la finestra només en acceptar sessions entrants sempre que s'utilitzi una contrasenya permanent"), + ("wayland_experiment_tip", "El suport per a Wayland està en fase experimental; es recomana l'ús d'x11 si us cal accés de forma desatesa."), + ("Right click to select tabs", "Feu clic amb el botó dret per a seleccionar pestanyes"), + ("Skipped", "S'ha omès"), ("Add to address book", "Afegeix a la llibreta d'adreces"), ("Group", "Grup"), ("Search", "Cerca"), - ("Closed manually by web console", "Tancat manualment amb la consola web"), + ("Closed manually by web console", "Tancat manualment per la consola web"), ("Local keyboard type", "Tipus de teclat local"), - ("Select local keyboard type", "Selecciona el tipus de teclat local"), - ("software_render_tip", ""), - ("Always use software rendering", "Fes servir sempre la renderització per software"), - ("config_input", ""), - ("config_microphone", ""), - ("request_elevation_tip", ""), - ("Wait", "Espera"), - ("Elevation Error", "Error d'elevació"), - ("Ask the remote user for authentication", "Demana autenticació a l'usuari remot"), - ("Choose this if the remote account is administrator", "Selecciona això si l'usuari remot és administrador"), - ("Transmit the username and password of administrator", "Transmet el nom d'usuari i la contrasenya de l'administrador"), - ("still_click_uac_tip", ""), - ("Request Elevation", "Demana l'elevació"), - ("wait_accept_uac_tip", ""), - ("Elevate successfully", "Elevació exitosa"), + ("Select local keyboard type", "Seleccioneu el tipus de teclat local"), + ("software_render_tip", "Si utilitzeu una gràfica Nvidia a Linux i la connexió remota es tanca immediatament en connectar, canviar al controlador lliure «Nouveau» amb renderització per programari, pot ajudar a solucionar el problema. Es requerirà en aquest cas reiniciar l'aplicació."), + ("Always use software rendering", "Utilitza sempre la renderització de programari"), + ("config_input", "Per a poder controlar el dispositiu remotament amb el teclat, faciliteu al RustDesk els permisos d'entrada necessaris."), + ("config_microphone", "Per a poder parlar remotament, faciliteu al RustDesk els permisos de gravació d'àudio necessaris."), + ("request_elevation_tip", "També, la part remota pot concedir aquests permisos de forma manual."), + ("Wait", "Espereu"), + ("Elevation Error", "Error de permisos"), + ("Ask the remote user for authentication", "Demaneu l'autenticació al client remot"), + ("Choose this if the remote account is administrator", "Trieu aquesta opció si el compte remot té permisos d'administrador"), + ("Transmit the username and password of administrator", "Indiqueu l'usuari i contrasenya de l'administrador"), + ("still_click_uac_tip", "Es requereix acceptació manual a la part remota de la finestra «UAC» del RustDesk en execució."), + ("Request Elevation", "Sol·licita els permisos"), + ("wait_accept_uac_tip", "Espereu fins que l'usuari remot accepti la finestra de diàleg de l'«UAC»."), + ("Elevate successfully", "S'han acceptat els permisos"), ("uppercase", "majúscula"), ("lowercase", "minúscula"), - ("digit", "dígit"), + ("digit", "número"), ("special character", "caràcter especial"), - ("length>=8", "longitut>=8"), - ("Weak", "Dèbil"), - ("Medium", "Mitja"), - ("Strong", "Forta"), - ("Switch Sides", "Canvia de costat"), - ("Please confirm if you want to share your desktop?", "Confirma que vols compartir el teu escriptori?"), + ("length>=8", "mida>=8"), + ("Weak", "Feble"), + ("Medium", "Acceptable"), + ("Strong", "Segura"), + ("Switch Sides", "Inverteix la connexió"), + ("Please confirm if you want to share your desktop?", "Realment voleu que es controli aquest equip?"), ("Display", "Pantalla"), - ("Default View Style", "Estil de visualització predeterminat"), - ("Default Scroll Style", "Estil de desplaçament predeterminat"), - ("Default Image Quality", "Qualitat d'imatge predeterminada"), - ("Default Codec", "Còdec predeterminat"), - ("Bitrate", "Ratio de bits"), + ("Default View Style", "Estil de vista per defecte"), + ("Default Scroll Style", "Estil de desplaçament per defecte"), + ("Default Image Quality", "Qualitat de la imatge per defecte"), + ("Default Codec", "Còdec per defecte"), + ("Bitrate", "Taxa de bits"), ("FPS", "FPS"), - ("Auto", "Auto"), - ("Other Default Options", "Altres opcions predeterminades"), - ("Voice call", "Trucada de veu"), - ("Text chat", "Xat de text"), - ("Stop voice call", "Penja la trucada de veu"), - ("relay_hint_tip", ""), - ("Reconnect", "Reconecta"), - ("Codec", "Còdec"), + ("Auto", "Automàtic"), + ("Other Default Options", "Altres opcions per defecte"), + ("Voice call", "Trucada"), + ("Text chat", "Xat"), + ("Stop voice call", "Penja la trucada"), + ("relay_hint_tip", "Quan no sigui possible la connexió directa, podeu provar mitjançant un repetidor. Addicionalment, si voleu que l'ús d'un repetidor sigui la primera opció per defecte, podeu afegir el sufix «/r» a la ID, o seleccionar l'opció «Connecta sempre mitjançant un repetidor» si ja existeix una fitxa amb aquesta ID a la pestanya de connexions recents."), + ("Reconnect", "Torna a connectar"), + ("Codec", "Còdec"), ("Resolution", "Resolució"), - ("No transfers in progress", "Sense transferències en curs"), - ("Set one-time password length", "Selecciona la longitud de la contrasenya d'un sol ús"), - ("RDP Settings", "Configuració RDP"), - ("Sort by", "Ordena per"), - ("New Connection", "Nova connexió"), + ("No transfers in progress", "Cap transferència iniciada"), + ("Set one-time password length", "Mida de la contrasenya d'un sol ús"), + ("RDP Settings", "Opcions de connexió RDP"), + ("Sort by", "Organitza per"), + ("New Connection", "Connexió nova"), ("Restore", "Restaura"), ("Minimize", "Minimitza"), - ("Maximize", "Maximtiza"), - ("Your Device", "El teu dispositiu"), - ("empty_recent_tip", ""), - ("empty_favorite_tip", ""), - ("empty_lan_tip", ""), - ("empty_address_book_tip", ""), - ("eg: admin", "p.ex.: admin"), - ("Empty Username", "Usuari buit"), + ("Maximize", "Maximitza"), + ("Your Device", "Aquest dispositiu"), + ("empty_recent_tip", "No s'ha trobat cap sessió recent!\nS'afegiran automàticament les connexions que realitzeu."), + ("empty_favorite_tip", "No heu afegit cap dispositiu aquí!\nPodeu afegir dispositius favorits en qualsevol moment."), + ("empty_lan_tip", "No s'ha trobat cap dispositiu proper."), + ("empty_address_book_tip", "Sembla que no teniu cap dispositiu a la vostra llista d'adreces."), + ("eg: admin", "p. ex.:admin"), + ("Empty Username", "Nom d'usuari buit"), ("Empty Password", "Contrasenya buida"), - ("Me", "Jo"), - ("identical_file_tip", ""), - ("show_monitors_tip", ""), - ("View Mode", "Tipus de visualització"), - ("login_linux_tip", ""), - ("verify_rustdesk_password_tip", ""), - ("remember_account_tip", ""), - ("os_account_desk_tip", ""), - ("OS Account", ""), - ("another_user_login_title_tip", ""), - ("another_user_login_text_tip", ""), - ("xorg_not_found_title_tip", ""), - ("xorg_not_found_text_tip", ""), - ("no_desktop_title_tip", ""), - ("no_desktop_text_tip", ""), - ("No need to elevate", "No cal elevar permisos"), - ("System Sound", "Sistema de so"), - ("Default", "Predeterminat"), - ("New RDP", "Nou RDP"), - ("Fingerprint", "Empremta digital"), - ("Copy Fingerprint", "Copia l'emprenta digital"), - ("no fingerprints", "sense emprentes"), - ("Select a peer", "Selecciona un peer"), - ("Select peers", "Selecciona diversos peers"), + ("Me", "Vós"), + ("identical_file_tip", "Aquest fitxer és idèntic al del client."), + ("show_monitors_tip", "Mostra les pantalles a la barra d'eines"), + ("View Mode", "Mode espectador"), + ("login_linux_tip", "És necessari que inicieu prèviament sessió amb un entorn d'escriptori x11 habilitat"), + ("verify_rustdesk_password_tip", "Verifica la contrasenya del RustDesk"), + ("remember_account_tip", "Recorda aquest compte"), + ("os_account_desk_tip", "S'utilitza aquest compte per iniciar la sessió al sistema remot i habilitar el mode sense cap pantalla connectada"), + ("OS Account", "Compte d'usuari"), + ("another_user_login_title_tip", "Altre usuari ha iniciat ja una sessió"), + ("another_user_login_text_tip", "Desconnecta"), + ("xorg_not_found_title_tip", "No s'ha trobat l'entorn Xorg"), + ("xorg_not_found_text_tip", "Instal·leu el Xorg"), + ("no_desktop_title_tip", "Cap escriptori disponible"), + ("no_desktop_text_tip", "Instal·leu l'entorn d'escriptori GNOME"), + ("No need to elevate", "No calen permisos ampliats"), + ("System Sound", "So del sistema"), + ("Default", "per defecte"), + ("New RDP", "Connexió RDP nova"), + ("Fingerprint", "Empremta"), + ("Copy Fingerprint", "Copia l'empremta"), + ("no fingerprints", "Cap empremta"), + ("Select a peer", "Seleccioneu un client"), + ("Select peers", "Seleccioneu els clients"), ("Plugins", "Complements"), ("Uninstall", "Desinstal·la"), ("Update", "Actualitza"), ("Enable", "Activa"), ("Disable", "Desactiva"), ("Options", "Opcions"), - ("resolution_original_tip", ""), - ("resolution_fit_local_tip", ""), - ("resolution_custom_tip", ""), - ("Collapse toolbar", "Col·lapsa la barra d'etiquetes"), - ("Accept and Elevate", "Accepta i eleva"), - ("accept_and_elevate_btn_tooltip", ""), - ("clipboard_wait_response_timeout_tip", ""), + ("resolution_original_tip", "Resolució original"), + ("resolution_fit_local_tip", "Ajusta la resolució local"), + ("resolution_custom_tip", "Resolució personalitzada"), + ("Collapse toolbar", "Minimitza la barra d'eines"), + ("Accept and Elevate", "Accepta i permet"), + ("accept_and_elevate_btn_tooltip", "Accepta la connexió i permet els permisos elevats UAC."), + ("clipboard_wait_response_timeout_tip", "S'ha esgotat el temps d'espera amb la resposta de còpia."), ("Incoming connection", "Connexió entrant"), ("Outgoing connection", "Connexió sortint"), - ("Exit", "Tanca"), + ("Exit", "Surt"), ("Open", "Obre"), - ("logout_tip", ""), + ("logout_tip", "Segur que voleu desconnectar?"), ("Service", "Servei"), ("Start", "Inicia"), ("Stop", "Atura"), - ("exceed_max_devices", ""), + ("exceed_max_devices", "Heu assolit el nombre màxim de dispositius administrables."), ("Sync with recent sessions", "Sincronitza amb les sessions recents"), - ("Sort tags", "Ordena per etiquetes"), - ("Open connection in new tab", "Obre la connexió en una nova pestanya"), - ("Move tab to new window", "Mou la pestanya a una nova finestra"), + ("Sort tags", "Ordena les etiquetes"), + ("Open connection in new tab", "Obre la connexió en una pestanya nova"), + ("Move tab to new window", "Mou la pestanya a una finestra nova"), ("Can not be empty", "No pot estar buit"), ("Already exists", "Ja existeix"), ("Change Password", "Canvia la contrasenya"), - ("Refresh Password", "Refresca la contrasenya"), + ("Refresh Password", "Actualitza la contrasenya"), ("ID", "ID"), - ("Grid View", "Visualització de graella"), - ("List View", "Visualització de llista"), + ("Grid View", "Disposició de graella"), + ("List View", "Disposició de llista"), ("Select", "Selecciona"), - ("Toggle Tags", "Activa/desactiva les etiquetes"), - ("pull_ab_failed_tip", ""), - ("push_ab_failed_tip", ""), - ("synced_peer_readded_tip", ""), + ("Toggle Tags", "Habilita les etiquetes"), + ("pull_ab_failed_tip", "Ha fallat en actualitzar la llista de contactes"), + ("push_ab_failed_tip", "Ha fallat en actualitzar la llista amb el servidor"), + ("synced_peer_readded_tip", "Els dispositius que es troben a la llista de sessions recents se sincronitzaran novament a la llista de contactes."), ("Change Color", "Canvia el color"), - ("Primary Color", "Color primari"), + ("Primary Color", "Color principal"), ("HSV Color", "Color HSV"), - ("Installation Successful!", "Instal·lació correcta!"), - ("Installation failed!", "Ha fallat la instal·lació!"), - ("Reverse mouse wheel", "Canvia l'orientació de la roda del ratolí"), + ("Installation Successful!", "S'ha instal·lat correctament"), + ("Installation failed!", "Ha fallat la instal·lació"), + ("Reverse mouse wheel", "Inverteix la roda del ratolí"), ("{} sessions", "{} sessions"), - ("scam_title", ""), - ("scam_text1", ""), - ("scam_text2", ""), - ("Don't show again", "No ho mostris més"), - ("I Agree", "Accepta"), - ("Decline", "Rebutja"), - ("Timeout in minutes", "Desconexió en minuts"), - ("auto_disconnect_option_tip", ""), - ("Connection failed due to inactivity", "Connexió fallada per inactivitat"), - ("Check for software update on startup", "Revisa les actualitzacions de software en iniciar"), - ("upgrade_rustdesk_server_pro_to_{}_tip", ""), - ("pull_group_failed_tip", ""), + ("scam_title", "Podríeu ser víctima d'una ESTAFA!"), + ("scam_text1", "Si cap persona qui NO coneixeu NI CONFIEU us demanés l'ús del RustDesk, no continueu i talleu la comunicació immediatament."), + ("scam_text2", "Habitualment solen ser atacants intentant fer-se amb els vostres diners o informació privada."), + ("Don't show again", "No tornis a mostrar"), + ("I Agree", "Accepto"), + ("Decline", "No accepto"), + ("Timeout in minutes", "Temps d'espera en minuts"), + ("auto_disconnect_option_tip", "Tanca automàticament les sessions entrants per inactivitat de l'usuari"), + ("Connection failed due to inactivity", "Ha fallat la connexió per inactivitat"), + ("Check for software update on startup", "Cerca actualitzacions en iniciar"), + ("upgrade_rustdesk_server_pro_to_{}_tip", "Actualitzeu el RustDesk Server Pro a la versió {} o superior!"), + ("pull_group_failed_tip", "Ha fallat en actualitzar el grup"), ("Filter by intersection", "Filtra per intersecció"), - ("Remove wallpaper during incoming sessions", "Amaga el fons de pantalla en les connexions entrants"), + ("Remove wallpaper during incoming sessions", "Inhabilita el fons d'escriptori durant la sessió entrant"), ("Test", "Prova"), - ("display_is_plugged_out_msg", ""), - ("No displays", "Sense pantalles"), - ("Open in new window", "Obre en una nova finestra"), - ("Show displays as individual windows", "Mostra les pantalles com finestres individuals"), - ("Use all my displays for the remote session", "Fes servir totes les meves pantalles per la sessió remota"), - ("selinux_tip", ""), + ("display_is_plugged_out_msg", "El monitor està desconnectat; canvieu primer al monitor principal."), + ("No displays", "Cap monitor"), + ("Open in new window", "Obre en una finestra nova"), + ("Show displays as individual windows", "Mostra cada monitor com una finestra individual"), + ("Use all my displays for the remote session", "Utilitza tots els meus monitors per a la connexió remota"), + ("selinux_tip", "SELinux està activat al vostre dispositiu, la qual cosa evita que el RustDesk funcioni correctament com a equip controlable."), ("Change view", "Canvia la vista"), - ("Big tiles", "Títols grans"), - ("Small tiles", "Títols petits"), + ("Big tiles", "Mosaic gran"), + ("Small tiles", "Mosaic petit"), ("List", "Llista"), ("Virtual display", "Pantalla virtual"), - ("Plug out all", "Desconnectar tots"), + ("Plug out all", "Desconnecta-ho tot"), ("True color (4:4:4)", "Color real (4:4:4)"), - ("Enable blocking user input", "Activa el bloqueig d'entrada d'usuari"), - ("id_input_tip", ""), - ("privacy_mode_impl_mag_tip", ""), - ("privacy_mode_impl_virtual_display_tip", ""), - ("Enter privacy mode", "Entra al mode de privacitat"), - ("Exit privacy mode", "Surt del mode de privacitat"), - ("idd_not_support_under_win10_2004_tip", ""), - ("input_source_1_tip", ""), - ("input_source_2_tip", ""), - ("Swap control-command key", "Canvia la tecla de control"), - ("swap-left-right-mouse", ""), + ("Enable blocking user input", "Bloca el control de l'usuari amb els dispositius d'entrada"), + ("id_input_tip", "Evita que l'usuari pugui interactuar p. ex. amb el teclat o ratolí"), + ("privacy_mode_impl_mag_tip", "Mode 1"), + ("privacy_mode_impl_virtual_display_tip", "Mode 2"), + ("Enter privacy mode", "Inicia el Mode privat"), + ("Exit privacy mode", "Surt del Mode privat"), + ("idd_not_support_under_win10_2004_tip", "El controlador indirecte de pantalla no està suportat; es requereix Windows 10 versió 2004 o superior."), + ("input_source_1_tip", "Font d'entrada 1"), + ("input_source_2_tip", "Font d'entrada 2"), + ("Swap control-command key", "Canvia el comportament de la tecla Control"), + ("swap-left-right-mouse", "Alterna el comportament dels botons esquerre-dret del ratolí"), ("2FA code", "Codi 2FA"), ("More", "Més"), - ("enable-2fa-title", ""), - ("enable-2fa-desc", ""), - ("wrong-2fa-code", ""), - ("enter-2fa-title", ""), - ("Email verification code must be 6 characters.", "El codi de verificació de l'email ha de tenir 6 caràcters."), - ("2FA code must be 6 digits.", "El codi 2FA ha de tenir 6 digits."), - ("Multiple Windows sessions found", "Multiples sessions de Windows trobades"), - ("Please select the session you want to connect to", "Selecciona la sessió a la què et vols connectar"), - ("powered_by_me", ""), - ("outgoing_only_desk_tip", ""), - ("preset_password_warning", ""), + ("enable-2fa-title", "Habilita el mètode d'autenticació de factor doble"), + ("enable-2fa-desc", "Configureu ara el vostre autenticador. Podeu utilitzar una aplicació com 2fast, FreeOTP, MultiOTP, Microsoft o Google Authenticator al vostre telèfon o escriptori.\n\nEscanegeu el codi QR amb l'aplicació i escriviu els caràcters resultants per habilitar l'autenticació de factor doble."), + ("wrong-2fa-code", "Codi 2FA no vàlid. Verifiqueu el que heu escrit i també que la configuració horària sigui correcta"), + ("enter-2fa-title", "Autenticació de factor doble"), + ("Email verification code must be 6 characters.", "El codi de verificació de correu-e són 6 caràcters"), + ("2FA code must be 6 digits.", "El codi de verificació 2FA haurien de ser almenys 6 dígits"), + ("Multiple Windows sessions found", "S'han trobat múltiples sessions en ús del Windows"), + ("Please select the session you want to connect to", "Indiqueu amb quina sessió voleu connectar"), + ("powered_by_me", "Amb la tecnologia de RustDesk"), + ("outgoing_only_desk_tip", "Aquesta és una versió personalitzada.\nPodeu connectar amb altres dispositius, però no s'accepten connexions d'entrada cap el vostre dispositiu."), + ("preset_password_warning", "Aquesta versió personalitzada té una contrasenya preestablerta. Qualsevol persona que la conegui pot tenir accés total al vostre dispositiu. Si no és el comportament desitjat, desinstal·leu aquest programa immediatament."), ("Security Alert", "Alerta de seguretat"), - ("My address book", "La meva llibreta d'adreces"), + ("My address book", "Llibreta d'adreces"), ("Personal", "Personal"), ("Owner", "Propietari"), - ("Set shared password", "Establir la contrasenya compartida"), - ("Exist in", "Existeix en"), + ("Set shared password", "Establiu una contrasenya compartida"), + ("Exist in", "Existeix a"), ("Read-only", "Només lectura"), ("Read/Write", "Lectura/Escriptura"), ("Full Control", "Control total"), - ("share_warning_tip", ""), - ("Everyone", "Tots"), - ("ab_web_console_tip", ""), - ("allow-only-conn-window-open-tip", ""), - ("no_need_privacy_mode_no_physical_displays_tip", ""), - ("Follow remote cursor", "Segueix el cursor remot"), - ("Follow remote window focus", "Segueix el focus de la finestra remota"), - ("default_proxy_tip", ""), - ("no_audio_input_device_tip", ""), + ("share_warning_tip", "Els camps a continuació estan compartits i són visibles a d'altres."), + ("Everyone", "Tothom"), + ("ab_web_console_tip", "Més a la consola web"), + ("allow-only-conn-window-open-tip", "Permet la connexió només si la finestra del RustDesk està activa"), + ("no_need_privacy_mode_no_physical_displays_tip", "Cap monitor físic. No cal l'ús del Mode privat"), + ("Follow remote cursor", "Segueix al cursor remot"), + ("Follow remote window focus", "Segueix el focus remot de la finestra activa"), + ("default_proxy_tip", "El protocol per defecte és Socks5 al port 1080"), + ("no_audio_input_device_tip", "No s'ha trobat cap dispositiu d'àudio."), ("Incoming", "Entrant"), ("Outgoing", "Sortint"), - ("Clear Wayland screen selection", "Neteja la selecció de pantalla Wayland"), - ("clear_Wayland_screen_selection_tip", ""), - ("confirm_clear_Wayland_screen_selection_tip", ""), - ("android_new_voice_call_tip", ""), - ("texture_render_tip", ""), - ("Use texture rendering", "Fes servir la renderització de textures"), + ("Clear Wayland screen selection", "Neteja la pantalla de selecció Wayland"), + ("clear_Wayland_screen_selection_tip", "En netejar la finestra de selecció, podreu tornar a triar quina pantalla compartir."), + ("confirm_clear_Wayland_screen_selection_tip", "Segur que voleu netejar la pantalla de selecció del Wayland"), + ("android_new_voice_call_tip", "S'ha rebut una petició de trucada entrant. Si accepteu, la font d'àudio canviarà a comunicació per veu."), + ("texture_render_tip", "Utilitzeu aquesta opció per suavitzar la imatge. Desactiveu-ho si trobeu cap problema amb el renderitzat"), + ("Use texture rendering", "Utilitza la renderització de textures"), ("Floating window", "Finestra flotant"), - ("floating_window_tip", ""), - ("Keep screen on", "Deixa la pantalla encesa"), + ("floating_window_tip", "Ajuda a mantenir el servei del RustDesk en rerefons"), + ("Keep screen on", "Manté la pantalla activa"), ("Never", "Mai"), - ("During controlled", "Mentre estigui controlat"), - ("During service is on", "Mentre el servei estigui encés"), - ("Capture screen using DirectX", "Captura de pantalla utilitzant DirectX"), + ("During controlled", "Durant la connexió"), + ("During service is on", "Mentre el servei està actiu"), + ("Capture screen using DirectX", "Captura utilitzant el DirectX"), ("Back", "Enrere"), ("Apps", "Aplicacions"), - ("Volume up", "Puja el volum"), - ("Volume down", "Baixa el volum"), - ("Power", "Engega"), - ("Telegram bot", "Bot de Telegram"), - ("enable-bot-tip", ""), - ("enable-bot-desc", ""), - ("cancel-2fa-confirm-tip", ""), - ("cancel-bot-confirm-tip", ""), - ("About RustDesk", ""), - ("Send clipboard keystrokes", ""), - ("network_error_tip", ""), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), - ("one-way-file-transfer-tip", ""), - ("Authentication Required", ""), - ("Authenticate", ""), - ("web_id_input_tip", ""), + ("Volume up", "Volum amunt"), + ("Volume down", "Volum avall"), + ("Power", "Encesa"), + ("Telegram bot", "Bot del Telegram"), + ("enable-bot-tip", "Si habiliteu aquesta característica, podreu rebre el codi 2FA mitjançant el vostre bot. També funciona com a notificador de la connexió."), + ("enable-bot-desc", "1. Obriu un xat amb @BotFather.\n2. Envieu l'ordre \"/newbot\". Rebreu un testimoni en acompletar aquest pas.\n3. Inicieu una conversa amb el vostre bot nou que acabeu de crear, enviant un missatge que comenci amb (\"/\"), com ara \"/hello\" per a activar-lo.\n"),"), + ("cancel-2fa-confirm-tip", "Segur que voleu cancel·lar l'autenticació 2FA?"), + ("cancel-bot-confirm-tip", "Segur que voleu cancel·lar el bot de Telegram?"), + ("About RustDesk", "Quant al RustDesk"), + ("Send clipboard keystrokes", "Envia les pulsacions de tecles del porta-retalls"), + ("network_error_tip", "Verifiqueu la vostra connexió a Internet i torneu a provar"), + ("Unlock with PIN", "Desbloca amb PIN"), + ("Requires at least {} characters", "Són necessaris almenys {} caràcters"), + ("Wrong PIN", "PIN no vàlid"), + ("Set PIN", "Definiu un codi PIN"), + ("Enable trusted devices", "Habilita els dispositius de confiança"), + ("Manage trusted devices", "Administra els dispositius de confiança"), + ("Platform", "Platforma"), + ("Days remaining", "Dies restants"), + ("enable-trusted-devices-tip", "Omet l'autenticació de factor doble (2FA) als dispositius de confiança"), + ("Parent directory", "Carpeta pare"), + ("Resume", "Continua"), + ("Invalid file name", "Nom de fitxer no vàlid"), + ("one-way-file-transfer-tip", "One-way file transfer is enabled on the controlled side."), + ("Authentication Required", "Autenticació requerida"), + ("Authenticate", "Autentica"), + ("web_id_input_tip", "Podeu inserir el número ID al propi servidor; l'accés directe per IP no és compatible amb el client web.\nSi voleu accedir a un dispositiu d'un altre servidor, afegiu l'adreça del servidor, com ara @?key= (p. ex.\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSi voleu accedir a un dispositiu en un servidor públic, no cal que inseriu la clau pública «@» per al servidor públic."), ].iter().cloned().collect(); } From 306dd77b8109995a9cd64654cc1a0b1dfa232ff8 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Thu, 3 Oct 2024 15:34:40 +0800 Subject: [PATCH 137/210] fix https://github.com/rustdesk/rustdesk/issues/9537 --- res/DEBIAN/prerm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/DEBIAN/prerm b/res/DEBIAN/prerm index f68be3c7e36..938424a8b3e 100755 --- a/res/DEBIAN/prerm +++ b/res/DEBIAN/prerm @@ -14,7 +14,7 @@ case $1 in rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service || true # workaround temp dev build between 1.1.9 and 1.2.0 - ubuntuVersion=$(grep -oP 'VERSION_ID="\K[\d]+' /etc/os-release | bc -l) + ubuntuVersion=$(grep -oP 'VERSION_ID="\K[\d]+' /etc/os-release | awk '{print $1}') waylandSupportVersion=21 if [ "$ubuntuVersion" != "" ] && [ "$ubuntuVersion" -ge "$waylandSupportVersion" ] then From c4d0b02478e866cfb2402679c07ec1ecbc2ba3b3 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Thu, 3 Oct 2024 15:38:55 +0800 Subject: [PATCH 138/210] fix ci --- src/lang/ca.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 4331e16be6e..cd262589da7 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -626,7 +626,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Power", "Encesa"), ("Telegram bot", "Bot del Telegram"), ("enable-bot-tip", "Si habiliteu aquesta característica, podreu rebre el codi 2FA mitjançant el vostre bot. També funciona com a notificador de la connexió."), - ("enable-bot-desc", "1. Obriu un xat amb @BotFather.\n2. Envieu l'ordre \"/newbot\". Rebreu un testimoni en acompletar aquest pas.\n3. Inicieu una conversa amb el vostre bot nou que acabeu de crear, enviant un missatge que comenci amb (\"/\"), com ara \"/hello\" per a activar-lo.\n"),"), + ("enable-bot-desc", "1. Obriu un xat amb @BotFather.\n2. Envieu l'ordre \"/newbot\". Rebreu un testimoni en acompletar aquest pas.\n3. Inicieu una conversa amb el vostre bot nou que acabeu de crear, enviant un missatge que comenci amb (\"/\"), com ara \"/hello\" per a activar-lo.\n"), ("cancel-2fa-confirm-tip", "Segur que voleu cancel·lar l'autenticació 2FA?"), ("cancel-bot-confirm-tip", "Segur que voleu cancel·lar el bot de Telegram?"), ("About RustDesk", "Quant al RustDesk"), From dec3cde9b32dd3fa5166bc8b0dd228f7249a260a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:56:21 +0800 Subject: [PATCH 139/210] fix: deb, build, prerm (#9552) Signed-off-by: fufesou --- res/DEBIAN/prerm | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/res/DEBIAN/prerm b/res/DEBIAN/prerm index 938424a8b3e..baef2e2e202 100755 --- a/res/DEBIAN/prerm +++ b/res/DEBIAN/prerm @@ -14,15 +14,10 @@ case $1 in rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service || true # workaround temp dev build between 1.1.9 and 1.2.0 - ubuntuVersion=$(grep -oP 'VERSION_ID="\K[\d]+' /etc/os-release | awk '{print $1}') - waylandSupportVersion=21 - if [ "$ubuntuVersion" != "" ] && [ "$ubuntuVersion" -ge "$waylandSupportVersion" ] + serverUser=$(ps -ef | grep -E 'rustdesk +--server' | grep -v 'sudo ' | awk '{print $1}' | head -1) + if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ] then - serverUser=$(ps -ef | grep -E 'rustdesk +--server' | grep -v 'sudo ' | awk '{print $1}' | head -1) - if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ] - then - systemctl --machine=${serverUser}@.host --user stop rustdesk >/dev/null 2>&1 || true - fi + systemctl --machine=${serverUser}@.host --user stop rustdesk >/dev/null 2>&1 || true fi rm /usr/lib/systemd/user/rustdesk.service >/dev/null 2>&1 || true fi From 3a97b63e95e2f48ac399884c832b9e3948da4bab Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:05:38 +0800 Subject: [PATCH 140/210] fix: windows, multi-window, move between monitors (#9562) Signed-off-by: fufesou --- flutter/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 5a4a74ed676..6210b5399c8 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -335,7 +335,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: ad889c35b896435eb7c828e200db2fb6f41d2c3b + resolved-ref: "0842f44d8644911f65a6b78be22474af0f8a9349" url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window" source: git version: "0.1.0" From 9ea09c1515c882cccd6061ce09b2e106de0282c7 Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki <36250619+cacing69@users.noreply.github.com> Date: Sat, 5 Oct 2024 09:54:17 +0700 Subject: [PATCH 141/210] Update id (Indonesia) translation on some part (#9566) * update id trans * update id trans --- src/lang/id.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/lang/id.rs b/src/lang/id.rs index 5d5d06a662f..c16243aedd2 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -569,23 +569,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enter privacy mode", "Masuk mode privasi"), ("Exit privacy mode", "Keluar mode privasi"), ("idd_not_support_under_win10_2004_tip", "Driver grafis yang Anda gunakan tidak kompatibel dengan versi Windows Anda dan memerlukan Windows 10 versi 2004 atau yang lebih baru"), - ("input_source_1_tip", ""), - ("input_source_2_tip", ""), - ("Swap control-command key", ""), - ("swap-left-right-mouse", ""), - ("2FA code", ""), - ("More", ""), - ("enable-2fa-title", ""), - ("enable-2fa-desc", ""), - ("wrong-2fa-code", ""), - ("enter-2fa-title", ""), - ("Email verification code must be 6 characters.", ""), - ("2FA code must be 6 digits.", ""), - ("Multiple Windows sessions found", ""), - ("Please select the session you want to connect to", ""), - ("powered_by_me", ""), - ("outgoing_only_desk_tip", ""), - ("preset_password_warning", ""), + ("input_source_1_tip", "Sumber input 1"), + ("input_source_2_tip", "Sumber input 2"), + ("Swap control-command key", "Menukar tombol control-command"), + ("swap-left-right-mouse", "Tukar fungsi tombol kiri dan kanan pada mouse"), + ("2FA code", "Kode 2FA"), + ("More", "Lainnya"), + ("enable-2fa-title", "Aktifkan autentikasi 2FA"), + ("enable-2fa-desc", "Silakan atur autentikator Anda sekarang. Anda dapat menggunakan aplikasi autentikator seperti Authy, Microsoft Authenticator, atau Google Authenticator di ponsel atau desktop Anda\n\nPindai kode QR dengan aplikasi Anda dan masukkan kode yang ditampilkan oleh aplikasi untuk mengaktifkan autentikasi 2FA."), + ("wrong-2fa-code", "Tidak dapat memverifikasi kode. Pastikan bahwa kode dan pengaturan waktu lokal sudah sesuai"), + ("enter-2fa-title", "Autentikasi dua faktor"), + ("Email verification code must be 6 characters.", "Kode verifikasi email harus terdiri dari 6 karakter."), + ("2FA code must be 6 digits.", "Kode 2FA harus terdiri dari 6 digit."), + ("Multiple Windows sessions found", "Terdapat beberapa sesi Windows"), + ("Please select the session you want to connect to", "Silakan pilih sesi yang ingin Anda sambungkan."), + ("powered_by_me", "Didukung oleh RustDesk"), + ("outgoing_only_desk_tip", "Ini adalah edisi yang sudah kustomisasi.\nAnda dapat terhubung ke perangkat lain, tetapi perangkat lain tidak dapat terhubung ke perangkat Anda."), + ("preset_password_warning", "Edisi yang dikustomisasi ini dilengkapi dengan kata sandi bawaan. Siapa pun yang mengetahui kata sandi ini dapat memperoleh kontrol penuh atas perangkat Anda. Jika Anda tidak mengharapkan ini, segera hapus pemasangan aplikasi tersebut."), ("Security Alert", ""), ("My address book", ""), ("Personal", ""), From ba832362a75f7c306688df462f109a6a65dd08f2 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 6 Oct 2024 08:32:04 +0800 Subject: [PATCH 142/210] fix: installed, copy&paste, special format (#9570) Signed-off-by: fufesou --- src/ipc.rs | 1 + src/server/clipboard_service.rs | 1 + src/ui_cm_interface.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/ipc.rs b/src/ipc.rs index 9815fdb748b..c7243d82170 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -121,6 +121,7 @@ pub struct ClipboardNonFile { pub height: i32, // message.proto: ClipboardFormat pub format: i32, + pub special_name: String, } #[cfg(not(any(target_os = "android", target_os = "ios")))] diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index 55ebbc2f2ec..3aadb3ad5dd 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -117,6 +117,7 @@ impl Handler { format: ClipboardFormat::from_i32(c.format) .unwrap_or(ClipboardFormat::Text) .into(), + special_name: c.special_name, ..Default::default() }) .collect(), diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 549798a5172..c34e15e26c8 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -512,6 +512,7 @@ impl IpcTaskRunner { width: c.width, height: c.height, format: c.format.value(), + special_name: c.special_name, }); } allow_err!(self.stream.send(&Data::ClipboardNonFile(Some(("".to_owned(), main_data)))).await); From e7353be0cd0747febada01216a20aeba1ee52ba3 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 6 Oct 2024 09:25:13 +0800 Subject: [PATCH 143/210] fix https://github.com/flathub/flathub/pull/5670#issuecomment-2395067890 --- src/platform/linux.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 9c549423045..3f0860f868e 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -610,8 +610,17 @@ pub fn get_env_var(k: &str) -> String { } } +fn is_flatpak() -> bool { + std::env::var("FLATPAK_SANDBOX_DIR").is_ok() || + std::env::var("FLATPAK_ID").is_ok() || + std::env::var("FLATPAK_SESSION_BUS_ADDRESS").is_ok() +} + // Headless is enabled, always return true. pub fn is_prelogin() -> bool { + if is_flatpak() { + return false; + } let n = get_active_userid().len(); n < 4 && n > 1 } From 560c1effe81c20180ce2fc96b3d86902531cc723 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 6 Oct 2024 16:20:37 +0800 Subject: [PATCH 144/210] remove mobile web setting, remove web/ios relay server setting (#9575) Signed-off-by: 21pages --- .../desktop/pages/desktop_setting_page.dart | 5 +- flutter/lib/mobile/widgets/dialog.dart | 17 +++-- flutter/lib/web/settings_page.dart | 73 +------------------ 3 files changed, 13 insertions(+), 82 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 041b5569ed5..ebe55ca191b 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1451,8 +1451,9 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { children: [ Obx(() => _LabeledTextField(context, 'ID Server', idController, idErrMsg.value, enabled, secure)), - Obx(() => _LabeledTextField(context, 'Relay Server', - relayController, relayErrMsg.value, enabled, secure)), + if (!isWeb) + Obx(() => _LabeledTextField(context, 'Relay Server', + relayController, relayErrMsg.value, enabled, secure)), Obx(() => _LabeledTextField(context, 'API Server', apiController, apiErrMsg.value, enabled, secure)), _LabeledTextField( diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 391bec669d5..2d17f3b5438 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -205,14 +205,15 @@ void showServerSettingsWithValue( ) ] + [ - TextFormField( - controller: relayCtrl, - decoration: InputDecoration( - labelText: translate('Relay Server'), - errorText: relayServerMsg.value.isEmpty - ? null - : relayServerMsg.value), - ) + if (isAndroid) + TextFormField( + controller: relayCtrl, + decoration: InputDecoration( + labelText: translate('Relay Server'), + errorText: relayServerMsg.value.isEmpty + ? null + : relayServerMsg.value), + ) ] + [ TextFormField( diff --git a/flutter/lib/web/settings_page.dart b/flutter/lib/web/settings_page.dart index cbd79a718ec..1cf23ecf9f8 100644 --- a/flutter/lib/web/settings_page.dart +++ b/flutter/lib/web/settings_page.dart @@ -1,23 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; -import 'package:flutter_hbb/mobile/pages/scan_page.dart'; -import 'package:flutter_hbb/mobile/pages/settings_page.dart'; -import 'package:provider/provider.dart'; - -import '../../common.dart'; -import '../../common/widgets/login.dart'; -import '../../models/model.dart'; class WebSettingsPage extends StatelessWidget { const WebSettingsPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - if (isWebDesktop) { - return _buildDesktopButton(context); - } else { - return _buildMobileMenu(context); - } + return _buildDesktopButton(context); } Widget _buildDesktopButton(BuildContext context) { @@ -34,64 +23,4 @@ class WebSettingsPage extends StatelessWidget { }, ); } - - Widget _buildMobileMenu(BuildContext context) { - Provider.of(context); - return PopupMenuButton( - tooltip: "", - icon: const Icon(Icons.more_vert), - itemBuilder: (context) { - return (isIOS - ? [ - const PopupMenuItem( - value: "scan", - child: Icon(Icons.qr_code_scanner, color: Colors.black), - ) - ] - : >[]) + - [ - PopupMenuItem( - value: "server", - child: Text(translate('ID/Relay Server')), - ) - ] + - [ - PopupMenuItem( - value: "login", - child: Text(gFFI.userModel.userName.value.isEmpty - ? translate("Login") - : '${translate("Logout")} (${gFFI.userModel.userName.value})'), - ) - ] + - [ - PopupMenuItem( - value: "about", - child: Text(translate('About RustDesk')), - ) - ]; - }, - onSelected: (value) { - if (value == 'server') { - showServerSettings(gFFI.dialogManager); - } - if (value == 'about') { - showAbout(gFFI.dialogManager); - } - if (value == 'login') { - if (gFFI.userModel.userName.value.isEmpty) { - loginDialog(); - } else { - logOutConfirmDialog(); - } - } - if (value == 'scan') { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => ScanPage(), - ), - ); - } - }); - } } From 83aba804d0fc87d1ffec6063e0d391d6c1a302f8 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 7 Oct 2024 00:01:14 +0800 Subject: [PATCH 145/210] fix: web fullscreen (#9577) Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 21 +++++++ flutter/lib/models/native_model.dart | 8 +++ flutter/lib/models/state_model.dart | 63 +++++++++++++------ flutter/lib/models/web_model.dart | 6 ++ 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 2d20d5931c9..d0fbc46731d 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -436,6 +436,7 @@ class _RemoteToolbarState extends State { shadowColor: MyTheme.color(context).shadow, borderRadius: borderRadius, child: _DraggableShowHide( + id: widget.id, sessionId: widget.ffi.sessionId, dragging: _dragging, fractionX: _fractionX, @@ -2218,6 +2219,7 @@ class RdoMenuButton extends StatelessWidget { } class _DraggableShowHide extends StatefulWidget { + final String id; final SessionID sessionId; final RxDouble fractionX; final RxBool dragging; @@ -2229,6 +2231,7 @@ class _DraggableShowHide extends StatefulWidget { const _DraggableShowHide({ Key? key, + required this.id, required this.sessionId, required this.fractionX, required this.dragging, @@ -2364,6 +2367,24 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { ), ))), ), + if (isWebDesktop) + Obx(() { + if (show.isTrue) { + return Offstage(); + } else { + return TextButton( + onPressed: () => closeConnection(id: widget.id), + child: Tooltip( + message: translate('Close'), + child: Icon( + Icons.close, + size: iconSize, + color: _ToolbarTheme.redColor, + ), + ), + ); + } + }) ], ); return TextButtonTheme( diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index b99cf2e7fb8..c8d5085e897 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -48,6 +48,12 @@ class PlatformFFI { static get isMain => instance._appType == kAppTypeMain; + static String getByName(String name, [String arg = '']) { + return ''; + } + + static void setByName(String name, [String value = '']) {} + static Future getVersion() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); return packageInfo.version; @@ -276,4 +282,6 @@ class PlatformFFI { void syncAndroidServiceAppDirConfigPath() { invokeMethod(AndroidChannel.kSyncAppDirConfigPath, _dir); } + + void setFullscreenCallback(void Function(bool) fun) {} } diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index a1b5fc736e9..62f92db1218 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -2,6 +2,7 @@ import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:get/get.dart'; +import 'native_model.dart' if (dart.library.html) 'web_model.dart'; import '../consts.dart'; import './platform_model.dart'; @@ -73,27 +74,47 @@ class StateGlobal { if (_fullscreen.value != v) { _fullscreen.value = v; _showTabBar.value = !_fullscreen.value; - refreshResizeEdgeSize(); - print( - "fullscreen: $fullscreen, resizeEdgeSize: ${_resizeEdgeSize.value}"); - _windowBorderWidth.value = fullscreen.isTrue ? 0 : kWindowBorderWidth; - if (procWnd) { - final wc = WindowController.fromWindowId(windowId); - wc.setFullscreen(_fullscreen.isTrue).then((_) { - // https://github.com/leanflutter/window_manager/issues/131#issuecomment-1111587982 - if (isWindows && !v) { - Future.delayed(Duration.zero, () async { - final frame = await wc.getFrame(); - final newRect = Rect.fromLTWH( - frame.left, frame.top, frame.width + 1, frame.height + 1); - await wc.setFrame(newRect); - }); - } - }); + if (isWebDesktop) { + procFullscreenWeb(); + } else { + procFullscreenNative(procWnd); } } } + procFullscreenWeb() { + final isFullscreen = PlatformFFI.getByName('fullscreen') == 'Y'; + String fullscreenValue = ''; + if (isFullscreen && _fullscreen.isFalse) { + fullscreenValue = 'N'; + } else if (!isFullscreen && fullscreen.isTrue) { + fullscreenValue = 'Y'; + } + if (fullscreenValue.isNotEmpty) { + PlatformFFI.setByName('fullscreen', fullscreenValue); + } + } + + procFullscreenNative(bool procWnd) { + refreshResizeEdgeSize(); + print("fullscreen: $fullscreen, resizeEdgeSize: ${_resizeEdgeSize.value}"); + _windowBorderWidth.value = fullscreen.isTrue ? 0 : kWindowBorderWidth; + if (procWnd) { + final wc = WindowController.fromWindowId(windowId); + wc.setFullscreen(_fullscreen.isTrue).then((_) { + // https://github.com/leanflutter/window_manager/issues/131#issuecomment-1111587982 + if (isWindows && _fullscreen.isFalse) { + Future.delayed(Duration.zero, () async { + final frame = await wc.getFrame(); + final newRect = Rect.fromLTWH( + frame.left, frame.top, frame.width + 1, frame.height + 1); + await wc.setFrame(newRect); + }); + } + }); + } + } + refreshResizeEdgeSize() => _resizeEdgeSize.value = fullscreen.isTrue ? kFullScreenEdgeSize : isMaximized.isTrue @@ -112,7 +133,13 @@ class StateGlobal { _inputSource = bind.mainGetInputSource(); } - StateGlobal._(); + StateGlobal._() { + if (isWebDesktop) { + platformFFI.setFullscreenCallback((v) { + _fullscreen.value = v; + }); + } + } static final StateGlobal instance = StateGlobal._(); } diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index ff0d1b806da..a5740af61a1 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -166,4 +166,10 @@ class PlatformFFI { // just for compilation void syncAndroidServiceAppDirConfigPath() {} + + void setFullscreenCallback(void Function(bool) fun) { + context["onFullscreenChanged"] = (bool v) { + fun(v); + }; + } } From 839e8180e03037b23936fc9bca883a41515c7d96 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:00:56 +0800 Subject: [PATCH 146/210] refact: web, no minimize btn on fullscreen (#9578) Signed-off-by: fufesou --- flutter/lib/desktop/widgets/remote_toolbar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index d0fbc46731d..e7b4d4d761f 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -2340,7 +2340,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { ), ), )), - if (!isMacOS) + if (!isMacOS && !isWebDesktop) Obx(() => Offstage( offstage: isFullscreen.isFalse, child: TextButton( From cc860b290603fc913c3196999a1ffec407a2083d Mon Sep 17 00:00:00 2001 From: rustdesk Date: Mon, 7 Oct 2024 15:26:41 +0800 Subject: [PATCH 147/210] https://github.com/flathub/flathub/pull/5670#issuecomment-2395337173 --- src/platform/linux.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 3f0860f868e..08cf0fb9a90 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -611,9 +611,7 @@ pub fn get_env_var(k: &str) -> String { } fn is_flatpak() -> bool { - std::env::var("FLATPAK_SANDBOX_DIR").is_ok() || - std::env::var("FLATPAK_ID").is_ok() || - std::env::var("FLATPAK_SESSION_BUS_ADDRESS").is_ok() + std::path::PathBuf::from("/.flatpak-info").exists() } // Headless is enabled, always return true. From e06f456bbd3241c20993fa4603a0c8e6ca6c9bdb Mon Sep 17 00:00:00 2001 From: Dmitry Beskov <43372966+besdar@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:55:25 +0400 Subject: [PATCH 148/210] manifest updates from the Flathub's PR (#9581) Signed-off-by: dmitry <43372966+besdar@users.noreply.github.com> --- .github/workflows/flutter-build.yml | 4 +- flatpak/com.rustdesk.RustDesk.metainfo.xml | 40 +++++++++++++++++ flatpak/rustdesk.json | 50 +++++++++++++--------- flatpak/xdotool.json | 15 ------- 4 files changed, 72 insertions(+), 37 deletions(-) create mode 100644 flatpak/com.rustdesk.RustDesk.metainfo.xml delete mode 100644 flatpak/xdotool.json diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index dd21515ba54..989aee65f92 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -1951,8 +1951,8 @@ jobs: wget # flatpak deps flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/23.08 - flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/23.08 + flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/24.08 + flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/24.08 # package pushd flatpak git clone https://github.com/flathub/shared-modules.git --depth=1 diff --git a/flatpak/com.rustdesk.RustDesk.metainfo.xml b/flatpak/com.rustdesk.RustDesk.metainfo.xml new file mode 100644 index 00000000000..12842fb8abe --- /dev/null +++ b/flatpak/com.rustdesk.RustDesk.metainfo.xml @@ -0,0 +1,40 @@ + + + com.rustdesk.RustDesk + RustDesk + com.rustdesk.RustDesk.desktop + CC0-1.0 + AGPL-3.0-only + RustDesk + An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer. + +

    + RustDesk is a full-featured open source remote control alternative for self-hosting and security with minimal configuration. +

    +
      +
    • Works on Windows, macOS, Linux, iOS, Android, Web.
    • +
    • Supports VP8 / VP9 / AV1 software codecs, and H264 / H265 hardware codecs.
    • +
    • Own your data, easily set up self-hosting solution on your infrastructure.
    • +
    • P2P connection with end-to-end encryption based on NaCl.
    • +
    • No administrative privileges or installation needed for Windows, elevate priviledge locally or from remote on demand.
    • +
    • We like to keep things simple and will strive to make simpler where possible.
    • +
    +

    + For self-hosting setup instructions please go to our home page. +

    +
    + + Utility + + + + https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png + + + https://rustdesk.com + https://github.com/rustdesk/rustdesk/issues + + + + +
    \ No newline at end of file diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json index 6d7acb5b89c..366f83f5b40 100644 --- a/flatpak/rustdesk.json +++ b/flatpak/rustdesk.json @@ -1,19 +1,36 @@ { "id": "com.rustdesk.RustDesk", "runtime": "org.freedesktop.Platform", - "runtime-version": "23.08", + "runtime-version": "24.08", "sdk": "org.freedesktop.Sdk", "command": "rustdesk", - "icon": "share/icons/hicolor/scalable/apps/rustdesk.svg", + "rename-desktop-file": "rustdesk.desktop", + "rename-icon": "rustdesk", + "cleanup": [ + "/include", + "/lib/pkgconfig", + "/share/gtk-doc" + ], "modules": [ "shared-modules/libappindicator/libappindicator-gtk3-12.10.json", - "xdotool.json", { - "name": "pam", - "buildsystem": "simple", - "build-commands": [ - "./configure --disable-selinux --prefix=/app && make -j4 install" + "name": "xdotool", + "no-autogen": true, + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz", + "sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada" + } + ] + }, + { + "name": "pam", + "buildsystem": "autotools", + "config-opts": [ "--disable-selinux" ], "sources": [ { "type": "archive", @@ -26,32 +43,25 @@ "name": "rustdesk", "buildsystem": "simple", "build-commands": [ - "bsdtar -zxvf rustdesk.deb", - "tar -xvf ./data.tar.xz", - "cp -r ./usr/* /app/", + "bsdtar -Oxf rustdesk.deb data.tar.xz | bsdtar -xf -", + "cp -r usr/* /app/", "mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk", - "mv /app/share/applications/rustdesk.desktop /app/share/applications/com.rustdesk.RustDesk.desktop", - "mv /app/share/applications/rustdesk-link.desktop /app/share/applications/com.rustdesk.RustDesk-link.desktop", - "sed -i '/^Icon=/ c\\Icon=com.rustdesk.RustDesk' /app/share/applications/*.desktop", - "mv /app/share/icons/hicolor/scalable/apps/rustdesk.svg /app/share/icons/hicolor/scalable/apps/com.rustdesk.RustDesk.svg", - "for size in 16 24 32 48 64 128 256 512; do\n rsvg-convert -w $size -h $size -f png -o $size.png scalable.svg\n install -Dm644 $size.png /app/share/icons/hicolor/${size}x${size}/apps/com.rustdesk.RustDesk.png\n done" + "install -Dm644 com.rustdesk.RustDesk.metainfo.xml /app/share/metainfo/com.rustdesk.RustDesk.metainfo.xml" ], - "cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"], "sources": [ { "type": "file", - "path": "./rustdesk.deb" + "path": "rustdesk.deb" }, { "type": "file", - "path": "../res/scalable.svg" + "path": "com.rustdesk.RustDesk.metainfo.xml" } ] } ], "finish-args": [ "--share=ipc", - "--socket=x11", "--socket=fallback-x11", "--socket=wayland", "--share=network", @@ -60,4 +70,4 @@ "--socket=pulseaudio", "--talk-name=org.freedesktop.Flatpak" ] -} +} \ No newline at end of file diff --git a/flatpak/xdotool.json b/flatpak/xdotool.json deleted file mode 100644 index d7f41bf0ec0..00000000000 --- a/flatpak/xdotool.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "xdotool", - "buildsystem": "simple", - "build-commands": [ - "make -j4 && PREFIX=./build make install", - "cp -r ./build/* /app/" - ], - "sources": [ - { - "type": "archive", - "url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz", - "sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada" - } - ] -} From 00d38260e170180519a1bdd3db2d0d73ea51246a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:37:43 +0800 Subject: [PATCH 149/210] fix: web send audit note (#9582) Signed-off-by: fufesou --- flutter/lib/models/platform_model.dart | 8 ++++++++ flutter/lib/models/state_model.dart | 5 ++--- flutter/lib/web/bridge.dart | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/flutter/lib/models/platform_model.dart b/flutter/lib/models/platform_model.dart index 6bc770ff666..0f21587ad8d 100644 --- a/flutter/lib/models/platform_model.dart +++ b/flutter/lib/models/platform_model.dart @@ -6,3 +6,11 @@ final platformFFI = PlatformFFI.instance; final localeName = PlatformFFI.localeName; RustdeskImpl get bind => platformFFI.ffiBind; + +String ffiGetByName(String name, [String arg = '']) { + return PlatformFFI.getByName(name, arg); +} + +void ffiSetByName(String name, [String value = '']) { + PlatformFFI.setByName(name, value); +} diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index 62f92db1218..3481aa2b3e1 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -2,7 +2,6 @@ import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:get/get.dart'; -import 'native_model.dart' if (dart.library.html) 'web_model.dart'; import '../consts.dart'; import './platform_model.dart'; @@ -83,7 +82,7 @@ class StateGlobal { } procFullscreenWeb() { - final isFullscreen = PlatformFFI.getByName('fullscreen') == 'Y'; + final isFullscreen = ffiGetByName('fullscreen') == 'Y'; String fullscreenValue = ''; if (isFullscreen && _fullscreen.isFalse) { fullscreenValue = 'N'; @@ -91,7 +90,7 @@ class StateGlobal { fullscreenValue = 'Y'; } if (fullscreenValue.isNotEmpty) { - PlatformFFI.setByName('fullscreen', fullscreenValue); + ffiSetByName('fullscreen', fullscreenValue); } } diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 5f699114ae0..6e02328b49c 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1188,7 +1188,7 @@ class RustdeskImpl { Future sessionSendNote( {required UuidValue sessionId, required String note, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', ['send_note', note])); } Future sessionAlternativeCodecs( From 28b6bc186fe3eaa153b25e714b31c26643557206 Mon Sep 17 00:00:00 2001 From: XLion Date: Mon, 7 Oct 2024 21:46:25 +0800 Subject: [PATCH 150/210] Add `Section` to Debian control (#9584) --- build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build.py b/build.py index 3ad206ab189..201766b3e2a 100755 --- a/build.py +++ b/build.py @@ -283,6 +283,7 @@ def generate_control_file(version): system2('/bin/rm -rf %s' % control_file_path) content = """Package: rustdesk +Section: net Version: %s Architecture: %s Maintainer: rustdesk From 5555ba6b2fead83092e3485b7d71b448efc09b51 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:50:01 +0800 Subject: [PATCH 151/210] Revert "manifest updates from the Flathub's PR (#9581)" (#9585) This reverts commit e06f456bbd3241c20993fa4603a0c8e6ca6c9bdb. --- .github/workflows/flutter-build.yml | 4 +- flatpak/com.rustdesk.RustDesk.metainfo.xml | 40 ----------------- flatpak/rustdesk.json | 50 +++++++++------------- flatpak/xdotool.json | 15 +++++++ 4 files changed, 37 insertions(+), 72 deletions(-) delete mode 100644 flatpak/com.rustdesk.RustDesk.metainfo.xml create mode 100644 flatpak/xdotool.json diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index 989aee65f92..dd21515ba54 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -1951,8 +1951,8 @@ jobs: wget # flatpak deps flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/24.08 - flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/24.08 + flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/23.08 + flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/23.08 # package pushd flatpak git clone https://github.com/flathub/shared-modules.git --depth=1 diff --git a/flatpak/com.rustdesk.RustDesk.metainfo.xml b/flatpak/com.rustdesk.RustDesk.metainfo.xml deleted file mode 100644 index 12842fb8abe..00000000000 --- a/flatpak/com.rustdesk.RustDesk.metainfo.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - com.rustdesk.RustDesk - RustDesk - com.rustdesk.RustDesk.desktop - CC0-1.0 - AGPL-3.0-only - RustDesk - An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer. - -

    - RustDesk is a full-featured open source remote control alternative for self-hosting and security with minimal configuration. -

    -
      -
    • Works on Windows, macOS, Linux, iOS, Android, Web.
    • -
    • Supports VP8 / VP9 / AV1 software codecs, and H264 / H265 hardware codecs.
    • -
    • Own your data, easily set up self-hosting solution on your infrastructure.
    • -
    • P2P connection with end-to-end encryption based on NaCl.
    • -
    • No administrative privileges or installation needed for Windows, elevate priviledge locally or from remote on demand.
    • -
    • We like to keep things simple and will strive to make simpler where possible.
    • -
    -

    - For self-hosting setup instructions please go to our home page. -

    -
    - - Utility - - - - https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png - - - https://rustdesk.com - https://github.com/rustdesk/rustdesk/issues - - - - -
    \ No newline at end of file diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json index 366f83f5b40..6d7acb5b89c 100644 --- a/flatpak/rustdesk.json +++ b/flatpak/rustdesk.json @@ -1,36 +1,19 @@ { "id": "com.rustdesk.RustDesk", "runtime": "org.freedesktop.Platform", - "runtime-version": "24.08", + "runtime-version": "23.08", "sdk": "org.freedesktop.Sdk", "command": "rustdesk", - "rename-desktop-file": "rustdesk.desktop", - "rename-icon": "rustdesk", - "cleanup": [ - "/include", - "/lib/pkgconfig", - "/share/gtk-doc" - ], + "icon": "share/icons/hicolor/scalable/apps/rustdesk.svg", "modules": [ "shared-modules/libappindicator/libappindicator-gtk3-12.10.json", - { - "name": "xdotool", - "no-autogen": true, - "make-install-args": [ - "PREFIX=${FLATPAK_DEST}" - ], - "sources": [ - { - "type": "archive", - "url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz", - "sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada" - } - ] - }, + "xdotool.json", { "name": "pam", - "buildsystem": "autotools", - "config-opts": [ "--disable-selinux" ], + "buildsystem": "simple", + "build-commands": [ + "./configure --disable-selinux --prefix=/app && make -j4 install" + ], "sources": [ { "type": "archive", @@ -43,25 +26,32 @@ "name": "rustdesk", "buildsystem": "simple", "build-commands": [ - "bsdtar -Oxf rustdesk.deb data.tar.xz | bsdtar -xf -", - "cp -r usr/* /app/", + "bsdtar -zxvf rustdesk.deb", + "tar -xvf ./data.tar.xz", + "cp -r ./usr/* /app/", "mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk", - "install -Dm644 com.rustdesk.RustDesk.metainfo.xml /app/share/metainfo/com.rustdesk.RustDesk.metainfo.xml" + "mv /app/share/applications/rustdesk.desktop /app/share/applications/com.rustdesk.RustDesk.desktop", + "mv /app/share/applications/rustdesk-link.desktop /app/share/applications/com.rustdesk.RustDesk-link.desktop", + "sed -i '/^Icon=/ c\\Icon=com.rustdesk.RustDesk' /app/share/applications/*.desktop", + "mv /app/share/icons/hicolor/scalable/apps/rustdesk.svg /app/share/icons/hicolor/scalable/apps/com.rustdesk.RustDesk.svg", + "for size in 16 24 32 48 64 128 256 512; do\n rsvg-convert -w $size -h $size -f png -o $size.png scalable.svg\n install -Dm644 $size.png /app/share/icons/hicolor/${size}x${size}/apps/com.rustdesk.RustDesk.png\n done" ], + "cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"], "sources": [ { "type": "file", - "path": "rustdesk.deb" + "path": "./rustdesk.deb" }, { "type": "file", - "path": "com.rustdesk.RustDesk.metainfo.xml" + "path": "../res/scalable.svg" } ] } ], "finish-args": [ "--share=ipc", + "--socket=x11", "--socket=fallback-x11", "--socket=wayland", "--share=network", @@ -70,4 +60,4 @@ "--socket=pulseaudio", "--talk-name=org.freedesktop.Flatpak" ] -} \ No newline at end of file +} diff --git a/flatpak/xdotool.json b/flatpak/xdotool.json new file mode 100644 index 00000000000..d7f41bf0ec0 --- /dev/null +++ b/flatpak/xdotool.json @@ -0,0 +1,15 @@ +{ + "name": "xdotool", + "buildsystem": "simple", + "build-commands": [ + "make -j4 && PREFIX=./build make install", + "cp -r ./build/* /app/" + ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz", + "sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada" + } + ] +} From 9bcd0d1b03d7205d29e99f1988479c2b19940389 Mon Sep 17 00:00:00 2001 From: XLion Date: Mon, 7 Oct 2024 22:08:17 +0800 Subject: [PATCH 152/210] Add `Priority:` to Debian control (#9586) --- build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build.py b/build.py index 201766b3e2a..b7ea0a1ef68 100755 --- a/build.py +++ b/build.py @@ -284,6 +284,7 @@ def generate_control_file(version): content = """Package: rustdesk Section: net +Priority: optional Version: %s Architecture: %s Maintainer: rustdesk From 2591d4f04418a936768146bbea62eb81bb353760 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:23:00 +0800 Subject: [PATCH 153/210] fix: web chat (#9588) * fix: web chat Signed-off-by: fufesou * add missing svg Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/assets/message_24dp_5F6368.svg | 1 + .../lib/desktop/widgets/remote_toolbar.dart | 59 ++++++++++++------- flutter/lib/models/chat_model.dart | 7 ++- flutter/lib/web/bridge.dart | 6 +- 4 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 flutter/assets/message_24dp_5F6368.svg diff --git a/flutter/assets/message_24dp_5F6368.svg b/flutter/assets/message_24dp_5F6368.svg new file mode 100644 index 00000000000..5347a3d2d65 --- /dev/null +++ b/flutter/assets/message_24dp_5F6368.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index e7b4d4d761f..d9263630177 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -479,8 +479,8 @@ class _RemoteToolbarState extends State { setFullscreen: _setFullscreen, )); toolbarItems.add(_KeyboardMenu(id: widget.id, ffi: widget.ffi)); + toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi)); if (!isWeb) { - toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi)); toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi)); } if (!isWeb) toolbarItems.add(_RecordMenu()); @@ -1781,34 +1781,49 @@ class _ChatMenuState extends State<_ChatMenu> { @override Widget build(BuildContext context) { - return _IconSubmenuButton( - tooltip: 'Chat', - key: chatButtonKey, - svg: 'assets/chat.svg', - ffi: widget.ffi, - color: _ToolbarTheme.blueColor, - hoverColor: _ToolbarTheme.hoverBlueColor, - menuChildrenGetter: () => [textChat(), voiceCall()]); + if (isWeb) { + return buildTextChatButton(); + } else { + return _IconSubmenuButton( + tooltip: 'Chat', + key: chatButtonKey, + svg: 'assets/chat.svg', + ffi: widget.ffi, + color: _ToolbarTheme.blueColor, + hoverColor: _ToolbarTheme.hoverBlueColor, + menuChildrenGetter: () => [textChat(), voiceCall()]); + } + } + + buildTextChatButton() { + return _IconMenuButton( + assetName: 'assets/message_24dp_5F6368.svg', + tooltip: 'Text chat', + key: chatButtonKey, + onPressed: _textChatOnPressed, + color: _ToolbarTheme.blueColor, + hoverColor: _ToolbarTheme.hoverBlueColor, + ); } textChat() { return MenuButton( child: Text(translate('Text chat')), ffi: widget.ffi, - onPressed: () { - RenderBox? renderBox = - chatButtonKey.currentContext?.findRenderObject() as RenderBox?; - - Offset? initPos; - if (renderBox != null) { - final pos = renderBox.localToGlobal(Offset.zero); - initPos = Offset(pos.dx, pos.dy + _ToolbarTheme.dividerHeight); - } + onPressed: _textChatOnPressed); + } - widget.ffi.chatModel.changeCurrentKey( - MessageKey(widget.ffi.id, ChatModel.clientModeID)); - widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos); - }); + _textChatOnPressed() { + RenderBox? renderBox = + chatButtonKey.currentContext?.findRenderObject() as RenderBox?; + Offset? initPos; + if (renderBox != null) { + final pos = renderBox.localToGlobal(Offset.zero); + initPos = Offset(pos.dx, pos.dy + _ToolbarTheme.dividerHeight); + } + widget.ffi.chatModel + .changeCurrentKey(MessageKey(widget.ffi.id, ChatModel.clientModeID)); + widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos); } voiceCall() { diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 778c9435704..d79c9f07022 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -235,13 +235,14 @@ class ChatModel with ChangeNotifier { } } - _isChatOverlayHide() => ((!isDesktop && chatIconOverlayEntry == null) || - chatWindowOverlayEntry == null); + _isChatOverlayHide() => + ((!(isDesktop || isWebDesktop) && chatIconOverlayEntry == null) || + chatWindowOverlayEntry == null); toggleChatOverlay({Offset? chatInitPos}) { if (_isChatOverlayHide()) { gFFI.invokeMethod("enable_soft_keyboard", true); - if (!isDesktop) { + if (!(isDesktop || isWebDesktop)) { showChatIconOverlay(); } showChatWindowOverlay(chatInitPos: chatInitPos); diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 6e02328b49c..321fdcd5a4f 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -468,7 +468,8 @@ class RustdeskImpl { Future sessionSendChat( {required UuidValue sessionId, required String text, dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('setByName', ['send_chat', text])); } Future sessionPeerOption( @@ -1188,7 +1189,8 @@ class RustdeskImpl { Future sessionSendNote( {required UuidValue sessionId, required String note, dynamic hint}) { - return Future(() => js.context.callMethod('setByName', ['send_note', note])); + return Future( + () => js.context.callMethod('setByName', ['send_note', note])); } Future sessionAlternativeCodecs( From 507de628c914a451f6a96916f5c971e5c215e8d4 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:35:25 +0800 Subject: [PATCH 154/210] fix: web, 2fa, trust (#9594) Signed-off-by: fufesou --- flutter/lib/web/bridge.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 321fdcd5a4f..2c8d6b4b033 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -152,7 +152,10 @@ class RustdeskImpl { required String code, required bool trustThisDevice, dynamic hint}) { - return Future(() => js.context.callMethod('setByName', ['send_2fa', code])); + return Future(() => js.context.callMethod('setByName', [ + 'send_2fa', + jsonEncode({'code': code, 'trust_this_device': trustThisDevice}) + ])); } Future sessionClose({required UuidValue sessionId, dynamic hint}) { @@ -1682,7 +1685,8 @@ class RustdeskImpl { bool sessionGetEnableTrustedDevices( {required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + return js.context.callMethod('getByName', ['enable_trusted_devices']) == + 'Y'; } Future mainGetTrustedDevices({dynamic hint}) { From e6d4067f489f6b4bf6b8cf6747a8cac6f251e57f Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 8 Oct 2024 21:16:07 +0800 Subject: [PATCH 155/210] refact: remote toolbar style (#9597) Signed-off-by: fufesou --- .../lib/desktop/widgets/remote_toolbar.dart | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index d9263630177..3d8ca5e1314 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -2336,15 +2336,33 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { ); final isFullscreen = stateGlobal.fullscreen; const double iconSize = 20; + + buttonWrapper(VoidCallback? onPressed, Widget child, + {Color hoverColor = _ToolbarTheme.blueColor}) { + final bgColor = buttonStyle.backgroundColor?.resolve({}); + return TextButton( + onPressed: onPressed, + child: child, + style: buttonStyle.copyWith( + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.hovered)) { + return (bgColor ?? hoverColor).withOpacity(0.15); + } + return bgColor; + }), + ), + ); + } + final child = Row( mainAxisSize: MainAxisSize.min, children: [ _buildDraggable(context), - Obx(() => TextButton( - onPressed: () { + Obx(() => buttonWrapper( + () { widget.setFullscreen(!isFullscreen.value); }, - child: Tooltip( + Tooltip( message: translate( isFullscreen.isTrue ? 'Exit Fullscreen' : 'Fullscreen'), child: Icon( @@ -2358,9 +2376,9 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { if (!isMacOS && !isWebDesktop) Obx(() => Offstage( offstage: isFullscreen.isFalse, - child: TextButton( - onPressed: () => widget.setMinimize(), - child: Tooltip( + child: buttonWrapper( + widget.setMinimize, + Tooltip( message: translate('Minimize'), child: Icon( Icons.remove, @@ -2369,11 +2387,11 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { ), ), )), - TextButton( - onPressed: () => setState(() { + buttonWrapper( + () => setState(() { widget.toolbarState.switchShow(widget.sessionId); }), - child: Obx((() => Tooltip( + Obx((() => Tooltip( message: translate(show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'), child: Icon( @@ -2387,9 +2405,9 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { if (show.isTrue) { return Offstage(); } else { - return TextButton( - onPressed: () => closeConnection(id: widget.id), - child: Tooltip( + return buttonWrapper( + () => closeConnection(id: widget.id), + Tooltip( message: translate('Close'), child: Icon( Icons.close, @@ -2397,7 +2415,8 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { color: _ToolbarTheme.redColor, ), ), - ); + hoverColor: _ToolbarTheme.redColor, + ).paddingOnly(left: iconSize / 2); } }) ], From 4b3b31147e2d9cbdf709ce21baaa40e18f2117ba Mon Sep 17 00:00:00 2001 From: James Date: Tue, 8 Oct 2024 23:01:54 -0300 Subject: [PATCH 156/210] Update eo.rs (#9600) --- src/lang/eo.rs | 240 ++++++++++++++++++++++++------------------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 3a406e55871..ba369e4ec11 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -6,11 +6,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("desk_tip", "Via aparato povas esti alirita kun tiu identigilo kaj pasvorto"), ("Password", "Pasvorto"), ("Ready", "Preta"), - ("Established", ""), + ("Established", "Establis"), ("connecting_status", "Konektante al la reto RustDesk..."), ("Enable service", "Ebligi servon"), ("Start service", "Starti servon"), - ("Service is running", ""), + ("Service is running", "La servo funkcias"), ("Service is not running", "La servo ne funkcias"), ("not_ready_status", "Ne preta, bonvolu kontroli la retkonekto"), ("Control Remote Desktop", "Kontroli foran aparaton"), @@ -29,33 +29,33 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable TCP tunneling", "Ebligi tunelado TCP"), ("IP Whitelisting", "Listo de IP akceptataj"), ("ID/Relay Server", "Identigila/Relajsa servilo"), - ("Import server config", "Enporti servilan agordon"), - ("Export Server Config", ""), + ("Import server config", "Importi servilan agordon"), + ("Export Server Config", "Eksporti servilan agordon"), ("Import server configuration successfully", "Importi servilan agordon sukcese"), - ("Export server configuration successfully", ""), + ("Export server configuration successfully", "Eksporti servilan agordon sukcese"), ("Invalid server configuration", "Nevalida servila agordo"), ("Clipboard is empty", "La poŝo estas malplena"), ("Stop service", "Haltu servon"), ("Change ID", "Ŝanĝi identigilon"), - ("Your new ID", ""), - ("length %min% to %max%", ""), - ("starts with a letter", ""), - ("allowed characters", ""), + ("Your new ID", "Via nova identigilo"), + ("length %min% to %max%", "longeco %min% al %max%"), + ("starts with a letter", "komencas kun letero"), + ("allowed characters", "permesitaj signoj"), ("id_change_tip", "Nur la signoj a-z, A-Z, 0-9, _ (substreko) povas esti uzataj. La unua litero povas esti inter a-z, A-Z. La longeco devas esti inter 6 kaj 16."), ("Website", "Retejo"), ("About", "Pri"), - ("Slogan_tip", ""), - ("Privacy Statement", ""), + ("Slogan_tip", "Farita kun koro en ĉi tiu ĥaosa mondo!"), + ("Privacy Statement", "Deklaro Pri Privateco"), ("Mute", "Muta"), - ("Build Date", ""), - ("Version", ""), - ("Home", ""), - ("Audio Input", "Aŭdia enigo"), - ("Enhancements", ""), - ("Hardware Codec", ""), - ("Adaptive bitrate", ""), + ("Build Date", "konstruada dato"), + ("Version", "Versio"), + ("Home", "Hejmo"), + ("Audio Input", "Aŭdia Enigo"), + ("Enhancements", "Plibonigoj"), + ("Hardware Codec", "Aparataro Kodeko"), + ("Adaptive bitrate", "Adapta bitrapido"), ("ID Server", "Servilo de identigiloj"), - ("Relay Server", "Relajsa servilo"), + ("Relay Server", "Relajsa Servilo"), ("API Server", "Servilo de API"), ("invalid_http", "Devas komenci kun http:// aŭ https://"), ("Invalid IP", "IP nevalida"), @@ -83,35 +83,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Successful", "Sukceso"), ("Connected, waiting for image...", "Konektita, atendante bildon..."), ("Name", "Nomo"), - ("Type", ""), + ("Type", "Tipo"), ("Modified", "Modifita"), ("Size", "Grandeco"), ("Show Hidden Files", "Montri kaŝitajn dosierojn"), ("Receive", "Akcepti"), ("Send", "Sendi"), - ("Refresh File", ""), - ("Local", ""), - ("Remote", ""), + ("Refresh File", "Aktualigu Dosieron"), + ("Local", "Loka"), + ("Remote", "Fora"), ("Remote Computer", "Fora komputilo"), ("Local Computer", "Loka komputilo"), ("Confirm Delete", "Konfermi la forigo"), - ("Delete", ""), - ("Properties", ""), - ("Multi Select", ""), - ("Select All", ""), - ("Unselect All", ""), - ("Empty Directory", ""), - ("Not an empty directory", ""), - ("Are you sure you want to delete this file?", "Ĉu vi vere volas forigi tiun dosieron?"), - ("Are you sure you want to delete this empty directory?", ""), - ("Are you sure you want to delete the file of this directory?", ""), + ("Delete", "Forigi"), + ("Properties", "Propraĵoj"), + ("Multi Select", "Pluropa Elekto"), + ("Select All", "Elektu Ĉiujn"), + ("Unselect All", "Malelektu Ĉiujn"), + ("Empty Directory", "Malplena Dosierujo"), + ("Not an empty directory", "Ne Malplena Dosierujo"), + ("Are you sure you want to delete this file?", "Ĉu vi certas, ke vi volas forigi ĉi tiun dosieron?"), + ("Are you sure you want to delete this empty directory?", "Ĉu vi certas, ke vi volas forigi ĉi tiun malplenan dosierujon?"), + ("Are you sure you want to delete the file of this directory?", "Ĉu vi certa. ke vi volas forigi la dosieron de ĉi tiu dosierujo"), ("Do this for all conflicts", "Same por ĉiuj konfliktoj"), - ("This is irreversible!", ""), + ("This is irreversible!", "Ĉi tio estas neinversigebla!"), ("Deleting", "Forigado"), ("files", "dosiero"), ("Waiting", "Atendante..."), ("Finished", "Finita"), - ("Speed", ""), + ("Speed", "Rapideco"), ("Custom Image Quality", "Agordi bildan kvaliton"), ("Privacy mode", "Modo privata"), ("Block user input", "Bloki uzanta enigo"), @@ -127,7 +127,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Optimize reaction time", "Optimigi reakcia tempo"), ("Custom", ""), ("Show remote cursor", "Montri foran kursoron"), - ("Show quality monitor", ""), + ("Show quality monitor", "Montri kvalito monitoron"), ("Disable clipboard", "Malebligi poŝon"), ("Lock after session end", "Ŝlosi foran komputilon post malkonektado"), ("Insert", "Enmeti"), @@ -170,8 +170,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Action", "Ago"), ("Add", "Aldoni"), ("Local Port", "Loka pordo"), - ("Local Address", ""), - ("Change Local Port", ""), + ("Local Address", "Loka Adreso"), + ("Change Local Port", "Ŝanĝi Loka Pordo"), ("setup_server_tip", "Se vi bezonas pli rapida konekcio, vi povas krei vian propran servilon"), ("Too short, at least 6 characters.", "Tro mallonga, almenaŭ 6 signoj."), ("The confirmation is not identical.", "Ambaŭ enigoj ne kongruas"), @@ -203,23 +203,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reboot required", "Restarto deviga"), ("Unsupported display server", "La aktuala bilda servilo ne estas subtenita"), ("x11 expected", "Bonvolu uzi x11"), - ("Port", ""), + ("Port", "Pordo"), ("Settings", "Agordoj"), ("Username", " Uzanta nomo"), ("Invalid port", "Pordo nevalida"), ("Closed manually by the peer", "Manuale fermita de la samtavolano"), ("Enable remote configuration modification", "Permesi foran redaktadon de la konfiguracio"), ("Run without install", "Plenumi sen instali"), - ("Connect via relay", ""), + ("Connect via relay", "Konekti per relajso"), ("Always connect via relay", "Ĉiam konekti per relajso"), ("whitelist_tip", "Nur la IP en la blanka listo povas kontroli mian komputilon"), - ("Login", "Konekti"), - ("Verify", ""), - ("Remember me", ""), - ("Trust this device", ""), - ("Verification code", ""), - ("verification_tip", ""), - ("Logout", "Malkonekti"), + ("Login", "Ensaluti"), + ("Verify", "Kontrolis"), + ("Remember me", "Memori min"), + ("Trust this device", "Fidu ĉi tiun aparaton"), + ("Verification code", "Konfirmkodo"), + ("verification_tip", "Konfirmkodo estis sendita al la registrita retpoŝta adreso, enigu la konfirmkodon por daŭrigi ensaluti."), + ("Logout", "Elsaluti"), ("Tags", "Etikedi"), ("Search ID", "Serĉi ID"), ("whitelist_sep", "Vi povas uzi komon, punktokomon, spacon aŭ linsalton kiel apartigilo"), @@ -241,86 +241,86 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Socks5 Proxy", "Socks5 prokura servilo"), ("Socks5/Http(s) Proxy", "Socks5/Http(s) prokura servilo"), ("Discovered", "Malkovritaj"), - ("install_daemon_tip", ""), + ("install_daemon_tip", "Por komenci ĉe ekŝargo, oni devas instali sisteman servon."), ("Remote ID", "Fora identigilo"), ("Paste", "Alglui"), - ("Paste here?", ""), + ("Paste here?", "Alglui ĉi tie?"), ("Are you sure to close the connection?", "Ĉu vi vere volas fermi la konekton?"), ("Download new version", "Elŝuti la novan version"), ("Touch mode", "Tuŝa modo"), - ("Mouse mode", ""), - ("One-Finger Tap", ""), - ("Left Mouse", ""), - ("One-Long Tap", ""), - ("Two-Finger Tap", ""), - ("Right Mouse", ""), - ("One-Finger Move", ""), - ("Double Tap & Move", ""), - ("Mouse Drag", ""), - ("Three-Finger vertically", ""), - ("Mouse Wheel", ""), - ("Two-Finger Move", ""), - ("Canvas Move", ""), - ("Pinch to Zoom", ""), - ("Canvas Zoom", ""), + ("Mouse mode", "musa modo"), + ("One-Finger Tap", "Unufingra Frapeto"), + ("Left Mouse", "Maldekstra Muso"), + ("One-Long Tap", "Unulonga Frapeto"), + ("Two-Finger Tap", "Dufingra Frapeto"), + ("Right Mouse", "Deskra Muso"), + ("One-Finger Move", "Unufingra Movo"), + ("Double Tap & Move", "Duobla Frapeto & Movo"), + ("Mouse Drag", "Muso Trenadi"), + ("Three-Finger vertically", "Tri Figroj Vertikale"), + ("Mouse Wheel", "Musa Rado"), + ("Two-Finger Move", "Dufingra Movo"), + ("Canvas Move", "Kanvasa Movo"), + ("Pinch to Zoom", "Pinĉi al Zomo"), + ("Canvas Zoom", "Kanvasa Zomo"), ("Reset canvas", "Restarigi kanvaso"), ("No permission of file transfer", "Neniu permeso de dosiertransigo"), ("Note", "Notu"), - ("Connection", ""), - ("Share Screen", ""), - ("Chat", ""), - ("Total", ""), - ("items", ""), - ("Selected", ""), - ("Screen Capture", ""), - ("Input Control", ""), - ("Audio Capture", ""), - ("File Connection", ""), - ("Screen Connection", ""), - ("Do you accept?", ""), - ("Open System Setting", ""), - ("How to get Android input permission?", ""), - ("android_input_permission_tip1", ""), - ("android_input_permission_tip2", ""), - ("android_new_connection_tip", ""), - ("android_service_will_start_tip", ""), - ("android_stop_service_tip", ""), - ("android_version_audio_tip", ""), - ("android_start_service_tip", ""), - ("android_permission_may_not_change_tip", ""), - ("Account", ""), - ("Overwrite", ""), - ("This file exists, skip or overwrite this file?", ""), - ("Quit", ""), - ("Help", ""), - ("Failed", ""), - ("Succeeded", ""), - ("Someone turns on privacy mode, exit", ""), - ("Unsupported", ""), - ("Peer denied", ""), - ("Please install plugins", ""), - ("Peer exit", ""), - ("Failed to turn off", ""), - ("Turned off", ""), - ("Language", ""), - ("Keep RustDesk background service", ""), - ("Ignore Battery Optimizations", ""), - ("android_open_battery_optimizations_tip", ""), - ("Start on boot", ""), - ("Start the screen sharing service on boot, requires special permissions", ""), - ("Connection not allowed", ""), + ("Connection", "Konekto"), + ("Share Screen", "Kunhavigi Ekranon"), + ("Chat", "Babilo"), + ("Total", "Sumo"), + ("items", "eroj"), + ("Selected", "Elektita"), + ("Screen Capture", "Ekrankapto"), + ("Input Control", "Eniga Kontrolo"), + ("Audio Capture", "Sonkontrolo"), + ("File Connection", "Dosiero Konekto"), + ("Screen Connection", "Ekrono konekto"), + ("Do you accept?", "Ĉu vi akceptas?"), + ("Open System Setting", "Malfermi Sistemajn Agordojn"), + ("How to get Android input permission?", "Kiel akiri Android enigajn permesojn"), + ("android_input_permission_tip1", "Por ke fora aparato regu vian Android-aparaton per muso aŭ tuŝo, vi devas permesi al RustDesk uzi la servon \"Alirebleco\"."), + ("android_input_permission_tip2", "Bonvolu iri al la sekva paĝo de sistemaj agordoj, trovi kaj eniri [Instatajn Servojn], ŝalti la servon [RustDesk Enigo]."), + ("android_new_connection_tip", "Nova kontrolpeto estis ricevita, kiu volas kontroli vian nunan aparaton."), + ("android_service_will_start_tip", "Ŝalti \"Ekrankapto\" aŭtomate startos la servon, permesante al aliaj aparatoj peti konekton al via aparato."), + ("android_stop_service_tip", "Fermante la servon aŭtomate fermos ĉiujn establitajn konektojn."), + ("android_version_audio_tip", "La nuna versio da Android ne subtenas sonkapton, bonvolu ĝisdatigi al Android 10 aŭ pli alta."), + ("android_start_service_tip", "Frapu [Komenci servo] aŭ ebligu la permeson de [Ekrankapto] por komenci la servon de kundivido de ekrano."), + ("android_permission_may_not_change_tip", "Permesoj por establitaj konektoj neble estas ŝanĝitaj tuj ĝis rekonektitaj."), + ("Account", "Konto"), + ("Overwrite", "anstataŭigi"), + ("This file exists, skip or overwrite this file?", "Ĉi tiu dosiero ekzistas, ĉu preterlasi aŭ anstataŭi ĉi tiun dosieron?"), + ("Quit", "Forlasi"), + ("Help", "Helpi"), + ("Failed", "Malsukcesa"), + ("Succeeded", "Sukcesa"), + ("Someone turns on privacy mode, exit", "Iu ŝaltas modon privata, Eliro"), + ("Unsupported", "Nesubtenata"), + ("Peer denied", "Samulo rifuzita"), + ("Please install plugins", "Bonvolu instali kromprogramojn"), + ("Peer exit", "Samulo eliras"), + ("Failed to turn off", "Malsukcesis malŝalti"), + ("Turned off", "Malŝaltita"), + ("Language", "Lingvo"), + ("Keep RustDesk background service", "Tenu RustDesk fonan servon"), + ("Ignore Battery Optimizations", "Ignoru Bateria Optimumigojn"), + ("android_open_battery_optimizations_tip", "Se vi volas malŝalti ĉi tiun funkcion, bonvolu iri al la sekva paĝo de agordoj de la aplikaĵo de RustDesk, trovi kaj eniri [Baterio], Malmarku [Senrestrikta]"), + ("Start on boot", "Komencu ĉe ekfunkciigo"), + ("Start the screen sharing service on boot, requires special permissions", "Komencu la servon de kundivido de ekrano ĉe lanĉo, postulas specialajn permesojn"), + ("Connection not allowed", "Konekto ne rajtas"), ("Legacy mode", ""), - ("Map mode", ""), - ("Translate mode", ""), - ("Use permanent password", ""), - ("Use both passwords", ""), - ("Set permanent password", ""), - ("Enable remote restart", ""), - ("Restart remote device", ""), - ("Are you sure you want to restart", ""), - ("Restarting remote device", ""), - ("remote_restarting_tip", ""), - ("Copied", ""), + ("Map mode", "Mapa modo"), + ("Translate mode", "Traduki modo"), + ("Use permanent password", "Uzu permanenta pasvorto"), + ("Use both passwords", "Uzu ambaŭ pasvorto"), + ("Set permanent password", "Starigi permanenta pasvorto"), + ("Enable remote restart", "Permesi fora restartas"), + ("Restart remote device", "Restartu fora aparato"), + ("Are you sure you want to restart", "Ĉu vi certas, ke vi volas restarti"), + ("Restarting remote device", "Restartas fora aparato"), + ("remote_restarting_tip", "Fora aparato restartiĝas, bonvolu fermi ĉi tiun mesaĝkeston kaj rekonekti kun permanenta pasvorto post iom da tempo"), + ("Copied", "Kopiita"), ("Exit Fullscreen", "Eliru Plenekranon"), ("Fullscreen", "Plenekrane"), ("Mobile Actions", "Poŝtelefonaj Agoj"), @@ -330,8 +330,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ratio", "Proporcio"), ("Image Quality", "Bilda Kvalito"), ("Scroll Style", "Ruluma Stilo"), - ("Show Toolbar", ""), - ("Hide Toolbar", ""), + ("Show Toolbar", "Montri Ilobreton"), + ("Hide Toolbar", "Kaŝi Ilobreton"), ("Direct Connection", "Rekta Konekto"), ("Relay Connection", "Relajsa Konekto"), ("Secure Connection", "Sekura Konekto"), From 38fcf4e039767bd8b00b1060fdfa76c0ad9cbde8 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 9 Oct 2024 10:19:24 +0800 Subject: [PATCH 157/210] typo --- src/lang/cn.rs | 2 +- src/lang/tw.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/cn.rs b/src/lang/cn.rs index db942f598f0..7cc616e1427 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -310,7 +310,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Start the screen sharing service on boot, requires special permissions", "开机自动启动屏幕共享服务,此功能需要一些特殊权限。"), ("Connection not allowed", "对方不允许连接"), ("Legacy mode", "传统模式"), - ("Map mode", "1:1 传输"), + ("Map mode", "1:1 传输"), ("Translate mode", "翻译模式"), ("Use permanent password", "使用固定密码"), ("Use both passwords", "同时使用两种密码"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index c935b5552ff..5bb26acce15 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -310,7 +310,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Start the screen sharing service on boot, requires special permissions", "開機時啟動螢幕分享服務,需要特殊權限。"), ("Connection not allowed", "不允許連線"), ("Legacy mode", "傳統模式"), - ("Map mode", "1:1 傳輸模式"), + ("Map mode", "1:1 傳輸模式"), ("Translate mode", "翻譯模式"), ("Use permanent password", "使用固定密碼"), ("Use both passwords", "同時使用兩種密碼"), From 59d7bf1e860adb4cf8bc287ba4db1726ed19e49c Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki <36250619+cacing69@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:44:20 +0700 Subject: [PATCH 158/210] Update indonesian translate (#9601) * update id trans * update id trans * update inv * update ind trans --- src/lang/id.rs | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/lang/id.rs b/src/lang/id.rs index c16243aedd2..c0920dfcb57 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -2,8 +2,8 @@ lazy_static::lazy_static! { pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ("Status", "Status"), - ("Your Desktop", "Desktop Anda"), - ("desk_tip", "Desktop Anda dapat diakses dengan ID dan kata sandi ini."), + ("Your Desktop", "Kamu"), + ("desk_tip", "Desktop kamu dapat diakses dengan ID dan kata sandi ini."), ("Password", "Kata sandi"), ("Ready", "Sudah siap"), ("Established", "Didirikan"), @@ -12,7 +12,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Start service", "Mulai Layanan"), ("Service is running", "Layanan berjalan"), ("Service is not running", "Layanan tidak berjalan"), - ("not_ready_status", "Belum siap. Silakan periksa koneksi Anda"), + ("not_ready_status", "Belum siap. Silakan periksa koneksi"), ("Control Remote Desktop", "Kontrol Remote Desktop"), ("Transfer file", "File Transfer"), ("Connect", "Hubungkan"), @@ -22,7 +22,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("TCP tunneling", "Tunneling TCP"), ("Remove", "Hapus"), ("Refresh random password", "Perbarui kata sandi acak"), - ("Set your own password", "Tetapkan kata sandi Anda"), + ("Set your own password", "Tetapkan kata sandi"), ("Enable keyboard/mouse", "Aktifkan Keyboard/Mouse"), ("Enable clipboard", "Aktifkan Papan Klip"), ("Enable file transfer", "Aktifkan Transfer file"), @@ -37,7 +37,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clipboard is empty", "Papan klip kosong"), ("Stop service", "Hentikan Layanan"), ("Change ID", "Ubah ID"), - ("Your new ID", "ID baru anda"), + ("Your new ID", "ID baru"), ("length %min% to %max%", "panjang %min% s/d %max%"), ("starts with a letter", "Dimulai dengan huruf"), ("allowed characters", "Karakter yang dapat digunakan"), @@ -69,10 +69,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Retry", "Coba lagi"), ("OK", "Oke"), ("Password Required", "Kata sandi tidak boleh kosong"), - ("Please enter your password", "Silahkan masukkan kata sandi anda"), + ("Please enter your password", "Silahkan masukkan kata sandi"), ("Remember password", "Ingat kata sandi"), ("Wrong Password", "Kata sandi Salah"), - ("Do you want to enter again?", "Apakah anda ingin masuk lagi?"), + ("Do you want to enter again?", "Apakah kamu ingin mencoba lagi?"), ("Connection Error", "Kesalahan koneksi"), ("Error", "Kesalahan"), ("Reset by the peer", "Direset oleh rekan"), @@ -102,9 +102,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Unselect All", "Batalkan Pilihan Semua"), ("Empty Directory", "Folder Kosong"), ("Not an empty directory", "Folder tidak kosong"), - ("Are you sure you want to delete this file?", "Apakah anda yakin untuk menghapus file ini?"), - ("Are you sure you want to delete this empty directory?", "Apakah anda yakin untuk menghapus folder ini?"), - ("Are you sure you want to delete the file of this directory?", "Apakah anda yakin untuk menghapus file dan folder ini?"), + ("Are you sure you want to delete this file?", "Apakah kamu yakin untuk menghapus file ini?"), + ("Are you sure you want to delete this empty directory?", "Apakah yakin yakin untuk menghapus folder ini?"), + ("Are you sure you want to delete the file of this directory?", "Apakah yakin yakin untuk menghapus file dan folder ini?"), ("Do this for all conflicts", "Lakukan untuk semua konflik"), ("This is irreversible!", "Ini tidak dapat diubah!"), ("Deleting", "Menghapus"), @@ -150,20 +150,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Click to download", "Klik untuk unduh"), ("Click to update", "Klik untuk memperbarui"), ("Configure", "Konfigurasi"), - ("config_acc", "Untuk mengontrol Desktop Anda dari jarak jauh, Anda perlu memberikan izin \"Aksesibilitas\" RustDesk."), - ("config_screen", "Untuk mengakses Desktop Anda dari jarak jauh, Anda perlu memberikan izin \"Perekaman Layar\" RustDesk."), + ("config_acc", "Agar bisa mengontrol Desktopmu dari jarak jauh, Kamu harus memberikan izin \"Aksesibilitas\" untuk RustDesk."), + ("config_screen", "Agar bisa mengakses Desktopmu dari jarak jauh, kamu harus memberikan izin \"Perekaman Layar\" untuk RustDesk."), ("Installing ...", "Menginstall"), ("Install", "Instal"), ("Installation", "Instalasi"), ("Installation Path", "Direktori Instalasi"), ("Create start menu shortcuts", "Buat pintasan start menu"), ("Create desktop icon", "Buat icon desktop"), - ("agreement_tip", "Dengan memulai instalasi, Anda menerima perjanjian lisensi."), + ("agreement_tip", "Dengan memulai proses instalasi, Kamu menerima perjanjian lisensi."), ("Accept and Install", "Terima dan Install"), ("End-user license agreement", "Perjanjian lisensi pengguna"), ("Generating ...", "Memproses..."), - ("Your installation is lower version.", "Instalasi Anda adalah versi yang lebih rendah."), - ("not_close_tcp_tip", "Jangan tutup jendela ini saat menggunakan tunnel"), + ("Your installation is lower version.", "Kamu menggunakan versi instalasi yang lebih rendah."), + ("not_close_tcp_tip", "Pastikan jendela ini tetap terbuka saat menggunakan tunnel."), ("Listening ...", "Menghubungkan..."), ("Remote Host", "Host Remote"), ("Remote Port", "Port Remote"), @@ -186,10 +186,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Direct and unencrypted connection", "Koneksi langsung dan tanpa enkripsi"), ("Relayed and unencrypted connection", "Koneksi relay dan tanpa enkripsi"), ("Enter Remote ID", "Masukkan ID Remote"), - ("Enter your password", "Masukkan kata sandi anda"), + ("Enter your password", "Masukkan kata sandi"), ("Logging in...", "Masuk..."), ("Enable RDP session sharing", "Aktifkan berbagi sesi RDP"), - ("Auto Login", "Login Otomatis (Hanya berlaku jika Anda mengatur \"Kunci setelah sesi berakhir\")"), + ("Auto Login", "Login Otomatis (Hanya berlaku jika sudah mengatur \"Kunci setelah sesi berakhir\")"), ("Enable direct IP access", "Aktifkan Akses IP Langsung"), ("Rename", "Ubah nama"), ("Space", "Spasi"), @@ -241,11 +241,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Socks5 Proxy", "Proksi Socks5"), ("Socks5/Http(s) Proxy", "Proksi Socks5/Http(s)"), ("Discovered", "Telah ditemukan"), - ("install_daemon_tip", "Untuk memulai saat boot, Anda perlu menginstal system service."), + ("install_daemon_tip", "Untuk dapat berjalan saat sistem menyala, kamu perlu menginstal layanan sistem (system service/daemon)."), ("Remote ID", "ID Remote"), ("Paste", "Tempel"), ("Paste here?", "Tempel disini?"), - ("Are you sure to close the connection?", "Apakah anda yakin akan menutup koneksi?"), + ("Are you sure to close the connection?", "Apakah kamu yakin akan menutup koneksi?"), ("Download new version", "Unduh versi baru"), ("Touch mode", "Mode Layar Sentuh"), ("Mouse mode", "Mode Mouse"), @@ -373,7 +373,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Write a message", "Tulis pesan"), ("Prompt", ""), ("Please wait for confirmation of UAC...", "Harap tunggu konfirmasi UAC"), - ("elevated_foreground_window_tip", "Jendela remote desktop ini memerlukan hak akses khusus, jadi anda tidak bisa menggunakan mouse dan keyboard untuk sementara. Anda bisa meminta pihak pengguna yang diremote untuk menyembunyikan jendela ini atau klik tombol elevasi di jendela pengaturan koneksi. Untuk menghindari masalah ini, direkomendasikan untuk menginstall aplikasi secara permanen"), + ("elevated_foreground_window_tip", "Jendela yang sedang aktif di remote desktop memerlukan hak istimewa yang lebih tinggi untuk beroperasi, sehingga mouse dan keyboard tidak dapat digunakan sementara waktu. Kamu bisa meminta pengguna jarak jauh untuk meminimalkan jendela saat ini, atau klik tombol elevasi di jendela manajemen koneksi. Untuk menghindari masalah ini, disarankan untuk menginstal software di perangkat remote secara permanen."), ("Disconnected", "Terputus"), ("Other", "Lainnya"), ("Confirm before closing multiple tabs", "Konfirmasi sebelum menutup banyak tab"), @@ -586,29 +586,29 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("powered_by_me", "Didukung oleh RustDesk"), ("outgoing_only_desk_tip", "Ini adalah edisi yang sudah kustomisasi.\nAnda dapat terhubung ke perangkat lain, tetapi perangkat lain tidak dapat terhubung ke perangkat Anda."), ("preset_password_warning", "Edisi yang dikustomisasi ini dilengkapi dengan kata sandi bawaan. Siapa pun yang mengetahui kata sandi ini dapat memperoleh kontrol penuh atas perangkat Anda. Jika Anda tidak mengharapkan ini, segera hapus pemasangan aplikasi tersebut."), - ("Security Alert", ""), - ("My address book", ""), - ("Personal", ""), - ("Owner", ""), - ("Set shared password", ""), - ("Exist in", ""), + ("Security Alert", "Peringatan Keamanan"), + ("My address book", "Daftar Kontak"), + ("Personal", "Personal"), + ("Owner", "Pemilik"), + ("Set shared password", "Atus kata sandi kolaboratif"), + ("Exist in", "Ada di"), ("Read-only", ""), ("Read/Write", ""), ("Full Control", ""), - ("share_warning_tip", ""), + ("share_warning_tip", "Informasi di atas bersifat publik dan dapat dilihat oleh orang lain."), ("Everyone", ""), - ("ab_web_console_tip", ""), - ("allow-only-conn-window-open-tip", ""), - ("no_need_privacy_mode_no_physical_displays_tip", ""), - ("Follow remote cursor", ""), - ("Follow remote window focus", ""), - ("default_proxy_tip", ""), - ("no_audio_input_device_tip", ""), + ("ab_web_console_tip", "Detail Lain di Konsol Web"), + ("allow-only-conn-window-open-tip", "Koneksi hanya diperbolehkan jika jendela RustDesk sedang terbuka."), + ("no_need_privacy_mode_no_physical_displays_tip", "Karena tidak ada layar fisik, mode privasi tidak perlu diaktifkan."), + ("Follow remote cursor", "Ikuti kursor yang terhubung"), + ("Follow remote window focus", "Ikuti jendela remote yang sedang aktif"), + ("default_proxy_tip", "Pengaturan standar untuk protokol dan port adalah Socks5 dan 1080."), + ("no_audio_input_device_tip", "Perangkat input audio tidak terdeteksi."), ("Incoming", ""), ("Outgoing", ""), - ("Clear Wayland screen selection", ""), - ("clear_Wayland_screen_selection_tip", ""), - ("confirm_clear_Wayland_screen_selection_tip", ""), + ("Clear Wayland screen selection", "Kosongkan pilihan layar Wayland"), + ("clear_Wayland_screen_selection_tip", "Setelah mengosongkan pilihan layar, Anda bisa memilih kembali layar untuk dibagi"), + ("confirm_clear_Wayland_screen_selection_tip", "Kamu yakin ingin membersihkan pemilihan layar Wayland?"), ("android_new_voice_call_tip", ""), ("texture_render_tip", ""), ("Use texture rendering", ""), From 227f154ee7a8ca6704345d7888b223fb29cae451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jernej=20Simon=C4=8Di=C4=8D?= <1800143+jernejs@users.noreply.github.com> Date: Thu, 10 Oct 2024 01:35:46 +0200 Subject: [PATCH 159/210] Add missing Slovenian translations (#9606) --- src/lang/sl.rs | 530 ++++++++++++++++++++++++------------------------- 1 file changed, 265 insertions(+), 265 deletions(-) diff --git a/src/lang/sl.rs b/src/lang/sl.rs index bba9499d75a..7d18ef82982 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -37,19 +37,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clipboard is empty", "Odložišče je prazno"), ("Stop service", "Ustavi storitev"), ("Change ID", "Spremeni ID"), - ("Your new ID", ""), - ("length %min% to %max%", ""), - ("starts with a letter", ""), - ("allowed characters", ""), + ("Your new ID", "Vaš nov ID"), + ("length %min% to %max%", "dolžina od %min% do %max%"), + ("starts with a letter", "začne se s črko"), + ("allowed characters", "dovoljeni znaki"), ("id_change_tip", "Dovoljeni znaki so a-z, A-Z (brez šumnikov), 0-9 in _. Prvi znak mora biti črka, dolžina od 6 do 16 znakov."), ("Website", "Spletna stran"), ("About", "O programu"), ("Slogan_tip", ""), - ("Privacy Statement", ""), + ("Privacy Statement", "Izjava o zasebnosti"), ("Mute", "Izklopi zvok"), - ("Build Date", ""), - ("Version", ""), - ("Home", ""), + ("Build Date", "Datum graditve"), + ("Version", "Različica"), + ("Home", "Začetek"), ("Audio Input", "Avdio vhod"), ("Enhancements", "Izboljšave"), ("Hardware Codec", "Strojni kodek"), @@ -210,27 +210,27 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by the peer", "Povezavo ročno prekinil odjemalec"), ("Enable remote configuration modification", "Omogoči oddaljeno spreminjanje nastavitev"), ("Run without install", "Zaženi brez namestitve"), - ("Connect via relay", ""), + ("Connect via relay", "Poveži preko posrednika"), ("Always connect via relay", "Vedno poveži preko posrednika"), ("whitelist_tip", "Dostop je možen samo iz dovoljenih IPjev"), ("Login", "Prijavi"), - ("Verify", ""), - ("Remember me", ""), - ("Trust this device", ""), - ("Verification code", ""), - ("verification_tip", ""), + ("Verify", "Preveri"), + ("Remember me", "Zapomni si me"), + ("Trust this device", "Zaupaj tej napravi"), + ("Verification code", "Koda za preverjanje"), + ("verification_tip", "Kodo za preverjanje prejmete na registrirani e-poštni naslov"), ("Logout", "Odjavi"), ("Tags", "Oznake"), ("Search ID", "Išči ID"), ("whitelist_sep", "Naslovi ločeni z vejico, podpičjem, presledkom ali novo vrstico"), ("Add ID", "Dodaj ID"), ("Add Tag", "Dodaj oznako"), - ("Unselect all tags", ""), + ("Unselect all tags", "Odznači vse oznake"), ("Network error", "Omrežna napaka"), ("Username missed", "Up. ime izpuščeno"), ("Password missed", "Geslo izpuščeno"), ("Wrong credentials", "Napačne poverilnice"), - ("The verification code is incorrect or has expired", ""), + ("The verification code is incorrect or has expired", "Koda za preverjanje je napačna, ali pa je potekla"), ("Edit Tag", "Uredi oznako"), ("Forget Password", "Pozabi geslo"), ("Favorites", "Priljubljene"), @@ -248,7 +248,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Are you sure to close the connection?", "Ali želite prekiniti povezavo?"), ("Download new version", "Prenesi novo različico"), ("Touch mode", "Način dotika"), - ("Mouse mode", "Način mišle"), + ("Mouse mode", "Način miške"), ("One-Finger Tap", "Tap z enim prstom"), ("Left Mouse", "Leva tipka miške"), ("One-Long Tap", "Dolg tap z enim prstom"), @@ -286,8 +286,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Z vklopom zajema zaslona se bo samodejno zagnala storitev, ki omogoča da oddaljene naprave pošljejo zahtevo za povezavo na vašo napravo."), ("android_stop_service_tip", "Z zaustavitvijo storitve bodo samodejno prekinjene vse oddaljene povezave."), ("android_version_audio_tip", "Trenutna različica Androida ne omogoča zajema zvoka. Za zajem zvoka nadgradite na Android 10 ali novejši."), - ("android_start_service_tip", ""), - ("android_permission_may_not_change_tip", ""), + ("android_start_service_tip", "Tapnite [Zaženi storitev] ali pa omogočite pravico [Zajemanje zaslona] za zagon storitve deljenja zaslona."), + ("android_permission_may_not_change_tip", "Pravic za že vzpostavljene povezave ne morete spremeniti brez ponovne vzpostavitve povezave."), ("Account", "Račun"), ("Overwrite", "Prepiši"), ("This file exists, skip or overwrite this file?", "Datoteka obstaja, izpusti ali prepiši?"), @@ -306,8 +306,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"), ("Ignore Battery Optimizations", "Prezri optimizacije baterije"), ("android_open_battery_optimizations_tip", "Če želite izklopiti to možnost, pojdite v nastavitve aplikacije RustDesk, poiščite »Baterija« in izklopite »Neomejeno«"), - ("Start on boot", ""), - ("Start the screen sharing service on boot, requires special permissions", ""), + ("Start on boot", "Zaženi ob vklopu"), + ("Start the screen sharing service on boot, requires special permissions", "Zaženi storitev deljenja zaslona ob vklopu, zahteva posebna dovoljenja"), ("Connection not allowed", "Povezava ni dovoljena"), ("Legacy mode", "Stari način"), ("Map mode", "Način preslikave"), @@ -330,8 +330,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ratio", "Razmerje"), ("Image Quality", "Kakovost slike"), ("Scroll Style", "Način drsenja"), - ("Show Toolbar", ""), - ("Hide Toolbar", ""), + ("Show Toolbar", "Prikaži orodno vrstico"), + ("Hide Toolbar", "Skrij orodno vrstico"), ("Direct Connection", "Neposredna povezava"), ("Relay Connection", "Posredovana povezava"), ("Secure Connection", "Zavarovana povezava"), @@ -342,7 +342,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Varnost"), ("Theme", "Tema"), ("Dark Theme", "Temna tema"), - ("Light Theme", ""), + ("Light Theme", "Svetla tema"), ("Dark", "Temna"), ("Light", "Svetla"), ("Follow System", "Sistemska"), @@ -359,8 +359,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Vhodna naprava za zvok"), ("Use IP Whitelisting", "Omogoči seznam dovoljenih IP naslovov"), ("Network", "Mreža"), - ("Pin Toolbar", ""), - ("Unpin Toolbar", ""), + ("Pin Toolbar", "Pripni orodno vrstico"), + ("Unpin Toolbar", "Odpni orodno vrstico"), ("Recording", "Snemanje"), ("Directory", "Imenik"), ("Automatically record incoming sessions", "Samodejno snemaj vhodne seje"), @@ -409,244 +409,244 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by web console", "Ročno zaprto iz spletne konzole"), ("Local keyboard type", "Lokalna vrsta tipkovnice"), ("Select local keyboard type", "Izberite lokalno vrsto tipkovnice"), - ("software_render_tip", ""), - ("Always use software rendering", ""), - ("config_input", ""), - ("config_microphone", ""), - ("request_elevation_tip", ""), - ("Wait", ""), - ("Elevation Error", ""), - ("Ask the remote user for authentication", ""), - ("Choose this if the remote account is administrator", ""), - ("Transmit the username and password of administrator", ""), - ("still_click_uac_tip", ""), - ("Request Elevation", ""), - ("wait_accept_uac_tip", ""), - ("Elevate successfully", ""), - ("uppercase", ""), - ("lowercase", ""), - ("digit", ""), - ("special character", ""), - ("length>=8", ""), - ("Weak", ""), - ("Medium", ""), - ("Strong", ""), - ("Switch Sides", ""), - ("Please confirm if you want to share your desktop?", ""), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), - ("Voice call", ""), - ("Text chat", ""), - ("Stop voice call", ""), - ("relay_hint_tip", ""), - ("Reconnect", ""), - ("Codec", ""), - ("Resolution", ""), - ("No transfers in progress", ""), - ("Set one-time password length", ""), - ("RDP Settings", ""), - ("Sort by", ""), - ("New Connection", ""), - ("Restore", ""), - ("Minimize", ""), - ("Maximize", ""), - ("Your Device", ""), - ("empty_recent_tip", ""), - ("empty_favorite_tip", ""), - ("empty_lan_tip", ""), - ("empty_address_book_tip", ""), - ("eg: admin", ""), - ("Empty Username", ""), - ("Empty Password", ""), - ("Me", ""), - ("identical_file_tip", ""), - ("show_monitors_tip", ""), - ("View Mode", ""), - ("login_linux_tip", ""), - ("verify_rustdesk_password_tip", ""), - ("remember_account_tip", ""), - ("os_account_desk_tip", ""), - ("OS Account", ""), - ("another_user_login_title_tip", ""), - ("another_user_login_text_tip", ""), - ("xorg_not_found_title_tip", ""), - ("xorg_not_found_text_tip", ""), - ("no_desktop_title_tip", ""), - ("no_desktop_text_tip", ""), - ("No need to elevate", ""), - ("System Sound", ""), - ("Default", ""), - ("New RDP", ""), - ("Fingerprint", ""), - ("Copy Fingerprint", ""), - ("no fingerprints", ""), - ("Select a peer", ""), - ("Select peers", ""), - ("Plugins", ""), - ("Uninstall", ""), - ("Update", ""), - ("Enable", ""), - ("Disable", ""), - ("Options", ""), - ("resolution_original_tip", ""), - ("resolution_fit_local_tip", ""), - ("resolution_custom_tip", ""), - ("Collapse toolbar", ""), - ("Accept and Elevate", ""), - ("accept_and_elevate_btn_tooltip", ""), - ("clipboard_wait_response_timeout_tip", ""), - ("Incoming connection", ""), - ("Outgoing connection", ""), - ("Exit", ""), - ("Open", ""), - ("logout_tip", ""), - ("Service", ""), - ("Start", ""), - ("Stop", ""), - ("exceed_max_devices", ""), - ("Sync with recent sessions", ""), - ("Sort tags", ""), - ("Open connection in new tab", ""), - ("Move tab to new window", ""), - ("Can not be empty", ""), - ("Already exists", ""), - ("Change Password", ""), - ("Refresh Password", ""), - ("ID", ""), - ("Grid View", ""), - ("List View", ""), - ("Select", ""), - ("Toggle Tags", ""), - ("pull_ab_failed_tip", ""), - ("push_ab_failed_tip", ""), - ("synced_peer_readded_tip", ""), - ("Change Color", ""), - ("Primary Color", ""), - ("HSV Color", ""), - ("Installation Successful!", ""), - ("Installation failed!", ""), - ("Reverse mouse wheel", ""), - ("{} sessions", ""), - ("scam_title", ""), - ("scam_text1", ""), - ("scam_text2", ""), - ("Don't show again", ""), - ("I Agree", ""), - ("Decline", ""), - ("Timeout in minutes", ""), - ("auto_disconnect_option_tip", ""), - ("Connection failed due to inactivity", ""), - ("Check for software update on startup", ""), - ("upgrade_rustdesk_server_pro_to_{}_tip", ""), - ("pull_group_failed_tip", ""), - ("Filter by intersection", ""), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), - ("Use all my displays for the remote session", ""), - ("selinux_tip", ""), - ("Change view", ""), - ("Big tiles", ""), - ("Small tiles", ""), - ("List", ""), - ("Virtual display", ""), - ("Plug out all", ""), - ("True color (4:4:4)", ""), - ("Enable blocking user input", ""), - ("id_input_tip", ""), - ("privacy_mode_impl_mag_tip", ""), - ("privacy_mode_impl_virtual_display_tip", ""), - ("Enter privacy mode", ""), - ("Exit privacy mode", ""), - ("idd_not_support_under_win10_2004_tip", ""), - ("input_source_1_tip", ""), - ("input_source_2_tip", ""), - ("Swap control-command key", ""), - ("swap-left-right-mouse", ""), - ("2FA code", ""), - ("More", ""), - ("enable-2fa-title", ""), - ("enable-2fa-desc", ""), - ("wrong-2fa-code", ""), - ("enter-2fa-title", ""), - ("Email verification code must be 6 characters.", ""), - ("2FA code must be 6 digits.", ""), - ("Multiple Windows sessions found", ""), - ("Please select the session you want to connect to", ""), - ("powered_by_me", ""), - ("outgoing_only_desk_tip", ""), - ("preset_password_warning", ""), - ("Security Alert", ""), - ("My address book", ""), - ("Personal", ""), - ("Owner", ""), - ("Set shared password", ""), - ("Exist in", ""), - ("Read-only", ""), - ("Read/Write", ""), - ("Full Control", ""), - ("share_warning_tip", ""), - ("Everyone", ""), - ("ab_web_console_tip", ""), - ("allow-only-conn-window-open-tip", ""), - ("no_need_privacy_mode_no_physical_displays_tip", ""), - ("Follow remote cursor", ""), - ("Follow remote window focus", ""), - ("default_proxy_tip", ""), - ("no_audio_input_device_tip", ""), - ("Incoming", ""), - ("Outgoing", ""), - ("Clear Wayland screen selection", ""), - ("clear_Wayland_screen_selection_tip", ""), - ("confirm_clear_Wayland_screen_selection_tip", ""), - ("android_new_voice_call_tip", ""), - ("texture_render_tip", ""), - ("Use texture rendering", ""), - ("Floating window", ""), - ("floating_window_tip", ""), - ("Keep screen on", ""), - ("Never", ""), - ("During controlled", ""), - ("During service is on", ""), - ("Capture screen using DirectX", ""), - ("Back", ""), - ("Apps", ""), - ("Volume up", ""), - ("Volume down", ""), - ("Power", ""), - ("Telegram bot", ""), - ("enable-bot-tip", ""), - ("enable-bot-desc", ""), - ("cancel-2fa-confirm-tip", ""), - ("cancel-bot-confirm-tip", ""), - ("About RustDesk", ""), - ("Send clipboard keystrokes", ""), - ("network_error_tip", ""), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), - ("one-way-file-transfer-tip", ""), - ("Authentication Required", ""), - ("Authenticate", ""), - ("web_id_input_tip", ""), + ("software_render_tip", "Če na Linuxu uporabljate Nvidino grafično kartico in se oddaljeno okno zapre takoj po vzpostavitvi povezave, lahko pomaga preklop na odprtokodni gonilnik Nouveau in uporaba programskega upodabljanja. Potreben je ponovni zagon programa."), + ("Always use software rendering", "Vedno uporabi programsko upodabljanje"), + ("config_input", "Za nadzor oddaljenega namizja s tipkovnico, rabi RustDesk pravico »Nadzor vnosa«."), + ("config_microphone", "Za zajem zvoka, rabi RustDesk pravico »Snemanje zvoka«."), + ("request_elevation_tip", "Lahko tudi zaprosite za dvig pravic, če je kdo na oddaljeni strani."), + ("Wait", "Čakaj"), + ("Elevation Error", "Napaka pri povzdigovanju"), + ("Ask the remote user for authentication", "Vprašaj oddaljenega uporabnika za prijavo"), + ("Choose this if the remote account is administrator", "Izberite to, če ima oddaljeni uporabnik skrbniške pravice"), + ("Transmit the username and password of administrator", "Vnesite poverilnice za skrbnika"), + ("still_click_uac_tip", "Oddaljeni uporabnik mora klikniti »Da« v oknu za nadzor uporabniškega računa."), + ("Request Elevation", "Zahtevaj povzdig pravic"), + ("wait_accept_uac_tip", "Počakajte na potrditev oddaljenega uporabnika v oknu za nadzor uporabniškega računa."), + ("Elevate successfully", "Povzdig pravic uspešen"), + ("uppercase", "velike črke"), + ("lowercase", "male črke"), + ("digit", "številke"), + ("special character", "posebni znaki"), + ("length>=8", "dolžina>=8"), + ("Weak", "Šibko"), + ("Medium", "Srednje"), + ("Strong", "Močno"), + ("Switch Sides", "Zamenjaj strani"), + ("Please confirm if you want to share your desktop?", "Potrdite, če želite deliti vaše namizje"), + ("Display", "Zaslon"), + ("Default View Style", "Privzeti način prikaza"), + ("Default Scroll Style", "Privzeti način drsenja"), + ("Default Image Quality", "Privzeta kakovost slike"), + ("Default Codec", "Privzeti kodek"), + ("Bitrate", "Bitna hitrost"), + ("FPS", "Sličice/sekundo"), + ("Auto", "Samodejno"), + ("Other Default Options", "Ostale privzete možnosti"), + ("Voice call", "Glasovni klic"), + ("Text chat", "Besedilni klepet"), + ("Stop voice call", "Prekini glasovni klic"), + ("relay_hint_tip", "Morda neposredna povezava ni možna; lahko se poikusite povezati preko posrednika. Če želite uporabiti posrednika ob prvem poizkusu vzpotavljanja povezave, lahko na konec IDja dodate »/r«, ali pa izberete možnost »Vedno poveži preko posrednika« v kartici nedavnih sej, če le-ta obstja."), + ("Reconnect", "Ponovna povezava"), + ("Codec", "Kodek"), + ("Resolution", "Ločljivost"), + ("No transfers in progress", "Trenutno ni prenosov"), + ("Set one-time password length", "Nastavi dolžino enkratnega gesla"), + ("RDP Settings", "Nastavitve za RDP"), + ("Sort by", "Razvrsti po"), + ("New Connection", "Nova povezava"), + ("Restore", "Obnovi"), + ("Minimize", "Minimiziraj"), + ("Maximize", "Maksimiziraj"), + ("Your Device", "Vaša naprava"), + ("empty_recent_tip", "Oops, ni nedavnih sej.\nPripravite novo."), + ("empty_favorite_tip", "Nimate še priljubljenih partnerjev?\nVzpostavite povezavo, in jo dodajte med priljubljene."), + ("empty_lan_tip", "Nismo našli še nobenih partnerjev."), + ("empty_address_book_tip", "Vaš adresar je prazen."), + ("eg: admin", "npr. admin"), + ("Empty Username", "Prazno uporabniško ime"), + ("Empty Password", "Prazno geslo"), + ("Me", "Jaz"), + ("identical_file_tip", "Datoteka je enaka partnerjevi"), + ("show_monitors_tip", "Prikaži monitorje v orodni vrstici"), + ("View Mode", "Način prikazovanja"), + ("login_linux_tip", "Prijaviti se morate v oddaljeni Linux račun in omogočiti namizno sejo X."), + ("verify_rustdesk_password_tip", "Preveri geslo za RustDesk"), + ("remember_account_tip", "Zapomni si ta račun"), + ("os_account_desk_tip", "Ta račun se uporabi za prijavo v oddaljeni sistem in omogči namizno sejo v napravi brez monitorja."), + ("OS Account", "Račun operacijskega sistema"), + ("another_user_login_title_tip", "Prijavljen je že drug uporabnik"), + ("another_user_login_text_tip", "Prekini"), + ("xorg_not_found_title_tip", "Xorg ni najden"), + ("xorg_not_found_text_tip", "Namestite Xorg"), + ("no_desktop_title_tip", "Namizno okolje ni na voljo"), + ("no_desktop_text_tip", "Namestite GNOME"), + ("No need to elevate", "Povzdig pravic ni potreben"), + ("System Sound", "Sistemski zvok"), + ("Default", "Privzeto"), + ("New RDP", "Nova RDP povezava"), + ("Fingerprint", "Prstni odtis"), + ("Copy Fingerprint", "Kopiraj prstni odtis"), + ("no fingerprints", "ni prstnega odtisa"), + ("Select a peer", "Izberite partnerja"), + ("Select peers", "Izberite partnerje"), + ("Plugins", "Vključki"), + ("Uninstall", "Odstrani"), + ("Update", "Posodobi"), + ("Enable", "Omogoči"), + ("Disable", "Onemogoči"), + ("Options", "Možnosti"), + ("resolution_original_tip", "Izvirna ločljivost"), + ("resolution_fit_local_tip", "Prilagodi lokalni ločljivosti"), + ("resolution_custom_tip", "Ločljivost po meri"), + ("Collapse toolbar", "Strni orodno vrstico"), + ("Accept and Elevate", "Sprejmi in povzdigni pravice"), + ("accept_and_elevate_btn_tooltip", "Sprejmi povezavo in preko nadzora uporabniškera računa povišaj pravice"), + ("clipboard_wait_response_timeout_tip", "Časovna omejitev pri kopiranju je potekla"), + ("Incoming connection", "Dohodna povezava"), + ("Outgoing connection", "Odhodna povezava"), + ("Exit", "Izhod"), + ("Open", "Odpri"), + ("logout_tip", "Ali ste prepričani, da se želite odjaviti?"), + ("Service", "Storitev"), + ("Start", "Zaženi"), + ("Stop", "Ustavi"), + ("exceed_max_devices", "Dosegli ste največje dovoljeno število upravljanih naprav."), + ("Sync with recent sessions", "Sinhroniziraj z nedavnimi sejami"), + ("Sort tags", "Uredi oznake"), + ("Open connection in new tab", "Odpri povezavo na novem zavihku"), + ("Move tab to new window", "Premakni zavihek v novo okno"), + ("Can not be empty", "Ne more biti prazno"), + ("Already exists", "Že obstaja"), + ("Change Password", "Spremeni geslo"), + ("Refresh Password", "Osveži geslo"), + ("ID", "ID"), + ("Grid View", "Mrežni pogled"), + ("List View", "Pogled seznama"), + ("Select", "Izberi"), + ("Toggle Tags", "Preklopi oznake"), + ("pull_ab_failed_tip", "Adresarja ni bilo mogoče osvežiti"), + ("push_ab_failed_tip", "Adresarja ni bilo mogoče poslati na strežnik"), + ("synced_peer_readded_tip", "Naprave, ki so bile prisotne v nedavnih sejah bodo sinhronizirane z adresarjem."), + ("Change Color", "Spremeni barvo"), + ("Primary Color", "Osnovne barve"), + ("HSV Color", "Barve HSV"), + ("Installation Successful!", "Namestitev uspešna"), + ("Installation failed!", "Namestitev ni uspela"), + ("Reverse mouse wheel", "Obrni smer drsenja miškinega kolesca"), + ("{} sessions", "{} sej"), + ("scam_title", "Lahko gre za prevaro!"), + ("scam_text1", "V primeru, da vas je nekdo, ki ga ne poznate in mu zaupate prosil, da uporabite RustDesk, prekinite klic in program zaprite."), + ("scam_text2", "RustDesk omogoča popoln nadzor nad vašim računalnikom in telefonom, in se lahko uporabi za krajo vašega denarja ali pa zasebnih podatkov."), + ("Don't show again", "Ne prikaži znova"), + ("I Agree", "Strinjam se"), + ("Decline", "Zavrni"), + ("Timeout in minutes", "Časovna omejitev v minutah"), + ("auto_disconnect_option_tip", "Samodejno prekini neaktivne seje"), + ("Connection failed due to inactivity", "Povezava je bila prekinjena zaradi neaktivnosti"), + ("Check for software update on startup", "Preveri za posodobitve ob zagonu"), + ("upgrade_rustdesk_server_pro_to_{}_tip", "Prosimo, nadgradite RustDesk Server Pro na različico {} ali novejšo."), + ("pull_group_failed_tip", "Osveževanje skupine ni uspelo"), + ("Filter by intersection", "Filtriraj po preseku"), + ("Remove wallpaper during incoming sessions", "Odstrani sliko ozadja ob dohodnih povezavah"), + ("Test", "Test"), + ("display_is_plugged_out_msg", "Zaslon je bil odklopljen, preklop na primarni zaslon."), + ("No displays", "Ni zaslonov"), + ("Open in new window", "Odpri v novem oknu"), + ("Show displays as individual windows", "Prikaži zaslone kot ločena okna"), + ("Use all my displays for the remote session", "Uporabi vse zaslone za oddaljeno sejo"), + ("selinux_tip", "Na vaši napravi je omogčen SELinux, kar lahko povzroča težave pri oddaljenem nadzoru"), + ("Change view", "Spremeni pogled"), + ("Big tiles", "Velike ploščice"), + ("Small tiles", "Majhne ploščice"), + ("List", "Seznam"), + ("Virtual display", "Navidezni zaslon"), + ("Plug out all", "Odklopi vse"), + ("True color (4:4:4)", "Popolne barve (4:4:4)"), + ("Enable blocking user input", "Omogoči blokiranje vnosa"), + ("id_input_tip", "Vnesete lahko ID, neposredni IP naslov, ali pa domeno in vrata (:)\nČe želite dostopati do naprave na drugem strežniku, pripnite naslov strežnika (@?key=), npr. 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nČe želite dostopati do naprave na javnem strežniku, vnesite »@public«; ključ za javni strežnik ni potreben.\nČe želite vsiliti povezavo preko posrednika, pripnite »/r« na konec IDja, npr. »9123456234/r«."), + ("privacy_mode_impl_mag_tip", "Način 1"), + ("privacy_mode_impl_virtual_display_tip", "Način 2"), + ("Enter privacy mode", "Vstopi v zasebni način"), + ("Exit privacy mode", "Izstopi iz zasebnega načina"), + ("idd_not_support_under_win10_2004_tip", "Posredni gonilnik ni podprt. Za uporabo rabite Windows 10 2004 ali novejšo različico."), + ("input_source_1_tip", "Vir vnosa 1"), + ("input_source_2_tip", "Vir vnosa 2"), + ("Swap control-command key", "Zamenjaj tipki Ctrl-Command"), + ("swap-left-right-mouse", "Zamenjaj levo in desno tipko miške"), + ("2FA code", "Koda za dvostopenjsko preverjanje"), + ("More", "Več"), + ("enable-2fa-title", "Omogoči dvostopenjsko preverjanje"), + ("enable-2fa-desc", "Pripravite vaš TOTP avtentikator. Uporabite lahko programe kot so Authy, Microsoft ali Google Authenticator, na vašem telefonu ali računalniku.\n\nZa omogočanje dvostopenjskega preverjanja, skenirajte QR kodo in vnesite kodo, ki jo prikaže aplikacija."), + ("wrong-2fa-code", "Kode ni bilo mogoče preveriti. Preverite, da je koda pravilna, in da je nastavitev ure točna."), + ("enter-2fa-title", "Dvostopenjsko preverjanje"), + ("Email verification code must be 6 characters.", "E-poštna koda za preverjanje mora imeti 6 znakov."), + ("2FA code must be 6 digits.", "Koda za dvostopenjsko preverjanje mora imeti 6 znakov."), + ("Multiple Windows sessions found", "Najdenih je bilo več Windows sej"), + ("Please select the session you want to connect to", "Izberite sejo, v katero se želite povezati"), + ("powered_by_me", "Uporablja tehnologijo RustDesk"), + ("outgoing_only_desk_tip", "To je prilagojena različica.\nLahko se povežete na druge naprave, druge naprave pa se k vam ne morejo povezati."), + ("preset_password_warning", "Ta prilagojena različica ima prednastavljeno geslo. Kdorkoli, ki pozna to geslo, lahko prevzame popoln nadzor nad vašim računalnikom. Če tega niste pričakovali, takoj odstranite program."), + ("Security Alert", "Varnostno opozorilo"), + ("My address book", "Moj adresar"), + ("Personal", "Osebni"), + ("Owner", "Lastnik"), + ("Set shared password", "Nastavi deljeno geslo"), + ("Exist in", "Obstaja v"), + ("Read-only", "Samo za branje"), + ("Read/Write", "Branje/pisanje"), + ("Full Control", "Popoln nadzor"), + ("share_warning_tip", "Zgornja polja so deljena, in vidna vsem"), + ("Everyone", "Vsi"), + ("ab_web_console_tip", "Več na spletni konzoli"), + ("allow-only-conn-window-open-tip", "Dovoli povezavo samo če je okno RustDeska odprto"), + ("no_need_privacy_mode_no_physical_displays_tip", "Ni fizičnih zaslonov, zasebni način ni potreben"), + ("Follow remote cursor", "Sledi oddaljenemu kazalcu"), + ("Follow remote window focus", "Sledi oddaljenemu fokusu"), + ("default_proxy_tip", "Privzeti protokol je Socks5 na vratih 1080"), + ("no_audio_input_device_tip", "Ni bilo možno najti vhodne zvočne naprave"), + ("Incoming", "Dohodno"), + ("Outgoing", "Odhodno"), + ("Clear Wayland screen selection", "Počisti izbiro Wayland zaslona"), + ("clear_Wayland_screen_selection_tip", "Po čiščenju izbire Wayland zaslona lahko ponovno izberete zaslon za delitev"), + ("confirm_clear_Wayland_screen_selection_tip", "Ali res želite počistiti izbiro Wayland zaslona?"), + ("android_new_voice_call_tip", "Prejeli ste prošnjo za nov glasovni klic. Če sprejmete, bo zvok preklopljen na glasovno komunikacijo."), + ("texture_render_tip", "Uporabi upodabljanje tekstur, za gladkejše slike. Izklopite, če imate težave pri upodabljanju."), + ("Use texture rendering", "Uporabi upodabljanje tekstur"), + ("Floating window", "Plavajoče okno"), + ("floating_window_tip", "Pomaga pri RustDesk storitvi v ozadju"), + ("Keep screen on", "Ohranite zaslon prižgan"), + ("Never", "Nikoli"), + ("During controlled", "Med nadzorom"), + ("During service is on", "Med vklopljeno storitvijo"), + ("Capture screen using DirectX", "Uporabi DirectX za zajem zaslona"), + ("Back", "Nazaj"), + ("Apps", "Aplikacije"), + ("Volume up", "Glasneje"), + ("Volume down", "Tišje"), + ("Power", "Vklop/izklop"), + ("Telegram bot", "Telegram bot"), + ("enable-bot-tip", "Če vklopite to možnost, lahko dobite kodo za dvostopenjsko preverjanje od bota. Lahko se uporabi tudi za obveščanje o povezavi."), + ("enable-bot-desc", "1. Odprite pogovor z @BotFather.\n2. Pošljite ukaz »/newbot« in prejeli boste žeton.\n3. Začnite pogovor z na novo narejenim botom. Pošljite sporočilo z desno poševnico (/) kot npr. »/hello« za aktivacijo."), + ("cancel-2fa-confirm-tip", "Ali ste prepričani, da želite ukiniti dvostopenjsko preverjanje?"), + ("cancel-bot-confirm-tip", "Ali ste prepričani, da želite ukiniti Telegram bota?"), + ("About RustDesk", "O RustDesku"), + ("Send clipboard keystrokes", "Vtipkaj vsebino odložišča"), + ("network_error_tip", "Preverite vašo mrežno povezavo, nato kliknite Ponovi."), + ("Unlock with PIN", "Odkleni s PINom"), + ("Requires at least {} characters", "Potrebuje vsaj {} znakov."), + ("Wrong PIN", "Napačen PIN"), + ("Set PIN", "Nastavi PIN"), + ("Enable trusted devices", "Omogoči zaupanja vredne naprave"), + ("Manage trusted devices", "Upravljaj zaupanja vredne naprave"), + ("Platform", "Platforma"), + ("Days remaining", "Preostane dni"), + ("enable-trusted-devices-tip", "Na zaupanja vrednih napravah ni potrebno dvostopenjsko preverjanje"), + ("Parent directory", "Nadrejena mapa"), + ("Resume", "Nadaljuj"), + ("Invalid file name", "Neveljavno ime datoteke"), + ("one-way-file-transfer-tip", "Enosmerni prenos datotek je omogočen na nadzorovani strani"), + ("Authentication Required", "Potrebno je preverjanje pristnosti"), + ("Authenticate", "Preverjanje pristnosti"), + ("web_id_input_tip", "Vnesete lahko ID iz istega strežnika, neposredni dostop preko IP naslova v spletnem odjemalcu ni podprt.\nČe želite dostopati do naprave na drugem strežniku, pripnite naslov strežnika (@?key=), npr. 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nČe želite dostopati do naprave na javnem strežniku, vnesite »@public«; ključ za javni strežnik ni potreben."), ].iter().cloned().collect(); } From 22c84bbbd19d85e4064355f94e48921893e909b3 Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki <36250619+cacing69@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:12:24 +0700 Subject: [PATCH 160/210] Update ID translation (#9609) * update id trans * update id trans * update inv * update ind trans * update ind trans * update ind trans --- src/lang/id.rs | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/lang/id.rs b/src/lang/id.rs index c0920dfcb57..b52bd7b946a 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -2,7 +2,7 @@ lazy_static::lazy_static! { pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ("Status", "Status"), - ("Your Desktop", "Kamu"), + ("Your Desktop", "Desktop Kamu"), ("desk_tip", "Desktop kamu dapat diakses dengan ID dan kata sandi ini."), ("Password", "Kata sandi"), ("Ready", "Sudah siap"), @@ -607,46 +607,46 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Incoming", ""), ("Outgoing", ""), ("Clear Wayland screen selection", "Kosongkan pilihan layar Wayland"), - ("clear_Wayland_screen_selection_tip", "Setelah mengosongkan pilihan layar, Anda bisa memilih kembali layar untuk dibagi"), + ("clear_Wayland_screen_selection_tip", "Setelah mengosongkan pilihan layar, Kamu bisa memilih kembali layar untuk dibagi"), ("confirm_clear_Wayland_screen_selection_tip", "Kamu yakin ingin membersihkan pemilihan layar Wayland?"), - ("android_new_voice_call_tip", ""), - ("texture_render_tip", ""), - ("Use texture rendering", ""), + ("android_new_voice_call_tip", "Kamu mendapatkan permintaan panggilan suara baru. Jika diterima, audio akan berubah menjadi komunikasi suara."), + ("texture_render_tip", "Aktifkan rendering tekstur untuk membuat tampilan gambar lebih mulus. Kamu dapat menonaktifkan opsi ini jika terjadi masalah saat merender."), + ("Use texture rendering", "Aktifkan rendering tekstur"), ("Floating window", ""), - ("floating_window_tip", ""), - ("Keep screen on", ""), - ("Never", ""), - ("During controlled", ""), + ("floating_window_tip", "Untuk menjaga layanan/service RustDesk agar tetap aktif"), + ("Keep screen on", "Biarkan layar tetap menyala"), + ("Never", "Tidak pernah"), + ("During controlled", "Dalam proses pengendalian"), ("During service is on", ""), - ("Capture screen using DirectX", ""), - ("Back", ""), - ("Apps", ""), - ("Volume up", ""), - ("Volume down", ""), + ("Capture screen using DirectX", "Rekam layar dengan DirectX"), + ("Back", "Kembali"), + ("Apps", "App"), + ("Volume up", "Naikkan volume"), + ("Volume down", "Turunkan volume"), ("Power", ""), ("Telegram bot", ""), - ("enable-bot-tip", ""), - ("enable-bot-desc", ""), - ("cancel-2fa-confirm-tip", ""), - ("cancel-bot-confirm-tip", ""), - ("About RustDesk", ""), - ("Send clipboard keystrokes", ""), - ("network_error_tip", ""), - ("Unlock with PIN", ""), - ("Requires at least {} characters", ""), - ("Wrong PIN", ""), - ("Set PIN", ""), - ("Enable trusted devices", ""), - ("Manage trusted devices", ""), - ("Platform", ""), - ("Days remaining", ""), - ("enable-trusted-devices-tip", ""), - ("Parent directory", ""), - ("Resume", ""), - ("Invalid file name", ""), - ("one-way-file-transfer-tip", ""), - ("Authentication Required", ""), - ("Authenticate", ""), - ("web_id_input_tip", ""), + ("enable-bot-tip", "Jika fitur ini diaktifkan, Kamu dapat menerima kode 2FA dari bot, serta mendapatkan notifikasi tentang koneksi."), + ("enable-bot-desc", "1. Buka chat dengan @BotFather.\n2. Kirim perintah \"/newbot\". Setelah menyelesaikan langkah ini, Kamu akan mendapatkan token\n3. Mulai percakapan dengan bot yang baru dibuat. Kirim pesan yang dimulai dengan garis miring (\"/\") seperti \"/hello\" untuk mengaktifkannya."), + ("cancel-2fa-confirm-tip", "Apakah Kamu yakin ingin membatalkan 2FA?"), + ("cancel-bot-confirm-tip", "Apakah Kamu yakin ingin membatalkan bot Telegram?"), + ("About RustDesk", "Tentang RustDesk"), + ("Send clipboard keystrokes", "Kirim keystrokes clipboard"), + ("network_error_tip", "Periksa koneksi internet, lalu klik \"Coba lagi\"."), + ("Unlock with PIN", "Buka menggunakan PIN"), + ("Requires at least {} characters", "Memerlukan setidaknya {} karakter."), + ("Wrong PIN", "PIN salah"), + ("Set PIN", "Atur PIN"), + ("Enable trusted devices", "Izinkan perangkat tepercaya"), + ("Manage trusted devices", "Kelola perangkat tepercaya"), + ("Platform", "Platform"), + ("Days remaining", "Sisa hari"), + ("enable-trusted-devices-tip", "Tidak memerlukan verifikasi 2FA pada perangkat tepercaya."), + ("Parent directory", "Direktori utama"), + ("Resume", "Lanjutkan"), + ("Invalid file name", "Nama file tidak valid"), + ("one-way-file-transfer-tip", "Transfer file satu arah (One-way) telah diaktifkan pada sisi yang dikendalikan."), + ("Authentication Required", "Diperlukan autentikasi"), + ("Authenticate", "Autentikasi"), + ("web_id_input_tip", "Kamu bisa memasukkan ID pada server yang sama, akses IP langsung tidak didukung di klien web.\nJika Anda ingin mengakses perangkat di server lain, silakan tambahkan alamat server (@?key=), contohnya:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nUntuk mengakses perangkat di server publik, cukup masukkan \"@public\", tanpa kunci/key."), ].iter().cloned().collect(); } From 97f02ed25ea3a2089fb13a04ebb864a2f1e0e1b7 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 11 Oct 2024 09:52:09 +0800 Subject: [PATCH 161/210] Web password source (#9618) * ensure window init finish Signed-off-by: 21pages * web password source Signed-off-by: 21pages --------- Signed-off-by: 21pages --- flutter/lib/common.dart | 2 +- flutter/lib/models/model.dart | 2 +- flutter/lib/models/web_model.dart | 6 ++++++ flutter/lib/web/bridge.dart | 22 ++++++++++++++-------- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 99a24781b95..8feb2402f8a 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2383,7 +2383,7 @@ connect(BuildContext context, String id, ), ); } else { - if (isWebDesktop) { + if (isWeb) { Navigator.push( context, MaterialPageRoute( diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 3f2dcade9ae..8466443ee80 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -372,7 +372,7 @@ class FfiModel with ChangeNotifier { } else if (name == 'plugin_option') { handleOption(evt); } else if (name == "sync_peer_hash_password_to_personal_ab") { - if (desktopType == DesktopType.main) { + if (desktopType == DesktopType.main || isWeb) { final id = evt['id']; final hash = evt['hash']; if (id != null && hash != null) { diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index a5740af61a1..a4312d959c7 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_web_libraries_in_flutter import 'dart:convert'; +import 'dart:js_interop'; import 'dart:typed_data'; import 'dart:js'; import 'dart:html'; @@ -107,6 +108,10 @@ class PlatformFFI { sessionId: sessionId, display: display, ptr: ptr); Future init(String appType) async { + Completer completer = Completer(); + context["onInitFinished"] = () { + completer.complete(); + }; context.callMethod('init'); version = getByName('version'); window.onContextMenu.listen((event) { @@ -121,6 +126,7 @@ class PlatformFFI { print('json.decode fail(): $e'); } }; + return completer.future; } void setEventCallback(void Function(Map) fun) { diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 2c8d6b4b033..adcbadb30e9 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -85,7 +85,11 @@ class RustdeskImpl { dynamic hint}) { return js.context.callMethod('setByName', [ 'session_add_sync', - jsonEncode({'id': id, 'password': password}) + jsonEncode({ + 'id': id, + 'password': password, + 'is_shared_password': isSharedPassword + }) ]); } @@ -1118,7 +1122,8 @@ class RustdeskImpl { } Future mainRemovePeer({required String id, dynamic hint}) { - return Future(() => js.context.callMethod('setByName', ['remove', id])); + return Future( + () => js.context.callMethod('setByName', ['remove_peer', id])); } bool mainHasHwcodec({dynamic hint}) { @@ -1146,27 +1151,28 @@ class RustdeskImpl { } Future mainSaveAb({required String json, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', ['save_ab', json])); } Future mainClearAb({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', ['clear_ab'])); } Future mainLoadAb({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['load_ab'])); } Future mainSaveGroup({required String json, dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('setByName', ['save_group', json])); } Future mainClearGroup({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', ['clear_group'])); } Future mainLoadGroup({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['load_group'])); } Future sessionSendPointer( From 844b853074bbdb8b9d7ec7f168a5965bb8d4d69b Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki <36250619+cacing69@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:43:22 +0700 Subject: [PATCH 162/210] update ID trans (#9621) --- src/lang/id.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lang/id.rs b/src/lang/id.rs index b52bd7b946a..efedb159778 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -2,8 +2,8 @@ lazy_static::lazy_static! { pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ("Status", "Status"), - ("Your Desktop", "Desktop Kamu"), - ("desk_tip", "Desktop kamu dapat diakses dengan ID dan kata sandi ini."), + ("Your Desktop", "Layar Utama"), + ("desk_tip", "Layar kamu dapat diakses dengan ID dan kata sandi ini."), ("Password", "Kata sandi"), ("Ready", "Sudah siap"), ("Established", "Didirikan"), @@ -12,10 +12,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Start service", "Mulai Layanan"), ("Service is running", "Layanan berjalan"), ("Service is not running", "Layanan tidak berjalan"), - ("not_ready_status", "Belum siap. Silakan periksa koneksi"), - ("Control Remote Desktop", "Kontrol Remote Desktop"), - ("Transfer file", "File Transfer"), - ("Connect", "Hubungkan"), + ("not_ready_status", "Belum siap digunakan. Silakan periksa koneksi"), + ("Control Remote Desktop", "Kontrol PC dari jarak jauh"), + ("Transfer file", "Transfer File"), + ("Connect", "Sambungkan"), ("Recent sessions", "Sesi Terkini"), ("Address book", "Buku Alamat"), ("Confirmation", "Konfirmasi"), @@ -172,14 +172,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Local Port", "Port Lokal"), ("Local Address", "Alamat lokal"), ("Change Local Port", "Ubah Port Lokal"), - ("setup_server_tip", "Untuk mendapatkan koneksi yang lebih baik, disarankan untuk menginstal di server anda sendiri"), + ("setup_server_tip", "Untuk koneksi yang lebih baik, silakan konfigurasi di server pribadi"), ("Too short, at least 6 characters.", "Terlalu pendek, setidaknya 6 karekter."), ("The confirmation is not identical.", "Konfirmasi tidak identik."), ("Permissions", "Perizinan"), ("Accept", "Terima"), ("Dismiss", "Hentikan"), ("Disconnect", "Terputus"), - ("Enable file copy and paste", "Izinkan salin dan tempel file"), + ("Enable file copy and paste", "Izinkan copy dan paste"), ("Connected", "Terhubung"), ("Direct and encrypted connection", "Koneksi langsung dan terenkripsi"), ("Relayed and encrypted connection", "Koneksi relay dan terenkripsi"), @@ -199,7 +199,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please enter the folder name", "Silahkan masukkan nama folder"), ("Fix it", "Perbaiki"), ("Warning", "Peringatan"), - ("Login screen using Wayland is not supported", "Layar masuk menggunakan Wayland tidak didukung"), + ("Login screen using Wayland is not supported", "Login screen dengan Wayland tidak didukung"), ("Reboot required", "Diperlukan boot ulang"), ("Unsupported display server", "Server tampilan tidak didukung "), ("x11 expected", "Diperlukan x11"), @@ -394,9 +394,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Accept sessions via click", "Izinkan sesi dengan klik"), ("Accept sessions via both", "Izinkan sesi dengan keduanya"), ("Please wait for the remote side to accept your session request...", "Harap tunggu pihak pengguna remote untuk menerima permintaan sesi..."), - ("One-time Password", "Kata sandi sekali pakai"), - ("Use one-time password", "Gunakan kata sandi sekali pakai"), - ("One-time password length", "Panjang kata sandi sekali pakai"), + ("One-time Password", "Kata sandi sementara"), + ("Use one-time password", "Gunakan kata sandi sementara"), + ("One-time password length", "Panjang kata sandi sementara"), ("Request access to your device", "Permintaan akses ke perangkat ini"), ("Hide connection management window", "Sembunyikan jendela pengaturan koneksi"), ("hide_cm_tip", "Izinkan untuk menyembunyikan hanya jika menerima sesi melalui kata sandi dan menggunakan kata sandi permanen"), From cde7620eda101b463c79ce7a47ef95cf4be6f3b3 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 11 Oct 2024 16:35:15 +0800 Subject: [PATCH 163/210] fix web peer card tap (#9622) Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_card.dart | 180 +++++++++++----------- flutter/lib/main.dart | 3 +- flutter/lib/models/peer_tab_model.dart | 2 +- 3 files changed, 94 insertions(+), 91 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 7827298b6d0..246b337a98b 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -58,27 +58,33 @@ class _PeerCardState extends State<_PeerCard> stateGlobal.isPortrait.isTrue ? _buildPortrait() : _buildLandscape()); } + Widget gestureDetector({required Widget child}) { + final PeerTabModel peerTabModel = Provider.of(context); + final peer = super.widget.peer; + return GestureDetector( + onDoubleTap: peerTabModel.multiSelectionMode || peerTabModel.isShiftDown + ? null + : () => widget.connect(context, peer.id), + onTap: () { + if (peerTabModel.multiSelectionMode) { + peerTabModel.select(peer); + } else { + if (isMobile) { + widget.connect(context, peer.id); + } else { + peerTabModel.select(peer); + } + } + }, + onLongPress: () => peerTabModel.select(peer), + child: child); + } + Widget _buildPortrait() { final peer = super.widget.peer; - final PeerTabModel peerTabModel = Provider.of(context); return Card( margin: EdgeInsets.symmetric(horizontal: 2), - child: GestureDetector( - onTap: () { - if (peerTabModel.multiSelectionMode) { - peerTabModel.select(peer); - } else { - if (!isWebDesktop) { - connectInPeerTab(context, peer, widget.tab); - } - } - }, - onDoubleTap: isWebDesktop - ? () => connectInPeerTab(context, peer, widget.tab) - : null, - onLongPress: () { - peerTabModel.select(peer); - }, + child: gestureDetector( child: Container( padding: EdgeInsets.only(left: 12, top: 8, bottom: 8), child: _buildPeerTile(context, peer, null)), @@ -86,7 +92,6 @@ class _PeerCardState extends State<_PeerCard> } Widget _buildLandscape() { - final PeerTabModel peerTabModel = Provider.of(context); final peer = super.widget.peer; var deco = Rx( BoxDecoration( @@ -115,30 +120,21 @@ class _PeerCardState extends State<_PeerCard> ), ); }, - child: GestureDetector( - onDoubleTap: - peerTabModel.multiSelectionMode || peerTabModel.isShiftDown - ? null - : () => widget.connect(context, peer.id), - onTap: () => peerTabModel.select(peer), - onLongPress: () => peerTabModel.select(peer), + child: gestureDetector( child: Obx(() => peerCardUiType.value == PeerUiType.grid ? _buildPeerCard(context, peer, deco) : _buildPeerTile(context, peer, deco))), ); } - Widget _buildPeerTile( - BuildContext context, Peer peer, Rx? deco) { - hideUsernameOnCard ??= - bind.mainGetBuildinOption(key: kHideUsernameOnCard) == 'Y'; + makeChild(bool isPortrait, Peer peer) { final name = hideUsernameOnCard == true ? peer.hostname : '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; final greyStyle = TextStyle( fontSize: 11, color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); - makeChild(bool isPortrait) => Row( + return Row( mainAxisSize: MainAxisSize.max, children: [ Container( @@ -210,6 +206,12 @@ class _PeerCardState extends State<_PeerCard> ) ], ); + } + + Widget _buildPeerTile( + BuildContext context, Peer peer, Rx? deco) { + hideUsernameOnCard ??= + bind.mainGetBuildinOption(key: kHideUsernameOnCard) == 'Y'; final colors = _frontN(peer.tags, 25) .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) .toList(); @@ -220,21 +222,22 @@ class _PeerCardState extends State<_PeerCard> ? '${translate('Tags')}: ${peer.tags.join(', ')}' : '', child: Stack(children: [ - Obx(() => deco == null - ? makeChild(stateGlobal.isPortrait.isTrue) - : Container( + Obx( + () => deco == null + ? makeChild(stateGlobal.isPortrait.isTrue, peer) + : Container( foregroundDecoration: deco.value, - child: makeChild(stateGlobal.isPortrait.isTrue), + child: makeChild(stateGlobal.isPortrait.isTrue, peer), ), - ), + ), if (colors.isNotEmpty) - Obx(()=> Positioned( - top: 2, - right: stateGlobal.isPortrait.isTrue ? 20 : 10, - child: CustomPaint( - painter: TagPainter(radius: 3, colors: colors), - ), - )) + Obx(() => Positioned( + top: 2, + right: stateGlobal.isPortrait.isTrue ? 20 : 10, + child: CustomPaint( + painter: TagPainter(radius: 3, colors: colors), + ), + )) ]), ); } @@ -1259,54 +1262,53 @@ void _rdpDialog(String id) async { ], ).marginOnly(bottom: isDesktop ? 8 : 0), Obx(() => Row( - children: [ - stateGlobal.isPortrait.isFalse - ? ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Text( - "${translate('Username')}:", - textAlign: TextAlign.right, - ).marginOnly(right: 10)) - : SizedBox.shrink(), - Expanded( - child: TextField( - decoration: InputDecoration( - labelText: isDesktop - ? null - : translate('Username')), - controller: userController, - ), - ), - ], - ).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)), - Obx(() => Row( - children: [ - stateGlobal.isPortrait.isFalse - ? ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Text( - "${translate('Password')}:", - textAlign: TextAlign.right, - ).marginOnly(right: 10)) - : SizedBox.shrink(), - Expanded( - child: Obx(() => TextField( - obscureText: secure.value, - maxLength: maxLength, + children: [ + stateGlobal.isPortrait.isFalse + ? ConstrainedBox( + constraints: const BoxConstraints(minWidth: 140), + child: Text( + "${translate('Username')}:", + textAlign: TextAlign.right, + ).marginOnly(right: 10)) + : SizedBox.shrink(), + Expanded( + child: TextField( decoration: InputDecoration( - labelText: isDesktop - ? null - : translate('Password'), - suffixIcon: IconButton( - onPressed: () => secure.value = !secure.value, - icon: Icon(secure.value - ? Icons.visibility_off - : Icons.visibility))), - controller: passwordController, - )), - ), - ], - )) + labelText: + isDesktop ? null : translate('Username')), + controller: userController, + ), + ), + ], + ).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)), + Obx(() => Row( + children: [ + stateGlobal.isPortrait.isFalse + ? ConstrainedBox( + constraints: const BoxConstraints(minWidth: 140), + child: Text( + "${translate('Password')}:", + textAlign: TextAlign.right, + ).marginOnly(right: 10)) + : SizedBox.shrink(), + Expanded( + child: Obx(() => TextField( + obscureText: secure.value, + maxLength: maxLength, + decoration: InputDecoration( + labelText: + isDesktop ? null : translate('Password'), + suffixIcon: IconButton( + onPressed: () => + secure.value = !secure.value, + icon: Icon(secure.value + ? Icons.visibility_off + : Icons.visibility))), + controller: passwordController, + )), + ), + ], + )) ], ), ), diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index dc02ac81fda..9342c9e5086 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -475,7 +475,8 @@ class _AppState extends State with WidgetsBindingObserver { : (context, child) { child = _keepScaleBuilder(context, child); child = botToastBuilder(context, child); - if (isDesktop && desktopType == DesktopType.main) { + if ((isDesktop && desktopType == DesktopType.main) || + isWebDesktop) { child = keyListenerBuilder(context, child); } if (isLinux) { diff --git a/flutter/lib/models/peer_tab_model.dart b/flutter/lib/models/peer_tab_model.dart index 7dab2574dcf..fbde560c204 100644 --- a/flutter/lib/models/peer_tab_model.dart +++ b/flutter/lib/models/peer_tab_model.dart @@ -152,7 +152,7 @@ class PeerTabModel with ChangeNotifier { // https://github.com/flutter/flutter/issues/101275#issuecomment-1604541700 // After onTap, the shift key should be pressed for a while when not in multiselection mode, // because onTap is delayed when onDoubleTap is not null - if (isDesktop && !_isShiftDown) return; + if ((isDesktop || isWebDesktop) && !_isShiftDown) return; _multiSelectionMode = true; } final cached = _currentTabCachedPeers.map((e) => e.id).toList(); From 29b01e9cef48b3278cf7f1fee44368139874d0dd Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 11 Oct 2024 16:47:08 +0800 Subject: [PATCH 164/210] remove shift & tap enable multiselect (#9625) Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_card.dart | 2 +- flutter/lib/models/peer_tab_model.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 246b337a98b..8dd54fb1a73 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -62,7 +62,7 @@ class _PeerCardState extends State<_PeerCard> final PeerTabModel peerTabModel = Provider.of(context); final peer = super.widget.peer; return GestureDetector( - onDoubleTap: peerTabModel.multiSelectionMode || peerTabModel.isShiftDown + onDoubleTap: peerTabModel.multiSelectionMode ? null : () => widget.connect(context, peer.id), onTap: () { diff --git a/flutter/lib/models/peer_tab_model.dart b/flutter/lib/models/peer_tab_model.dart index fbde560c204..83df1f05d6a 100644 --- a/flutter/lib/models/peer_tab_model.dart +++ b/flutter/lib/models/peer_tab_model.dart @@ -152,7 +152,7 @@ class PeerTabModel with ChangeNotifier { // https://github.com/flutter/flutter/issues/101275#issuecomment-1604541700 // After onTap, the shift key should be pressed for a while when not in multiselection mode, // because onTap is delayed when onDoubleTap is not null - if ((isDesktop || isWebDesktop) && !_isShiftDown) return; + if (isDesktop || isWebDesktop) return; _multiSelectionMode = true; } final cached = _currentTabCachedPeers.map((e) => e.id).toList(); From eb1ef0969cd7bc0f48528c903eb6e84cf30336f2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 12 Oct 2024 09:03:13 +0800 Subject: [PATCH 165/210] web file transfer (#9587) Signed-off-by: 21pages --- flutter/lib/common.dart | 36 +- flutter/lib/common/widgets/peer_card.dart | 2 +- .../lib/desktop/pages/file_manager_page.dart | 103 +++-- flutter/lib/models/file_model.dart | 43 +- flutter/lib/models/model.dart | 4 + flutter/lib/web/bridge.dart | 370 ++++++++++-------- flutter/lib/web/dummy.dart | 14 + flutter/lib/web/web_unique.dart | 30 ++ src/flutter_ffi.rs | 3 +- 9 files changed, 411 insertions(+), 194 deletions(-) create mode 100644 flutter/lib/web/dummy.dart create mode 100644 flutter/lib/web/web_unique.dart diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 8feb2402f8a..881cf3b8b98 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -30,6 +30,7 @@ import 'common/widgets/overlay.dart'; import 'mobile/pages/file_manager_page.dart'; import 'mobile/pages/remote_page.dart'; import 'desktop/pages/remote_page.dart' as desktop_remote; +import 'desktop/pages/file_manager_page.dart' as desktop_file_manager; import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; import 'models/model.dart'; import 'models/platform_model.dart'; @@ -2370,18 +2371,33 @@ connect(BuildContext context, String id, } } else { if (isFileTransfer) { - if (!await AndroidPermissionManager.check(kManageExternalStorage)) { - if (!await AndroidPermissionManager.request(kManageExternalStorage)) { - return; + if (isAndroid) { + if (!await AndroidPermissionManager.check(kManageExternalStorage)) { + if (!await AndroidPermissionManager.request(kManageExternalStorage)) { + return; + } } } - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => FileManagerPage( - id: id, password: password, isSharedPassword: isSharedPassword), - ), - ); + if (isWeb) { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + desktop_file_manager.FileManagerPage( + id: id, + password: password, + isSharedPassword: isSharedPassword), + ), + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => FileManagerPage( + id: id, password: password, isSharedPassword: isSharedPassword), + ), + ); + } } else { if (isWeb) { Navigator.push( diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 8dd54fb1a73..690c829775f 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -879,7 +879,7 @@ class RecentPeerCard extends BasePeerCard { BuildContext context) async { final List> menuItems = [ _connectAction(context), - if (!isWeb) _transferFileAction(context), + _transferFileAction(context), ]; final List favs = (await bind.mainGetFav()).toList(); diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index b75a946c06b..5557783b5c7 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -17,6 +17,8 @@ import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; +import 'package:flutter_hbb/web/dummy.dart' + if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart'; import '../../consts.dart'; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; @@ -55,14 +57,14 @@ class FileManagerPage extends StatefulWidget { required this.id, required this.password, required this.isSharedPassword, - required this.tabController, + this.tabController, this.forceRelay}) : super(key: key); final String id; final String? password; final bool? isSharedPassword; final bool? forceRelay; - final DesktopTabController tabController; + final DesktopTabController? tabController; @override State createState() => _FileManagerPageState(); @@ -97,11 +99,14 @@ class _FileManagerPageState extends State if (!isLinux) { WakelockPlus.enable(); } + if (isWeb) { + _ffi.ffiModel.updateEventListener(_ffi.sessionId, widget.id); + } debugPrint("File manager page init success with id ${widget.id}"); _ffi.dialogManager.setOverlayState(_overlayKeyState); // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState. WidgetsBinding.instance.addPostFrameCallback((_) { - widget.tabController.onSelected?.call(widget.id); + widget.tabController?.onSelected?.call(widget.id); }); WidgetsBinding.instance.addObserver(this); } @@ -140,10 +145,11 @@ class _FileManagerPageState extends State backgroundColor: Theme.of(context).scaffoldBackgroundColor, body: Row( children: [ - Flexible( - flex: 3, - child: dropArea(FileManagerView( - model.localController, _ffi, _mouseFocusScope))), + if (!isWeb) + Flexible( + flex: 3, + child: dropArea(FileManagerView( + model.localController, _ffi, _mouseFocusScope))), Flexible( flex: 3, child: dropArea(FileManagerView( @@ -192,7 +198,13 @@ class _FileManagerPageState extends State return Icon(Icons.delete_outline, color: color); default: return Transform.rotate( - angle: job.isRemoteToLocal ? pi : 0, + angle: isWeb + ? job.isRemoteToLocal + ? pi / 2 + : pi / 2 * 3 + : job.isRemoteToLocal + ? pi + : 0, child: Icon(Icons.arrow_forward_ios, color: color), ); } @@ -800,6 +812,50 @@ class _FileManagerViewState extends State { ], ), ), + if (isWeb) + Obx(() => ElevatedButton.icon( + style: ButtonStyle( + padding: MaterialStateProperty.all( + isLocal + ? EdgeInsets.only(left: 10) + : EdgeInsets.only(right: 10)), + backgroundColor: MaterialStateProperty.all( + selectedItems.items.isEmpty + ? MyTheme.accent80 + : MyTheme.accent, + ), + ), + onPressed: () => {webselectFiles(is_folder: true)}, + icon: Offstage(), + label: Text( + translate('Upload folder'), + textAlign: TextAlign.right, + style: TextStyle( + color: Colors.white, + ), + ))).marginOnly(left: 16), + if (isWeb) + Obx(() => ElevatedButton.icon( + style: ButtonStyle( + padding: MaterialStateProperty.all( + isLocal + ? EdgeInsets.only(left: 10) + : EdgeInsets.only(right: 10)), + backgroundColor: MaterialStateProperty.all( + selectedItems.items.isEmpty + ? MyTheme.accent80 + : MyTheme.accent, + ), + ), + onPressed: () => {webselectFiles(is_folder: false)}, + icon: Offstage(), + label: Text( + translate('Upload files'), + textAlign: TextAlign.right, + style: TextStyle( + color: Colors.white, + ), + ))).marginOnly(left: 16), Obx(() => ElevatedButton.icon( style: ButtonStyle( padding: MaterialStateProperty.all( @@ -833,19 +889,22 @@ class _FileManagerViewState extends State { : Colors.white, ), ) - : RotatedBox( - quarterTurns: 2, - child: SvgPicture.asset( - "assets/arrow.svg", - colorFilter: svgColor(selectedItems.items.isEmpty - ? Theme.of(context).brightness == - Brightness.light - ? MyTheme.grayBg - : MyTheme.darkGray - : Colors.white), - alignment: Alignment.bottomRight, - ), - ), + : isWeb + ? Offstage() + : RotatedBox( + quarterTurns: 2, + child: SvgPicture.asset( + "assets/arrow.svg", + colorFilter: svgColor( + selectedItems.items.isEmpty + ? Theme.of(context).brightness == + Brightness.light + ? MyTheme.grayBg + : MyTheme.darkGray + : Colors.white), + alignment: Alignment.bottomRight, + ), + ), label: isLocal ? SvgPicture.asset( "assets/arrow.svg", @@ -857,7 +916,7 @@ class _FileManagerViewState extends State { : Colors.white), ) : Text( - translate('Receive'), + translate(isWeb ? 'Download' : 'Receive'), style: TextStyle( color: selectedItems.items.isEmpty ? Theme.of(context).brightness == diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index a0d5bc0b558..05c79ae86a7 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -7,6 +7,8 @@ import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/utils/event_loop.dart'; import 'package:get/get.dart'; import 'package:path/path.dart' as path; +import 'package:flutter_hbb/web/dummy.dart' + if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart'; import '../consts.dart'; import 'model.dart'; @@ -74,7 +76,7 @@ class FileModel { Future onReady() async { await evtLoop.onReady(); - await localController.onReady(); + if (!isWeb) await localController.onReady(); await remoteController.onReady(); } @@ -86,7 +88,7 @@ class FileModel { } Future refreshAll() async { - await localController.refresh(); + if (!isWeb) await localController.refresh(); await remoteController.refresh(); } @@ -228,6 +230,33 @@ class FileModel { ); }, useAnimation: false); } + + void onSelectedFiles(dynamic obj) { + localController.selectedItems.clear(); + + try { + int handleIndex = int.parse(obj['handleIndex']); + final file = jsonDecode(obj['file']); + var entry = Entry.fromJson(file); + entry.path = entry.name; + final otherSideData = remoteController.directoryData(); + final toPath = otherSideData.directory.path; + final isWindows = otherSideData.options.isWindows; + final showHidden = otherSideData.options.showHidden; + final jobID = jobController.addTransferJob(entry, false); + webSendLocalFiles( + handleIndex: handleIndex, + actId: jobID, + path: entry.path, + to: PathUtil.join(toPath, entry.name, isWindows), + fileNum: 0, + includeHidden: showHidden, + isRemote: false, + ); + } catch (e) { + debugPrint("Failed to decode onSelectedFiles: $e"); + } + } } class DirectoryData { @@ -462,7 +491,8 @@ class FileController { to: PathUtil.join(toPath, from.name, isWindows), fileNum: 0, includeHidden: showHidden, - isRemote: isRemoteToLocal); + isRemote: isRemoteToLocal, + isDir: from.isDirectory); debugPrint( "path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}"); } @@ -489,7 +519,7 @@ class FileController { } else if (item.isDirectory) { title = translate("Not an empty directory"); dialogManager?.showLoading(translate("Waiting")); - final fd = await fileFetcher.fetchDirectoryRecursive( + final fd = await fileFetcher.fetchDirectoryRecursiveToRemove( jobID, item.path, items.isLocal, true); if (fd.path.isEmpty) { fd.path = item.path; @@ -809,7 +839,6 @@ class JobController { job.speed = double.parse(evt['speed']); job.finishedSize = int.parse(evt['finished_size']); job.recvJobRes = true; - debugPrint("update job $id with $evt"); jobTable.refresh(); } } catch (e) { @@ -1116,11 +1145,11 @@ class FileFetcher { } } - Future fetchDirectoryRecursive( + Future fetchDirectoryRecursiveToRemove( int actID, String path, bool isLocal, bool showHidden) async { // TODO test Recursive is show hidden default? try { - await bind.sessionReadDirRecursive( + await bind.sessionReadDirToRemoveRecursive( sessionId: sessionId, actId: actID, path: path, diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 8466443ee80..2833f38349e 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -390,6 +390,10 @@ class FfiModel with ChangeNotifier { handleFollowCurrentDisplay(evt, sessionId, peerId); } else if (name == 'use_texture_render') { _handleUseTextureRender(evt, sessionId, peerId); + } else if (name == "selected_files") { + if (isWeb) { + parent.target?.fileModel.onSelectedFiles(evt); + } } else { debugPrint('Event is not handled in the fixed branch: $name'); } diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index adcbadb30e9..0362490cdf1 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -52,12 +52,12 @@ class EventToUI_Texture implements EventToUI { class RustdeskImpl { Future stopGlobalEventStream({required String appType, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("stopGlobalEventStream"); } Future hostStopSystemKeyPropagate( {required bool stopped, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("hostStopSystemKeyPropagate"); } int peerGetDefaultSessionsCount({required String id, dynamic hint}) { @@ -88,7 +88,8 @@ class RustdeskImpl { jsonEncode({ 'id': id, 'password': password, - 'is_shared_password': isSharedPassword + 'is_shared_password': isSharedPassword, + 'isFileTransfer': isFileTransfer }) ]); } @@ -107,7 +108,7 @@ class RustdeskImpl { required String id, required Int32List displays, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionStartWithDisplays"); } Future sessionGetRemember( @@ -178,12 +179,12 @@ class RustdeskImpl { required int width, required int height, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionRecordScreen"); } Future sessionRecordStatus( {required UuidValue sessionId, required bool status, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionRecordStatus"); } Future sessionReconnect( @@ -435,7 +436,7 @@ class RustdeskImpl { required int lockModes, required bool downOrUp, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionHandleFlutterRawKeyEvent"); } void sessionEnterOrLeave( @@ -507,7 +508,10 @@ class RustdeskImpl { required String path, required bool includeHidden, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'read_remote_dir', + jsonEncode({'path': path, 'include_hidden': includeHidden}) + ])); } Future sessionSendFiles( @@ -518,8 +522,20 @@ class RustdeskImpl { required int fileNum, required bool includeHidden, required bool isRemote, + required bool isDir, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'send_files', + jsonEncode({ + 'id': actId, + 'path': path, + 'to': to, + 'file_num': fileNum, + 'include_hidden': includeHidden, + 'is_remote': isRemote, + 'is_dir': isDir, + }) + ])); } Future sessionSetConfirmOverrideFile( @@ -530,7 +546,16 @@ class RustdeskImpl { required bool remember, required bool isUpload, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'confirm_override_file', + jsonEncode({ + 'id': actId, + 'file_num': fileNum, + 'need_override': needOverride, + 'remember': remember, + 'is_upload': isUpload + }) + ])); } Future sessionRemoveFile( @@ -540,17 +565,33 @@ class RustdeskImpl { required int fileNum, required bool isRemote, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'remove_file', + jsonEncode({ + 'id': actId, + 'path': path, + 'file_num': fileNum, + 'is_remote': isRemote + }) + ])); } - Future sessionReadDirRecursive( + Future sessionReadDirToRemoveRecursive( {required UuidValue sessionId, required int actId, required String path, required bool isRemote, required bool showHidden, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'read_dir_to_remove_recursive', + jsonEncode({ + 'id': actId, + 'path': path, + 'is_remote': isRemote, + 'show_hidden': showHidden + }) + ])); } Future sessionRemoveAllEmptyDirs( @@ -559,12 +600,16 @@ class RustdeskImpl { required String path, required bool isRemote, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'remove_all_empty_dirs', + jsonEncode({'id': actId, 'path': path, 'is_remote': isRemote}) + ])); } Future sessionCancelJob( {required UuidValue sessionId, required int actId, dynamic hint}) { - throw UnimplementedError(); + return Future( + () => js.context.callMethod('setByName', ['cancel_job', actId])); } Future sessionCreateDir( @@ -573,7 +618,10 @@ class RustdeskImpl { required String path, required bool isRemote, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'create_dir', + jsonEncode({'id': actId, 'path': path, 'is_remote': isRemote}) + ])); } Future sessionReadLocalDirSync( @@ -581,17 +629,21 @@ class RustdeskImpl { required String path, required bool showHidden, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionReadLocalDirSync"); } Future sessionGetPlatform( {required UuidValue sessionId, required bool isRemote, dynamic hint}) { - throw UnimplementedError(); + if (isRemote) { + return Future(() => js.context.callMethod('getByName', ['platform'])); + } else { + return Future(() => 'Web'); + } } Future sessionLoadLastTransferJobs( {required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionLoadLastTransferJobs"); } Future sessionAddJob( @@ -603,7 +655,7 @@ class RustdeskImpl { required bool includeHidden, required bool isRemote, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionAddJob"); } Future sessionResumeJob( @@ -611,7 +663,7 @@ class RustdeskImpl { required int actId, required bool isRemote, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionResumeJob"); } Future sessionElevateDirect( @@ -632,7 +684,7 @@ class RustdeskImpl { Future sessionSwitchSides( {required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionSwitchSides"); } Future sessionChangeResolution( @@ -642,7 +694,7 @@ class RustdeskImpl { required int height, dynamic hint}) { // note: restore on disconnected - throw UnimplementedError(); + throw UnimplementedError("sessionChangeResolution"); } Future sessionSetSize( @@ -656,15 +708,15 @@ class RustdeskImpl { Future sessionSendSelectedSessionId( {required UuidValue sessionId, required String sid, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionSendSelectedSessionId"); } Future> mainGetSoundInputs({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetSoundInputs"); } Future mainGetDefaultSoundInput({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetDefaultSoundInput"); } String mainGetLoginDeviceInfo({dynamic hint}) { @@ -680,11 +732,11 @@ class RustdeskImpl { } Future mainChangeId({required String newId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainChangeId"); } Future mainGetAsyncStatus({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetAsyncStatus"); } Future mainGetOption({required String key, dynamic hint}) { @@ -696,11 +748,11 @@ class RustdeskImpl { } Future mainGetError({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetError"); } bool mainShowOption({required String key, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainShowOption"); } Future mainSetOption( @@ -737,11 +789,11 @@ class RustdeskImpl { required String username, required String password, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainSetSocks"); } Future> mainGetSocks({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetSocks"); } Future mainGetAppName({dynamic hint}) { @@ -753,7 +805,7 @@ class RustdeskImpl { } String mainUriPrefixSync({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainUriPrefixSync"); } Future mainGetLicense({dynamic hint}) { @@ -785,11 +837,11 @@ class RustdeskImpl { String mainGetPeerSync({required String id, dynamic hint}) { // TODO: - throw UnimplementedError(); + throw UnimplementedError("mainGetPeerSync"); } Future mainGetLanPeers({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetLanPeers"); } Future mainGetConnectStatus({dynamic hint}) { @@ -798,7 +850,7 @@ class RustdeskImpl { } Future mainCheckConnectStatus({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainCheckConnectStatus"); } Future mainIsUsingPublicServer({dynamic hint}) { @@ -808,7 +860,7 @@ class RustdeskImpl { } Future mainDiscover({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainDiscover"); } Future mainGetApiServer({dynamic hint}) { @@ -820,7 +872,7 @@ class RustdeskImpl { required String body, required String header, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainPostRequest"); } Future mainGetProxyStatus({dynamic hint}) { @@ -834,11 +886,11 @@ class RustdeskImpl { required String header, dynamic hint, }) { - throw UnimplementedError(); + throw UnimplementedError("mainHttpRequest"); } Future mainGetHttpStatus({required String url, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetHttpStatus"); } String mainGetLocalOption({required String key, dynamic hint}) { @@ -846,7 +898,7 @@ class RustdeskImpl { } String mainGetEnv({required String key, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetEnv"); } Future mainSetLocalOption( @@ -940,7 +992,7 @@ class RustdeskImpl { } Future mainGetNewStoredPeers({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetNewStoredPeers"); } Future mainForgetPassword({required String id, dynamic hint}) { @@ -973,7 +1025,7 @@ class RustdeskImpl { Future mainLoadRecentPeersForAb( {required String filter, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainLoadRecentPeersForAb"); } Future mainLoadFavPeers({dynamic hint}) { @@ -981,23 +1033,23 @@ class RustdeskImpl { } Future mainLoadLanPeers({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainLoadLanPeers"); } Future mainRemoveDiscovered({required String id, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainRemoveDiscovered"); } Future mainChangeTheme({required String dark, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainChangeTheme"); } Future mainChangeLanguage({required String lang, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainChangeLanguage"); } String mainVideoSaveDirectory({required bool root, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainVideoSaveDirectory"); } Future mainSetUserDefaultOption( @@ -1026,7 +1078,7 @@ class RustdeskImpl { } String mainGetDisplays({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetDisplays"); } Future sessionAddPortForward( @@ -1035,35 +1087,35 @@ class RustdeskImpl { required String remoteHost, required int remotePort, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionAddPortForward"); } Future sessionRemovePortForward( {required UuidValue sessionId, required int localPort, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionRemovePortForward"); } Future sessionNewRdp({required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionNewRdp"); } Future sessionRequestVoiceCall( {required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionRequestVoiceCall"); } Future sessionCloseVoiceCall( {required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionCloseVoiceCall"); } Future cmHandleIncomingVoiceCall( {required int id, required bool accept, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmHandleIncomingVoiceCall"); } Future cmCloseVoiceCall({required int id, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmCloseVoiceCall"); } Future mainGetLastRemoteId({dynamic hint}) { @@ -1072,7 +1124,7 @@ class RustdeskImpl { } Future mainGetSoftwareUpdateUrl({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetSoftwareUpdateUrl"); } Future mainGetHomeDir({dynamic hint}) { @@ -1096,15 +1148,15 @@ class RustdeskImpl { } Future cmGetClientsState({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmGetClientsState"); } Future cmCheckClientsLength({required int length, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmCheckClientsLength"); } Future cmGetClientsLength({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmCheckClientsLength"); } Future mainInit({required String appDir, dynamic hint}) { @@ -1113,12 +1165,12 @@ class RustdeskImpl { Future mainDeviceId({required String id, dynamic hint}) { // TODO: ? - throw UnimplementedError(); + throw UnimplementedError("mainDeviceId"); } Future mainDeviceName({required String name, dynamic hint}) { // TODO: ? - throw UnimplementedError(); + throw UnimplementedError("mainDeviceName"); } Future mainRemovePeer({required String id, dynamic hint}) { @@ -1127,11 +1179,11 @@ class RustdeskImpl { } bool mainHasHwcodec({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainHasHwcodec"); } bool mainHasVram({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainHasVram"); } String mainSupportedHwdecodings({dynamic hint}) { @@ -1139,15 +1191,15 @@ class RustdeskImpl { } Future mainIsRoot({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsRoot"); } int getDoubleClickTime({dynamic hint}) { - throw UnimplementedError(); + return 500; } Future mainStartDbusServer({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainStartDbusServer"); } Future mainSaveAb({required String json, dynamic hint}) { @@ -1177,7 +1229,7 @@ class RustdeskImpl { Future sessionSendPointer( {required UuidValue sessionId, required String msg, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionSendPointer"); } Future sessionSendMouse( @@ -1231,76 +1283,75 @@ class RustdeskImpl { } Future mainSetHomeDir({required String home, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainSetHomeDir"); } String mainGetDataDirIos({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetDataDirIos"); } Future mainStopService({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainStopService"); } Future mainStartService({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainStartService"); } Future mainUpdateTemporaryPassword({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainUpdateTemporaryPassword"); } Future mainSetPermanentPassword( {required String password, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainSetPermanentPassword"); } Future mainCheckSuperUserPermission({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainCheckSuperUserPermission"); } Future mainCheckMouseTime({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainCheckMouseTime"); } Future mainGetMouseTime({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetMouseTime"); } Future mainWol({required String id, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainWol"); } Future mainCreateShortcut({required String id, dynamic hint}) { - // TODO: - throw UnimplementedError(); + throw UnimplementedError("mainCreateShortcut"); } Future cmSendChat( {required int connId, required String msg, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmSendChat"); } Future cmLoginRes( {required int connId, required bool res, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmLoginRes"); } Future cmCloseConnection({required int connId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmCloseConnection"); } Future cmRemoveDisconnectedConnection( {required int connId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmRemoveDisconnectedConnection"); } Future cmCheckClickTime({required int connId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmCheckClickTime"); } Future cmGetClickTime({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmGetClickTime"); } Future cmSwitchPermission( @@ -1308,23 +1359,23 @@ class RustdeskImpl { required String name, required bool enabled, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmSwitchPermission"); } bool cmCanElevate({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmCanElevate"); } Future cmElevatePortable({required int connId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmElevatePortable"); } Future cmSwitchBack({required int connId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmSwitchBack"); } Future cmGetConfig({required String name, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmGetConfig"); } Future mainGetBuildDate({dynamic hint}) { @@ -1377,89 +1428,89 @@ class RustdeskImpl { } bool mainIsInstalled({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsInstalled"); } void mainInitInputSource({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsInstalled"); } bool mainIsInstalledLowerVersion({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsInstalledLowerVersion"); } bool mainIsInstalledDaemon({required bool prompt, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsInstalledDaemon"); } bool mainIsProcessTrusted({required bool prompt, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsProcessTrusted"); } bool mainIsCanScreenRecording({required bool prompt, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsCanScreenRecording"); } bool mainIsCanInputMonitoring({required bool prompt, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsCanInputMonitoring"); } bool mainIsShareRdp({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainIsShareRdp"); } Future mainSetShareRdp({required bool enable, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainSetShareRdp"); } bool mainGotoInstall({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGotoInstall"); } String mainGetNewVersion({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetNewVersion"); } bool mainUpdateMe({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainUpdateMe"); } Future setCurSessionId({required UuidValue sessionId, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("setCurSessionId"); } bool installShowRunWithoutInstall({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("installShowRunWithoutInstall"); } Future installRunWithoutInstall({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("installRunWithoutInstall"); } Future installInstallMe( {required String options, required String path, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("installInstallMe"); } String installInstallPath({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("installInstallPath"); } Future mainAccountAuth( {required String op, required bool rememberMe, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainAccountAuth"); } Future mainAccountAuthCancel({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainAccountAuthCancel"); } Future mainAccountAuthResult({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainAccountAuthResult"); } Future mainOnMainWindowClose({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainOnMainWindowClose"); } bool mainCurrentIsWayland({dynamic hint}) { @@ -1471,7 +1522,7 @@ class RustdeskImpl { } bool mainHideDock({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainHideDock"); } bool mainHasFileClipboard({dynamic hint}) { @@ -1483,11 +1534,11 @@ class RustdeskImpl { } Future cmInit({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("cmInit"); } Future mainStartIpcUrlServer({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainStartIpcUrlServer"); } Future mainTestWallpaper({required int second, dynamic hint}) { @@ -1537,7 +1588,7 @@ class RustdeskImpl { } Future sendUrlScheme({required String url, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sendUrlScheme"); } Future pluginEvent( @@ -1545,12 +1596,12 @@ class RustdeskImpl { required String peer, required Uint8List event, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginEvent"); } Stream pluginRegisterEventStream( {required String id, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginRegisterEventStream"); } String? pluginGetSessionOption( @@ -1558,7 +1609,7 @@ class RustdeskImpl { required String peer, required String key, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginGetSessionOption"); } Future pluginSetSessionOption( @@ -1567,12 +1618,12 @@ class RustdeskImpl { required String key, required String value, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginSetSessionOption"); } String? pluginGetSharedOption( {required String id, required String key, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginGetSharedOption"); } Future pluginSetSharedOption( @@ -1580,36 +1631,36 @@ class RustdeskImpl { required String key, required String value, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginSetSharedOption"); } Future pluginReload({required String id, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginReload"); } void pluginEnable({required String id, required bool v, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginEnable"); } bool pluginIsEnabled({required String id, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginIsEnabled"); } bool pluginFeatureIsEnabled({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginFeatureIsEnabled"); } Future pluginSyncUi({required String syncTo, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginSyncUi"); } Future pluginListReload({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginListReload"); } Future pluginInstall( {required String id, required bool b, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("pluginInstall"); } bool isSupportMultiUiSession({required String version, dynamic hint}) { @@ -1621,11 +1672,11 @@ class RustdeskImpl { } String mainDefaultPrivacyModeImpl({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainDefaultPrivacyModeImpl"); } String mainSupportedPrivacyModeImpls({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainSupportedPrivacyModeImpls"); } String mainSupportedInputSource({dynamic hint}) { @@ -1636,33 +1687,33 @@ class RustdeskImpl { } Future mainGenerate2Fa({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGenerate2Fa"); } Future mainVerify2Fa({required String code, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainVerify2Fa"); } bool mainHasValid2FaSync({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainHasValid2FaSync"); } String mainGetHardOption({required String key, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetHardOption"); } Future mainCheckHwcodec({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainCheckHwcodec"); } Future sessionRequestNewDisplayInitMsgs( {required UuidValue sessionId, required int display, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("sessionRequestNewDisplayInitMsgs"); } Future mainHandleWaylandScreencastRestoreToken( {required String key, required String value, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainHandleWaylandScreencastRestoreToken"); } bool mainIsOptionFixed({required String key, dynamic hint}) { @@ -1670,23 +1721,23 @@ class RustdeskImpl { } bool mainGetUseTextureRender({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetUseTextureRender"); } bool mainHasValidBotSync({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainHasValidBotSync"); } Future mainVerifyBot({required String token, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainVerifyBot"); } String mainGetUnlockPin({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetUnlockPin"); } String mainSetUnlockPin({required String pin, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainSetUnlockPin"); } bool sessionGetEnableTrustedDevices( @@ -1696,28 +1747,28 @@ class RustdeskImpl { } Future mainGetTrustedDevices({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainGetTrustedDevices"); } Future mainRemoveTrustedDevices({required String json, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainRemoveTrustedDevices"); } Future mainClearTrustedDevices({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainClearTrustedDevices"); } Future getVoiceCallInputDevice({required bool isCm, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("getVoiceCallInputDevice"); } Future setVoiceCallInputDevice( {required bool isCm, required String device, dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("setVoiceCallInputDevice"); } bool isPresetPasswordMobileOnly({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("isPresetPasswordMobileOnly"); } String mainGetBuildinOption({required String key, dynamic hint}) { @@ -1725,21 +1776,34 @@ class RustdeskImpl { } String installInstallOptions({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("installInstallOptions"); } int mainMaxEncryptLen({dynamic hint}) { - throw UnimplementedError(); + throw UnimplementedError("mainMaxEncryptLen"); } - sessionRenameFile( + Future sessionRenameFile( {required UuidValue sessionId, required int actId, required String path, required String newName, required bool isRemote, dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('setByName', [ + 'rename_file', + jsonEncode({ + 'id': actId, + 'path': path, + 'new_name': newName, + 'is_remote': isRemote + }) + ])); + } + + Future sessionSelectFiles( + {required UuidValue sessionId, dynamic hint}) { + return Future(() => js.context.callMethod('setByName', ['select_files'])); } void dispose() {} diff --git a/flutter/lib/web/dummy.dart b/flutter/lib/web/dummy.dart new file mode 100644 index 00000000000..b9e3b80b6ed --- /dev/null +++ b/flutter/lib/web/dummy.dart @@ -0,0 +1,14 @@ +Future webselectFiles({required bool is_folder}) async { + throw UnimplementedError("webselectFiles"); +} + +Future webSendLocalFiles( + {required int handleIndex, + required int actId, + required String path, + required String to, + required int fileNum, + required bool includeHidden, + required bool isRemote}) { + throw UnimplementedError("webSendLocalFiles"); +} diff --git a/flutter/lib/web/web_unique.dart b/flutter/lib/web/web_unique.dart new file mode 100644 index 00000000000..14774e668b0 --- /dev/null +++ b/flutter/lib/web/web_unique.dart @@ -0,0 +1,30 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:js' as js; + +Future webselectFiles({required bool is_folder}) async { + return Future( + () => js.context.callMethod('setByName', ['select_files', is_folder])); +} + +Future webSendLocalFiles( + {required int handleIndex, + required int actId, + required String path, + required String to, + required int fileNum, + required bool includeHidden, + required bool isRemote}) { + return Future(() => js.context.callMethod('setByName', [ + 'send_local_files', + jsonEncode({ + 'id': actId, + 'handle_index': handleIndex, + 'path': path, + 'to': to, + 'file_num': fileNum, + 'include_hidden': includeHidden, + 'is_remote': isRemote, + }) + ])); +} diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 020bc98aa92..d029de1d256 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -602,6 +602,7 @@ pub fn session_send_files( file_num: i32, include_hidden: bool, is_remote: bool, + _is_dir: bool, ) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.send_files(act_id, path, to, file_num, include_hidden, is_remote); @@ -633,7 +634,7 @@ pub fn session_remove_file( } } -pub fn session_read_dir_recursive( +pub fn session_read_dir_to_remove_recursive( session_id: SessionID, act_id: i32, path: String, From 65683cc3e6da99990d8e4bf5e3d18f6403c3e41e Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 13 Oct 2024 01:07:47 +0800 Subject: [PATCH 166/210] refact: remove redundant escape (#9634) * refact: remove redundant escape Signed-off-by: fufesou * flutter, early assert Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/common.dart | 4 ++++ flutter/lib/main.dart | 1 + flutter/lib/mobile/pages/remote_page.dart | 8 ++++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 881cf3b8b98..2bbb224cbb1 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -3616,3 +3616,7 @@ List? get subWindowManagerEnableResizeEdges => isWindows SubWindowResizeEdge.topRight, ] : null; + +void earlyAssert() { + assert('\1' == '1'); +} diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 9342c9e5086..62574dfe62c 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -36,6 +36,7 @@ WindowType? kWindowType; late List kBootArgs; Future main(List args) async { + earlyAssert(); WidgetsFlutterBinding.ensureInitialized(); debugPrint("launch args: $args"); diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 74b56cd45fc..19f7cc57530 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -155,9 +155,9 @@ class _RemotePageState extends State { var oldValue = _value; _value = newValue; var i = newValue.length - 1; - for (; i >= 0 && newValue[i] != '\1'; --i) {} + for (; i >= 0 && newValue[i] != '1'; --i) {} var j = oldValue.length - 1; - for (; j >= 0 && oldValue[j] != '\1'; --j) {} + for (; j >= 0 && oldValue[j] != '1'; --j) {} if (i < j) j = i; var subNewValue = newValue.substring(j + 1); var subOldValue = oldValue.substring(j + 1); @@ -206,8 +206,8 @@ class _RemotePageState extends State { _value = newValue; if (oldValue.isNotEmpty && newValue.isNotEmpty && - oldValue[0] == '\1' && - newValue[0] != '\1') { + oldValue[0] == '1' && + newValue[0] != '1') { // clipboard oldValue = ''; } From 9c7f51bc76572c9ab6a2e311cf379e271eec0b84 Mon Sep 17 00:00:00 2001 From: Yevhen Popok Date: Mon, 14 Oct 2024 06:02:44 +0300 Subject: [PATCH 167/210] Update uk.rs (#9638) --- src/lang/uk.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 05031dcc930..ac38e3479a5 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -351,7 +351,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable audio", "Увімкнути аудіо"), ("Unlock Network Settings", "Розблокувати мережеві налаштування"), ("Server", "Сервер"), - ("Direct IP Access", "Прямий IP доступ"), + ("Direct IP Access", "Прямий IP-доступ"), ("Proxy", "Проксі"), ("Apply", "Застосувати"), ("Disconnect all devices?", "Відʼєднати всі прилади?"), @@ -469,12 +469,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("identical_file_tip", "Цей файл ідентичний з тим, що на вузлі"), ("show_monitors_tip", "Показувати монітори на панелі інструментів"), ("View Mode", "Режим перегляду"), - ("login_linux_tip", "Вам необхідно залогуватися у віддалений обліковий запис Linux, щоб увімкнути стільничний сеанс X"), + ("login_linux_tip", "Вам необхідно увійти у віддалений обліковий запис Linux, щоб увімкнути стільничний сеанс X"), ("verify_rustdesk_password_tip", "Перевірте пароль RustDesk"), ("remember_account_tip", "Запамʼятати цей обліковий запис"), - ("os_account_desk_tip", "Цей обліковий запис використовується для входу до віддаленої ОС та вмикання сеансу стільниці в неграфічному режимі"), + ("os_account_desk_tip", "Цей обліковий запис використовується для входу до віддаленої ОС та вмикання сеансу стільниці в режимі без графічного інтерфейсу"), ("OS Account", "Користувач ОС"), - ("another_user_login_title_tip", "Інший користувач вже залогований"), + ("another_user_login_title_tip", "Інший користувач вже в системі"), ("another_user_login_text_tip", "Відʼєднатися"), ("xorg_not_found_title_tip", "Xorg не знайдено"), ("xorg_not_found_text_tip", "Будь ласка, встановіть Xorg"), @@ -506,7 +506,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Outgoing connection", "Вихідне підключення"), ("Exit", "Вийти"), ("Open", "Відкрити"), - ("logout_tip", "Ви впевнені, що хочете вилогуватися?"), + ("logout_tip", "Ви впевнені, що хочете вийти з системи?"), ("Service", "Служба"), ("Start", "Запустити"), ("Stop", "Зупинити"), @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Plug out all", "Відключити все"), ("True color (4:4:4)", "Справжній колір (4:4:4)"), ("Enable blocking user input", "Блокувати введення для користувача"), - ("id_input_tip", "Ви можете ввести ID, безпосередню IP, або ж домен з портом (<домен>:<порт>).\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"@public\", ключ для публічного сервера не потрібен."), + ("id_input_tip", "Ви можете ввести ID, безпосередню IP, або ж домен з портом (<домен>:<порт>).\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"@public\", для публічного сервера ключ не потрібен."), ("privacy_mode_impl_mag_tip", "Режим 1"), ("privacy_mode_impl_virtual_display_tip", "Режим 2"), ("Enter privacy mode", "Увійти в режим конфіденційності"), @@ -631,7 +631,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("cancel-bot-confirm-tip", "Ви впевнені, що хочете скасувати Telegram бота?"), ("About RustDesk", "Про Rustdesk"), ("Send clipboard keystrokes", "Надіслати вміст буфера обміну"), - ("network_error_tip", "Будь ласка, перевірте ваше підключення до мережі та натисність \"Повторити\""), + ("network_error_tip", "Будь ласка, перевірте ваше підключення до мережі та натисніть \"Повторити\""), ("Unlock with PIN", "Розблокування PIN-кодом"), ("Requires at least {} characters", "Потрібно щонайменше {} символів"), ("Wrong PIN", "Неправильний PIN-код"), @@ -640,13 +640,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Manage trusted devices", "Керувати довіреними пристроями"), ("Platform", "Платформа"), ("Days remaining", "Залишилося днів"), - ("enable-trusted-devices-tip", "Дозволити довіреним пристроям пропускати двофакторну автентифікацію?"), + ("enable-trusted-devices-tip", "Пропускати двофакторну автентифікацію на довірених пристроях"), ("Parent directory", "Батьківський каталог"), ("Resume", "Продовжити"), - ("Invalid file name", "Неправильне ім'я файлу"), - ("one-way-file-transfer-tip", "На керованій стороні ввімкнено одностороннє передавання файлів."), + ("Invalid file name", "Неправильна назва файлу"), + ("one-way-file-transfer-tip", "На стороні, що керується, увімкнено односторонню передачу файлів."), ("Authentication Required", "Потрібна автентифікація"), ("Authenticate", "Автентифікувати"), - ("web_id_input_tip", ""), + ("web_id_input_tip", "Ви можете ввести ID з того самого серверу, прямий IP-доступ у веб-клієнті не підтримується.\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"@public\", для публічного сервера ключ не потрібен."), ].iter().cloned().collect(); } From 6cdbcfc0821cc3b073fa45de29b245b18166ad71 Mon Sep 17 00:00:00 2001 From: Yevhen Popok Date: Mon, 14 Oct 2024 06:02:57 +0300 Subject: [PATCH 168/210] Update README-UA.md (#9639) --- docs/README-UA.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/README-UA.md b/docs/README-UA.md index c4d2e6f9f39..8f226914d70 100644 --- a/docs/README-UA.md +++ b/docs/README-UA.md @@ -1,20 +1,18 @@

    RustDesk - Ваша віддалена стільниця
    - Сервери • + СервериЗбиранняDockerСтруктура • - Знімки
    - [English] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe]
    - Нам потрібна ваша допомога для перекладу цього README, інтерфейсу та документації RustDesk на вашу рідну мову + Знімки екрана
    + [English] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe]
    + Нам потрібна ваша допомога для перекладу цього README, інтерфейсу та документації RustDesk вашою рідною мовою

    Спілкування з нами: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Frustdesk%2Fbounties%3Fstatus%3Dopen)](https://console.algora.io/org/rustdesk/bounties?status=open) - Ще один застосунок для віддаленого керування стільницею, написаний на Rust. Працює з коробки, не потребує налаштування. Ви повністю контролюєте свої дані, не турбуючись про безпеку. Ви можете використовувати наш сервер ретрансляції, [налаштувати свій власний](https://rustdesk.com/server), або [написати свій власний сервер ретрансляції](https://github.com/rustdesk/rustdesk-server-demo). ![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) @@ -61,19 +59,19 @@ RustDesk вітає внесок кожного. Ознайомтеся з [CONT ```sh sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ - libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev ``` ### openSUSE Tumbleweed ```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel +sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel ``` ### Fedora 28 (CentOS 8) ```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel +sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel ``` ### Arch (Manjaro) @@ -158,18 +156,22 @@ target/release/rustdesk - **[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)**: графічний інтерфейс користувача - **[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), очікування віддаленого прямого (обхід TCP NAT) або ретрансльованого з'єднання +- **[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), очікування віддаленого прямого (обхід TCP NAT) або ретрансльованого зʼєднання - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфічний для платформи код - **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: код Flutter для мобільних пристроїв -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript для Flutter веб клієнту +- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript для веб клієнта на Flutter + +## Знімки екрана + +![Менеджер зʼєднань](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) -## Знімки +![Підключення до ПК з Windows](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) -![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) +![Передача файлів](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) -![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) +![Тунелювання TCP](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) -![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) +## [Публічні сервери](#публічні-сервери) -![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) +RustDesk підтримується безкоштовним європейським сервером, любʼязно наданим [Codext GmbH](https://codext.link/rustdesk?utm_source=github) From af610b2408b7de0878c65b63622a6d695cef5e80 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 14 Oct 2024 11:15:52 +0800 Subject: [PATCH 169/210] web (#9640) resolution, image quality, tab name, last remote id Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_card.dart | 8 +++--- flutter/lib/main.dart | 4 ++- flutter/lib/mobile/pages/connection_page.dart | 8 +++++- flutter/lib/mobile/pages/home_page.dart | 2 +- flutter/lib/web/bridge.dart | 28 +++++++++---------- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 690c829775f..0a15eb45b88 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -938,7 +938,7 @@ class FavoritePeerCard extends BasePeerCard { BuildContext context) async { final List> menuItems = [ _connectAction(context), - if (!isWeb) _transferFileAction(context), + _transferFileAction(context), ]; if (isDesktop && peer.platform != kPeerPlatformAndroid) { menuItems.add(_tcpTunnelingAction(context)); @@ -991,7 +991,7 @@ class DiscoveredPeerCard extends BasePeerCard { BuildContext context) async { final List> menuItems = [ _connectAction(context), - if (!isWeb) _transferFileAction(context), + _transferFileAction(context), ]; final List favs = (await bind.mainGetFav()).toList(); @@ -1044,7 +1044,7 @@ class AddressBookPeerCard extends BasePeerCard { BuildContext context) async { final List> menuItems = [ _connectAction(context), - if (!isWeb) _transferFileAction(context), + _transferFileAction(context), ]; if (isDesktop && peer.platform != kPeerPlatformAndroid) { menuItems.add(_tcpTunnelingAction(context)); @@ -1176,7 +1176,7 @@ class MyGroupPeerCard extends BasePeerCard { BuildContext context) async { final List> menuItems = [ _connectAction(context), - if (!isWeb) _transferFileAction(context), + _transferFileAction(context), ]; if (isDesktop && peer.platform != kPeerPlatformAndroid) { menuItems.add(_tcpTunnelingAction(context)); diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 62574dfe62c..18578f81b56 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -445,7 +445,9 @@ class _AppState extends State with WidgetsBindingObserver { child: GetMaterialApp( navigatorKey: globalKey, debugShowCheckedModeBanner: false, - title: 'RustDesk', + title: isWeb + ? '${bind.mainGetAppNameSync()} Web Client V2 (Preview)' + : bind.mainGetAppNameSync(), theme: MyTheme.lightTheme, darkTheme: MyTheme.darkTheme, themeMode: MyTheme.currentThemeMode(), diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index e71cc1c5647..89b71c177c9 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -212,6 +212,8 @@ class _ConnectionPageState extends State { FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { fieldTextEditingController.text = _idController.text; + Get.put( + fieldTextEditingController); fieldFocusNode.addListener(() async { _idEmpty.value = fieldTextEditingController.text.isEmpty; @@ -352,7 +354,8 @@ class _ConnectionPageState extends State { ); final child = Column(children: [ if (isWebDesktop) - getConnectionPageTitle(context, true).marginOnly(bottom: 10, top: 15, left: 12), + getConnectionPageTitle(context, true) + .marginOnly(bottom: 10, top: 15, left: 12), w ]); return Align( @@ -367,6 +370,9 @@ class _ConnectionPageState extends State { if (Get.isRegistered()) { Get.delete(); } + if (Get.isRegistered()) { + Get.delete(); + } if (!bind.isCustomClient()) { platformFFI.unregisterEventHandler( kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish); diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index e329acdfe17..bad569afeab 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -165,7 +165,7 @@ class WebHomePage extends StatelessWidget { // backgroundColor: MyTheme.grayBg, appBar: AppBar( centerTitle: true, - title: Text(bind.mainGetAppNameSync()), + title: Text("${bind.mainGetAppNameSync()} (Preview)"), actions: connectionPage.appBarActions, ), body: connectionPage, diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 0362490cdf1..ec1fea86e78 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -284,16 +284,14 @@ class RustdeskImpl { Future sessionGetImageQuality( {required UuidValue sessionId, dynamic hint}) { - return Future(() => js.context - .callMethod('getByName', ['option:session', 'image_quality'])); + return Future(() => js.context.callMethod('getByName', ['image_quality'])); } Future sessionSetImageQuality( {required UuidValue sessionId, required String value, dynamic hint}) { - return Future(() => js.context.callMethod('setByName', [ - 'option:session', - jsonEncode({'name': 'image_quality', 'value': value}) - ])); + print('set image quality: $value'); + return Future( + () => js.context.callMethod('setByName', ['image_quality', value])); } Future sessionGetKeyboardMode( @@ -374,17 +372,15 @@ class RustdeskImpl { Future sessionSetCustomImageQuality( {required UuidValue sessionId, required int value, dynamic hint}) { return Future(() => js.context.callMethod('setByName', [ - 'option:session', - jsonEncode({'name': 'custom_image_quality', 'value': value}) + 'custom_image_quality', + value, ])); } Future sessionSetCustomFps( {required UuidValue sessionId, required int fps, dynamic hint}) { - return Future(() => js.context.callMethod('setByName', [ - 'option:session', - jsonEncode({'name': 'custom_fps', 'value': fps}) - ])); + return Future( + () => js.context.callMethod('setByName', ['custom-fps', fps])); } Future sessionLockScreen({required UuidValue sessionId, dynamic hint}) { @@ -694,7 +690,10 @@ class RustdeskImpl { required int height, dynamic hint}) { // note: restore on disconnected - throw UnimplementedError("sessionChangeResolution"); + return Future(() => js.context.callMethod('setByName', [ + 'change_resolution', + jsonEncode({'display': display, 'width': width, 'height': height}) + ])); } Future sessionSetSize( @@ -1119,8 +1118,7 @@ class RustdeskImpl { } Future mainGetLastRemoteId({dynamic hint}) { - return Future( - () => js.context.callMethod('getByName', ['option', 'last_remote_id'])); + return Future(() => mainGetLocalOption(key: 'last_remote_id')); } Future mainGetSoftwareUpdateUrl({dynamic hint}) { From 498b8ba3d616dc318e9cbed403ff722c7eac3da0 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:46:28 +0800 Subject: [PATCH 170/210] fix: wayland CapsLock (#9641) * fix: wayland CapsLock Signed-off-by: fufesou * unformat for less changes Signed-off-by: fufesou * refact: input, LockModes, do not check evt.down Remove `evt.down`(revert the change) to avoid potential bugs. Signed-off-by: fufesou --------- Signed-off-by: fufesou --- src/server/input_service.rs | 36 +++++++++++++++++++++++++++++++++--- src/server/uinput.rs | 4 ++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index fd4291cc8f6..afab184abe9 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -175,6 +175,22 @@ impl LockModesHandler { } } + #[cfg(target_os = "linux")] + fn sleep_to_ensure_locked(v: bool, k: enigo::Key, en: &mut Enigo) { + if wayland_use_uinput() { + // Sleep at most 500ms to ensure the lock state is applied. + for _ in 0..50 { + std::thread::sleep(std::time::Duration::from_millis(10)); + if en.get_key_state(k) == v { + break; + } + } + } else if wayland_use_rdp_input() { + // We can't call `en.get_key_state(k)` because there's no api for this. + std::thread::sleep(std::time::Duration::from_millis(50)); + } + } + #[cfg(any(target_os = "windows", target_os = "linux"))] fn new(key_event: &KeyEvent, is_numpad_key: bool) -> Self { let mut en = ENIGO.lock().unwrap(); @@ -183,12 +199,15 @@ impl LockModesHandler { let caps_lock_changed = event_caps_enabled != local_caps_enabled; if caps_lock_changed { en.key_click(enigo::Key::CapsLock); + #[cfg(target_os = "linux")] + Self::sleep_to_ensure_locked(event_caps_enabled, enigo::Key::CapsLock, &mut en); } let mut num_lock_changed = false; + let mut event_num_enabled = false; if is_numpad_key { let local_num_enabled = en.get_key_state(enigo::Key::NumLock); - let event_num_enabled = Self::is_modifier_enabled(key_event, ControlKey::NumLock); + event_num_enabled = Self::is_modifier_enabled(key_event, ControlKey::NumLock); num_lock_changed = event_num_enabled != local_num_enabled; } else if is_legacy_mode(key_event) { #[cfg(target_os = "windows")] @@ -199,6 +218,8 @@ impl LockModesHandler { } if num_lock_changed { en.key_click(enigo::Key::NumLock); + #[cfg(target_os = "linux")] + Self::sleep_to_ensure_locked(event_num_enabled, enigo::Key::NumLock, &mut en); } Self { @@ -236,6 +257,14 @@ impl LockModesHandler { #[cfg(any(target_os = "windows", target_os = "linux"))] impl Drop for LockModesHandler { fn drop(&mut self) { + // Do not change led state if is Wayland uinput. + // Because there must be a delay to ensure the lock state is applied on Wayland uinput, + // which may affect the user experience. + #[cfg(target_os = "linux")] + if wayland_use_uinput() { + return; + } + let mut en = ENIGO.lock().unwrap(); if self.caps_lock_changed { en.key_click(enigo::Key::CapsLock); @@ -1633,12 +1662,13 @@ pub fn handle_key_(evt: &KeyEvent) { let is_numpad_key = false; #[cfg(any(target_os = "windows", target_os = "linux"))] let is_numpad_key = crate::keyboard::is_numpad_rdev_key(&key); - _lock_mode_handler = Some(LockModesHandler::new_handler(evt, is_numpad_key)); + _lock_mode_handler = + Some(LockModesHandler::new_handler(evt, is_numpad_key)); } } } _ => {} - }; + } match evt.mode.enum_value() { Ok(KeyboardMode::Map) => { diff --git a/src/server/uinput.rs b/src/server/uinput.rs index 942f3753a76..60c647862ad 100644 --- a/src/server/uinput.rs +++ b/src/server/uinput.rs @@ -431,8 +431,8 @@ pub mod service { allow_err!(keyboard.emit(&[down_event])); } DataKeyboard::KeyUp(enigo::Key::Raw(code)) => { - let down_event = InputEvent::new(EventType::KEY, *code - 8, 0); - allow_err!(keyboard.emit(&[down_event])); + let up_event = InputEvent::new(EventType::KEY, *code - 8, 0); + allow_err!(keyboard.emit(&[up_event])); } DataKeyboard::KeyDown(key) => { if let Ok((k, is_shift)) = map_key(key) { From ce924cc0d3232c451a66bbb2fcc247b0c29cb2b2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 14 Oct 2024 15:46:21 +0800 Subject: [PATCH 171/210] combine upload files/folder button (#9643) * combine upload files/folder button Signed-off-by: 21pages * web compress cache Signed-off-by: 21pages --------- Signed-off-by: 21pages --- .gitignore | 1 + .../lib/desktop/pages/file_manager_page.dart | 99 +++++++++++-------- flutter/lib/web/bridge.dart | 14 ++- src/lang/ar.rs | 3 + src/lang/be.rs | 3 + src/lang/bg.rs | 3 + src/lang/ca.rs | 3 + src/lang/cn.rs | 3 + src/lang/cs.rs | 3 + src/lang/da.rs | 3 + src/lang/de.rs | 3 + src/lang/el.rs | 3 + src/lang/eo.rs | 3 + src/lang/es.rs | 3 + src/lang/et.rs | 3 + src/lang/eu.rs | 3 + src/lang/fa.rs | 3 + src/lang/fr.rs | 3 + src/lang/he.rs | 3 + src/lang/hr.rs | 3 + src/lang/hu.rs | 3 + src/lang/id.rs | 3 + src/lang/it.rs | 3 + src/lang/ja.rs | 3 + src/lang/ko.rs | 3 + src/lang/kz.rs | 3 + src/lang/lt.rs | 3 + src/lang/lv.rs | 3 + src/lang/nb.rs | 3 + src/lang/nl.rs | 3 + src/lang/pl.rs | 3 + src/lang/pt_PT.rs | 3 + src/lang/ptbr.rs | 3 + src/lang/ro.rs | 3 + src/lang/ru.rs | 3 + src/lang/sk.rs | 3 + src/lang/sl.rs | 3 + src/lang/sq.rs | 3 + src/lang/sr.rs | 3 + src/lang/sv.rs | 3 + src/lang/template.rs | 3 + src/lang/th.rs | 3 + src/lang/tr.rs | 3 + src/lang/tw.rs | 3 + src/lang/uk.rs | 3 + src/lang/vn.rs | 3 + 46 files changed, 201 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 30e1aafe2f1..b4ea6266046 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ examples/**/target/ vcpkg_installed flutter/lib/generated_plugin_registrant.dart libsciter.dylib +flutter/web/ \ No newline at end of file diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 5557783b5c7..ba1a37fb154 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -490,6 +490,9 @@ class _FileManagerViewState extends State { } Widget headTools() { + var uploadButtonTapPosition = RelativeRect.fill; + RxBool isUploadFolder = + (bind.mainGetLocalOption(key: 'upload-folder-button') == 'Y').obs; return Container( child: Column( children: [ @@ -814,48 +817,64 @@ class _FileManagerViewState extends State { ), if (isWeb) Obx(() => ElevatedButton.icon( - style: ButtonStyle( - padding: MaterialStateProperty.all( - isLocal - ? EdgeInsets.only(left: 10) - : EdgeInsets.only(right: 10)), - backgroundColor: MaterialStateProperty.all( - selectedItems.items.isEmpty - ? MyTheme.accent80 - : MyTheme.accent, - ), - ), - onPressed: () => {webselectFiles(is_folder: true)}, - icon: Offstage(), - label: Text( - translate('Upload folder'), - textAlign: TextAlign.right, - style: TextStyle( - color: Colors.white, - ), - ))).marginOnly(left: 16), - if (isWeb) - Obx(() => ElevatedButton.icon( - style: ButtonStyle( - padding: MaterialStateProperty.all( - isLocal - ? EdgeInsets.only(left: 10) - : EdgeInsets.only(right: 10)), - backgroundColor: MaterialStateProperty.all( - selectedItems.items.isEmpty - ? MyTheme.accent80 - : MyTheme.accent, + style: ButtonStyle( + padding: MaterialStateProperty.all( + isLocal + ? EdgeInsets.only(left: 10) + : EdgeInsets.only(right: 10)), + backgroundColor: MaterialStateProperty.all( + selectedItems.items.isEmpty + ? MyTheme.accent80 + : MyTheme.accent, + ), ), - ), - onPressed: () => {webselectFiles(is_folder: false)}, - icon: Offstage(), - label: Text( - translate('Upload files'), - textAlign: TextAlign.right, - style: TextStyle( - color: Colors.white, + onPressed: () => + {webselectFiles(is_folder: isUploadFolder.value)}, + label: InkWell( + hoverColor: Colors.transparent, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + focusColor: Colors.transparent, + onTapDown: (e) { + final x = e.globalPosition.dx; + final y = e.globalPosition.dy; + uploadButtonTapPosition = + RelativeRect.fromLTRB(x, y, x, y); + }, + onTap: () async { + final value = await showMenu( + context: context, + position: uploadButtonTapPosition, + items: [ + PopupMenuItem( + value: false, + child: Text(translate('Upload files')), + ), + PopupMenuItem( + value: true, + child: Text(translate('Upload folder')), + ), + ]); + if (value != null) { + isUploadFolder.value = value; + bind.mainSetLocalOption( + key: 'upload-folder-button', + value: value ? 'Y' : ''); + webselectFiles(is_folder: value); + } + }, + child: Icon(Icons.arrow_drop_down), ), - ))).marginOnly(left: 16), + icon: Text( + translate(isUploadFolder.isTrue + ? 'Upload folder' + : 'Upload files'), + textAlign: TextAlign.right, + style: TextStyle( + color: Colors.white, + ), + ).marginOnly(left: 8), + )).marginOnly(left: 16), Obx(() => ElevatedButton.icon( style: ButtonStyle( padding: MaterialStateProperty.all( diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index ec1fea86e78..9238d054a6d 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1209,7 +1209,12 @@ class RustdeskImpl { } Future mainLoadAb({dynamic hint}) { - return Future(() => js.context.callMethod('getByName', ['load_ab'])); + Completer completer = Completer(); + js.context["onLoadAbFinished"] = (String s) { + completer.complete(s); + }; + js.context.callMethod('setByName', ['load_ab']); + return completer.future; } Future mainSaveGroup({required String json, dynamic hint}) { @@ -1222,7 +1227,12 @@ class RustdeskImpl { } Future mainLoadGroup({dynamic hint}) { - return Future(() => js.context.callMethod('getByName', ['load_group'])); + Completer completer = Completer(); + js.context["onLoadGroupFinished"] = (String s) { + completer.complete(s); + }; + js.context.callMethod('setByName', ['load_group']); + return completer.future; } Future sessionSendPointer( diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 0990614e2f0..879414cd613 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index 2b9167a9cc2..501438fd0e4 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 7e7e7a05c21..cf972ee766c 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index cd262589da7..4001c1e6e69 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Autenticació requerida"), ("Authenticate", "Autentica"), ("web_id_input_tip", "Podeu inserir el número ID al propi servidor; l'accés directe per IP no és compatible amb el client web.\nSi voleu accedir a un dispositiu d'un altre servidor, afegiu l'adreça del servidor, com ara @?key= (p. ex.\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSi voleu accedir a un dispositiu en un servidor públic, no cal que inseriu la clau pública «@» per al servidor públic."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 7cc616e1427..17b3ba159af 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "需要身份验证"), ("Authenticate", "认证"), ("web_id_input_tip", "可以输入同一个服务器内的 ID,web 客户端不支持直接 IP 访问。\n要访问另一台服务器上的设备,请附加服务器地址(@<服务器地址>?key=<密钥>)。比如,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=。\n要访问公共服务器上的设备,请输入 \"@public\",无需密钥。"), + ("Download", "下载"), + ("Upload folder", "上传文件夹"), + ("Upload files", "上传文件"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index c93dd37b7f9..8404ee3e41d 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index f59accfe1c7..c59f3efc9c7 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 4a7bb08dbf7..11a0dbb3275 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Authentifizierung erforderlich"), ("Authenticate", "Authentifizieren"), ("web_id_input_tip", "Sie können eine ID auf demselben Server eingeben, direkter IP-Zugriff wird im Web-Client nicht unterstützt.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen wollen, fügen Sie bitte die Serveradresse (@?key=) hinzu, zum Beispiel\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nWenn Sie auf ein Gerät auf einem öffentlichen Server zugreifen wollen, geben Sie bitte \"@public\" ein. Der Schlüssel wird für öffentliche Server nicht benötigt."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 779e71d23ea..eaa31c55fad 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index ba369e4ec11..e7daf0ff795 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 369cd5b9515..bd7e4df153d 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Se requiere autenticación"), ("Authenticate", "Autenticar"), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index db7970f16e4..131e3b2156b 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 90976236e57..267431efd1d 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 71bea4e7892..c070f18e20d 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 97f8d651406..2fa41f3fe6e 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 526873de3d2..5d5e4637e81 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index e210e7566b6..378f0e90977 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index ccb2be1cb02..8aaaa3a13a7 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index efedb159778..44703afc666 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Diperlukan autentikasi"), ("Authenticate", "Autentikasi"), ("web_id_input_tip", "Kamu bisa memasukkan ID pada server yang sama, akses IP langsung tidak didukung di klien web.\nJika Anda ingin mengakses perangkat di server lain, silakan tambahkan alamat server (@?key=), contohnya:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nUntuk mengakses perangkat di server publik, cukup masukkan \"@public\", tanpa kunci/key."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 68a24265999..3aa0966d48a 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Richiesta autenticazione"), ("Authenticate", "Autentica"), ("web_id_input_tip", "È possibile inserire un ID nello stesso server, nel client web non è supportato l'accesso con IP diretto.\nSe vuoi accedere ad un dispositivo in un altro server, aggiungi l'indirizzo del server (@?key=), ad esempio,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSe vuoi accedere ad un dispositivo in un server pubblico, inserisci \"@public\", la chiave non è necessaria per il server pubblico."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index eca28da863e..41fcca10ce2 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index a3c24e98cd7..5c9660560d0 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "인증 필요함"), ("Authenticate", "인증"), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 404e9b511af..3b5aba5e4cd 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index a93cef88c3d..c6915aab317 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 0cddcb951b2..4a24a38a0cb 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Nepieciešama autentifikācija"), ("Authenticate", "Autentificēt"), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index 7f08f83d10c..a5b501aa46c 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 1b8a96e540d..6263cccb8e3 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Verificatie vereist"), ("Authenticate", "Verificatie"), ("web_id_input_tip", "Je kunt een ID invoeren op dezelfde server, directe IP-toegang wordt niet ondersteund in de webclient.\nAls je toegang wilt tot een apparaat op een andere server, voeg je het serveradres toe (@?key=), bijvoorbeeld,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \"@public\" in, sleutel is niet nodig voor de publieke server."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index a7216e78041..8479949b317 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 994d5cc63db..a30692c42e1 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index ba6fe051b81..45dc2259ba8 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 429c2534ab7..50687686544 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 3ab54b42a08..4bc1516d115 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Требуется аутентификация"), ("Authenticate", "Аутентификация"), ("web_id_input_tip", "Можно ввести ID на том же сервере, прямой доступ по IP в веб-клиенте не поддерживается.\nЕсли вы хотите получить доступ к устройству на другом сервере, добавьте адрес сервера (@<адрес_сервера>?key=<ключ>), например,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли вы хотите получить доступ к устройству на публичном сервере, введите \"@public\", для публичного сервера ключ не нужен."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 1e5a51f9f55..dabec3ed3de 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 7d18ef82982..f834687c78f 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Potrebno je preverjanje pristnosti"), ("Authenticate", "Preverjanje pristnosti"), ("web_id_input_tip", "Vnesete lahko ID iz istega strežnika, neposredni dostop preko IP naslova v spletnem odjemalcu ni podprt.\nČe želite dostopati do naprave na drugem strežniku, pripnite naslov strežnika (@?key=), npr. 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nČe želite dostopati do naprave na javnem strežniku, vnesite »@public«; ključ za javni strežnik ni potreben."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index b3e85a351e1..acc5a627f89 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index ee84a68e1b8..0ade32f819d 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 9ba9bb5362a..5dd93ea4ea2 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index e6211984348..eb62d5621cf 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index a2f8bc9103c..deb7f974191 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 78b09dca47e..656feb0ef07 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 5bb26acce15..776151ae7c4 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "需要身分驗證"), ("Authenticate", "認證"), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index ac38e3479a5..35ffa1c290d 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Потрібна автентифікація"), ("Authenticate", "Автентифікувати"), ("web_id_input_tip", "Ви можете ввести ID з того самого серверу, прямий IP-доступ у веб-клієнті не підтримується.\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"@public\", для публічного сервера ключ не потрібен."), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index bc6311fcd9c..d07225ba0a4 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -648,5 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", ""), ("Authenticate", ""), ("web_id_input_tip", ""), + ("Download", ""), + ("Upload folder", ""), + ("Upload files", ""), ].iter().cloned().collect(); } From cdd58e77ebbf0718b0cdbef8e5314b7cde0bdc4a Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:48:56 +0800 Subject: [PATCH 172/210] fix: flickers child screen when resizing window (#9645) Signed-off-by: fufesou --- flutter/lib/utils/multi_window_manager.dart | 3 +++ flutter/pubspec.lock | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 191152c8625..fa35b4fe971 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -124,6 +124,9 @@ class RustDeskMultiWindowManager { bool withScreenRect, ) async { final windowController = await DesktopMultiWindow.createWindow(msg); + if (isWindows) { + windowController.setInitBackgroundColor(Colors.black); + } final windowId = windowController.windowId; if (!withScreenRect) { windowController diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 6210b5399c8..6551bbb37bf 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -335,7 +335,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "0842f44d8644911f65a6b78be22474af0f8a9349" + resolved-ref: "519350f1f40746798299e94786197d058353bac9" url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window" source: git version: "0.1.0" From ae1c1a56e6292558a3de4524fe974073ddd8547d Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 14 Oct 2024 15:51:51 +0800 Subject: [PATCH 173/210] add missing code of last commit (#9646) Signed-off-by: 21pages --- flutter/lib/web/bridge.dart | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 9238d054a6d..d38f0f9cf39 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1210,11 +1210,18 @@ class RustdeskImpl { Future mainLoadAb({dynamic hint}) { Completer completer = Completer(); + Future timeoutFuture = completer.future.timeout( + Duration(seconds: 2), + onTimeout: () { + completer.completeError(TimeoutException('Load ab timed out')); + return 'Timeout'; + }, + ); js.context["onLoadAbFinished"] = (String s) { completer.complete(s); }; js.context.callMethod('setByName', ['load_ab']); - return completer.future; + return timeoutFuture; } Future mainSaveGroup({required String json, dynamic hint}) { @@ -1228,11 +1235,18 @@ class RustdeskImpl { Future mainLoadGroup({dynamic hint}) { Completer completer = Completer(); + Future timeoutFuture = completer.future.timeout( + Duration(seconds: 2), + onTimeout: () { + completer.completeError(TimeoutException('Load group timed out')); + return 'Timeout'; + }, + ); js.context["onLoadGroupFinished"] = (String s) { completer.complete(s); }; js.context.callMethod('setByName', ['load_group']); - return completer.future; + return timeoutFuture; } Future sessionSendPointer( From 55187e92438af316732818998ff97e5aaddd72f0 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 14 Oct 2024 17:46:38 +0800 Subject: [PATCH 174/210] fix theme radio update (#9647) Signed-off-by: 21pages --- flutter/lib/common.dart | 2 +- flutter/lib/desktop/pages/desktop_setting_page.dart | 4 ++-- flutter/lib/main.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 2bbb224cbb1..86f532d2abe 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -555,7 +555,7 @@ class MyTheme { return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme)); } - static void changeDarkMode(ThemeMode mode) async { + static Future changeDarkMode(ThemeMode mode) async { Get.changeThemeMode(mode); if (desktopType == DesktopType.main || isAndroid || isIOS || isWeb) { if (mode == ThemeMode.system) { diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index ebe55ca191b..ada38ecf025 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -369,8 +369,8 @@ class _GeneralState extends State<_General> { Widget theme() { final current = MyTheme.getThemeModePreference().toShortString(); - onChanged(String value) { - MyTheme.changeDarkMode(MyTheme.themeModeFromString(value)); + onChanged(String value) async { + await MyTheme.changeDarkMode(MyTheme.themeModeFromString(value)); setState(() {}); } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 18578f81b56..16324c0e5c5 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -507,7 +507,7 @@ _registerEventHandler() { platformFFI.registerEventHandler('theme', 'theme', (evt) async { String? dark = evt['dark']; if (dark != null) { - MyTheme.changeDarkMode(MyTheme.themeModeFromString(dark)); + await MyTheme.changeDarkMode(MyTheme.themeModeFromString(dark)); } }); platformFFI.registerEventHandler('language', 'language', (_) async { From 36e11c61a99d11642de90d437ffaa1f744626ad5 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:26:55 +0200 Subject: [PATCH 175/210] Update Italian language (#9650) --- src/lang/it.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 3aa0966d48a..b791276b940 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -648,8 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Richiesta autenticazione"), ("Authenticate", "Autentica"), ("web_id_input_tip", "È possibile inserire un ID nello stesso server, nel client web non è supportato l'accesso con IP diretto.\nSe vuoi accedere ad un dispositivo in un altro server, aggiungi l'indirizzo del server (@?key=), ad esempio,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSe vuoi accedere ad un dispositivo in un server pubblico, inserisci \"@public\", la chiave non è necessaria per il server pubblico."), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), + ("Download", "Download"), + ("Upload folder", "Cartella upload"), + ("Upload files", "File upload"), ].iter().cloned().collect(); } From 09083b3afaf2ebe57076bde0ec69c853a1b95013 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Wed, 16 Oct 2024 05:23:12 +0200 Subject: [PATCH 176/210] Update de.rs (#9668) --- src/lang/de.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 11a0dbb3275..f54cddac96d 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -648,8 +648,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Authentifizierung erforderlich"), ("Authenticate", "Authentifizieren"), ("web_id_input_tip", "Sie können eine ID auf demselben Server eingeben, direkter IP-Zugriff wird im Web-Client nicht unterstützt.\nWenn Sie auf ein Gerät auf einem anderen Server zugreifen wollen, fügen Sie bitte die Serveradresse (@?key=) hinzu, zum Beispiel\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nWenn Sie auf ein Gerät auf einem öffentlichen Server zugreifen wollen, geben Sie bitte \"@public\" ein. Der Schlüssel wird für öffentliche Server nicht benötigt."), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), + ("Download", "Herunterladen"), + ("Upload folder", "Ordner hochladen"), + ("Upload files", "Dateien hochladen"), ].iter().cloned().collect(); } From ace98d98ad4089deda11135e0ce3001178cd6385 Mon Sep 17 00:00:00 2001 From: shleyZ Date: Wed, 16 Oct 2024 19:25:27 +0800 Subject: [PATCH 177/210] fix: TextFormField onChanged event triggered multiple times when Korean input (#9644) * fix: TextFormField onChanged event triggered multiple times when Korean input * logic fix for iOS * add comments * move 'onChanged' logic to handleSoftKeyboardInput --- flutter/lib/mobile/pages/remote_page.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 19f7cc57530..e8a88869601 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -242,12 +242,16 @@ class _RemotePageState extends State { } } - // handle mobile virtual keyboard - void handleSoftKeyboardInput(String newValue) { + async void handleSoftKeyboardInput(String newValue) { if (isIOS) { - _handleIOSSoftKeyboardInput(newValue); + // fix: TextFormField onChanged event triggered multiple times when Korean input + // https://github.com/rustdesk/rustdesk/pull/9644 + await Future.delayed(const Duration(milliseconds: 10)); + + if (newValue != _textController.text) return; + return _handleIOSSoftKeyboardInput(_textController.text); } else { - _handleNonIOSSoftKeyboardInput(newValue); + return _handleNonIOSSoftKeyboardInput(newValue); } } From 1a0814b201485b23402c3687c0ead6e4f312f838 Mon Sep 17 00:00:00 2001 From: XLion Date: Wed, 16 Oct 2024 19:33:27 +0800 Subject: [PATCH 178/210] Update tw.rs (#9672) --- src/lang/tw.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 776151ae7c4..1f96700b03d 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Plug out all", "拔出所有"), ("True color (4:4:4)", "全彩模式(4:4:4)"), ("Enable blocking user input", "允許封鎖使用者輸入"), - ("id_input_tip", "您可以輸入 ID、IP、或網域名稱+通訊埠號(<網域名稱>:<通訊埠號>)。\n如果您要存取位於其他伺服器上的設備,請在ID之後添加伺服器地址(@<伺服器地址>?key=<金鑰>)\n例如:9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\n要存取公共伺服器上的設備,請輸入\"@public\",不需輸入金鑰。\n\n如果您想要在第一次連線時,強制使用中繼連接,請在 ID 的末尾添加 \"/r\",例如,\"9123456234/r\"。"), + ("id_input_tip", "您可以輸入 ID、IP、或網域名稱+通訊埠號(<網域名稱>:<通訊埠號>)。\n如果您要存取位於其他伺服器上的設備,請在 ID 之後添加伺服器地址(@<伺服器地址>?key=<金鑰>)\n例如:9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\n要存取公共伺服器上的設備,請輸入\"@public\",不需輸入金鑰。\n\n如果您想要在第一次連線時,強制使用中繼連接,請在 ID 的末尾添加 \"/r\",例如,\"9123456234/r\"。"), ("privacy_mode_impl_mag_tip", "模式 1"), ("privacy_mode_impl_virtual_display_tip", "模式 2"), ("Enter privacy mode", "進入隱私模式"), @@ -647,9 +647,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "被控端啟用了單向文件傳輸"), ("Authentication Required", "需要身分驗證"), ("Authenticate", "認證"), - ("web_id_input_tip", ""), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), + ("web_id_input_tip", "您可以輸入同一個伺服器內的 ID,Web 客戶端不支援直接 IP 存取。\n如果您要存取位於其他伺服器上的設備,請在 ID 之後添加伺服器地址(@<伺服器地址>?key=<金鑰>)\n例如:9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\n要存取公共伺服器上的設備,請輸入\"@public\",不需輸入金鑰。"), + ("Download", "下載"), + ("Upload folder", "上傳資料夾"), + ("Upload files", "上傳檔案"), ].iter().cloned().collect(); } From 5e920f0fd048e7e49046a17f833657a26d4c001a Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 16 Oct 2024 22:37:03 +0800 Subject: [PATCH 179/210] fix ci --- flutter/lib/mobile/pages/remote_page.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index e8a88869601..0072a2608db 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -242,16 +242,16 @@ class _RemotePageState extends State { } } - async void handleSoftKeyboardInput(String newValue) { + Future handleSoftKeyboardInput(String newValue) async { if (isIOS) { // fix: TextFormField onChanged event triggered multiple times when Korean input // https://github.com/rustdesk/rustdesk/pull/9644 await Future.delayed(const Duration(milliseconds: 10)); if (newValue != _textController.text) return; - return _handleIOSSoftKeyboardInput(_textController.text); + _handleIOSSoftKeyboardInput(_textController.text); } else { - return _handleNonIOSSoftKeyboardInput(newValue); + _handleNonIOSSoftKeyboardInput(newValue); } } From ae8dfe84a078ba043386dbb06ccf6f2b6c9c65f3 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:23:09 +0800 Subject: [PATCH 180/210] feat, web toast (#9686) Signed-off-by: fufesou --- flutter/lib/models/model.dart | 34 ++++++++++++++++++++++++++++++++++ src/lang/ar.rs | 1 + src/lang/be.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/eu.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/uk.rs | 1 + src/lang/vn.rs | 1 + 44 files changed, 77 insertions(+) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 2833f38349e..71effd797e2 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:bot_toast/bot_toast.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -269,6 +270,8 @@ class FfiModel with ChangeNotifier { var name = evt['name']; if (name == 'msgbox') { handleMsgBox(evt, sessionId, peerId); + } else if (name == 'toast') { + handleToast(evt, sessionId, peerId); } else if (name == 'set_multiple_windows_session') { handleMultipleWindowsSession(evt, sessionId, peerId); } else if (name == 'peer_info') { @@ -595,6 +598,37 @@ class FfiModel with ChangeNotifier { } } + handleToast(Map evt, SessionID sessionId, String peerId) { + final type = evt['type'] ?? 'info'; + final text = evt['text'] ?? ''; + final durMsc = evt['dur_msec'] ?? 2000; + final duration = Duration(milliseconds: durMsc); + if ((text).isEmpty) { + BotToast.showLoading( + duration: duration, + clickClose: true, + allowClick: true, + ); + } else { + if (type.contains('error')) { + BotToast.showText( + contentColor: Colors.red, + text: translate(text), + duration: duration, + clickClose: true, + onlyOne: true, + ); + } else { + BotToast.showText( + text: translate(text), + duration: duration, + clickClose: true, + onlyOne: true, + ); + } + } + } + /// Show a message box with [type], [title] and [text]. showMsgBox(SessionID sessionId, String type, String title, String text, String link, bool hasRetry, OverlayDialogManager dialogManager, diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 879414cd613..6cae020b6e4 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index 501438fd0e4..7e9f928ae57 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index cf972ee766c..9f6282e7523 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 4001c1e6e69..64748c9ac09 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 17b3ba159af..5ad91ada84f 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", "下载"), ("Upload folder", "上传文件夹"), ("Upload files", "上传文件"), + ("Clipboard is synchronized", "剪贴板已同步"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 8404ee3e41d..8db1470b98a 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index c59f3efc9c7..f867cac467a 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index f54cddac96d..73e57183d0d 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", "Herunterladen"), ("Upload folder", "Ordner hochladen"), ("Upload files", "Dateien hochladen"), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index eaa31c55fad..625867c343e 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index e7daf0ff795..217b37a6a5e 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index bd7e4df153d..70bd9d9e52f 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 131e3b2156b..8187759d575 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 267431efd1d..f8ba679a756 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index c070f18e20d..cf5ce0f0f70 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 2fa41f3fe6e..0fd093688b1 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 5d5e4637e81..e36252afe90 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index 378f0e90977..e2480eb6350 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 8aaaa3a13a7..00c56edfbca 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 44703afc666..2f95411f082 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index b791276b940..8fff93a6edf 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", "Download"), ("Upload folder", "Cartella upload"), ("Upload files", "File upload"), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 41fcca10ce2..dbc40b78979 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 5c9660560d0..966ac90097e 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 3b5aba5e4cd..d62bbc393bf 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index c6915aab317..bc7d856b1e8 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 4a24a38a0cb..fa266ac2e31 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index a5b501aa46c..d91eb93258d 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 6263cccb8e3..b780ce7daf7 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 8479949b317..8e998908449 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index a30692c42e1..4c01d0b628a 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 45dc2259ba8..240fae99ae4 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 50687686544..26858b13436 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 4bc1516d115..d011eae6006 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index dabec3ed3de..28e322460df 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index f834687c78f..e7f6248160b 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index acc5a627f89..58dc1ed5502 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 0ade32f819d..d38d20e9ef3 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 5dd93ea4ea2..cfbac29b903 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index eb62d5621cf..1c4606e9479 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index deb7f974191..2dbb564e870 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 656feb0ef07..9bdbec110c6 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 1f96700b03d..b37949206ac 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", "下載"), ("Upload folder", "上傳資料夾"), ("Upload files", "上傳檔案"), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 35ffa1c290d..2bce8cc8173 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index d07225ba0a4..9693d35c3c8 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -651,5 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", ""), ("Upload folder", ""), ("Upload files", ""), + ("Clipboard is synchronized", ""), ].iter().cloned().collect(); } From defb3e6c73c4fc74bf315cc238bcb5339d24e887 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 17 Oct 2024 20:05:13 +0800 Subject: [PATCH 181/210] fix gtk-sudo in non-English linux (#9680) change LC_ALL from C.UTF-8 to C Signed-off-by: 21pages --- src/platform/gtk_sudo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/gtk_sudo.rs b/src/platform/gtk_sudo.rs index 0105b335c31..9aeea1e2b01 100644 --- a/src/platform/gtk_sudo.rs +++ b/src/platform/gtk_sudo.rs @@ -505,7 +505,7 @@ fn child(su_user: Option, args: Vec) -> ResultType<()> { command = format!("'{}'", quote_shell_arg(&command, false)); } params.push(command); - std::env::set_var("LC_ALL", "C.UTF-8"); + std::env::set_var("LC_ALL", "C"); if let Some(user) = &su_user { let su_subcommand = params From 53d11e99d73eaf48fa8bf1a2c47796485c57c532 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 18 Oct 2024 08:45:16 +0800 Subject: [PATCH 182/210] web only decode the latest image (#9689) 1. web only decode the latest image 2. web/ios remove relay server config when import Signed-off-by: 21pages --- flutter/lib/common.dart | 3 +++ flutter/lib/models/model.dart | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 86f532d2abe..8bf8cb05671 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -3177,6 +3177,9 @@ importConfig(List? controllers, List? errMsgs, if (text != null && text.isNotEmpty) { try { final sc = ServerConfig.decode(text); + if (isWeb || isIOS) { + sc.relayServer = ''; + } if (sc.idServer.isNotEmpty) { Future success = setServerConfig(controllers, errMsgs, sc); success.then((value) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 71effd797e2..d836f62a6e8 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -1225,6 +1225,27 @@ class ImageModel with ChangeNotifier { clearImage() => _image = null; + bool _webDecodingRgba = false; + final List _webRgbaList = List.empty(growable: true); + webOnRgba(int display, Uint8List rgba) async { + // deep copy needed, otherwise "instantiateCodec failed: TypeError: Cannot perform Construct on a detached ArrayBuffer" + _webRgbaList.add(Uint8List.fromList(rgba)); + if (_webDecodingRgba) { + return; + } + _webDecodingRgba = true; + try { + while (_webRgbaList.isNotEmpty) { + final rgba2 = _webRgbaList.last; + _webRgbaList.clear(); + await decodeAndUpdate(display, rgba2); + } + } catch (e) { + debugPrint('onRgba error: $e'); + } + _webDecodingRgba = false; + } + onRgba(int display, Uint8List rgba) async { try { await decodeAndUpdate(display, rgba); From 0d3243e6dd07aca3e770b78cb1cbc90ae6eda3ab Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 18 Oct 2024 08:55:18 +0800 Subject: [PATCH 183/210] fix: android, Korean input (#9667) Signed-off-by: fufesou --- flutter/lib/mobile/pages/remote_page.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 0072a2608db..395ce333365 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -57,6 +57,9 @@ class _RemotePageState extends State { final TextEditingController _textController = TextEditingController(text: initText); + // This timer is used to check the composing status of the soft keyboard. + // It is used for Android, Korean(and other similar) input method. + Timer? _composingTimer; _RemotePageState(String id) { initSharedStates(id); @@ -104,6 +107,7 @@ class _RemotePageState extends State { _physicalFocusNode.dispose(); await gFFI.close(); _timer?.cancel(); + _composingTimer?.cancel(); gFFI.dialogManager.dismissAll(); await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); @@ -139,6 +143,7 @@ class _RemotePageState extends State { gFFI.ffiModel.pi.version.isNotEmpty) { gFFI.invokeMethod("enable_soft_keyboard", false); } + _composingTimer?.cancel(); } else { _timer?.cancel(); _timer = Timer(kMobileDelaySoftKeyboardFocus, () { @@ -202,6 +207,13 @@ class _RemotePageState extends State { } void _handleNonIOSSoftKeyboardInput(String newValue) { + _composingTimer?.cancel(); + if (_textController.value.isComposingRangeValid) { + _composingTimer = Timer(Duration(milliseconds: 25), () { + _handleNonIOSSoftKeyboardInput(_textController.value.text); + }); + return; + } var oldValue = _value; _value = newValue; if (oldValue.isNotEmpty && From 0f6d28def724f0392974f5b3bba2f9d4735692c3 Mon Sep 17 00:00:00 2001 From: flusheDData <116861809+flusheDData@users.noreply.github.com> Date: Fri, 18 Oct 2024 02:55:39 +0200 Subject: [PATCH 184/210] Update es.rs (#9687) New terms added --- src/lang/es.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index 70bd9d9e52f..25cd7bf283c 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -647,10 +647,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("one-way-file-transfer-tip", "La transferencia en un sentido está habilitada en el lado controlado."), ("Authentication Required", "Se requiere autenticación"), ("Authenticate", "Autenticar"), - ("web_id_input_tip", ""), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), - ("Clipboard is synchronized", ""), + ("web_id_input_tip", "Puedes introducir una ID en el mismo servidor, el cliente web no soporta acceso vía IP.\nSi quieres acceder a un dispositivo en otro servidor, por favor, agrega la dirección del servidor (@?clave=), por ejemplo,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSi quieres accedder a un dispositivo en un servidor público, por favor, introduce \"@public\", la clave no es necesaria para el servidor público."), + ("Download", "Descarga"), + ("Upload folder", "Subir carpeta"), + ("Upload files", "Subir archivos"), + ("Clipboard is synchronized", "Portapapeles sincronizado"), ].iter().cloned().collect(); } From 844caf8c15c3fcf5de4ab6cb38653580a12c0244 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:07:49 +0200 Subject: [PATCH 185/210] Update Italian language (#9692) --- src/lang/it.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 8fff93a6edf..baaa1d10d31 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -651,6 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", "Download"), ("Upload folder", "Cartella upload"), ("Upload files", "File upload"), - ("Clipboard is synchronized", ""), + ("Clipboard is synchronized", "Gli appunti sono sincronizzati"), ].iter().cloned().collect(); } From 675ffe038164c802233428736af013a1e0e1e844 Mon Sep 17 00:00:00 2001 From: Kleofass <4000163+Kleofass@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:51:17 +0300 Subject: [PATCH 186/210] Update lv.rs (#9694) --- src/lang/lv.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/lv.rs b/src/lang/lv.rs index fa266ac2e31..bf67345c3a4 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -647,10 +647,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("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"), - ("web_id_input_tip", ""), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), - ("Clipboard is synchronized", ""), + ("web_id_input_tip", "Varat ievadīt ID tajā pašā serverī, tīmekļa klientā tiešā IP piekļuve netiek atbalstīta.\nJa vēlaties piekļūt ierīcei citā serverī, lūdzu, pievienojiet servera adresi (@?key=), piemēram,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJa vēlaties piekļūt ierīcei publiskajā serverī, lūdzu, ievadiet \"@public\", publiskajam serverim atslēga nav nepieciešama."), + ("Download", "Lejupielādēt"), + ("Upload folder", "Augšupielādēt mapi"), + ("Upload files", "Augšupielādēt failus"), + ("Clipboard is synchronized", "Starpliktuve ir sinhronizēta"), ].iter().cloned().collect(); } From 8c8a643ccec7c37cce42f2daae7e63caa444bf85 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sat, 19 Oct 2024 10:57:42 +0800 Subject: [PATCH 187/210] fix: workaround physical display rotation (#9696) Signed-off-by: fufesou --- libs/scrap/src/common/mod.rs | 8 ++++---- libs/scrap/src/common/vram.rs | 7 ++++++- libs/scrap/src/dxgi/mod.rs | 12 +++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index d6060e1315f..635f0ec26d4 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -156,7 +156,7 @@ pub trait TraitPixelBuffer { #[cfg(not(any(target_os = "ios")))] pub enum Frame<'a> { PixelBuffer(PixelBuffer<'a>), - Texture(*mut c_void), + Texture((*mut c_void, usize)), } #[cfg(not(any(target_os = "ios")))] @@ -164,7 +164,7 @@ impl Frame<'_> { pub fn valid<'a>(&'a self) -> bool { match self { Frame::PixelBuffer(pixelbuffer) => !pixelbuffer.data().is_empty(), - Frame::Texture(texture) => !texture.is_null(), + Frame::Texture((texture, _)) => !texture.is_null(), } } @@ -186,7 +186,7 @@ impl Frame<'_> { pub enum EncodeInput<'a> { YUV(&'a [u8]), - Texture(*mut c_void), + Texture((*mut c_void, usize)), } impl<'a> EncodeInput<'a> { @@ -197,7 +197,7 @@ impl<'a> EncodeInput<'a> { } } - pub fn texture(&self) -> ResultType<*mut c_void> { + pub fn texture(&self) -> ResultType<(*mut c_void, usize)> { match self { Self::Texture(f) => Ok(*f), _ => bail!("not texture frame"), diff --git a/libs/scrap/src/common/vram.rs b/libs/scrap/src/common/vram.rs index a2b4d348c46..aae961df614 100644 --- a/libs/scrap/src/common/vram.rs +++ b/libs/scrap/src/common/vram.rs @@ -101,7 +101,12 @@ impl EncoderApi for VRamEncoder { frame: EncodeInput, ms: i64, ) -> ResultType { - let texture = frame.texture()?; + let (texture, rotation) = frame.texture()?; + if rotation != 0 { + // to-do: support rotation + // Both the encoder and display(w,h) information need to be changed. + bail!("rotation not supported"); + } let mut vf = VideoFrame::new(); let mut frames = Vec::new(); for frame in self diff --git a/libs/scrap/src/dxgi/mod.rs b/libs/scrap/src/dxgi/mod.rs index abd1f502699..33a60e7d991 100644 --- a/libs/scrap/src/dxgi/mod.rs +++ b/libs/scrap/src/dxgi/mod.rs @@ -253,7 +253,17 @@ impl Capturer { pub fn frame<'a>(&'a mut self, timeout: UINT) -> io::Result> { if self.output_texture { - Ok(Frame::Texture(self.get_texture(timeout)?)) + let rotation = match self.display.rotation() { + DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED => 0, + DXGI_MODE_ROTATION_ROTATE90 => 90, + DXGI_MODE_ROTATION_ROTATE180 => 180, + DXGI_MODE_ROTATION_ROTATE270 => 270, + _ => { + // Unsupported rotation, try anyway + 0 + } + }; + Ok(Frame::Texture((self.get_texture(timeout)?, rotation))) } else { let width = self.width; let height = self.height; From 1212d9fa2df6b0f5d2e1970e1a0e4743dc95b4a7 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 19 Oct 2024 15:32:17 +0800 Subject: [PATCH 188/210] web uni link (#9697) Signed-off-by: 21pages --- flutter/lib/common.dart | 11 ++++- flutter/lib/main.dart | 2 +- flutter/lib/mobile/pages/home_page.dart | 60 +++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 8bf8cb05671..1d59d0202d8 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2038,6 +2038,8 @@ Future restoreWindowPosition(WindowType type, return false; } +var webInitialLink = ""; + /// Initialize uni links for macos/windows /// /// [Availability] @@ -2054,7 +2056,12 @@ Future initUniLinks() async { if (initialLink == null || initialLink.isEmpty) { return false; } - return handleUriLink(uriString: initialLink); + if (isWeb) { + webInitialLink = initialLink; + return false; + } else { + return handleUriLink(uriString: initialLink); + } } catch (err) { debugPrintStack(label: "$err"); return false; @@ -2067,7 +2074,7 @@ Future initUniLinks() async { /// /// Returns a [StreamSubscription] which can listen the uni links. StreamSubscription? listenUniLinks({handleByFlutter = true}) { - if (isLinux) { + if (isLinux || isWeb) { return null; } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 16324c0e5c5..00afbb001e7 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -162,7 +162,7 @@ void runMobileApp() async { await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]); gFFI.userModel.refreshCurrentUser(); runApp(App()); - if (!isWeb) await initUniLinks(); + await initUniLinks(); } void runMultiWindow( diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index bad569afeab..efccc5de65e 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -161,6 +161,7 @@ class WebHomePage extends StatelessWidget { @override Widget build(BuildContext context) { stateGlobal.isInMainPage = true; + handleUnilink(context); return Scaffold( // backgroundColor: MyTheme.grayBg, appBar: AppBar( @@ -171,4 +172,63 @@ class WebHomePage extends StatelessWidget { body: connectionPage, ); } + + handleUnilink(BuildContext context) { + if (webInitialLink.isEmpty) { + return; + } + final link = webInitialLink; + webInitialLink = ''; + final splitter = ["/#/", "/#", "#/", "#"]; + var fakelink = ''; + for (var s in splitter) { + if (link.contains(s)) { + var list = link.split(s); + if (list.length < 2 || list[1].isEmpty) { + return; + } + list.removeAt(0); + fakelink = "rustdesk://${list.join(s)}"; + break; + } + } + if (fakelink.isEmpty) { + return; + } + final uri = Uri.tryParse(fakelink); + if (uri == null) { + return; + } + final args = urlLinkToCmdArgs(uri); + if (args == null || args.isEmpty) { + return; + } + bool isFileTransfer = false; + String? id; + String? password; + for (int i = 0; i < args.length; i++) { + switch (args[i]) { + case '--connect': + case '--play': + isFileTransfer = false; + id = args[i + 1]; + i++; + break; + case '--file-transfer': + isFileTransfer = true; + id = args[i + 1]; + i++; + break; + case '--password': + password = args[i + 1]; + i++; + break; + default: + break; + } + } + if (id != null) { + connect(context, id, isFileTransfer: isFileTransfer, password: password); + } + } } From 1bf4ef1f46ea49b6941d3334cc5fe0a2a6ed1552 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:41:14 +0200 Subject: [PATCH 189/210] Update de.rs (#9699) --- src/lang/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 73e57183d0d..4213c86de74 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -651,6 +651,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Download", "Herunterladen"), ("Upload folder", "Ordner hochladen"), ("Upload files", "Dateien hochladen"), - ("Clipboard is synchronized", ""), + ("Clipboard is synchronized", "Zwischenablage ist synchronisiert"), ].iter().cloned().collect(); } From 547da310957f597ae6d0608cc5f329b9f9ec6a77 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:41:55 +0800 Subject: [PATCH 190/210] fix: windows, exit fullscreen, remove redraw (#9700) Signed-off-by: fufesou --- flutter/lib/models/state_model.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index 3481aa2b3e1..f8f06cc3fb8 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -101,15 +101,8 @@ class StateGlobal { if (procWnd) { final wc = WindowController.fromWindowId(windowId); wc.setFullscreen(_fullscreen.isTrue).then((_) { - // https://github.com/leanflutter/window_manager/issues/131#issuecomment-1111587982 - if (isWindows && _fullscreen.isFalse) { - Future.delayed(Duration.zero, () async { - final frame = await wc.getFrame(); - final newRect = Rect.fromLTWH( - frame.left, frame.top, frame.width + 1, frame.height + 1); - await wc.setFrame(newRect); - }); - } + // We remove the redraw (width + 1, height + 1), because this issue cannot be reproduced. + // https://github.com/rustdesk/rustdesk/issues/9675 }); } } From 289076aa705870f3849dd5c4f571b2a7d14512bd Mon Sep 17 00:00:00 2001 From: solokot Date: Mon, 21 Oct 2024 09:12:58 +0300 Subject: [PATCH 191/210] Update ru.rs (#9712) --- src/lang/ru.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index d011eae6006..00ae750f27d 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -147,7 +147,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("OS Password", "Пароль входа в ОС"), ("install_tip", "В некоторых случаях из-за UAC RustDesk может работать неправильно на удалённом узле. Чтобы избежать возможных проблем с UAC, нажмите кнопку ниже для установки RustDesk в системе."), ("Click to upgrade", "Нажмите, чтобы обновить"), - ("Click to download", "Нажмите, чтобы загрузить"), + ("Click to download", "Нажмите, чтобы скачать"), ("Click to update", "Нажмите, чтобы обновить"), ("Configure", "Настроить"), ("config_acc", "Чтобы удалённо управлять своим рабочим столом, вы должны предоставить RustDesk права \"доступа\""), @@ -648,9 +648,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Требуется аутентификация"), ("Authenticate", "Аутентификация"), ("web_id_input_tip", "Можно ввести ID на том же сервере, прямой доступ по IP в веб-клиенте не поддерживается.\nЕсли вы хотите получить доступ к устройству на другом сервере, добавьте адрес сервера (@<адрес_сервера>?key=<ключ>), например,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли вы хотите получить доступ к устройству на публичном сервере, введите \"@public\", для публичного сервера ключ не нужен."), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), - ("Clipboard is synchronized", ""), + ("Download", "Скачать"), + ("Upload folder", "Загрузить папку"), + ("Upload files", "Загрузить файлы"), + ("Clipboard is synchronized", "Буфер обмена синхронизирован"), ].iter().cloned().collect(); } From e8187588c14150d1b8102650ca33d2052a1c434b Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 21 Oct 2024 14:34:06 +0800 Subject: [PATCH 192/210] auto record outgoing (#9711) * Add option auto record outgoing session * In the same connection, all displays and all windows share the same recording state. todo: Android check external storage permission Known issue: * Sciter old issue, stop the process directly without stop record, the record file can't play. Signed-off-by: 21pages --- Cargo.lock | 2 +- flutter/lib/consts.dart | 1 + .../desktop/pages/desktop_setting_page.dart | 93 +++---- flutter/lib/desktop/pages/remote_page.dart | 3 +- .../lib/desktop/widgets/remote_toolbar.dart | 3 +- flutter/lib/mobile/pages/remote_page.dart | 9 +- flutter/lib/mobile/pages/settings_page.dart | 73 ++++-- flutter/lib/models/model.dart | 73 +----- libs/hbb_common/src/config.rs | 6 + libs/scrap/Cargo.toml | 1 - libs/scrap/src/common/codec.rs | 12 +- libs/scrap/src/common/mod.rs | 29 +++ libs/scrap/src/common/record.rs | 234 ++++++++++-------- src/client.rs | 72 +++--- src/client/io_loop.rs | 8 +- src/flutter.rs | 7 +- src/flutter_ffi.rs | 16 +- src/lang/ar.rs | 1 + src/lang/be.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 3 +- src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/eu.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/uk.rs | 1 + src/lang/vn.rs | 1 + src/server/video_service.rs | 24 +- src/ui/header.tis | 22 +- src/ui/index.tis | 3 + src/ui/remote.rs | 8 +- src/ui_session_interface.rs | 20 +- 65 files changed, 442 insertions(+), 322 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1474f2f3a5..923325c4ea7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3051,7 +3051,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hwcodec" version = "0.7.0" -source = "git+https://github.com/rustdesk-org/hwcodec#f74410edec91435252b8394c38f8eeca87ad2a26" +source = "git+https://github.com/rustdesk-org/hwcodec#8bbd05bb300ad07cc345356ad85570f9ea99fbfa" dependencies = [ "bindgen 0.59.2", "cc", diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 1be9c7712c7..89306bb7ae8 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -89,6 +89,7 @@ const String kOptionAllowAutoDisconnect = "allow-auto-disconnect"; const String kOptionAutoDisconnectTimeout = "auto-disconnect-timeout"; const String kOptionEnableHwcodec = "enable-hwcodec"; const String kOptionAllowAutoRecordIncoming = "allow-auto-record-incoming"; +const String kOptionAllowAutoRecordOutgoing = "allow-auto-record-outgoing"; const String kOptionVideoSaveDirectory = "video-save-directory"; const String kOptionAccessMode = "access-mode"; const String kOptionEnableKeyboard = "enable-keyboard"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index ada38ecf025..766c160e042 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -575,12 +575,17 @@ class _GeneralState extends State<_General> { bool root_dir_exists = map['root_dir_exists']!; bool user_dir_exists = map['user_dir_exists']!; return _Card(title: 'Recording', children: [ - _OptionCheckBox(context, 'Automatically record incoming sessions', - kOptionAllowAutoRecordIncoming), - if (showRootDir) + if (!bind.isOutgoingOnly()) + _OptionCheckBox(context, 'Automatically record incoming sessions', + kOptionAllowAutoRecordIncoming), + if (!bind.isIncomingOnly()) + _OptionCheckBox(context, 'Automatically record outgoing sessions', + kOptionAllowAutoRecordOutgoing), + if (showRootDir && !bind.isOutgoingOnly()) Row( children: [ - Text('${translate("Incoming")}:'), + Text( + '${translate(bind.isIncomingOnly() ? "Directory" : "Incoming")}:'), Expanded( child: GestureDetector( onTap: root_dir_exists @@ -597,45 +602,49 @@ class _GeneralState extends State<_General> { ), ], ).marginOnly(left: _kContentHMargin), - Row( - children: [ - Text('${translate(showRootDir ? "Outgoing" : "Directory")}:'), - Expanded( - child: GestureDetector( - onTap: user_dir_exists - ? () => launchUrl(Uri.file(user_dir)) - : null, - child: Text( - user_dir, - softWrap: true, - style: user_dir_exists - ? const TextStyle(decoration: TextDecoration.underline) + if (!(showRootDir && bind.isIncomingOnly())) + Row( + children: [ + Text( + '${translate((showRootDir && !bind.isOutgoingOnly()) ? "Outgoing" : "Directory")}:'), + Expanded( + child: GestureDetector( + onTap: user_dir_exists + ? () => launchUrl(Uri.file(user_dir)) : null, - )).marginOnly(left: 10), - ), - ElevatedButton( - onPressed: isOptionFixed(kOptionVideoSaveDirectory) - ? null - : () async { - String? initialDirectory; - if (await Directory.fromUri(Uri.directory(user_dir)) - .exists()) { - initialDirectory = user_dir; - } - String? selectedDirectory = - await FilePicker.platform.getDirectoryPath( - initialDirectory: initialDirectory); - if (selectedDirectory != null) { - await bind.mainSetOption( - key: kOptionVideoSaveDirectory, - value: selectedDirectory); - setState(() {}); - } - }, - child: Text(translate('Change'))) - .marginOnly(left: 5), - ], - ).marginOnly(left: _kContentHMargin), + child: Text( + user_dir, + softWrap: true, + style: user_dir_exists + ? const TextStyle( + decoration: TextDecoration.underline) + : null, + )).marginOnly(left: 10), + ), + ElevatedButton( + onPressed: isOptionFixed(kOptionVideoSaveDirectory) + ? null + : () async { + String? initialDirectory; + if (await Directory.fromUri( + Uri.directory(user_dir)) + .exists()) { + initialDirectory = user_dir; + } + String? selectedDirectory = + await FilePicker.platform.getDirectoryPath( + initialDirectory: initialDirectory); + if (selectedDirectory != null) { + await bind.mainSetOption( + key: kOptionVideoSaveDirectory, + value: selectedDirectory); + setState(() {}); + } + }, + child: Text(translate('Change'))) + .marginOnly(left: 5), + ], + ).marginOnly(left: _kContentHMargin), ]); }); } diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 4ef8157da19..cca2074a242 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -115,6 +115,8 @@ class _RemotePageState extends State _ffi.imageModel.addCallbackOnFirstImage((String peerId) { showKBLayoutTypeChooserIfNeeded( _ffi.ffiModel.pi.platform, _ffi.dialogManager); + _ffi.recordingModel + .updateStatus(bind.sessionGetIsRecording(sessionId: _ffi.sessionId)); }); _ffi.start( widget.id, @@ -253,7 +255,6 @@ class _RemotePageState extends State _ffi.dialogManager.hideMobileActionsOverlay(); _ffi.imageModel.disposeImage(); _ffi.cursorModel.disposeImages(); - _ffi.recordingModel.onClose(); _rawKeyFocusNode.dispose(); await _ffi.close(closeSession: closeSession); _timer?.cancel(); diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 3d8ca5e1314..75791ad093c 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1924,8 +1924,7 @@ class _RecordMenu extends StatelessWidget { var ffi = Provider.of(context); var recordingModel = Provider.of(context); final visible = - (recordingModel.start || ffi.permissions['recording'] != false) && - ffi.pi.currentDisplay != kAllDisplayValue; + (recordingModel.start || ffi.permissions['recording'] != false); if (!visible) return Offstage(); return _IconMenuButton( assetName: 'assets/rec.svg', diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 395ce333365..40890f228e6 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -92,6 +92,13 @@ class _RemotePageState extends State { gFFI.chatModel .changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID)); _blockableOverlayState.applyFfi(gFFI); + gFFI.imageModel.addCallbackOnFirstImage((String peerId) { + gFFI.recordingModel + .updateStatus(bind.sessionGetIsRecording(sessionId: gFFI.sessionId)); + if (gFFI.recordingModel.start) { + showToast(translate('Automatically record outgoing sessions')); + } + }); } @override @@ -207,7 +214,7 @@ class _RemotePageState extends State { } void _handleNonIOSSoftKeyboardInput(String newValue) { - _composingTimer?.cancel(); + _composingTimer?.cancel(); if (_textController.value.isComposingRangeValid) { _composingTimer = Timer(Duration(milliseconds: 25), () { _handleNonIOSSoftKeyboardInput(_textController.value.text); diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 8fac2ea2a04..e70ee5b35c7 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -79,6 +79,7 @@ class _SettingsState extends State with WidgetsBindingObserver { var _enableRecordSession = false; var _enableHardwareCodec = false; var _autoRecordIncomingSession = false; + var _autoRecordOutgoingSession = false; var _allowAutoDisconnect = false; var _localIP = ""; var _directAccessPort = ""; @@ -104,6 +105,8 @@ class _SettingsState extends State with WidgetsBindingObserver { bind.mainGetOptionSync(key: kOptionEnableHwcodec)); _autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming, bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming)); + _autoRecordOutgoingSession = option2bool(kOptionAllowAutoRecordOutgoing, + bind.mainGetOptionSync(key: kOptionAllowAutoRecordOutgoing)); _localIP = bind.mainGetOptionSync(key: 'local-ip-addr'); _directAccessPort = bind.mainGetOptionSync(key: kOptionDirectAccessPort); _allowAutoDisconnect = option2bool(kOptionAllowAutoDisconnect, @@ -231,6 +234,7 @@ class _SettingsState extends State with WidgetsBindingObserver { Widget build(BuildContext context) { Provider.of(context); final outgoingOnly = bind.isOutgoingOnly(); + final incommingOnly = bind.isIncomingOnly(); final customClientSection = CustomSettingsSection( child: Column( children: [ @@ -674,32 +678,55 @@ class _SettingsState extends State with WidgetsBindingObserver { }, ), ]), - if (isAndroid && !outgoingOnly) + if (isAndroid) SettingsSection( title: Text(translate("Recording")), tiles: [ - SettingsTile.switchTile( - title: - Text(translate('Automatically record incoming sessions')), - leading: Icon(Icons.videocam), - description: Text( - "${translate("Directory")}: ${bind.mainVideoSaveDirectory(root: false)}"), - initialValue: _autoRecordIncomingSession, - onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming) - ? null - : (v) async { - await bind.mainSetOption( - key: kOptionAllowAutoRecordIncoming, - value: - bool2option(kOptionAllowAutoRecordIncoming, v)); - final newValue = option2bool( - kOptionAllowAutoRecordIncoming, - await bind.mainGetOption( - key: kOptionAllowAutoRecordIncoming)); - setState(() { - _autoRecordIncomingSession = newValue; - }); - }, + if (!outgoingOnly) + SettingsTile.switchTile( + title: + Text(translate('Automatically record incoming sessions')), + initialValue: _autoRecordIncomingSession, + onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionAllowAutoRecordIncoming, + value: bool2option( + kOptionAllowAutoRecordIncoming, v)); + final newValue = option2bool( + kOptionAllowAutoRecordIncoming, + await bind.mainGetOption( + key: kOptionAllowAutoRecordIncoming)); + setState(() { + _autoRecordIncomingSession = newValue; + }); + }, + ), + if (!incommingOnly) + SettingsTile.switchTile( + title: + Text(translate('Automatically record outgoing sessions')), + initialValue: _autoRecordOutgoingSession, + onToggle: isOptionFixed(kOptionAllowAutoRecordOutgoing) + ? null + : (v) async { + await bind.mainSetOption( + key: kOptionAllowAutoRecordOutgoing, + value: bool2option( + kOptionAllowAutoRecordOutgoing, v)); + final newValue = option2bool( + kOptionAllowAutoRecordOutgoing, + await bind.mainGetOption( + key: kOptionAllowAutoRecordOutgoing)); + setState(() { + _autoRecordOutgoingSession = newValue; + }); + }, + ), + SettingsTile( + title: Text(translate("Directory")), + description: Text(bind.mainVideoSaveDirectory(root: false)), ), ], ), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index d836f62a6e8..f0e4cd75f9e 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -397,6 +397,10 @@ class FfiModel with ChangeNotifier { if (isWeb) { parent.target?.fileModel.onSelectedFiles(evt); } + } else if (name == "record_status") { + if (desktopType == DesktopType.remote || isMobile) { + parent.target?.recordingModel.updateStatus(evt['start'] == 'true'); + } } else { debugPrint('Event is not handled in the fixed branch: $name'); } @@ -527,7 +531,6 @@ class FfiModel with ChangeNotifier { } } - parent.target?.recordingModel.onSwitchDisplay(); if (!_pi.isSupportMultiUiSession || _pi.currentDisplay == display) { handleResolutions(peerId, evt['resolutions']); } @@ -1135,8 +1138,6 @@ class FfiModel with ChangeNotifier { // Directly switch to the new display without waiting for the response. switchToNewDisplay(int display, SessionID sessionId, String peerId, {bool updateCursorPos = false}) { - // VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays. - parent.target?.recordingModel.onClose(); // no need to wait for the response pi.currentDisplay = display; updateCurDisplay(sessionId, updateCursorPos: updateCursorPos); @@ -2342,25 +2343,7 @@ class RecordingModel with ChangeNotifier { WeakReference parent; RecordingModel(this.parent); bool _start = false; - get start => _start; - - onSwitchDisplay() { - if (isIOS || !_start) return; - final sessionId = parent.target?.sessionId; - int? width = parent.target?.canvasModel.getDisplayWidth(); - int? height = parent.target?.canvasModel.getDisplayHeight(); - if (sessionId == null || width == null || height == null) return; - final pi = parent.target?.ffiModel.pi; - if (pi == null) return; - final currentDisplay = pi.currentDisplay; - if (currentDisplay == kAllDisplayValue) return; - bind.sessionRecordScreen( - sessionId: sessionId, - start: true, - display: currentDisplay, - width: width, - height: height); - } + bool get start => _start; toggle() async { if (isIOS) return; @@ -2368,48 +2351,16 @@ class RecordingModel with ChangeNotifier { if (sessionId == null) return; final pi = parent.target?.ffiModel.pi; if (pi == null) return; - final currentDisplay = pi.currentDisplay; - if (currentDisplay == kAllDisplayValue) return; - _start = !_start; - notifyListeners(); - await _sendStatusMessage(sessionId, pi, _start); - if (_start) { - sessionRefreshVideo(sessionId, pi); - if (versionCmp(pi.version, '1.2.4') >= 0) { - // will not receive SwitchDisplay since 1.2.4 - onSwitchDisplay(); - } - } else { - bind.sessionRecordScreen( - sessionId: sessionId, - start: false, - display: currentDisplay, - width: 0, - height: 0); + bool value = !_start; + if (value) { + await sessionRefreshVideo(sessionId, pi); } + await bind.sessionRecordScreen(sessionId: sessionId, start: value); } - onClose() async { - if (isIOS) return; - final sessionId = parent.target?.sessionId; - if (sessionId == null) return; - if (!_start) return; - _start = false; - final pi = parent.target?.ffiModel.pi; - if (pi == null) return; - final currentDisplay = pi.currentDisplay; - if (currentDisplay == kAllDisplayValue) return; - await _sendStatusMessage(sessionId, pi, false); - bind.sessionRecordScreen( - sessionId: sessionId, - start: false, - display: currentDisplay, - width: 0, - height: 0); - } - - _sendStatusMessage(SessionID sessionId, PeerInfo pi, bool status) async { - await bind.sessionRecordStatus(sessionId: sessionId, status: status); + updateStatus(bool status) { + _start = status; + notifyListeners(); } } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index f0f7ec7317e..94f4ec9d988 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -965,6 +965,10 @@ impl Config { .unwrap_or_default() } + pub fn get_bool_option(k: &str) -> bool { + option2bool(k, &Self::get_option(k)) + } + pub fn set_option(k: String, v: String) { if !is_option_can_save(&OVERWRITE_SETTINGS, &k, &DEFAULT_SETTINGS, &v) { return; @@ -2198,6 +2202,7 @@ pub mod keys { pub const OPTION_AUTO_DISCONNECT_TIMEOUT: &str = "auto-disconnect-timeout"; pub const OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN: &str = "allow-only-conn-window-open"; pub const OPTION_ALLOW_AUTO_RECORD_INCOMING: &str = "allow-auto-record-incoming"; + pub const OPTION_ALLOW_AUTO_RECORD_OUTGOING: &str = "allow-auto-record-outgoing"; pub const OPTION_VIDEO_SAVE_DIRECTORY: &str = "video-save-directory"; pub const OPTION_ENABLE_ABR: &str = "enable-abr"; pub const OPTION_ALLOW_REMOVE_WALLPAPER: &str = "allow-remove-wallpaper"; @@ -2342,6 +2347,7 @@ pub mod keys { OPTION_AUTO_DISCONNECT_TIMEOUT, OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN, OPTION_ALLOW_AUTO_RECORD_INCOMING, + OPTION_ALLOW_AUTO_RECORD_OUTGOING, OPTION_VIDEO_SAVE_DIRECTORY, OPTION_ENABLE_ABR, OPTION_ALLOW_REMOVE_WALLPAPER, diff --git a/libs/scrap/Cargo.toml b/libs/scrap/Cargo.toml index 3a0784e7cca..529010f1607 100644 --- a/libs/scrap/Cargo.toml +++ b/libs/scrap/Cargo.toml @@ -62,4 +62,3 @@ gstreamer-video = { version = "0.16", optional = true } git = "https://github.com/rustdesk-org/hwcodec" optional = true - diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs index 07ff0f91d24..dad924c862c 100644 --- a/libs/scrap/src/common/codec.rs +++ b/libs/scrap/src/common/codec.rs @@ -15,7 +15,7 @@ use crate::{ aom::{self, AomDecoder, AomEncoder, AomEncoderConfig}, common::GoogleImage, vpxcodec::{self, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig, VpxVideoCodecId}, - CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb, + CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb, ImageTexture, }; use hbb_common::{ @@ -623,7 +623,7 @@ impl Decoder { &mut self, frame: &video_frame::Union, rgb: &mut ImageRgb, - _texture: &mut *mut c_void, + _texture: &mut ImageTexture, _pixelbuffer: &mut bool, chroma: &mut Option, ) -> ResultType { @@ -777,12 +777,16 @@ impl Decoder { fn handle_vram_video_frame( decoder: &mut VRamDecoder, frames: &EncodedVideoFrames, - texture: &mut *mut c_void, + texture: &mut ImageTexture, ) -> ResultType { let mut ret = false; for h26x in frames.frames.iter() { for image in decoder.decode(&h26x.data)? { - *texture = image.frame.texture; + *texture = ImageTexture { + texture: image.frame.texture, + w: image.frame.width as _, + h: image.frame.height as _, + }; ret = true; } } diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index 635f0ec26d4..ee96f57c851 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -96,6 +96,22 @@ impl ImageRgb { } } +pub struct ImageTexture { + pub texture: *mut c_void, + pub w: usize, + pub h: usize, +} + +impl Default for ImageTexture { + fn default() -> Self { + Self { + texture: std::ptr::null_mut(), + w: 0, + h: 0, + } + } +} + #[inline] pub fn would_block_if_equal(old: &mut Vec, b: &[u8]) -> std::io::Result<()> { // does this really help? @@ -296,6 +312,19 @@ impl From<&VideoFrame> for CodecFormat { } } +impl From<&video_frame::Union> for CodecFormat { + fn from(it: &video_frame::Union) -> Self { + match it { + video_frame::Union::Vp8s(_) => CodecFormat::VP8, + video_frame::Union::Vp9s(_) => CodecFormat::VP9, + video_frame::Union::Av1s(_) => CodecFormat::AV1, + video_frame::Union::H264s(_) => CodecFormat::H264, + video_frame::Union::H265s(_) => CodecFormat::H265, + _ => CodecFormat::Unknown, + } + } +} + impl From<&CodecName> for CodecFormat { fn from(value: &CodecName) -> Self { match value { diff --git a/libs/scrap/src/common/record.rs b/libs/scrap/src/common/record.rs index 52973c2b244..c53b7743147 100644 --- a/libs/scrap/src/common/record.rs +++ b/libs/scrap/src/common/record.rs @@ -25,22 +25,28 @@ pub struct RecorderContext { pub server: bool, pub id: String, pub dir: String, + pub display: usize, + pub tx: Option>, +} + +#[derive(Debug, Clone)] +pub struct RecorderContext2 { pub filename: String, pub width: usize, pub height: usize, pub format: CodecFormat, - pub tx: Option>, } -impl RecorderContext { - pub fn set_filename(&mut self) -> ResultType<()> { - if !PathBuf::from(&self.dir).exists() { - std::fs::create_dir_all(&self.dir)?; +impl RecorderContext2 { + pub fn set_filename(&mut self, ctx: &RecorderContext) -> ResultType<()> { + if !PathBuf::from(&ctx.dir).exists() { + std::fs::create_dir_all(&ctx.dir)?; } - let file = if self.server { "incoming" } else { "outgoing" }.to_string() + let file = if ctx.server { "incoming" } else { "outgoing" }.to_string() + "_" - + &self.id.clone() + + &ctx.id.clone() + &chrono::Local::now().format("_%Y%m%d%H%M%S%3f_").to_string() + + &format!("display{}_", ctx.display) + &self.format.to_string().to_lowercase() + if self.format == CodecFormat::VP9 || self.format == CodecFormat::VP8 @@ -50,11 +56,10 @@ impl RecorderContext { } else { ".mp4" }; - self.filename = PathBuf::from(&self.dir) + self.filename = PathBuf::from(&ctx.dir) .join(file) .to_string_lossy() .to_string(); - log::info!("video will save to {}", self.filename); Ok(()) } } @@ -63,7 +68,7 @@ unsafe impl Send for Recorder {} unsafe impl Sync for Recorder {} pub trait RecorderApi { - fn new(ctx: RecorderContext) -> ResultType + fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType where Self: Sized; fn write_video(&mut self, frame: &EncodedVideoFrame) -> bool; @@ -78,13 +83,15 @@ pub enum RecordState { } pub struct Recorder { - pub inner: Box, + pub inner: Option>, ctx: RecorderContext, + ctx2: Option, pts: Option, + check_failed: bool, } impl Deref for Recorder { - type Target = Box; + type Target = Option>; fn deref(&self) -> &Self::Target { &self.inner @@ -98,114 +105,123 @@ impl DerefMut for Recorder { } impl Recorder { - pub fn new(mut ctx: RecorderContext) -> ResultType { - ctx.set_filename()?; - let recorder = match ctx.format { - CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Recorder { - inner: Box::new(WebmRecorder::new(ctx.clone())?), - ctx, - pts: None, - }, - #[cfg(feature = "hwcodec")] - _ => Recorder { - inner: Box::new(HwRecorder::new(ctx.clone())?), - ctx, - pts: None, - }, - #[cfg(not(feature = "hwcodec"))] - _ => bail!("unsupported codec type"), - }; - recorder.send_state(RecordState::NewFile(recorder.ctx.filename.clone())); - Ok(recorder) + pub fn new(ctx: RecorderContext) -> ResultType { + Ok(Self { + inner: None, + ctx, + ctx2: None, + pts: None, + check_failed: false, + }) } - fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> { - ctx.set_filename()?; - self.inner = match ctx.format { - CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => { - Box::new(WebmRecorder::new(ctx.clone())?) + fn check(&mut self, w: usize, h: usize, format: CodecFormat) -> ResultType<()> { + match self.ctx2 { + Some(ref ctx2) => { + if ctx2.width != w || ctx2.height != h || ctx2.format != format { + let mut ctx2 = RecorderContext2 { + width: w, + height: h, + format, + filename: Default::default(), + }; + ctx2.set_filename(&self.ctx)?; + self.ctx2 = Some(ctx2); + self.inner = None; + } } - #[cfg(feature = "hwcodec")] - _ => Box::new(HwRecorder::new(ctx.clone())?), - #[cfg(not(feature = "hwcodec"))] - _ => bail!("unsupported codec type"), + None => { + let mut ctx2 = RecorderContext2 { + width: w, + height: h, + format, + filename: Default::default(), + }; + ctx2.set_filename(&self.ctx)?; + self.ctx2 = Some(ctx2); + self.inner = None; + } + } + let Some(ctx2) = &self.ctx2 else { + bail!("ctx2 is None"); }; - self.ctx = ctx; - self.pts = None; - self.send_state(RecordState::NewFile(self.ctx.filename.clone())); + if self.inner.is_none() { + self.inner = match format { + CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Some(Box::new( + WebmRecorder::new(self.ctx.clone(), (*ctx2).clone())?, + )), + #[cfg(feature = "hwcodec")] + _ => Some(Box::new(HwRecorder::new( + self.ctx.clone(), + (*ctx2).clone(), + )?)), + #[cfg(not(feature = "hwcodec"))] + _ => bail!("unsupported codec type"), + }; + self.pts = None; + self.send_state(RecordState::NewFile(ctx2.filename.clone())); + } Ok(()) } - pub fn write_message(&mut self, msg: &Message) { + pub fn write_message(&mut self, msg: &Message, w: usize, h: usize) { if let Some(message::Union::VideoFrame(vf)) = &msg.union { if let Some(frame) = &vf.union { - self.write_frame(frame).ok(); + self.write_frame(frame, w, h).ok(); } } } - pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> { + pub fn write_frame( + &mut self, + frame: &video_frame::Union, + w: usize, + h: usize, + ) -> ResultType<()> { + if self.check_failed { + bail!("check failed"); + } + let format = CodecFormat::from(frame); + if format == CodecFormat::Unknown { + bail!("unsupported frame type"); + } + let res = self.check(w, h, format); + if res.is_err() { + self.check_failed = true; + log::error!("check failed: {:?}", res); + res?; + } match frame { video_frame::Union::Vp8s(vp8s) => { - if self.ctx.format != CodecFormat::VP8 { - self.change(RecorderContext { - format: CodecFormat::VP8, - ..self.ctx.clone() - })?; - } for f in vp8s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } video_frame::Union::Vp9s(vp9s) => { - if self.ctx.format != CodecFormat::VP9 { - self.change(RecorderContext { - format: CodecFormat::VP9, - ..self.ctx.clone() - })?; - } for f in vp9s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } video_frame::Union::Av1s(av1s) => { - if self.ctx.format != CodecFormat::AV1 { - self.change(RecorderContext { - format: CodecFormat::AV1, - ..self.ctx.clone() - })?; - } for f in av1s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } #[cfg(feature = "hwcodec")] video_frame::Union::H264s(h264s) => { - if self.ctx.format != CodecFormat::H264 { - self.change(RecorderContext { - format: CodecFormat::H264, - ..self.ctx.clone() - })?; - } for f in h264s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } #[cfg(feature = "hwcodec")] video_frame::Union::H265s(h265s) => { - if self.ctx.format != CodecFormat::H265 { - self.change(RecorderContext { - format: CodecFormat::H265, - ..self.ctx.clone() - })?; - } for f in h265s.frames.iter() { - self.check_pts(f.pts)?; - self.write_video(f); + self.check_pts(f.pts, w, h, format)?; + self.as_mut().map(|x| x.write_video(f)); } } _ => bail!("unsupported frame type"), @@ -214,13 +230,21 @@ impl Recorder { Ok(()) } - fn check_pts(&mut self, pts: i64) -> ResultType<()> { + fn check_pts(&mut self, pts: i64, w: usize, h: usize, format: CodecFormat) -> ResultType<()> { // https://stackoverflow.com/questions/76379101/how-to-create-one-playable-webm-file-from-two-different-video-tracks-with-same-c let old_pts = self.pts; self.pts = Some(pts); if old_pts.clone().unwrap_or_default() > pts { log::info!("pts {:?} -> {}, change record filename", old_pts, pts); - self.change(self.ctx.clone())?; + self.inner = None; + self.ctx2 = None; + let res = self.check(w, h, format); + if res.is_err() { + self.check_failed = true; + log::error!("check failed: {:?}", res); + res?; + } + self.pts = Some(pts); } Ok(()) } @@ -234,21 +258,22 @@ struct WebmRecorder { vt: VideoTrack, webm: Option>>, ctx: RecorderContext, + ctx2: RecorderContext2, key: bool, written: bool, start: Instant, } impl RecorderApi for WebmRecorder { - fn new(ctx: RecorderContext) -> ResultType { + fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType { let out = match { OpenOptions::new() .write(true) .create_new(true) - .open(&ctx.filename) + .open(&ctx2.filename) } { Ok(file) => file, - Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => File::create(&ctx.filename)?, + Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => File::create(&ctx2.filename)?, Err(e) => return Err(e.into()), }; let mut webm = match mux::Segment::new(mux::Writer::new(out)) { @@ -256,18 +281,18 @@ impl RecorderApi for WebmRecorder { None => bail!("Failed to create webm mux"), }; let vt = webm.add_video_track( - ctx.width as _, - ctx.height as _, + ctx2.width as _, + ctx2.height as _, None, - if ctx.format == CodecFormat::VP9 { + if ctx2.format == CodecFormat::VP9 { mux::VideoCodecId::VP9 - } else if ctx.format == CodecFormat::VP8 { + } else if ctx2.format == CodecFormat::VP8 { mux::VideoCodecId::VP8 } else { mux::VideoCodecId::AV1 }, ); - if ctx.format == CodecFormat::AV1 { + if ctx2.format == CodecFormat::AV1 { // [129, 8, 12, 0] in 3.6.0, but zero works let codec_private = vec![0, 0, 0, 0]; if !webm.set_codec_private(vt.track_number(), &codec_private) { @@ -278,6 +303,7 @@ impl RecorderApi for WebmRecorder { vt, webm: Some(webm), ctx, + ctx2, key: false, written: false, start: Instant::now(), @@ -307,7 +333,7 @@ impl Drop for WebmRecorder { let _ = std::mem::replace(&mut self.webm, None).map_or(false, |webm| webm.finalize(None)); let mut state = RecordState::WriteTail; if !self.written || self.start.elapsed().as_secs() < MIN_SECS { - std::fs::remove_file(&self.ctx.filename).ok(); + std::fs::remove_file(&self.ctx2.filename).ok(); state = RecordState::RemoveFile; } self.ctx.tx.as_ref().map(|tx| tx.send(state)); @@ -318,6 +344,7 @@ impl Drop for WebmRecorder { struct HwRecorder { muxer: Muxer, ctx: RecorderContext, + ctx2: RecorderContext2, written: bool, key: bool, start: Instant, @@ -325,18 +352,19 @@ struct HwRecorder { #[cfg(feature = "hwcodec")] impl RecorderApi for HwRecorder { - fn new(ctx: RecorderContext) -> ResultType { + fn new(ctx: RecorderContext, ctx2: RecorderContext2) -> ResultType { let muxer = Muxer::new(MuxContext { - filename: ctx.filename.clone(), - width: ctx.width, - height: ctx.height, - is265: ctx.format == CodecFormat::H265, + filename: ctx2.filename.clone(), + width: ctx2.width, + height: ctx2.height, + is265: ctx2.format == CodecFormat::H265, framerate: crate::hwcodec::DEFAULT_FPS as _, }) .map_err(|_| anyhow!("Failed to create hardware muxer"))?; Ok(HwRecorder { muxer, ctx, + ctx2, written: false, key: false, start: Instant::now(), @@ -365,7 +393,7 @@ impl Drop for HwRecorder { self.muxer.write_tail().ok(); let mut state = RecordState::WriteTail; if !self.written || self.start.elapsed().as_secs() < MIN_SECS { - std::fs::remove_file(&self.ctx.filename).ok(); + std::fs::remove_file(&self.ctx2.filename).ok(); state = RecordState::RemoveFile; } self.ctx.tx.as_ref().map(|tx| tx.send(state)); diff --git a/src/client.rs b/src/client.rs index 9e49b84e2e2..a1b2b83d436 100644 --- a/src/client.rs +++ b/src/client.rs @@ -30,7 +30,6 @@ pub use file_trait::FileManager; #[cfg(not(feature = "flutter"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::tokio::sync::mpsc::UnboundedSender; -use hbb_common::tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use hbb_common::{ allow_err, anyhow::{anyhow, Context}, @@ -54,11 +53,15 @@ use hbb_common::{ }, AddrMangle, ResultType, Stream, }; +use hbb_common::{ + config::keys::OPTION_ALLOW_AUTO_RECORD_OUTGOING, + tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}, +}; pub use helper::*; use scrap::{ codec::Decoder, record::{Recorder, RecorderContext}, - CodecFormat, ImageFormat, ImageRgb, + CodecFormat, ImageFormat, ImageRgb, ImageTexture, }; use crate::{ @@ -1146,7 +1149,7 @@ impl AudioHandler { pub struct VideoHandler { decoder: Decoder, pub rgb: ImageRgb, - pub texture: *mut c_void, + pub texture: ImageTexture, recorder: Arc>>, record: bool, _display: usize, // useful for debug @@ -1172,7 +1175,7 @@ impl VideoHandler { VideoHandler { decoder: Decoder::new(format, luid), rgb: ImageRgb::new(ImageFormat::ARGB, crate::get_dst_align_rgba()), - texture: std::ptr::null_mut(), + texture: Default::default(), recorder: Default::default(), record: false, _display, @@ -1220,11 +1223,14 @@ impl VideoHandler { } self.first_frame = false; if self.record { - self.recorder - .lock() - .unwrap() - .as_mut() - .map(|r| r.write_frame(frame)); + self.recorder.lock().unwrap().as_mut().map(|r| { + let (w, h) = if *pixelbuffer { + (self.rgb.w, self.rgb.h) + } else { + (self.texture.w, self.texture.h) + }; + r.write_frame(frame, w, h).ok(); + }); } res } @@ -1248,17 +1254,14 @@ impl VideoHandler { } /// Start or stop screen record. - pub fn record_screen(&mut self, start: bool, w: i32, h: i32, id: String) { + pub fn record_screen(&mut self, start: bool, id: String, display: usize) { self.record = false; if start { self.recorder = Recorder::new(RecorderContext { server: false, id, dir: crate::ui_interface::video_save_directory(false), - filename: "".to_owned(), - width: w as _, - height: h as _, - format: scrap::CodecFormat::VP9, + display, tx: None, }) .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))); @@ -1347,6 +1350,7 @@ pub struct LoginConfigHandler { password_source: PasswordSource, // where the sent password comes from shared_password: Option, // Store the shared password pub enable_trusted_devices: bool, + pub record: bool, } impl Deref for LoginConfigHandler { @@ -1438,6 +1442,7 @@ impl LoginConfigHandler { self.adapter_luid = adapter_luid; self.selected_windows_session_id = None; self.shared_password = shared_password; + self.record = Config::get_bool_option(OPTION_ALLOW_AUTO_RECORD_OUTGOING); } /// Check if the client should auto login. @@ -2227,7 +2232,7 @@ pub enum MediaData { AudioFrame(Box), AudioFormat(AudioFormat), Reset(Option), - RecordScreen(bool, usize, i32, i32, String), + RecordScreen(bool), } pub type MediaSender = mpsc::Sender; @@ -2303,10 +2308,16 @@ where let start = std::time::Instant::now(); let format = CodecFormat::from(&vf); if !handler_controller_map.contains_key(&display) { + let mut handler = VideoHandler::new(format, display); + let record = session.lc.read().unwrap().record; + let id = session.lc.read().unwrap().id.clone(); + if record { + handler.record_screen(record, id, display); + } handler_controller_map.insert( display, VideoHandlerController { - handler: VideoHandler::new(format, display), + handler, skip_beginning: 0, }, ); @@ -2325,7 +2336,7 @@ where video_callback( display, &mut handler_controller.handler.rgb, - handler_controller.handler.texture, + handler_controller.handler.texture.texture, pixelbuffer, ); @@ -2399,18 +2410,19 @@ where } } } - MediaData::RecordScreen(start, display, w, h, id) => { - log::info!("record screen command: start: {start}, display: {display}"); - // Compatible with the sciter version(single ui session). - // For the sciter version, there're no multi-ui-sessions for one connection. - // The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler. - if let Some(handler_controler) = handler_controller_map.get_mut(&display) { - handler_controler.handler.record_screen(start, w, h, id); - } else if handler_controller_map.len() == 1 { - if let Some(handler_controler) = - handler_controller_map.values_mut().next() - { - handler_controler.handler.record_screen(start, w, h, id); + MediaData::RecordScreen(start) => { + log::info!("record screen command: start: {start}"); + let record = session.lc.read().unwrap().record; + session.update_record_status(start); + if record != start { + session.lc.write().unwrap().record = start; + let id = session.lc.read().unwrap().id.clone(); + for (display, handler_controler) in handler_controller_map.iter_mut() { + handler_controler.handler.record_screen( + start, + id.clone(), + *display, + ); } } } @@ -3169,7 +3181,7 @@ pub enum Data { SetConfirmOverrideFile((i32, i32, bool, bool, bool)), AddJob((i32, String, String, i32, bool, bool)), ResumeJob((i32, bool)), - RecordScreen(bool, usize, i32, i32, String), + RecordScreen(bool), ElevateDirect, ElevateWithLogon(String, String), NewVoiceCall, diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 84d8a897cf5..cc74c96edd1 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -837,10 +837,8 @@ impl Remote { self.handle_job_status(id, -1, err); } } - Data::RecordScreen(start, display, w, h, id) => { - let _ = self - .video_sender - .send(MediaData::RecordScreen(start, display, w, h, id)); + Data::RecordScreen(start) => { + let _ = self.video_sender.send(MediaData::RecordScreen(start)); } Data::ElevateDirect => { let mut request = ElevationRequest::new(); @@ -1218,7 +1216,7 @@ impl Remote { crate::plugin::handle_listen_event( crate::plugin::EVENT_ON_CONN_CLIENT.to_owned(), self.handler.get_id(), - ) + ); } if self.handler.is_file_transfer() { diff --git a/src/flutter.rs b/src/flutter.rs index cbeb3e2c3d3..69266f51c1c 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -17,7 +17,7 @@ use serde::Serialize; use serde_json::json; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, ffi::CString, os::raw::{c_char, c_int, c_void}, str::FromStr, @@ -1010,6 +1010,10 @@ impl InvokeUiSession for FlutterHandler { rgba_data.valid = false; } } + + fn update_record_status(&self, start: bool) { + self.push_event("record_status", &[("start", &start.to_string())], &[]); + } } impl FlutterHandler { @@ -1830,7 +1834,6 @@ pub(super) fn session_update_virtual_display(session: &FlutterSession, index: i3 // sessions mod is used to avoid the big lock of sessions' map. pub mod sessions { - use std::collections::HashSet; use super::*; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index d029de1d256..979e25e8d26 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -241,21 +241,17 @@ pub fn session_is_multi_ui_session(session_id: SessionID) -> SyncReturn { } } -pub fn session_record_screen( - session_id: SessionID, - start: bool, - display: usize, - width: usize, - height: usize, -) { +pub fn session_record_screen(session_id: SessionID, start: bool) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.record_screen(start, display as _, width as _, height as _); + session.record_screen(start); } } -pub fn session_record_status(session_id: SessionID, status: bool) { +pub fn session_get_is_recording(session_id: SessionID) -> SyncReturn { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.record_status(status); + SyncReturn(session.is_recording()) + } else { + SyncReturn(false) } } diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 6cae020b6e4..fe7e853815b 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "التسجيل"), ("Directory", "المسار"), ("Automatically record incoming sessions", "تسجيل الجلسات القادمة تلقائيا"), + ("Automatically record outgoing sessions", ""), ("Change", "تغيير"), ("Start session recording", "بدء تسجيل الجلسة"), ("Stop session recording", "ايقاف تسجيل الجلسة"), diff --git a/src/lang/be.rs b/src/lang/be.rs index 7e9f928ae57..da9be46401c 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Запіс"), ("Directory", "Тэчка"), ("Automatically record incoming sessions", "Аўтаматычна запісваць уваходныя сесіі"), + ("Automatically record outgoing sessions", ""), ("Change", "Змяніць"), ("Start session recording", "Пачаць запіс сесіі"), ("Stop session recording", "Спыніць запіс сесіі"), diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 9f6282e7523..72b5fcf1971 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Записване"), ("Directory", "Директория"), ("Automatically record incoming sessions", "Автоматичен запис на входящи сесии"), + ("Automatically record outgoing sessions", ""), ("Change", "Промяна"), ("Start session recording", "Започванена запис"), ("Stop session recording", "Край на запис"), diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 64748c9ac09..fbe3cde5fcd 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Gravació"), ("Directory", "Contactes"), ("Automatically record incoming sessions", "Enregistrament automàtic de sessions entrants"), + ("Automatically record outgoing sessions", ""), ("Change", "Canvia"), ("Start session recording", "Inicia la gravació de la sessió"), ("Stop session recording", "Atura la gravació de la sessió"), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 5ad91ada84f..f57d100d5e5 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -363,7 +363,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Unpin Toolbar", "取消固定工具栏"), ("Recording", "录屏"), ("Directory", "目录"), - ("Automatically record incoming sessions", "自动录制来访会话"), + ("Automatically record incoming sessions", "自动录制传入会话"), + ("Automatically record outgoing sessions", "自动录制传出会话"), ("Change", "更改"), ("Start session recording", "开始录屏"), ("Stop session recording", "结束录屏"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 8db1470b98a..d72b85a0cf5 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Nahrávání"), ("Directory", "Adresář"), ("Automatically record incoming sessions", "Automaticky nahrávat příchozí relace"), + ("Automatically record outgoing sessions", ""), ("Change", "Změnit"), ("Start session recording", "Spustit záznam relace"), ("Stop session recording", "Zastavit záznam relace"), diff --git a/src/lang/da.rs b/src/lang/da.rs index f867cac467a..558b2fa4541 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Optager"), ("Directory", "Mappe"), ("Automatically record incoming sessions", "Optag automatisk indgående sessioner"), + ("Automatically record outgoing sessions", ""), ("Change", "Ændr"), ("Start session recording", "Start sessionsoptagelse"), ("Stop session recording", "Stop sessionsoptagelse"), diff --git a/src/lang/de.rs b/src/lang/de.rs index 4213c86de74..0a78ee2b55e 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Aufnahme"), ("Directory", "Verzeichnis"), ("Automatically record incoming sessions", "Eingehende Sitzungen automatisch aufzeichnen"), + ("Automatically record outgoing sessions", ""), ("Change", "Ändern"), ("Start session recording", "Sitzungsaufzeichnung starten"), ("Stop session recording", "Sitzungsaufzeichnung beenden"), diff --git a/src/lang/el.rs b/src/lang/el.rs index 625867c343e..9725ecc7886 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Εγγραφή"), ("Directory", "Φάκελος εγγραφών"), ("Automatically record incoming sessions", "Αυτόματη εγγραφή εισερχόμενων συνεδριών"), + ("Automatically record outgoing sessions", ""), ("Change", "Αλλαγή"), ("Start session recording", "Έναρξη εγγραφής συνεδρίας"), ("Stop session recording", "Διακοπή εγγραφής συνεδρίας"), diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 217b37a6a5e..2f585b1c6e6 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/es.rs b/src/lang/es.rs index 25cd7bf283c..92015df0b20 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Grabando"), ("Directory", "Directorio"), ("Automatically record incoming sessions", "Grabación automática de sesiones entrantes"), + ("Automatically record outgoing sessions", ""), ("Change", "Cambiar"), ("Start session recording", "Comenzar grabación de sesión"), ("Stop session recording", "Detener grabación de sesión"), diff --git a/src/lang/et.rs b/src/lang/et.rs index 8187759d575..96ca16f964f 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/eu.rs b/src/lang/eu.rs index f8ba679a756..d68e5c42aeb 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Grabatzen"), ("Directory", "Direktorioa"), ("Automatically record incoming sessions", "Automatikoki grabatu sarrerako saioak"), + ("Automatically record outgoing sessions", ""), ("Change", "Aldatu"), ("Start session recording", "Hasi saioaren grabaketa"), ("Stop session recording", "Gelditu saioaren grabaketa"), diff --git a/src/lang/fa.rs b/src/lang/fa.rs index cf5ce0f0f70..207dfbbdbd9 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "در حال ضبط"), ("Directory", "مسیر"), ("Automatically record incoming sessions", "ضبط خودکار جلسات ورودی"), + ("Automatically record outgoing sessions", ""), ("Change", "تغییر"), ("Start session recording", "شروع ضبط جلسه"), ("Stop session recording", "توقف ضبط جلسه"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 0fd093688b1..9844167404a 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Enregistrement"), ("Directory", "Répertoire"), ("Automatically record incoming sessions", "Enregistrement automatique des sessions entrantes"), + ("Automatically record outgoing sessions", ""), ("Change", "Modifier"), ("Start session recording", "Commencer l'enregistrement"), ("Stop session recording", "Stopper l'enregistrement"), diff --git a/src/lang/he.rs b/src/lang/he.rs index e36252afe90..408829b6c64 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/hr.rs b/src/lang/hr.rs index e2480eb6350..b9f9409fc39 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Snimanje"), ("Directory", "Mapa"), ("Automatically record incoming sessions", "Automatski snimi dolazne sesije"), + ("Automatically record outgoing sessions", ""), ("Change", "Promijeni"), ("Start session recording", "Započni snimanje sesije"), ("Stop session recording", "Zaustavi snimanje sesije"), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 00c56edfbca..e9caf1917f4 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Felvétel"), ("Directory", "Könyvtár"), ("Automatically record incoming sessions", "A bejövő munkamenetek automatikus rögzítése"), + ("Automatically record outgoing sessions", ""), ("Change", "Változtatás"), ("Start session recording", "Munkamenet rögzítés indítása"), ("Stop session recording", "Munkamenet rögzítés leállítása"), diff --git a/src/lang/id.rs b/src/lang/id.rs index 2f95411f082..52c1741915e 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Perekaman"), ("Directory", "Direktori"), ("Automatically record incoming sessions", "Otomatis merekam sesi masuk"), + ("Automatically record outgoing sessions", ""), ("Change", "Ubah"), ("Start session recording", "Mulai sesi perekaman"), ("Stop session recording", "Hentikan sesi perekaman"), diff --git a/src/lang/it.rs b/src/lang/it.rs index baaa1d10d31..4f85db09a15 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Registrazione"), ("Directory", "Cartella"), ("Automatically record incoming sessions", "Registra automaticamente le sessioni in entrata"), + ("Automatically record outgoing sessions", ""), ("Change", "Modifica"), ("Start session recording", "Inizia registrazione sessione"), ("Stop session recording", "Ferma registrazione sessione"), diff --git a/src/lang/ja.rs b/src/lang/ja.rs index dbc40b78979..0bca730dc7c 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "録画"), ("Directory", "ディレクトリ"), ("Automatically record incoming sessions", "受信したセッションを自動で記録する"), + ("Automatically record outgoing sessions", ""), ("Change", "変更"), ("Start session recording", "セッションの録画を開始"), ("Stop session recording", "セッションの録画を停止"), diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 966ac90097e..dc9a0a69d59 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "녹화"), ("Directory", "경로"), ("Automatically record incoming sessions", "들어오는 세션을 자동으로 녹화"), + ("Automatically record outgoing sessions", ""), ("Change", "변경"), ("Start session recording", "세션 녹화 시작"), ("Stop session recording", "세션 녹화 중지"), diff --git a/src/lang/kz.rs b/src/lang/kz.rs index d62bbc393bf..9ea54b97540 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/lt.rs b/src/lang/lt.rs index bc7d856b1e8..df795401b76 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Įrašymas"), ("Directory", "Katalogas"), ("Automatically record incoming sessions", "Automatiškai įrašyti įeinančius seansus"), + ("Automatically record outgoing sessions", ""), ("Change", "Keisti"), ("Start session recording", "Pradėti seanso įrašinėjimą"), ("Stop session recording", "Sustabdyti seanso įrašinėjimą"), diff --git a/src/lang/lv.rs b/src/lang/lv.rs index bf67345c3a4..9a54daa005b 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Ierakstīšana"), ("Directory", "Direktorija"), ("Automatically record incoming sessions", "Automātiski ierakstīt ienākošās sesijas"), + ("Automatically record outgoing sessions", ""), ("Change", "Mainīt"), ("Start session recording", "Sākt sesijas ierakstīšanu"), ("Stop session recording", "Apturēt sesijas ierakstīšanu"), diff --git a/src/lang/nb.rs b/src/lang/nb.rs index d91eb93258d..4c8b1550c5e 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Opptak"), ("Directory", "Mappe"), ("Automatically record incoming sessions", "Ta opp innkommende sesjoner automatisk"), + ("Automatically record outgoing sessions", ""), ("Change", "Rediger"), ("Start session recording", "Start sesjonsopptak"), ("Stop session recording", "Stopp sesjonsopptak"), diff --git a/src/lang/nl.rs b/src/lang/nl.rs index b780ce7daf7..2728f2edc89 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Opnemen"), ("Directory", "Map"), ("Automatically record incoming sessions", "Automatisch inkomende sessies opnemen"), + ("Automatically record outgoing sessions", ""), ("Change", "Wissel"), ("Start session recording", "Start de sessieopname"), ("Stop session recording", "Stop de sessieopname"), diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 8e998908449..caf992978c9 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Nagrywanie"), ("Directory", "Folder"), ("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"), + ("Automatically record outgoing sessions", ""), ("Change", "Zmień"), ("Start session recording", "Zacznij nagrywać sesję"), ("Stop session recording", "Zatrzymaj nagrywanie sesji"), diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 4c01d0b628a..1194c11ec94 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 240fae99ae4..012ca3538fa 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Gravando"), ("Directory", "Diretório"), ("Automatically record incoming sessions", "Gravar automaticamente sessões de entrada"), + ("Automatically record outgoing sessions", ""), ("Change", "Alterar"), ("Start session recording", "Iniciar gravação da sessão"), ("Stop session recording", "Parar gravação da sessão"), diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 26858b13436..e09888c58a8 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Înregistrare"), ("Directory", "Director"), ("Automatically record incoming sessions", "Înregistrează automat sesiunile viitoare"), + ("Automatically record outgoing sessions", ""), ("Change", "Modifică"), ("Start session recording", "Începe înregistrarea"), ("Stop session recording", "Oprește înregistrarea"), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 00ae750f27d..b547208d7ab 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Запись"), ("Directory", "Папка"), ("Automatically record incoming sessions", "Автоматически записывать входящие сеансы"), + ("Automatically record outgoing sessions", ""), ("Change", "Изменить"), ("Start session recording", "Начать запись сеанса"), ("Stop session recording", "Остановить запись сеанса"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 28e322460df..bb8e872c7cf 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Nahrávanie"), ("Directory", "Adresár"), ("Automatically record incoming sessions", "Automaticky nahrávať prichádzajúce relácie"), + ("Automatically record outgoing sessions", ""), ("Change", "Zmeniť"), ("Start session recording", "Spustiť záznam relácie"), ("Stop session recording", "Zastaviť záznam relácie"), diff --git a/src/lang/sl.rs b/src/lang/sl.rs index e7f6248160b..1563a02c573 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Snemanje"), ("Directory", "Imenik"), ("Automatically record incoming sessions", "Samodejno snemaj vhodne seje"), + ("Automatically record outgoing sessions", ""), ("Change", "Spremeni"), ("Start session recording", "Začni snemanje seje"), ("Stop session recording", "Ustavi snemanje seje"), diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 58dc1ed5502..ccc4b805fac 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Regjistrimi"), ("Directory", "Direktoria"), ("Automatically record incoming sessions", "Regjistro automatikisht seancat hyrëse"), + ("Automatically record outgoing sessions", ""), ("Change", "Ndrysho"), ("Start session recording", "Fillo regjistrimin e sesionit"), ("Stop session recording", "Ndalo regjistrimin e sesionit"), diff --git a/src/lang/sr.rs b/src/lang/sr.rs index d38d20e9ef3..6df6b7ad83b 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Snimanje"), ("Directory", "Direktorijum"), ("Automatically record incoming sessions", "Automatski snimaj dolazne sesije"), + ("Automatically record outgoing sessions", ""), ("Change", "Promeni"), ("Start session recording", "Započni snimanje sesije"), ("Stop session recording", "Zaustavi snimanje sesije"), diff --git a/src/lang/sv.rs b/src/lang/sv.rs index cfbac29b903..2f95488bd42 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Spelar in"), ("Directory", "Katalog"), ("Automatically record incoming sessions", "Spela in inkommande sessioner automatiskt"), + ("Automatically record outgoing sessions", ""), ("Change", "Byt"), ("Start session recording", "Starta inspelning"), ("Stop session recording", "Avsluta inspelning"), diff --git a/src/lang/template.rs b/src/lang/template.rs index 1c4606e9479..962506b99ec 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", ""), ("Directory", ""), ("Automatically record incoming sessions", ""), + ("Automatically record outgoing sessions", ""), ("Change", ""), ("Start session recording", ""), ("Stop session recording", ""), diff --git a/src/lang/th.rs b/src/lang/th.rs index 2dbb564e870..673ebf31981 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "การบันทึก"), ("Directory", "ไดเรกทอรี่"), ("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"), + ("Automatically record outgoing sessions", ""), ("Change", "เปลี่ยน"), ("Start session recording", "เริ่มต้นการบันทึกเซสชัน"), ("Stop session recording", "หยุดการบันทึกเซสซัน"), diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 9bdbec110c6..04687287893 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Kayıt Ediliyor"), ("Directory", "Klasör"), ("Automatically record incoming sessions", "Gelen oturumları otomatik olarak kayıt et"), + ("Automatically record outgoing sessions", ""), ("Change", "Değiştir"), ("Start session recording", "Oturum kaydını başlat"), ("Stop session recording", "Oturum kaydını sonlandır"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index b37949206ac..25da736b837 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "錄製"), ("Directory", "路徑"), ("Automatically record incoming sessions", "自動錄製連入的工作階段"), + ("Automatically record outgoing sessions", ""), ("Change", "變更"), ("Start session recording", "開始錄影"), ("Stop session recording", "停止錄影"), diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 2bce8cc8173..97743266cad 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Запис"), ("Directory", "Директорія"), ("Automatically record incoming sessions", "Автоматично записувати вхідні сеанси"), + ("Automatically record outgoing sessions", ""), ("Change", "Змінити"), ("Start session recording", "Розпочати запис сеансу"), ("Stop session recording", "Закінчити запис сеансу"), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 9693d35c3c8..b49aea67bb7 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -364,6 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Đang ghi hình"), ("Directory", "Thư mục"), ("Automatically record incoming sessions", "Tự động ghi những phiên kết nối vào"), + ("Automatically record outgoing sessions", ""), ("Change", "Thay đổi"), ("Start session recording", "Bắt đầu ghi hình phiên kết nối"), ("Stop session recording", "Dừng ghi hình phiên kết nối"), diff --git a/src/server/video_service.rs b/src/server/video_service.rs index aeff1911e01..55bfa08f0e6 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -487,6 +487,8 @@ fn run(vs: VideoService) -> ResultType<()> { let repeat_encode_max = 10; let mut encode_fail_counter = 0; let mut first_frame = true; + let capture_width = c.width; + let capture_height = c.height; while sp.ok() { #[cfg(windows)] @@ -576,6 +578,8 @@ fn run(vs: VideoService) -> ResultType<()> { recorder.clone(), &mut encode_fail_counter, &mut first_frame, + capture_width, + capture_height, )?; frame_controller.set_send(now, send_conn_ids); } @@ -632,6 +636,8 @@ fn run(vs: VideoService) -> ResultType<()> { recorder.clone(), &mut encode_fail_counter, &mut first_frame, + capture_width, + capture_height, )?; frame_controller.set_send(now, send_conn_ids); } @@ -722,7 +728,13 @@ fn setup_encoder( ); Encoder::set_fallback(&encoder_cfg); let codec_format = Encoder::negotiated_codec(); - let recorder = get_recorder(c.width, c.height, &codec_format, record_incoming); + let recorder = get_recorder( + c.width, + c.height, + &codec_format, + record_incoming, + display_idx, + ); let use_i444 = Encoder::use_i444(&encoder_cfg); let encoder = Encoder::new(encoder_cfg.clone(), use_i444)?; Ok((encoder, encoder_cfg, codec_format, use_i444, recorder)) @@ -809,6 +821,7 @@ fn get_recorder( height: usize, codec_format: &CodecFormat, record_incoming: bool, + display: usize, ) -> Arc>> { #[cfg(windows)] let root = crate::platform::is_root(); @@ -828,10 +841,7 @@ fn get_recorder( server: true, id: Config::get_id(), dir: crate::ui_interface::video_save_directory(root), - filename: "".to_owned(), - width, - height, - format: codec_format.clone(), + display, tx, }) .map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r)))) @@ -910,6 +920,8 @@ fn handle_one_frame( recorder: Arc>>, encode_fail_counter: &mut usize, first_frame: &mut bool, + width: usize, + height: usize, ) -> ResultType> { sp.snapshot(|sps| { // so that new sub and old sub share the same encoder after switch @@ -933,7 +945,7 @@ fn handle_one_frame( .lock() .unwrap() .as_mut() - .map(|r| r.write_message(&msg)); + .map(|r| r.write_message(&msg, width, height)); send_conn_ids = sp.send_video_frame(msg); } Err(e) => { diff --git a/src/ui/header.tis b/src/ui/header.tis index 36aa624b48f..3116f1f542f 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -301,26 +301,12 @@ class Header: Reactor.Component { } event click $(span#recording) (_, me) { - recording = !recording; header.update(); - handler.record_status(recording); - // 0 is just a dummy value. It will be ignored by the handler. - if (recording) { - handler.refresh_video(0); - if (handler.version_cmp(pi.version, '1.2.4') >= 0) handler.record_screen(recording, pi.current_display, display_width, display_height); - } - else { - handler.record_screen(recording, pi.current_display, display_width, display_height); - } + handler.record_screen(!recording) } event click $(#screen) (_, me) { if (pi.current_display == me.index) return; - if (recording) { - recording = false; - handler.record_screen(false, pi.current_display, display_width, display_height); - handler.record_status(false); - } handler.switch_display(me.index); } @@ -518,6 +504,7 @@ if (!(is_file_transfer || is_port_forward)) { handler.updatePi = function(v) { pi = v; + recording = handler.is_recording(); header.update(); if (is_port_forward) { view.windowState = View.WINDOW_MINIMIZED; @@ -682,3 +669,8 @@ handler.setConnectionType = function(secured, direct) { direct_connection: direct, }); } + +handler.updateRecordStatus = function(status) { + recording = status; + header.update(); +} \ No newline at end of file diff --git a/src/ui/index.tis b/src/ui/index.tis index b10df76233e..daf2c10201e 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -253,10 +253,12 @@ class Enhancements: Reactor.Component { var root_dir = show_root_dir ? handler.video_save_directory(true) : ""; var ts0 = handler.get_option("enable-record-session") == '' ? { checked: true } : {}; var ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {}; + var ts2 = handler.get_option("allow-auto-record-outgoing") == 'Y' ? { checked: true } : {}; msgbox("custom-recording", translate('Recording'),
    {translate('Enable recording session')}
    {translate('Automatically record incoming sessions')}
    +
    {translate('Automatically record outgoing sessions')}
    {show_root_dir ?
    {translate("Incoming")}:  {root_dir}
    : ""}
    {translate(show_root_dir ? "Outgoing" : "Directory")}:  {user_dir}
    @@ -267,6 +269,7 @@ class Enhancements: Reactor.Component { if (!res) return; handler.set_option("enable-record-session", res.enable_record_session ? '' : 'N'); handler.set_option("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : ''); + handler.set_option("allow-auto-record-outgoing", res.auto_record_outgoing ? 'Y' : ''); handler.set_option("video-save-directory", $(#folderPath).text); }); } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index baf9d1f6451..f0829e75eee 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -335,6 +335,10 @@ impl InvokeUiSession for SciterHandler { } fn next_rgba(&self, _display: usize) {} + + fn update_record_status(&self, start: bool) { + self.call("updateRecordStatus", &make_args!(start)); + } } pub struct SciterSession(Session); @@ -478,8 +482,7 @@ impl sciter::EventHandler for SciterSession { fn save_image_quality(String); fn save_custom_image_quality(i32); fn refresh_video(i32); - fn record_screen(bool, i32, i32, i32); - fn record_status(bool); + fn record_screen(bool); fn get_toggle_option(String); fn is_privacy_mode_supported(); fn toggle_option(String); @@ -496,6 +499,7 @@ impl sciter::EventHandler for SciterSession { fn close_voice_call(); fn version_cmp(String, String); fn set_selected_windows_session_id(String); + fn is_recording(); } } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index ce28b78d8e9..00e9459db47 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -389,22 +389,17 @@ impl Session { self.send(Data::Message(LoginConfigHandler::refresh())); } - pub fn record_screen(&self, start: bool, display: i32, w: i32, h: i32) { - self.send(Data::RecordScreen( - start, - display as usize, - w, - h, - self.get_id(), - )); - } - - pub fn record_status(&self, status: bool) { + pub fn record_screen(&self, start: bool) { let mut misc = Misc::new(); - misc.set_client_record_status(status); + misc.set_client_record_status(start); let mut msg = Message::new(); msg.set_misc(misc); self.send(Data::Message(msg)); + self.send(Data::RecordScreen(start)); + } + + pub fn is_recording(&self) -> bool { + self.lc.read().unwrap().record } pub fn save_custom_image_quality(&self, custom_image_quality: i32) { @@ -1557,6 +1552,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn set_current_display(&self, disp_idx: i32); #[cfg(feature = "flutter")] fn is_multi_ui_session(&self) -> bool; + fn update_record_status(&self, start: bool); } impl Deref for Session { From 6088920f8dcb546572fd6f0bd68eb1385a8ceb0d Mon Sep 17 00:00:00 2001 From: Andrzej Rudnik Date: Mon, 21 Oct 2024 13:55:34 +0200 Subject: [PATCH 193/210] Update pl.rs (#9713) * Update pl.rs * Update pl.rs Updated quota chars. --- src/lang/pl.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lang/pl.rs b/src/lang/pl.rs index caf992978c9..7ec3572e813 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -364,7 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Nagrywanie"), ("Directory", "Folder"), ("Automatically record incoming sessions", "Automatycznie nagrywaj sesje przychodzące"), - ("Automatically record outgoing sessions", ""), + ("Automatically record outgoing sessions", "Automatycznie nagrywaj sesje wychodzące"), ("Change", "Zmień"), ("Start session recording", "Zacznij nagrywać sesję"), ("Stop session recording", "Zatrzymaj nagrywanie sesji"), @@ -645,13 +645,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Parent directory", "Folder nadrzędny"), ("Resume", "Wznów"), ("Invalid file name", "Nieprawidłowa nazwa pliku"), - ("one-way-file-transfer-tip", ""), - ("Authentication Required", ""), - ("Authenticate", ""), - ("web_id_input_tip", ""), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), - ("Clipboard is synchronized", ""), + ("one-way-file-transfer-tip", "Jednokierunkowy transfer plików jest włączony po stronie kontrolowanej."), + ("Authentication Required", "Wymagana autoryzacja"), + ("Authenticate", "Uwierzytelnienie"), + ("web_id_input_tip", "Jeśli chcesz uzyskać dostęp do urządzenia na innym serwerze, dodaj adres serwera (@?key=) na przykład, \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJeśli chcesz uzyskać dostęp do urządzenia na serwerze publicznym, wprowadź \"@public\", klucz nie jest wymagany dla serwera publicznego."), + ("Download", "Pobierz"), + ("Upload folder", "Wyślij folder"), + ("Upload files", "Wyślij pliki"), + ("Clipboard is synchronized", "Schowek jest zsynchronizowany"), ].iter().cloned().collect(); } From 6159449eba86a9185accec5cc8aee423efdc8382 Mon Sep 17 00:00:00 2001 From: 21pages Date: Tue, 22 Oct 2024 08:24:52 +0800 Subject: [PATCH 194/210] move option `video-save-directory` and `allow-auto-record-outgoing` to local (#9715) Signed-off-by: 21pages --- .../desktop/pages/desktop_setting_page.dart | 5 +++-- flutter/lib/mobile/pages/settings_page.dart | 6 +++--- libs/hbb_common/src/config.rs | 19 +++++++++++++++++-- src/client.rs | 2 +- src/ipc.rs | 2 +- src/ui/index.tis | 6 +++--- src/ui_interface.rs | 8 ++++++-- 7 files changed, 34 insertions(+), 14 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 766c160e042..15cf2173b5e 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -580,7 +580,8 @@ class _GeneralState extends State<_General> { kOptionAllowAutoRecordIncoming), if (!bind.isIncomingOnly()) _OptionCheckBox(context, 'Automatically record outgoing sessions', - kOptionAllowAutoRecordOutgoing), + kOptionAllowAutoRecordOutgoing, + isServer: false), if (showRootDir && !bind.isOutgoingOnly()) Row( children: [ @@ -635,7 +636,7 @@ class _GeneralState extends State<_General> { await FilePicker.platform.getDirectoryPath( initialDirectory: initialDirectory); if (selectedDirectory != null) { - await bind.mainSetOption( + await bind.mainSetLocalOption( key: kOptionVideoSaveDirectory, value: selectedDirectory); setState(() {}); diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index e70ee5b35c7..eb386593314 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -106,7 +106,7 @@ class _SettingsState extends State with WidgetsBindingObserver { _autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming, bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming)); _autoRecordOutgoingSession = option2bool(kOptionAllowAutoRecordOutgoing, - bind.mainGetOptionSync(key: kOptionAllowAutoRecordOutgoing)); + bind.mainGetLocalOption(key: kOptionAllowAutoRecordOutgoing)); _localIP = bind.mainGetOptionSync(key: 'local-ip-addr'); _directAccessPort = bind.mainGetOptionSync(key: kOptionDirectAccessPort); _allowAutoDisconnect = option2bool(kOptionAllowAutoDisconnect, @@ -711,13 +711,13 @@ class _SettingsState extends State with WidgetsBindingObserver { onToggle: isOptionFixed(kOptionAllowAutoRecordOutgoing) ? null : (v) async { - await bind.mainSetOption( + await bind.mainSetLocalOption( key: kOptionAllowAutoRecordOutgoing, value: bool2option( kOptionAllowAutoRecordOutgoing, v)); final newValue = option2bool( kOptionAllowAutoRecordOutgoing, - await bind.mainGetOption( + bind.mainGetLocalOption( key: kOptionAllowAutoRecordOutgoing)); setState(() { _autoRecordOutgoingSession = newValue; diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 94f4ec9d988..0cb370cd812 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1562,6 +1562,21 @@ impl LocalConfig { .unwrap_or_default() } + // Usually get_option should be used. + pub fn get_option_from_file(k: &str) -> String { + get_or( + &OVERWRITE_LOCAL_SETTINGS, + &Self::load().options, + &DEFAULT_LOCAL_SETTINGS, + k, + ) + .unwrap_or_default() + } + + pub fn get_bool_option(k: &str) -> bool { + option2bool(k, &Self::get_option(k)) + } + pub fn set_option(k: String, v: String) { if !is_option_can_save(&OVERWRITE_LOCAL_SETTINGS, &k, &DEFAULT_LOCAL_SETTINGS, &v) { return; @@ -2326,6 +2341,8 @@ pub mod keys { OPTION_DISABLE_GROUP_PANEL, OPTION_PRE_ELEVATE_SERVICE, OPTION_ALLOW_REMOTE_CM_MODIFICATION, + OPTION_ALLOW_AUTO_RECORD_OUTGOING, + OPTION_VIDEO_SAVE_DIRECTORY, ]; // DEFAULT_SETTINGS, OVERWRITE_SETTINGS pub const KEYS_SETTINGS: &[&str] = &[ @@ -2347,8 +2364,6 @@ pub mod keys { OPTION_AUTO_DISCONNECT_TIMEOUT, OPTION_ALLOW_ONLY_CONN_WINDOW_OPEN, OPTION_ALLOW_AUTO_RECORD_INCOMING, - OPTION_ALLOW_AUTO_RECORD_OUTGOING, - OPTION_VIDEO_SAVE_DIRECTORY, OPTION_ENABLE_ABR, OPTION_ALLOW_REMOVE_WALLPAPER, OPTION_ALLOW_ALWAYS_SOFTWARE_RENDER, diff --git a/src/client.rs b/src/client.rs index a1b2b83d436..4b86d189b0c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1442,7 +1442,7 @@ impl LoginConfigHandler { self.adapter_luid = adapter_luid; self.selected_windows_session_id = None; self.shared_password = shared_password; - self.record = Config::get_bool_option(OPTION_ALLOW_AUTO_RECORD_OUTGOING); + self.record = LocalConfig::get_bool_option(OPTION_ALLOW_AUTO_RECORD_OUTGOING); } /// Check if the client should auto login. diff --git a/src/ipc.rs b/src/ipc.rs index c7243d82170..81693a73558 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -890,7 +890,7 @@ pub async fn set_data(data: &Data) -> ResultType<()> { set_data_async(data).await } -pub async fn set_data_async(data: &Data) -> ResultType<()> { +async fn set_data_async(data: &Data) -> ResultType<()> { let mut c = connect(1000, "").await?; c.send(data).await?; Ok(()) diff --git a/src/ui/index.tis b/src/ui/index.tis index daf2c10201e..3ae54637f4b 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -253,7 +253,7 @@ class Enhancements: Reactor.Component { var root_dir = show_root_dir ? handler.video_save_directory(true) : ""; var ts0 = handler.get_option("enable-record-session") == '' ? { checked: true } : {}; var ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {}; - var ts2 = handler.get_option("allow-auto-record-outgoing") == 'Y' ? { checked: true } : {}; + var ts2 = handler.get_local_option("allow-auto-record-outgoing") == 'Y' ? { checked: true } : {}; msgbox("custom-recording", translate('Recording'),
    {translate('Enable recording session')}
    @@ -269,8 +269,8 @@ class Enhancements: Reactor.Component { if (!res) return; handler.set_option("enable-record-session", res.enable_record_session ? '' : 'N'); handler.set_option("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : ''); - handler.set_option("allow-auto-record-outgoing", res.auto_record_outgoing ? 'Y' : ''); - handler.set_option("video-save-directory", $(#folderPath).text); + handler.set_local_option("allow-auto-record-outgoing", res.auto_record_outgoing ? 'Y' : ''); + handler.set_local_option("video-save-directory", $(#folderPath).text); }); } this.toggleMenuState(); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index e9f2875be5d..bab54c79a3c 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -212,7 +212,7 @@ pub fn get_builtin_option(key: &str) -> String { #[inline] pub fn set_local_option(key: String, value: String) { - LocalConfig::set_option(key, value); + LocalConfig::set_option(key.clone(), value.clone()); } #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] @@ -844,7 +844,11 @@ pub fn video_save_directory(root: bool) -> String { return dir.to_string_lossy().to_string(); } } - let dir = Config::get_option("video-save-directory"); + // Get directory from config file otherwise --server will use the old value from global var. + #[cfg(any(target_os = "linux", target_os = "macos"))] + let dir = LocalConfig::get_option_from_file(OPTION_VIDEO_SAVE_DIRECTORY); + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + let dir = LocalConfig::get_option(OPTION_VIDEO_SAVE_DIRECTORY); if !dir.is_empty() { return dir; } From 2cdaca0fa35670ac604a5e5316fe185cdc471d03 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:20:35 +0200 Subject: [PATCH 195/210] Update Italian language (#9718) --- src/lang/it.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 4f85db09a15..426cb7c901c 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -363,8 +363,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Unpin Toolbar", "Sblocca barra strumenti"), ("Recording", "Registrazione"), ("Directory", "Cartella"), - ("Automatically record incoming sessions", "Registra automaticamente le sessioni in entrata"), - ("Automatically record outgoing sessions", ""), + ("Automatically record incoming sessions", "Registra automaticamente sessioni in entrata"), + ("Automatically record outgoing sessions", "Registra automaticamente sessioni in uscita"), ("Change", "Modifica"), ("Start session recording", "Inizia registrazione sessione"), ("Stop session recording", "Ferma registrazione sessione"), From cc6f919080e541eeb1eecd32765dd5d68d3f69ae Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 23 Oct 2024 09:28:39 +0800 Subject: [PATCH 196/210] feat: mobile map mode (#9717) Signed-off-by: fufesou --- Cargo.lock | 2 +- flutter/lib/models/input_model.dart | 3 +- src/flutter_ffi.rs | 4 ++ src/keyboard.rs | 62 ++++++++++++++++++----------- src/ui_session_interface.rs | 37 ++++++++--------- 5 files changed, 64 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 923325c4ea7..7565d34eed6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5205,7 +5205,7 @@ dependencies = [ [[package]] name = "rdev" version = "0.5.0-2" -source = "git+https://github.com/rustdesk-org/rdev#d4c1759926d693ba269e2cb8cf9f87b13e424e4e" +source = "git+https://github.com/rustdesk-org/rdev#961d25cc00c6b3ef80f444e6a7bed9872e2c35ea" dependencies = [ "cocoa 0.24.1", "core-foundation 0.9.4", diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index b57048a08e4..c7e1e6131c3 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -544,8 +544,7 @@ class InputModel { handleKeyDownEventModifiers(e); } - // * Currently mobile does not enable map mode - if ((isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) { + if (isMobile || (isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) { // FIXME: e.character is wrong for dead keys, eg: ^ in de newKeyboardMode( e.character ?? '', diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 979e25e8d26..5dfcebacc8f 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -222,6 +222,10 @@ pub fn session_get_enable_trusted_devices(session_id: SessionID) -> SyncReturn) { let keyboard_mode = get_keyboard_mode_enum(keyboard_mode); - if is_long_press(&event) { return; } - - for key_event in event_to_key_events(&event, keyboard_mode, lock_modes) { + let peer = get_peer_platform().to_lowercase(); + for key_event in event_to_key_events(peer, &event, keyboard_mode, lock_modes) { send_key_event(&key_event); } } + pub fn process_event_with_session( + keyboard_mode: &str, + event: &Event, + lock_modes: Option, + session: &Session, + ) { + let keyboard_mode = get_keyboard_mode_enum(keyboard_mode); + if is_long_press(&event) { + return; + } + let peer = session.peer_platform().to_lowercase(); + for key_event in event_to_key_events(peer, &event, keyboard_mode, lock_modes) { + session.send_key_event(&key_event); + } + } + pub fn get_modifiers_state( alt: bool, ctrl: bool, @@ -171,6 +187,7 @@ pub mod client { } } + #[cfg(target_os = "android")] pub fn map_key_to_control_key(key: &rdev::Key) -> Option { match key { Key::Alt => Some(ControlKey::Alt), @@ -358,7 +375,6 @@ pub fn is_long_press(event: &Event) -> bool { return false; } -#[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn release_remote_keys(keyboard_mode: &str) { // todo!: client quit suddenly, how to release keys? let to_release = TO_RELEASE.lock().unwrap().clone(); @@ -387,7 +403,6 @@ pub fn get_keyboard_mode_enum(keyboard_mode: &str) -> KeyboardMode { } #[inline] -#[cfg(not(any(target_os = "ios")))] pub fn is_modifier(key: &rdev::Key) -> bool { matches!( key, @@ -403,7 +418,6 @@ pub fn is_modifier(key: &rdev::Key) -> bool { } #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn is_numpad_rdev_key(key: &rdev::Key) -> bool { matches!( key, @@ -426,7 +440,6 @@ pub fn is_numpad_rdev_key(key: &rdev::Key) -> bool { } #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn is_letter_rdev_key(key: &rdev::Key) -> bool { matches!( key, @@ -462,7 +475,6 @@ pub fn is_letter_rdev_key(key: &rdev::Key) -> bool { // https://github.com/rustdesk/rustdesk/issues/8599 // We just add these keys as letter keys. #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn is_letter_rdev_key_ex(key: &rdev::Key) -> bool { matches!( key, @@ -471,7 +483,6 @@ pub fn is_letter_rdev_key_ex(key: &rdev::Key) -> bool { } #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] fn is_numpad_key(event: &Event) -> bool { matches!(event.event_type, EventType::KeyPress(key) | EventType::KeyRelease(key) if is_numpad_rdev_key(&key)) } @@ -479,12 +490,10 @@ fn is_numpad_key(event: &Event) -> bool { // Check is letter key for lock modes. // Only letter keys need to check and send Lock key state. #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] fn is_letter_key_4_lock_modes(event: &Event) -> bool { matches!(event.event_type, EventType::KeyPress(key) | EventType::KeyRelease(key) if (is_letter_rdev_key(&key) || is_letter_rdev_key_ex(&key))) } -#[cfg(not(any(target_os = "android", target_os = "ios")))] fn parse_add_lock_modes_modifiers( key_event: &mut KeyEvent, lock_modes: i32, @@ -555,10 +564,13 @@ fn update_modifiers_state(event: &Event) { } pub fn event_to_key_events( + mut peer: String, event: &Event, keyboard_mode: KeyboardMode, _lock_modes: Option, ) -> Vec { + peer.retain(|c| !c.is_whitespace()); + let mut key_event = KeyEvent::new(); update_modifiers_state(event); @@ -572,16 +584,9 @@ pub fn event_to_key_events( _ => {} } - let mut peer = get_peer_platform().to_lowercase(); - peer.retain(|c| !c.is_whitespace()); - key_event.mode = keyboard_mode.into(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let mut key_events; - #[cfg(any(target_os = "android", target_os = "ios"))] - let key_events; - key_events = match keyboard_mode { + let mut key_events = match keyboard_mode { KeyboardMode::Map => map_keyboard_mode(peer.as_str(), event, key_event), KeyboardMode::Translate => translate_keyboard_mode(peer.as_str(), event, key_event), _ => { @@ -596,15 +601,14 @@ pub fn event_to_key_events( } }; - #[cfg(not(any(target_os = "android", target_os = "ios")))] let is_numpad_key = is_numpad_key(&event); - #[cfg(not(any(target_os = "android", target_os = "ios")))] if keyboard_mode != KeyboardMode::Translate || is_numpad_key { let is_letter_key = is_letter_key_4_lock_modes(&event); for key_event in &mut key_events { if let Some(lock_modes) = _lock_modes { parse_add_lock_modes_modifiers(key_event, lock_modes, is_numpad_key, is_letter_key); } else { + #[cfg(not(any(target_os = "android", target_os = "ios")))] add_lock_modes_modifiers(key_event, is_numpad_key, is_letter_key); } } @@ -617,6 +621,7 @@ pub fn send_key_event(key_event: &KeyEvent) { if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() { session.send_key_event(key_event); } + #[cfg(feature = "flutter")] if let Some(session) = flutter::get_cur_session() { session.send_key_event(key_event); @@ -936,8 +941,19 @@ fn _map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Op _ => event.position_code as _, }; #[cfg(any(target_os = "android", target_os = "ios"))] - let keycode = 0; - + let keycode = match _peer { + OS_LOWER_WINDOWS => rdev::usb_hid_code_to_win_scancode(event.usb_hid as _)?, + OS_LOWER_LINUX => rdev::usb_hid_code_to_linux_code(event.usb_hid as _)?, + OS_LOWER_MACOS => { + if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" { + rdev::usb_hid_code_to_macos_iso_code(event.usb_hid as _)? + } else { + rdev::usb_hid_code_to_macos_code(event.usb_hid as _)? + } + } + OS_LOWER_ANDROID => rdev::usb_hid_code_to_android_key_code(event.usb_hid as _)?, + _ => event.usb_hid as _, + }; key_event.set_chr(keycode as _); Some(key_event) } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 00e9459db47..4160561be71 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -861,24 +861,13 @@ impl Session { platform_code, position_code: position_code as _, event_type, + usb_hid: 0, #[cfg(any(target_os = "windows", target_os = "macos"))] extra_data: 0, }; - keyboard::client::process_event(keyboard_mode, &event, Some(lock_modes)); + keyboard::client::process_event_with_session(keyboard_mode, &event, Some(lock_modes), self); } - #[cfg(any(target_os = "ios"))] - pub fn handle_flutter_key_event( - &self, - _keyboard_mode: &str, - _character: &str, - _usb_hid: i32, - _lock_modes: i32, - _down_or_up: bool, - ) { - } - - #[cfg(not(any(target_os = "ios")))] pub fn handle_flutter_key_event( &self, keyboard_mode: &str, @@ -900,7 +889,6 @@ impl Session { } } - #[cfg(not(any(target_os = "ios")))] fn _handle_key_flutter_simulation( &self, _keyboard_mode: &str, @@ -925,7 +913,6 @@ impl Session { self.send_key_event(&key_event); } - #[cfg(not(any(target_os = "ios")))] fn _handle_key_non_flutter_simulation( &self, keyboard_mode: &str, @@ -936,14 +923,24 @@ impl Session { ) { let key = rdev::usb_hid_key_from_code(usb_hid as _); + #[cfg(any(target_os = "android", target_os = "ios"))] + let position_code: KeyCode = 0; + #[cfg(any(target_os = "android", target_os = "ios"))] + let platform_code: KeyCode = 0; + #[cfg(target_os = "windows")] let platform_code: u32 = rdev::win_code_from_key(key).unwrap_or(0); #[cfg(target_os = "windows")] let position_code: KeyCode = rdev::win_scancode_from_key(key).unwrap_or(0) as _; - #[cfg(not(target_os = "windows"))] + #[cfg(not(any(target_os = "windows", target_os = "android", target_os = "ios")))] let position_code: KeyCode = rdev::code_from_key(key).unwrap_or(0) as _; - #[cfg(not(any(target_os = "windows", target_os = "linux")))] + #[cfg(not(any( + target_os = "windows", + target_os = "android", + target_os = "ios", + target_os = "linux" + )))] let platform_code: u32 = position_code as _; // For translate mode. // We need to set the platform code (keysym) if is AltGr. @@ -972,10 +969,14 @@ impl Session { platform_code, position_code: position_code as _, event_type, + #[cfg(any(target_os = "android", target_os = "ios"))] + usb_hid: usb_hid as _, + #[cfg(not(any(target_os = "android", target_os = "ios")))] + usb_hid: 0, #[cfg(any(target_os = "windows", target_os = "macos"))] extra_data: 0, }; - keyboard::client::process_event(keyboard_mode, &event, Some(lock_modes)); + keyboard::client::process_event_with_session(keyboard_mode, &event, Some(lock_modes), self); } // flutter only TODO new input From dfa9519d5860fa4fcea6a7ea2bafe9049cc72f70 Mon Sep 17 00:00:00 2001 From: Alex Rijckaert Date: Wed, 23 Oct 2024 12:13:05 +0200 Subject: [PATCH 197/210] Update nl.rs (#9726) --- src/lang/nl.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 2728f2edc89..76f23f04249 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -649,9 +649,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Authentication Required", "Verificatie vereist"), ("Authenticate", "Verificatie"), ("web_id_input_tip", "Je kunt een ID invoeren op dezelfde server, directe IP-toegang wordt niet ondersteund in de webclient.\nAls je toegang wilt tot een apparaat op een andere server, voeg je het serveradres toe (@?key=), bijvoorbeeld,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nAls je toegang wilt krijgen tot een apparaat op een publieke server, voer dan \"@public\" in, sleutel is niet nodig voor de publieke server."), - ("Download", ""), - ("Upload folder", ""), - ("Upload files", ""), - ("Clipboard is synchronized", ""), + ("Download", "Downloaden"), + ("Upload folder", "Map uploaden"), + ("Upload files", "Bestanden uploaden"), + ("Clipboard is synchronized", "Klembord is gesynchroniseerd"), ].iter().cloned().collect(); } From 7a3e1fe64861b60b545adf7ba7bffedf7d2a0787 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:33:37 +0800 Subject: [PATCH 198/210] fix: ->macos, mouse events, key flags (#9733) * fix: win->macos, mouse events, key flags Signed-off-by: fufesou * comments Signed-off-by: fufesou --------- Signed-off-by: fufesou --- libs/enigo/src/macos/macos_impl.rs | 11 +++++++++- src/server/input_service.rs | 35 ++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/libs/enigo/src/macos/macos_impl.rs b/libs/enigo/src/macos/macos_impl.rs index a1f5d2e4a9f..e7d7d9e8d33 100644 --- a/libs/enigo/src/macos/macos_impl.rs +++ b/libs/enigo/src/macos/macos_impl.rs @@ -111,11 +111,17 @@ pub struct Enigo { double_click_interval: u32, last_click_time: Option, multiple_click: i64, + ignore_flags: bool, flags: CGEventFlags, char_to_vkey_map: Map>, } impl Enigo { + /// Set if ignore flags when posting events. + pub fn set_ignore_flags(&mut self, ignore: bool) { + self.ignore_flags = ignore; + } + /// pub fn reset_flag(&mut self) { self.flags = CGEventFlags::CGEventFlagNull; @@ -136,7 +142,9 @@ impl Enigo { } fn post(&self, event: CGEvent) { - event.set_flags(self.flags); + if !self.ignore_flags { + event.set_flags(self.flags); + } event.set_integer_value_field(EventField::EVENT_SOURCE_USER_DATA, ENIGO_INPUT_EXTRA_VALUE); event.post(CGEventTapLocation::HID); } @@ -164,6 +172,7 @@ impl Default for Enigo { double_click_interval, multiple_click: 1, last_click_time: None, + ignore_flags: false, flags: CGEventFlags::CGEventFlagNull, char_to_vkey_map: Default::default(), } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index afab184abe9..3189520be0a 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -453,6 +453,17 @@ const MOUSE_ACTIVE_DISTANCE: i32 = 5; static RECORD_CURSOR_POS_RUNNING: AtomicBool = AtomicBool::new(false); +// https://github.com/rustdesk/rustdesk/issues/9729 +// We need to do some special handling for macOS when using the legacy mode. +#[cfg(target_os = "macos")] +static LAST_KEY_LEGACY_MODE: AtomicBool = AtomicBool::new(false); +// We use enigo to simulate mouse events. Only the legacy mode uses the key flags. +#[inline] +#[cfg(target_os = "macos")] +fn enigo_ignore_flags() -> bool { + !LAST_KEY_LEGACY_MODE.load(Ordering::SeqCst) +} + pub fn try_start_record_cursor_pos() -> Option> { if RECORD_CURSOR_POS_RUNNING.load(Ordering::SeqCst) { return None; @@ -505,6 +516,19 @@ impl VirtualInputState { fn new() -> Option { VirtualInput::new( CGEventSourceStateID::CombinedSessionState, + // Note: `CGEventTapLocation::Session` will be affected by the mouse events. + // When we're simulating key events, then move the physical mouse, the key events will be affected. + // It looks like https://github.com/rustdesk/rustdesk/issues/9729#issuecomment-2432306822 + // 1. Press "Command" key in RustDesk + // 2. Move the physical mouse + // 3. Press "V" key in RustDesk + // Then the controlled side just prints "v" instead of pasting. + // + // Changing `CGEventTapLocation::Session` to `CGEventTapLocation::HID` fixes it. + // But we do not consider this as a bug, because it's not a common case, + // we consider only RustDesk operates the controlled side. + // + // https://developer.apple.com/documentation/coregraphics/cgeventtaplocation/ CGEventTapLocation::Session, ) .map(|virtual_input| Self { @@ -945,6 +969,8 @@ pub fn handle_mouse_(evt: &MouseEvent, conn: i32) { let buttons = evt.mask >> 3; let evt_type = evt.mask & 0x7; let mut en = ENIGO.lock().unwrap(); + #[cfg(target_os = "macos")] + en.set_ignore_flags(enigo_ignore_flags()); #[cfg(not(target_os = "macos"))] let mut to_release = Vec::new(); if evt_type == MOUSE_TYPE_DOWN { @@ -1662,8 +1688,7 @@ pub fn handle_key_(evt: &KeyEvent) { let is_numpad_key = false; #[cfg(any(target_os = "windows", target_os = "linux"))] let is_numpad_key = crate::keyboard::is_numpad_rdev_key(&key); - _lock_mode_handler = - Some(LockModesHandler::new_handler(evt, is_numpad_key)); + _lock_mode_handler = Some(LockModesHandler::new_handler(evt, is_numpad_key)); } } } @@ -1672,12 +1697,18 @@ pub fn handle_key_(evt: &KeyEvent) { match evt.mode.enum_value() { Ok(KeyboardMode::Map) => { + #[cfg(target_os = "macos")] + LAST_KEY_LEGACY_MODE.store(false, Ordering::SeqCst); map_keyboard_mode(evt); } Ok(KeyboardMode::Translate) => { + #[cfg(target_os = "macos")] + LAST_KEY_LEGACY_MODE.store(false, Ordering::SeqCst); translate_keyboard_mode(evt); } _ => { + #[cfg(target_os = "macos")] + LAST_KEY_LEGACY_MODE.store(true, Ordering::SeqCst); legacy_keyboard_mode(evt); } } From 445e9ac2853ed20113bf2d186ff010b959f992c7 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 24 Oct 2024 17:20:48 +0800 Subject: [PATCH 199/210] no password required for file transfer action in remote control menu (#9731) Signed-off-by: 21pages --- flutter/lib/common.dart | 5 + flutter/lib/common/widgets/toolbar.dart | 16 +- .../lib/desktop/pages/desktop_home_page.dart | 1 + .../lib/desktop/pages/file_manager_page.dart | 3 + .../desktop/pages/file_manager_tab_page.dart | 4 +- .../lib/desktop/pages/port_forward_page.dart | 3 + .../desktop/pages/port_forward_tab_page.dart | 2 + .../lib/desktop/pages/remote_tab_page.dart | 2 +- flutter/lib/models/model.dart | 4 +- flutter/lib/utils/multi_window_manager.dart | 25 ++- src/client.rs | 41 ++++- src/flutter.rs | 2 + src/flutter_ffi.rs | 10 ++ src/server/connection.rs | 148 +++++++++++++----- src/ui/remote.rs | 4 +- src/ui_session_interface.rs | 4 + 16 files changed, 222 insertions(+), 52 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 1d59d0202d8..099a04a1569 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2304,16 +2304,19 @@ connectMainDesktop(String id, required bool isRDP, bool? forceRelay, String? password, + String? connToken, bool? isSharedPassword}) async { if (isFileTransfer) { await rustDeskWinManager.newFileTransfer(id, password: password, isSharedPassword: isSharedPassword, + connToken: connToken, forceRelay: forceRelay); } else if (isTcpTunneling || isRDP) { await rustDeskWinManager.newPortForward(id, isRDP, password: password, isSharedPassword: isSharedPassword, + connToken: connToken, forceRelay: forceRelay); } else { await rustDeskWinManager.newRemoteDesktop(id, @@ -2333,6 +2336,7 @@ connect(BuildContext context, String id, bool isRDP = false, bool forceRelay = false, String? password, + String? connToken, bool? isSharedPassword}) async { if (id == '') return; if (!isDesktop || desktopType == DesktopType.main) { @@ -2374,6 +2378,7 @@ connect(BuildContext context, String id, 'password': password, 'isSharedPassword': isSharedPassword, 'forceRelay': forceRelay, + 'connToken': connToken, }); } } else { diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 0b56d9f4c14..269d7904d1a 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -147,12 +147,23 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { child: Text(translate('Reset canvas')), onPressed: () => ffi.cursorModel.reset())); } + + connectWithToken( + {required bool isFileTransfer, required bool isTcpTunneling}) { + final connToken = bind.sessionGetConnToken(sessionId: ffi.sessionId); + connect(context, id, + isFileTransfer: isFileTransfer, + isTcpTunneling: isTcpTunneling, + connToken: connToken); + } + // transferFile if (isDesktop) { v.add( TTextMenu( child: Text(translate('Transfer file')), - onPressed: () => connect(context, id, isFileTransfer: true)), + onPressed: () => + connectWithToken(isFileTransfer: true, isTcpTunneling: false)), ); } // tcpTunneling @@ -160,7 +171,8 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { v.add( TTextMenu( child: Text(translate('TCP tunneling')), - onPressed: () => connect(context, id, isTcpTunneling: true)), + onPressed: () => + connectWithToken(isFileTransfer: false, isTcpTunneling: true)), ); } // note diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 90fa67dedde..493e4ca47bb 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -774,6 +774,7 @@ class _DesktopHomePageState extends State isRDP: call.arguments['isRDP'], password: call.arguments['password'], forceRelay: call.arguments['forceRelay'], + connToken: call.arguments['connToken'], ); } else if (call.method == kWindowEventMoveTabToNewWindow) { final args = call.arguments.split(','); diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index ba1a37fb154..90b8d7dcbf3 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -58,12 +58,14 @@ class FileManagerPage extends StatefulWidget { required this.password, required this.isSharedPassword, this.tabController, + this.connToken, this.forceRelay}) : super(key: key); final String id; final String? password; final bool? isSharedPassword; final bool? forceRelay; + final String? connToken; final DesktopTabController? tabController; @override @@ -90,6 +92,7 @@ class _FileManagerPageState extends State isFileTransfer: true, password: widget.password, isSharedPassword: widget.isSharedPassword, + connToken: widget.connToken, forceRelay: widget.forceRelay); WidgetsBinding.instance.addPostFrameCallback((_) { _ffi.dialogManager diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index ca17ac3ff06..cc77cdd9581 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -48,6 +48,7 @@ class _FileManagerTabPageState extends State { isSharedPassword: params['isSharedPassword'], tabController: tabController, forceRelay: params['forceRelay'], + connToken: params['connToken'], ))); } @@ -56,7 +57,7 @@ class _FileManagerTabPageState extends State { super.initState(); rustDeskWinManager.setMethodHandler((call, fromWindowId) async { - print( + debugPrint( "[FileTransfer] call ${call.method} with args ${call.arguments} from window $fromWindowId to ${windowId()}"); // for simplify, just replace connectionId if (call.method == kWindowEventNewFileTransfer) { @@ -76,6 +77,7 @@ class _FileManagerTabPageState extends State { isSharedPassword: args['isSharedPassword'], tabController: tabController, forceRelay: args['forceRelay'], + connToken: args['connToken'], ))); } else if (call.method == "onDestroy") { tabController.clear(); diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart index 5541cb8b33b..d6d243c5026 100644 --- a/flutter/lib/desktop/pages/port_forward_page.dart +++ b/flutter/lib/desktop/pages/port_forward_page.dart @@ -33,6 +33,7 @@ class PortForwardPage extends StatefulWidget { required this.isRDP, required this.isSharedPassword, this.forceRelay, + this.connToken, }) : super(key: key); final String id; final String? password; @@ -40,6 +41,7 @@ class PortForwardPage extends StatefulWidget { final bool isRDP; final bool? forceRelay; final bool? isSharedPassword; + final String? connToken; @override State createState() => _PortForwardPageState(); @@ -62,6 +64,7 @@ class _PortForwardPageState extends State password: widget.password, isSharedPassword: widget.isSharedPassword, forceRelay: widget.forceRelay, + connToken: widget.connToken, isRdp: widget.isRDP); Get.put(_ffi, tag: 'pf_${widget.id}'); debugPrint("Port forward page init success with id ${widget.id}"); diff --git a/flutter/lib/desktop/pages/port_forward_tab_page.dart b/flutter/lib/desktop/pages/port_forward_tab_page.dart index 812f7aa99ad..f399f7cab68 100644 --- a/flutter/lib/desktop/pages/port_forward_tab_page.dart +++ b/flutter/lib/desktop/pages/port_forward_tab_page.dart @@ -48,6 +48,7 @@ class _PortForwardTabPageState extends State { tabController: tabController, isRDP: isRDP, forceRelay: params['forceRelay'], + connToken: params['connToken'], ))); } @@ -82,6 +83,7 @@ class _PortForwardTabPageState extends State { isRDP: isRDP, tabController: tabController, forceRelay: args['forceRelay'], + connToken: args['connToken'], ))); } else if (call.method == "onDestroy") { tabController.clear(); diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index dc0153da0f4..efd437e1ff7 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -395,7 +395,7 @@ class _ConnectionTabPageState extends State { RemoteCountState.find().value = tabController.length; Future _remoteMethodHandler(call, fromWindowId) async { - print( + debugPrint( "[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId"); dynamic returnValue; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index f0e4cd75f9e..8bd0530f13c 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -375,7 +375,7 @@ class FfiModel with ChangeNotifier { } else if (name == 'plugin_option') { handleOption(evt); } else if (name == "sync_peer_hash_password_to_personal_ab") { - if (desktopType == DesktopType.main || isWeb) { + if (desktopType == DesktopType.main || isWeb || isMobile) { final id = evt['id']; final hash = evt['hash']; if (id != null && hash != null) { @@ -2462,6 +2462,7 @@ class FFI { String? switchUuid, String? password, bool? isSharedPassword, + String? connToken, bool? forceRelay, int? tabWindowId, int? display, @@ -2498,6 +2499,7 @@ class FFI { forceRelay: forceRelay ?? false, password: password ?? '', isSharedPassword: isSharedPassword ?? false, + connToken: connToken, ); } else if (display != null) { if (displays == null) { diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index fa35b4fe971..70001ffdff4 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -201,6 +201,7 @@ class RustDeskMultiWindowManager { String? switchUuid, bool? isRDP, bool? isSharedPassword, + String? connToken, }) async { var params = { "type": type.index, @@ -217,6 +218,9 @@ class RustDeskMultiWindowManager { if (isSharedPassword != null) { params['isSharedPassword'] = isSharedPassword; } + if (connToken != null) { + params['connToken'] = connToken; + } final msg = jsonEncode(params); // separate window for file transfer is not supported @@ -254,8 +258,13 @@ class RustDeskMultiWindowManager { ); } - Future newFileTransfer(String remoteId, - {String? password, bool? isSharedPassword, bool? forceRelay}) async { + Future newFileTransfer( + String remoteId, { + String? password, + bool? isSharedPassword, + bool? forceRelay, + String? connToken, + }) async { return await newSession( WindowType.FileTransfer, kWindowEventNewFileTransfer, @@ -264,11 +273,18 @@ class RustDeskMultiWindowManager { password: password, forceRelay: forceRelay, isSharedPassword: isSharedPassword, + connToken: connToken, ); } - Future newPortForward(String remoteId, bool isRDP, - {String? password, bool? isSharedPassword, bool? forceRelay}) async { + Future newPortForward( + String remoteId, + bool isRDP, { + String? password, + bool? isSharedPassword, + bool? forceRelay, + String? connToken, + }) async { return await newSession( WindowType.PortForward, kWindowEventNewPortForward, @@ -278,6 +294,7 @@ class RustDeskMultiWindowManager { forceRelay: forceRelay, isRDP: isRDP, isSharedPassword: isSharedPassword, + connToken: connToken, ); } diff --git a/src/client.rs b/src/client.rs index 4b86d189b0c..0b5293d22ce 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,6 +11,7 @@ use crossbeam_queue::ArrayQueue; use magnum_opus::{Channels::*, Decoder as AudioDecoder}; #[cfg(not(any(target_os = "android", target_os = "linux")))] use ringbuf::{ring_buffer::RbBase, Rb}; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::{ collections::HashMap, @@ -1274,7 +1275,7 @@ impl VideoHandler { } // The source of sent password -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] enum PasswordSource { PersonalAb(Vec), SharedAb(String), @@ -1320,6 +1321,13 @@ impl PasswordSource { } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct ConnToken { + password: Vec, + password_source: PasswordSource, + session_id: u64, +} + /// Login config handler for [`Client`]. #[derive(Default)] pub struct LoginConfigHandler { @@ -1376,6 +1384,7 @@ impl LoginConfigHandler { mut force_relay: bool, adapter_luid: Option, shared_password: Option, + conn_token: Option, ) { let mut id = id; if id.contains("@") { @@ -1419,10 +1428,22 @@ impl LoginConfigHandler { let config = self.load_config(); self.remember = !config.password.is_empty(); self.config = config; - let mut sid = rand::random(); + + let conn_token = conn_token + .map(|x| serde_json::from_str::(&x).ok()) + .flatten(); + let mut sid = 0; + if let Some(token) = conn_token { + sid = token.session_id; + self.password = token.password; // use as last password + self.password_source = token.password_source; + } if sid == 0 { - // you won the lottery - sid = 1; + sid = rand::random(); + if sid == 0 { + // you won the lottery + sid = 1; + } } self.session_id = sid; self.supported_encoding = Default::default(); @@ -2223,6 +2244,18 @@ impl LoginConfigHandler { msg_out.set_misc(misc); msg_out } + + pub fn get_conn_token(&self) -> Option { + if self.password.is_empty() { + return None; + } + serde_json::to_string(&ConnToken { + password: self.password.clone(), + password_source: self.password_source.clone(), + session_id: self.session_id, + }) + .ok() + } } /// Media data. diff --git a/src/flutter.rs b/src/flutter.rs index 69266f51c1c..a1c9c7e3441 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -1126,6 +1126,7 @@ pub fn session_add( force_relay: bool, password: String, is_shared_password: bool, + conn_token: Option, ) -> ResultType { let conn_type = if is_file_transfer { ConnType::FILE_TRANSFER @@ -1180,6 +1181,7 @@ pub fn session_add( force_relay, get_adapter_luid(), shared_password, + conn_token, ); let session = Arc::new(session.clone()); diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 5dfcebacc8f..7a0c5e87449 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -121,6 +121,7 @@ pub fn session_add_sync( force_relay: bool, password: String, is_shared_password: bool, + conn_token: Option, ) -> SyncReturn { if let Err(e) = session_add( &session_id, @@ -132,6 +133,7 @@ pub fn session_add_sync( force_relay, password, is_shared_password, + conn_token, ) { SyncReturn(format!("Failed to add session with id {}, {}", &id, e)) } else { @@ -1341,6 +1343,14 @@ pub fn session_close_voice_call(session_id: SessionID) { } } +pub fn session_get_conn_token(session_id: SessionID) -> SyncReturn> { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + SyncReturn(session.get_conn_token()) + } else { + SyncReturn(None) + } +} + pub fn cm_handle_incoming_voice_call(id: i32, accept: bool) { crate::ui_cm_interface::handle_incoming_voice_call(id, accept); } diff --git a/src/server/connection.rs b/src/server/connection.rs index dbe8b961431..dc184ceac8e 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -64,9 +64,9 @@ pub type Sender = mpsc::UnboundedSender<(Instant, Arc)>; lazy_static::lazy_static! { static ref LOGIN_FAILURES: [Arc::>>; 2] = Default::default(); - static ref SESSIONS: Arc::>> = Default::default(); + static ref SESSIONS: Arc::>> = Default::default(); static ref ALIVE_CONNS: Arc::>> = Default::default(); - pub static ref AUTHED_CONNS: Arc::>> = Default::default(); + pub static ref AUTHED_CONNS: Arc::>> = Default::default(); static ref SWITCH_SIDES_UUID: Arc::>> = Default::default(); static ref WAKELOCK_SENDER: Arc::>> = Arc::new(Mutex::new(start_wakelock_thread())); } @@ -140,13 +140,20 @@ enum MessageInput { BlockOffPlugin(String), } -#[derive(Clone, Debug)] -struct Session { +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct SessionKey { + peer_id: String, name: String, session_id: u64, +} + +#[derive(Clone, Debug)] +struct Session { last_recv_time: Arc>, random_password: String, tfa: bool, + conn_type: AuthConnType, + conn_id: i32, } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -1131,6 +1138,7 @@ impl Connection { self.authed_conn_id = Some(self::raii::AuthedConnID::new( self.inner.id(), auth_conn_type, + self.session_key(), )); self.post_conn_audit( json!({"peer": ((&self.lr.my_id, &self.lr.my_name)), "type": conn_type}), @@ -1541,14 +1549,14 @@ impl Connection { if password::temporary_enabled() { let password = password::temporary_password(); if self.validate_one_password(password.clone()) { - SESSIONS.lock().unwrap().insert( - self.lr.my_id.clone(), + raii::AuthedConnID::insert_session( + self.session_key(), Session { - name: self.lr.my_name.clone(), - session_id: self.lr.session_id, last_recv_time: self.last_recv_time.clone(), random_password: password, tfa: false, + conn_type: self.conn_type(), + conn_id: self.inner.id(), }, ); return true; @@ -1570,21 +1578,19 @@ impl Connection { let session = SESSIONS .lock() .unwrap() - .get(&self.lr.my_id) + .get(&self.session_key()) .map(|s| s.to_owned()); // last_recv_time is a mutex variable shared with connection, can be updated lively. if let Some(mut session) = session { - if session.name == self.lr.my_name - && session.session_id == self.lr.session_id - && !self.lr.password.is_empty() + if !self.lr.password.is_empty() && (tfa && session.tfa || !tfa && self.validate_one_password(session.random_password.clone())) { session.last_recv_time = self.last_recv_time.clone(); - SESSIONS - .lock() - .unwrap() - .insert(self.lr.my_id.clone(), session); + session.conn_id = self.inner.id(); + session.conn_type = self.conn_type(); + raii::AuthedConnID::insert_session(self.session_key(), session); + log::info!("is recent session"); return true; } } @@ -1844,23 +1850,22 @@ impl Connection { let session = SESSIONS .lock() .unwrap() - .get(&self.lr.my_id) + .get(&self.session_key()) .map(|s| s.to_owned()); if let Some(mut session) = session { session.tfa = true; - SESSIONS - .lock() - .unwrap() - .insert(self.lr.my_id.clone(), session); + session.conn_id = self.inner.id(); + session.conn_type = self.conn_type(); + raii::AuthedConnID::insert_session(self.session_key(), session); } else { - SESSIONS.lock().unwrap().insert( - self.lr.my_id.clone(), + raii::AuthedConnID::insert_session( + self.session_key(), Session { - name: self.lr.my_name.clone(), - session_id: self.lr.session_id, last_recv_time: self.last_recv_time.clone(), random_password: "".to_owned(), tfa: true, + conn_type: self.conn_type(), + conn_id: self.inner.id(), }, ); } @@ -2159,12 +2164,8 @@ impl Connection { _ => {} } if let Some(job_id) = job_id { - self.send(fs::new_error( - job_id, - "one-way-file-transfer-tip", - 0, - )) - .await; + self.send(fs::new_error(job_id, "one-way-file-transfer-tip", 0)) + .await; return true; } } @@ -2399,7 +2400,10 @@ impl Connection { } Some(misc::Union::CloseReason(_)) => { self.on_close("Peer close", true).await; - SESSIONS.lock().unwrap().remove(&self.lr.my_id); + raii::AuthedConnID::remove_session_if_last_duplication( + self.inner.id(), + self.session_key(), + ); return false; } @@ -3159,7 +3163,7 @@ impl Connection { let mut msg_out = Message::new(); msg_out.set_misc(misc); self.send(msg_out).await; - SESSIONS.lock().unwrap().remove(&self.lr.my_id); + raii::AuthedConnID::remove_session_if_last_duplication(self.inner.id(), self.session_key()); } fn read_dir(&mut self, dir: &str, include_hidden: bool) { @@ -3313,6 +3317,26 @@ impl Connection { } } } + + #[inline] + fn conn_type(&self) -> AuthConnType { + if self.file_transfer.is_some() { + AuthConnType::FileTransfer + } else if self.port_forward_socket.is_some() { + AuthConnType::PortForward + } else { + AuthConnType::Remote + } + } + + #[inline] + fn session_key(&self) -> SessionKey { + SessionKey { + peer_id: self.lr.my_id.clone(), + name: self.lr.my_name.clone(), + session_id: self.lr.session_id, + } + } } pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) { @@ -3810,15 +3834,18 @@ mod raii { pub struct AuthedConnID(i32, AuthConnType); impl AuthedConnID { - pub fn new(id: i32, conn_type: AuthConnType) -> Self { - AUTHED_CONNS.lock().unwrap().push((id, conn_type)); + pub fn new(conn_id: i32, conn_type: AuthConnType, session_key: SessionKey) -> Self { + AUTHED_CONNS + .lock() + .unwrap() + .push((conn_id, conn_type, session_key)); Self::check_wake_lock(); use std::sync::Once; static _ONCE: Once = Once::new(); _ONCE.call_once(|| { shutdown_hooks::add_shutdown_hook(connection_shutdown_hook); }); - Self(id, conn_type) + Self(conn_id, conn_type) } fn check_wake_lock() { @@ -3843,6 +3870,53 @@ mod raii { .filter(|c| c.1 == AuthConnType::Remote || c.1 == AuthConnType::FileTransfer) .count() } + + pub fn remove_session_if_last_duplication(conn_id: i32, key: SessionKey) { + let contains = SESSIONS.lock().unwrap().contains_key(&key); + if contains { + let another = AUTHED_CONNS + .lock() + .unwrap() + .iter() + .any(|c| c.0 != conn_id && c.2 == key && c.1 != AuthConnType::PortForward); + if !another { + // Keep the session if there is another connection with same peer_id and session_id. + SESSIONS.lock().unwrap().remove(&key); + log::info!("remove session"); + } else { + log::info!("skip remove session"); + } + } + } + + pub fn insert_session(key: SessionKey, session: Session) { + let mut insert = true; + if session.conn_type == AuthConnType::PortForward { + // port forward doesn't update last received time + let other_alive_conns = AUTHED_CONNS + .lock() + .unwrap() + .iter() + .filter(|c| { + c.2 == key && c.1 != AuthConnType::PortForward // port forward doesn't remove itself + }) + .map(|c| c.0) + .collect::>(); + let another = SESSIONS.lock().unwrap().get(&key).map(|s| { + other_alive_conns.contains(&s.conn_id) + && s.tfa == session.tfa + && s.conn_type != AuthConnType::PortForward + }) == Some(true); + if another { + insert = false; + log::info!("skip insert session for port forward"); + } + } + if insert { + log::info!("insert session for {:?}", session.conn_type); + SESSIONS.lock().unwrap().insert(key, session); + } + } } impl Drop for AuthedConnID { @@ -3850,7 +3924,7 @@ mod raii { if self.1 == AuthConnType::Remote { scrap::codec::Encoder::update(scrap::codec::EncodingUpdate::Remove(self.0)); } - AUTHED_CONNS.lock().unwrap().retain(|&c| c.0 != self.0); + AUTHED_CONNS.lock().unwrap().retain(|c| c.0 != self.0); let remote_count = AUTHED_CONNS .lock() .unwrap() diff --git a/src/ui/remote.rs b/src/ui/remote.rs index f0829e75eee..0296d82bda5 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -506,7 +506,7 @@ impl sciter::EventHandler for SciterSession { impl SciterSession { pub fn new(cmd: String, id: String, password: String, args: Vec) -> Self { let force_relay = args.contains(&"--relay".to_string()); - let mut session: Session = Session { + let session: Session = Session { password: password.clone(), args, server_keyboard_enabled: Arc::new(RwLock::new(true)), @@ -529,7 +529,7 @@ impl SciterSession { .lc .write() .unwrap() - .initialize(id, conn_type, None, force_relay, None, None); + .initialize(id, conn_type, None, force_relay, None, None, None); Self(session) } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 4160561be71..321707d3f63 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1490,6 +1490,10 @@ impl Session { msg.set_misc(misc); self.send(Data::Message(msg)); } + + pub fn get_conn_token(&self) -> Option { + self.lc.read().unwrap().get_conn_token() + } } pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { From b35b48086a3b5cf535beb726e4d9142e6461a357 Mon Sep 17 00:00:00 2001 From: Tobias Degen <40177712+tobiasdegen@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:52:39 +0200 Subject: [PATCH 200/210] Add translation string for better translationj capability (#9736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new translation für sending Ctrl+Alt+Del * Add new translation string for sending Ctrl+Alt+Del --- flutter/lib/common/widgets/toolbar.dart | 2 +- src/lang/ar.rs | 1 + src/lang/be.rs | 1 + src/lang/bg.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/en.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/et.rs | 1 + src/lang/eu.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/he.rs | 1 + src/lang/hr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nb.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/uk.rs | 1 + src/lang/vn.rs | 1 + 45 files changed, 45 insertions(+), 1 deletion(-) diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 269d7904d1a..153121057e5 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -195,7 +195,7 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { (pi.platform == kPeerPlatformLinux || pi.sasEnabled)) { v.add( TTextMenu( - child: Text('${translate("Insert")} Ctrl + Alt + Del'), + child: Text('${translate("Insert Ctrl + Alt + Del")}'), onPressed: () => bind.sessionCtrlAltDel(sessionId: sessionId)), ); } diff --git a/src/lang/ar.rs b/src/lang/ar.rs index fe7e853815b..1d413019594 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index da9be46401c..5e4313357fa 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 72b5fcf1971..11bd4037892 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index fbe3cde5fcd..7fa884ea672 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index f57d100d5e5..bba63cd12d9 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "上传文件夹"), ("Upload files", "上传文件"), ("Clipboard is synchronized", "剪贴板已同步"), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index d72b85a0cf5..5dfc808b878 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 558b2fa4541..004afc1f80b 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 0a78ee2b55e..33371a197f5 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Ordner hochladen"), ("Upload files", "Dateien hochladen"), ("Clipboard is synchronized", "Zwischenablage ist synchronisiert"), + ("Insert Ctrl + Alt + Del", "Strg + Alt + Entf senden"), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 9725ecc7886..aac308aa815 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 7ed83a8fe4f..ac382966149 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -236,5 +236,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enable-trusted-devices-tip", "Skip 2FA verification on trusted devices"), ("one-way-file-transfer-tip", "One-way file transfer is enabled on the controlled side."), ("web_id_input_tip", "You can input an ID in the same server, direct IP access is not supported in web client.\nIf you want to access a device on another server, please append the server address (@?key=), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"@public\", the key is not needed for public server."), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 2f585b1c6e6..90fa251ff4f 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 92015df0b20..72d3e14cdb5 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Subir carpeta"), ("Upload files", "Subir archivos"), ("Clipboard is synchronized", "Portapapeles sincronizado"), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 96ca16f964f..631ae11fd9b 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index d68e5c42aeb..22d23dfca1c 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 207dfbbdbd9..d36a0c925c9 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 9844167404a..5920eb0e3ea 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 408829b6c64..b82a6c8f3dd 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index b9f9409fc39..ce591f58bca 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index e9caf1917f4..20c50959456 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 52c1741915e..25fa0e5dbe8 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 426cb7c901c..4f47b4aac18 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Cartella upload"), ("Upload files", "File upload"), ("Clipboard is synchronized", "Gli appunti sono sincronizzati"), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 0bca730dc7c..5934683b4a3 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index dc9a0a69d59..036af2a4d53 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 9ea54b97540..874ecfc5730 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index df795401b76..7f5b7b02331 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 9a54daa005b..0a89e02e2cf 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Augšupielādēt mapi"), ("Upload files", "Augšupielādēt failus"), ("Clipboard is synchronized", "Starpliktuve ir sinhronizēta"), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index 4c8b1550c5e..b07a103c15f 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 76f23f04249..71384cee002 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Map uploaden"), ("Upload files", "Bestanden uploaden"), ("Clipboard is synchronized", "Klembord is gesynchroniseerd"), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 7ec3572e813..f6f4db4f7ec 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Wyślij folder"), ("Upload files", "Wyślij pliki"), ("Clipboard is synchronized", "Schowek jest zsynchronizowany"), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 1194c11ec94..80676744855 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 012ca3538fa..8d83ea394b2 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index e09888c58a8..7c8ea1d1e86 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index b547208d7ab..bdf4ba46256 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Загрузить папку"), ("Upload files", "Загрузить файлы"), ("Clipboard is synchronized", "Буфер обмена синхронизирован"), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index bb8e872c7cf..01d2a7507cd 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 1563a02c573..ba20e0148c0 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index ccc4b805fac..fe398bc5994 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 6df6b7ad83b..cca6bb361ad 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 2f95488bd42..0cb2f92c8f2 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 962506b99ec..b015e177dbc 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 673ebf31981..8e0711525ef 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 04687287893..2d1be69d3ad 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 25da736b837..8ac7c13ff41 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "上傳資料夾"), ("Upload files", "上傳檔案"), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 97743266cad..63e3cc199a3 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index b49aea67bb7..1454981ac00 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), + ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } From bd22b01370b290a2fdf34aaf03a2164318516043 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Thu, 24 Oct 2024 22:27:51 +0800 Subject: [PATCH 201/210] fix "insert ctrl+alt+del" --- flutter/lib/desktop/widgets/remote_toolbar.dart | 2 +- src/lang/ar.rs | 3 +-- src/lang/be.rs | 3 +-- src/lang/bg.rs | 3 +-- src/lang/ca.rs | 3 +-- src/lang/cn.rs | 3 +-- src/lang/cs.rs | 3 +-- src/lang/da.rs | 3 +-- src/lang/de.rs | 3 +-- src/lang/el.rs | 3 +-- src/lang/en.rs | 1 - src/lang/eo.rs | 3 +-- src/lang/es.rs | 3 +-- src/lang/et.rs | 3 +-- src/lang/eu.rs | 3 +-- src/lang/fa.rs | 3 +-- src/lang/fr.rs | 3 +-- src/lang/he.rs | 3 +-- src/lang/hr.rs | 3 +-- src/lang/hu.rs | 3 +-- src/lang/id.rs | 3 +-- src/lang/it.rs | 3 +-- src/lang/ja.rs | 3 +-- src/lang/ko.rs | 3 +-- src/lang/kz.rs | 3 +-- src/lang/lt.rs | 3 +-- src/lang/lv.rs | 3 +-- src/lang/nb.rs | 3 +-- src/lang/nl.rs | 3 +-- src/lang/pl.rs | 3 +-- src/lang/pt_PT.rs | 3 +-- src/lang/ptbr.rs | 3 +-- src/lang/ro.rs | 3 +-- src/lang/ru.rs | 3 +-- src/lang/sk.rs | 3 +-- src/lang/sl.rs | 3 +-- src/lang/sq.rs | 3 +-- src/lang/sr.rs | 3 +-- src/lang/sv.rs | 3 +-- src/lang/template.rs | 3 +-- src/lang/th.rs | 3 +-- src/lang/tr.rs | 3 +-- src/lang/tw.rs | 3 +-- src/lang/uk.rs | 3 +-- src/lang/vn.rs | 3 +-- 45 files changed, 44 insertions(+), 88 deletions(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 75791ad093c..4857464861f 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -305,7 +305,7 @@ class RemoteMenuEntry { }) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( - '${translate("Insert")} Ctrl + Alt + Del', + '${translate("Insert Ctrl + Alt + Del")', style: style, ), proc: () { diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 1d413019594..31fd680fd60 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "عرض مراقب الجودة"), ("Disable clipboard", "تعطيل الحافظة"), ("Lock after session end", "القفل بعد نهاية هذه الجلسة"), - ("Insert", "ادخال"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del دخال"), ("Insert Lock", "قفل الادخال"), ("Refresh", "تحديث"), ("ID does not exist", "المعرف غير موجود"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index 5e4313357fa..fbe16153543 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Паказваць манітор якасці"), ("Disable clipboard", "Адключыць буфер абмену"), ("Lock after session end", "Заблакаваць уліковы запіс пасля сеансу"), - ("Insert", "Уставіць"), + ("Insert Ctrl + Alt + Del", "Уставіць Ctrl + Alt + Del"), ("Insert Lock", "Заблакаваць уліковы запіс"), ("Refresh", "Абнавіць"), ("ID does not exist", "ID не існуе"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 11bd4037892..4f0131cc878 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Показвай прозорец за качество"), ("Disable clipboard", "Забрана за достъп до клипборд"), ("Lock after session end", "Заключване след край на ползване"), - ("Insert", "Поставяне"), + ("Insert Ctrl + Alt + Del", "Поставяне Ctrl + Alt + Del"), ("Insert Lock", "Заявка за заключване"), ("Refresh", "Обновяване"), ("ID does not exist", "Несъществуващ определител (ID)"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 7fa884ea672..8e0ff1479cc 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Mostra la informació de flux"), ("Disable clipboard", "Inhabilita el porta-retalls"), ("Lock after session end", "Bloca en finalitzar la sessió"), - ("Insert", "Insereix"), + ("Insert Ctrl + Alt + Del", "Insereix Ctrl + Alt + Del"), ("Insert Lock", "Bloca"), ("Refresh", "Actualitza"), ("ID does not exist", "Aquesta ID no existeix"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index bba63cd12d9..8b1d3a5f9a4 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "显示质量监测"), ("Disable clipboard", "禁用剪贴板"), ("Lock after session end", "会话结束后锁定远程电脑"), - ("Insert", "插入"), + ("Insert Ctrl + Alt + Del", "插入 Ctrl + Alt + Del"), ("Insert Lock", "锁定远程电脑"), ("Refresh", "刷新画面"), ("ID does not exist", "ID 不存在"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "上传文件夹"), ("Upload files", "上传文件"), ("Clipboard is synchronized", "剪贴板已同步"), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 5dfc808b878..a9fb5b233cb 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Zobrazit monitor kvality"), ("Disable clipboard", "Vypnout schránku"), ("Lock after session end", "Po ukončení relace zamknout plochu"), - ("Insert", "Vložit"), + ("Insert Ctrl + Alt + Del", "Vložit Ctrl + Alt + Del"), ("Insert Lock", "Zamknout"), ("Refresh", "Načíst znovu"), ("ID does not exist", "Toto ID neexistuje"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 004afc1f80b..34e5433f517 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Vis billedkvalitet"), ("Disable clipboard", "Deaktiver udklipsholder"), ("Lock after session end", "Lås efter afslutningen af fjernstyring"), - ("Insert", "Indsæt"), + ("Insert Ctrl + Alt + Del", "Indsæt Ctrl + Alt + Del"), ("Insert Lock", "Indsæt lås"), ("Refresh", "Genopfrisk"), ("ID does not exist", "ID findes ikke"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 33371a197f5..e59c3b5050c 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Qualitätsüberwachung anzeigen"), ("Disable clipboard", "Zwischenablage deaktivieren"), ("Lock after session end", "Nach Sitzungsende sperren"), - ("Insert", "Einfügen"), + ("Insert Ctrl + Alt + Del", "Einfügen Ctrl + Alt + Del"), ("Insert Lock", "Win+L (Sperren) senden"), ("Refresh", "Aktualisieren"), ("ID does not exist", "Diese ID existiert nicht."), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Ordner hochladen"), ("Upload files", "Dateien hochladen"), ("Clipboard is synchronized", "Zwischenablage ist synchronisiert"), - ("Insert Ctrl + Alt + Del", "Strg + Alt + Entf senden"), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index aac308aa815..e6df3bc3d5b 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Εμφάνιση παρακολούθησης ποιότητας σύνδεσης"), ("Disable clipboard", "Απενεργοποίηση προχείρου"), ("Lock after session end", "Κλείδωμα μετά το τέλος της συνεδρίας"), - ("Insert", "Εισαγωγή"), + ("Insert Ctrl + Alt + Del", "Εισαγωγή Ctrl + Alt + Del"), ("Insert Lock", "Κλείδωμα απομακρυσμένου σταθμού"), ("Refresh", "Ανανέωση"), ("ID does not exist", "Το αναγνωριστικό ID δεν υπάρχει"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index ac382966149..7ed83a8fe4f 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -236,6 +236,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enable-trusted-devices-tip", "Skip 2FA verification on trusted devices"), ("one-way-file-transfer-tip", "One-way file transfer is enabled on the controlled side."), ("web_id_input_tip", "You can input an ID in the same server, direct IP access is not supported in web client.\nIf you want to access a device on another server, please append the server address (@?key=), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"@public\", the key is not needed for public server."), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 90fa251ff4f..876c901a481 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Montri kvalito monitoron"), ("Disable clipboard", "Malebligi poŝon"), ("Lock after session end", "Ŝlosi foran komputilon post malkonektado"), - ("Insert", "Enmeti"), + ("Insert Ctrl + Alt + Del", "Enmeti Ctrl + Alt + Del"), ("Insert Lock", "Ŝlosi foran komputilon"), ("Refresh", "Refreŝigi ekranon"), ("ID does not exist", "La identigilo ne ekzistas"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 72d3e14cdb5..ce77f620c71 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Mostrar calidad del monitor"), ("Disable clipboard", "Deshabilitar portapapeles"), ("Lock after session end", "Bloquear después del final de la sesión"), - ("Insert", "Insertar"), + ("Insert Ctrl + Alt + Del", "Insertar Ctrl + Alt + Del"), ("Insert Lock", "Insertar bloqueo"), ("Refresh", "Actualizar"), ("ID does not exist", "La ID no existe"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Subir carpeta"), ("Upload files", "Subir archivos"), ("Clipboard is synchronized", "Portapapeles sincronizado"), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 631ae11fd9b..21de56c9e69 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", ""), ("Disable clipboard", ""), ("Lock after session end", ""), - ("Insert", ""), + ("Insert Ctrl + Alt + Del", ""), ("Insert Lock", "Sisesta lukk"), ("Refresh", ""), ("ID does not exist", ""), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 22d23dfca1c..ac958d79c67 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Erakutsi kalitate monitorea"), ("Disable clipboard", "Desgaitu arbela"), ("Lock after session end", "Blokeatu sesioa amaitu ostean"), - ("Insert", "Sartu"), + ("Insert Ctrl + Alt + Del", "Sartu Ctrl + Alt + Del"), ("Insert Lock", "Sarrera-blokeoa"), ("Refresh", "Freskatu"), ("ID does not exist", "IDa ez da existitzen"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index d36a0c925c9..ff43815739a 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "نمایش کیفیت مانیتور"), ("Disable clipboard", " غیرفعالسازی کلیپبورد"), ("Lock after session end", "قفل کردن حساب کاربری سیستم عامل پس از پایان جلسه"), - ("Insert", "افزودن"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del افزودن"), ("Insert Lock", "قفل کردن سیستم"), ("Refresh", "تازه سازی"), ("ID does not exist", "شناسه وجود ندارد"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 5920eb0e3ea..85b1354c318 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Afficher le moniteur de qualité"), ("Disable clipboard", "Désactiver le presse-papier"), ("Lock after session end", "Verrouiller l'appareil distant après la déconnexion"), - ("Insert", "Envoyer"), + ("Insert Ctrl + Alt + Del", "Envoyer Ctrl + Alt + Del"), ("Insert Lock", "Verrouiller l'appareil distant"), ("Refresh", "Rafraîchir l'écran"), ("ID does not exist", "L'ID n'existe pas"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index b82a6c8f3dd..b63d4212246 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", ""), ("Disable clipboard", ""), ("Lock after session end", ""), - ("Insert", ""), + ("Insert Ctrl + Alt + Del", ""), ("Insert Lock", "הוסף נעילה"), ("Refresh", ""), ("ID does not exist", ""), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index ce591f58bca..4a3136c833f 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Prikaži kvalitetu monitora"), ("Disable clipboard", "Zabrani međuspremnik"), ("Lock after session end", "Zaključaj po završetku sesije"), - ("Insert", "Umetni"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del umetanje"), ("Insert Lock", "Zaključaj umetanje"), ("Refresh", "Osvježi"), ("ID does not exist", "ID ne postoji"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 20c50959456..f1f8ac1ae04 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", ""), ("Disable clipboard", "Közös vágólap kikapcsolása"), ("Lock after session end", "Távoli fiók zárolása a munkamenet végén"), - ("Insert", ""), + ("Insert Ctrl + Alt + Del", "Illessze be a Ctrl + Alt + Del"), ("Insert Lock", "Távoli fiók zárolása"), ("Refresh", "Frissítés"), ("ID does not exist", "Az azonosító nem létezik"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 25fa0e5dbe8..066f2980cc1 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Tampilkan kualitas monitor"), ("Disable clipboard", "Matikan papan klip"), ("Lock after session end", "Kunci setelah sesi berakhir"), - ("Insert", "Menyisipkan"), + ("Insert Ctrl + Alt + Del", "Menyisipkan Ctrl + Alt + Del"), ("Insert Lock", "Masukkan Kunci"), ("Refresh", "Segarkan"), ("ID does not exist", "ID tidak ada"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 4f47b4aac18..dd944af9f4d 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Visualizza qualità video"), ("Disable clipboard", "Disabilita appunti"), ("Lock after session end", "Blocca al termine della sessione"), - ("Insert", "Inserisci"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del inserimento"), ("Insert Lock", "Blocco inserimento"), ("Refresh", "Aggiorna"), ("ID does not exist", "L'ID non esiste"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Cartella upload"), ("Upload files", "File upload"), ("Clipboard is synchronized", "Gli appunti sono sincronizzati"), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 5934683b4a3..5edd5057299 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "品質モニターを表示"), ("Disable clipboard", "クリップボードを無効化"), ("Lock after session end", "セッション終了後にロックする"), - ("Insert", "送信"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del 送信"), ("Insert Lock", "ロック命令を送信"), ("Refresh", "更新"), ("ID does not exist", "IDが存在しません"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 036af2a4d53..71bff511908 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "품질 모니터 보기"), ("Disable clipboard", "클립보드 비활성화"), ("Lock after session end", "세션 종료 후 화면 잠금"), - ("Insert", "입력"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del 입력"), ("Insert Lock", "원격 입력 잠금"), ("Refresh", "새로고침"), ("ID does not exist", "ID가 존재하지 않습니다"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 874ecfc5730..07ca645f272 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Сапа мониторын көрсету"), ("Disable clipboard", "Көшіру-тақтасын өшіру"), ("Lock after session end", "Сеш аяқталған соң құлыптау"), - ("Insert", "Кірістіру"), + ("Insert Ctrl + Alt + Del", "Кірістіру Ctrl + Alt + Del"), ("Insert Lock", "Кірістіруді Құлыптау"), ("Refresh", "Жаңарту"), ("ID does not exist", "ID табылмады"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 7f5b7b02331..9a2069163ed 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Rodyti kokybės monitorių"), ("Disable clipboard", "Išjungti mainų sritį"), ("Lock after session end", "Užrakinti pasibaigus seansui"), - ("Insert", "Įdėti"), + ("Insert Ctrl + Alt + Del", "Įdėti Ctrl + Alt + Del"), ("Insert Lock", "Įterpti užraktą"), ("Refresh", "Atnaujinti"), ("ID does not exist", "ID neegzistuoja"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 0a89e02e2cf..e8ba903dfb6 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Rādīt kvalitātes monitoru"), ("Disable clipboard", "Atspējot starpliktuvi"), ("Lock after session end", "Bloķēt pēc sesijas beigām"), - ("Insert", "Ievietot"), + ("Insert Ctrl + Alt + Del", "Ievietot Ctrl + Alt + Del"), ("Insert Lock", "Ievietot Bloķēt"), ("Refresh", "Atsvaidzināt"), ("ID does not exist", "ID neeksistē"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Augšupielādēt mapi"), ("Upload files", "Augšupielādēt failus"), ("Clipboard is synchronized", "Starpliktuve ir sinhronizēta"), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index b07a103c15f..a91e31e45be 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Vis bildekvalitet"), ("Disable clipboard", "Deaktiver utklipstavle"), ("Lock after session end", "Lås etter avsluttet fjernstyring"), - ("Insert", "Sett inn"), + ("Insert Ctrl + Alt + Del", "Sett inn Ctrl + Alt + Del"), ("Insert Lock", "Sett inn lås"), ("Refresh", "Oppdater"), ("ID does not exist", "ID finnes ikke"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 71384cee002..ca46a32853e 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Kwaliteitsmonitor tonen"), ("Disable clipboard", "Klembord uitschakelen"), ("Lock after session end", "Vergrendelen na einde sessie"), - ("Insert", "Invoegen"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del Invoegen"), ("Insert Lock", "Vergrendeling Invoegen"), ("Refresh", "Vernieuwen"), ("ID does not exist", "ID bestaat niet"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Map uploaden"), ("Upload files", "Bestanden uploaden"), ("Clipboard is synchronized", "Klembord is gesynchroniseerd"), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index f6f4db4f7ec..fd5641ac133 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Parametry połączenia"), ("Disable clipboard", "Wyłącz schowek"), ("Lock after session end", "Zablokuj po zakończeniu sesji"), - ("Insert", "Wyślij"), + ("Insert Ctrl + Alt + Del", "Wyślij Ctrl + Alt + Del"), ("Insert Lock", "Zablokuj zdalne urządzenie"), ("Refresh", "Odśwież"), ("ID does not exist", "ID nie istnieje"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Wyślij folder"), ("Upload files", "Wyślij pliki"), ("Clipboard is synchronized", "Schowek jest zsynchronizowany"), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 80676744855..c0564e0f401 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", ""), ("Disable clipboard", "Desabilitar área de transferência"), ("Lock after session end", "Bloquear após o fim da sessão"), - ("Insert", "Inserir"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del Inserir"), ("Insert Lock", "Bloquear Inserir"), ("Refresh", "Actualizar"), ("ID does not exist", "ID não existente"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 8d83ea394b2..14254388cc1 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Exibir monitor de qualidade"), ("Disable clipboard", "Desabilitar área de transferência"), ("Lock after session end", "Bloquear após o fim da sessão"), - ("Insert", "Inserir"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del Inserir"), ("Insert Lock", "Bloquear computador"), ("Refresh", "Atualizar"), ("ID does not exist", "ID não existe"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 7c8ea1d1e86..cbce2f2a927 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Afișează detalii despre conexiune"), ("Disable clipboard", "Dezactivează clipboard"), ("Lock after session end", "Blochează după deconectare"), - ("Insert", "Introdu"), + ("Insert Ctrl + Alt + Del", "Introdu Ctrl + Alt + Del"), ("Insert Lock", "Blochează computer"), ("Refresh", "Reîmprospătează"), ("ID does not exist", "ID neexistent"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index bdf4ba46256..3fb856098c7 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Показывать монитор качества"), ("Disable clipboard", "Отключить буфер обмена"), ("Lock after session end", "Заблокировать учётную запись после сеанса"), - ("Insert", "Вставить"), + ("Insert Ctrl + Alt + Del", "Вставить Ctrl + Alt + Del"), ("Insert Lock", "Заблокировать учётную запись"), ("Refresh", "Обновить"), ("ID does not exist", "ID не существует"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "Загрузить папку"), ("Upload files", "Загрузить файлы"), ("Clipboard is synchronized", "Буфер обмена синхронизирован"), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 01d2a7507cd..b3c8fddf916 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Zobraziť monitor kvality"), ("Disable clipboard", "Vypnúť schránku"), ("Lock after session end", "Po skončení uzamknúť plochu"), - ("Insert", "Vložiť"), + ("Insert Ctrl + Alt + Del", "Vložiť Ctrl + Alt + Del"), ("Insert Lock", "Uzamknúť"), ("Refresh", "Aktualizovať"), ("ID does not exist", "ID neexistuje"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index ba20e0148c0..20fd24c9ca0 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Prikaži nadzornik kakovosti"), ("Disable clipboard", "Onemogoči odložišče"), ("Lock after session end", "Zakleni ob koncu seje"), - ("Insert", "Vstavi"), + ("Insert Ctrl + Alt + Del", "Vstavi Ctrl + Alt + Del"), ("Insert Lock", "Zakleni oddaljeni računalnik"), ("Refresh", "Osveži"), ("ID does not exist", "ID ne obstaja"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index fe398bc5994..7c63c8ea5ab 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Shaq cilësinë e monitorit"), ("Disable clipboard", "Ç'aktivizo clipboard"), ("Lock after session end", "Kyç pasi sesioni të përfundoj"), - ("Insert", "Fut"), + ("Insert Ctrl + Alt + Del", "Fut Ctrl + Alt + Del"), ("Insert Lock", "Fut bllokimin"), ("Refresh", "Rifresko"), ("ID does not exist", "ID nuk ekziston"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index cca6bb361ad..e80bb61812f 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Prikaži monitor kvaliteta"), ("Disable clipboard", "Zabrani clipboard"), ("Lock after session end", "Zaključaj po završetku sesije"), - ("Insert", "Umetni"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del umetanje"), ("Insert Lock", "Zaključaj umetanje"), ("Refresh", "Osveži"), ("ID does not exist", "ID ne postoji"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 0cb2f92c8f2..dae48e7a368 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Visa bildkvalitet"), ("Disable clipboard", "Stäng av urklipp"), ("Lock after session end", "Lås efter sessionens slut"), - ("Insert", "Insert"), + ("Insert Ctrl + Alt + Del", "Insert Ctrl + Alt + Del"), ("Insert Lock", "Insert lås"), ("Refresh", "Uppdatera"), ("ID does not exist", "Detta ID existerar inte"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index b015e177dbc..60b28185137 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", ""), ("Disable clipboard", ""), ("Lock after session end", ""), - ("Insert", ""), + ("Insert Ctrl + Alt + Del", ""), ("Insert Lock", ""), ("Refresh", ""), ("ID does not exist", ""), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 8e0711525ef..71af446c144 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "แสดงคุณภาพหน้าจอ"), ("Disable clipboard", "ปิดการใช้งานคลิปบอร์ด"), ("Lock after session end", "ล็อคหลังจากจบเซสชัน"), - ("Insert", "แทรก"), + ("Insert Ctrl + Alt + Del", "แทรก Ctrl + Alt + Del"), ("Insert Lock", "แทรกล็อค"), ("Refresh", "รีเฟรช"), ("ID does not exist", "ไม่พอข้อมูล ID"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 2d1be69d3ad..ce11544b509 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Kalite monitörünü göster"), ("Disable clipboard", "Hafızadaki kopyalanmışları engelle"), ("Lock after session end", "Bağlantıdan sonra kilitle"), - ("Insert", "Ekle"), + ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del Ekle"), ("Insert Lock", "Kilit Ekle"), ("Refresh", "Yenile"), ("ID does not exist", "ID bulunamadı"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 8ac7c13ff41..b0f64f82d39 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "顯示品質監測"), ("Disable clipboard", "停用剪貼簿"), ("Lock after session end", "工作階段結束後鎖定電腦"), - ("Insert", "插入"), + ("Insert Ctrl + Alt + Del", "插入 Ctrl + Alt + Del"), ("Insert Lock", "鎖定遠端電腦"), ("Refresh", "重新載入"), ("ID does not exist", "ID 不存在"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", "上傳資料夾"), ("Upload files", "上傳檔案"), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 63e3cc199a3..ff5c8b64ae7 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Показати якість"), ("Disable clipboard", "Вимкнути буфер обміну"), ("Lock after session end", "Блокування після завершення сеансу"), - ("Insert", "Вставити"), + ("Insert Ctrl + Alt + Del", "Вставити Ctrl + Alt + Del"), ("Insert Lock", "Встановити замок"), ("Refresh", "Оновити"), ("ID does not exist", "ID не існує"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 1454981ac00..5a2c47befcc 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Hiện thị chất lượng của màn hình"), ("Disable clipboard", "Tắt clipboard"), ("Lock after session end", "Khóa sau khi kết thúc phiên kết nối"), - ("Insert", "Cài"), + ("Insert Ctrl + Alt + Del", "Cài Ctrl + Alt + Del"), ("Insert Lock", "Cài khóa"), ("Refresh", "Làm mới"), ("ID does not exist", "ID không tồn tại"), @@ -653,6 +653,5 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Upload folder", ""), ("Upload files", ""), ("Clipboard is synchronized", ""), - ("Insert Ctrl + Alt + Del", ""), ].iter().cloned().collect(); } From 4da584055d791d6533ff0376d6216a2df1c175e2 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Thu, 24 Oct 2024 22:52:06 +0800 Subject: [PATCH 202/210] fix ci --- flutter/lib/desktop/widgets/remote_toolbar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 4857464861f..839ea1a81db 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -305,7 +305,7 @@ class RemoteMenuEntry { }) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( - '${translate("Insert Ctrl + Alt + Del")', + translate("Insert Ctrl + Alt + Del"), style: style, ), proc: () { From c8b90319966a54ea5629344d328c54701f671a08 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 24 Oct 2024 23:14:43 +0800 Subject: [PATCH 203/210] refactor session insert, update if already exists (#9739) * All share the same last_receive_time * Not second port forward Signed-off-by: 21pages --- src/server/connection.rs | 130 ++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 69 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index dc184ceac8e..12157ddbbcc 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -152,8 +152,6 @@ struct Session { last_recv_time: Arc>, random_password: String, tfa: bool, - conn_type: AuthConnType, - conn_id: i32, } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -217,7 +215,7 @@ pub struct Connection { server_audit_conn: String, server_audit_file: String, lr: LoginRequest, - last_recv_time: Arc>, + session_last_recv_time: Option>>, chat_unanswered: bool, file_transferred: bool, #[cfg(windows)] @@ -364,7 +362,7 @@ impl Connection { server_audit_conn: "".to_owned(), server_audit_file: "".to_owned(), lr: Default::default(), - last_recv_time: Arc::new(Mutex::new(Instant::now())), + session_last_recv_time: None, chat_unanswered: false, file_transferred: false, #[cfg(windows)] @@ -595,7 +593,7 @@ impl Connection { }, Ok(bytes) => { last_recv_time = Instant::now(); - *conn.last_recv_time.lock().unwrap() = Instant::now(); + conn.session_last_recv_time.as_mut().map(|t| *t.lock().unwrap() = Instant::now()); if let Ok(msg_in) = Message::parse_from_bytes(&bytes) { if !conn.on_message(msg_in).await { break; @@ -762,6 +760,10 @@ impl Connection { } if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await { conn.on_close(&err.to_string(), false).await; + raii::AuthedConnID::remove_session_if_last_duplication( + conn.inner.id(), + conn.session_key(), + ); } conn.post_conn_audit(json!({ @@ -1140,6 +1142,11 @@ impl Connection { auth_conn_type, self.session_key(), )); + self.session_last_recv_time = SESSIONS + .lock() + .unwrap() + .get(&self.session_key()) + .map(|s| s.last_recv_time.clone()); self.post_conn_audit( json!({"peer": ((&self.lr.my_id, &self.lr.my_name)), "type": conn_type}), ); @@ -1549,15 +1556,10 @@ impl Connection { if password::temporary_enabled() { let password = password::temporary_password(); if self.validate_one_password(password.clone()) { - raii::AuthedConnID::insert_session( + raii::AuthedConnID::update_or_insert_session( self.session_key(), - Session { - last_recv_time: self.last_recv_time.clone(), - random_password: password, - tfa: false, - conn_type: self.conn_type(), - conn_id: self.inner.id(), - }, + Some(password), + Some(false), ); return true; } @@ -1581,15 +1583,11 @@ impl Connection { .get(&self.session_key()) .map(|s| s.to_owned()); // last_recv_time is a mutex variable shared with connection, can be updated lively. - if let Some(mut session) = session { + if let Some(session) = session { if !self.lr.password.is_empty() && (tfa && session.tfa || !tfa && self.validate_one_password(session.random_password.clone())) { - session.last_recv_time = self.last_recv_time.clone(); - session.conn_id = self.inner.id(); - session.conn_type = self.conn_type(); - raii::AuthedConnID::insert_session(self.session_key(), session); log::info!("is recent session"); return true; } @@ -1841,34 +1839,13 @@ impl Connection { if res { self.update_failure(failure, true, 1); self.require_2fa.take(); + raii::AuthedConnID::set_session_2fa(self.session_key()); self.send_logon_response().await; self.try_start_cm( self.lr.my_id.to_owned(), self.lr.my_name.to_owned(), self.authorized, ); - let session = SESSIONS - .lock() - .unwrap() - .get(&self.session_key()) - .map(|s| s.to_owned()); - if let Some(mut session) = session { - session.tfa = true; - session.conn_id = self.inner.id(); - session.conn_type = self.conn_type(); - raii::AuthedConnID::insert_session(self.session_key(), session); - } else { - raii::AuthedConnID::insert_session( - self.session_key(), - Session { - last_recv_time: self.last_recv_time.clone(), - random_password: "".to_owned(), - tfa: true, - conn_type: self.conn_type(), - conn_id: self.inner.id(), - }, - ); - } if !tfa.hwid.is_empty() && Self::enable_trusted_devices() { Config::add_trusted_device(TrustedDevice { hwid: tfa.hwid, @@ -3872,16 +3849,17 @@ mod raii { } pub fn remove_session_if_last_duplication(conn_id: i32, key: SessionKey) { - let contains = SESSIONS.lock().unwrap().contains_key(&key); + let mut lock = SESSIONS.lock().unwrap(); + let contains = lock.contains_key(&key); if contains { let another = AUTHED_CONNS .lock() .unwrap() .iter() - .any(|c| c.0 != conn_id && c.2 == key && c.1 != AuthConnType::PortForward); + .any(|c| c.0 != conn_id && c.2 == key); if !another { // Keep the session if there is another connection with same peer_id and session_id. - SESSIONS.lock().unwrap().remove(&key); + lock.remove(&key); log::info!("remove session"); } else { log::info!("skip remove session"); @@ -3889,32 +3867,46 @@ mod raii { } } - pub fn insert_session(key: SessionKey, session: Session) { - let mut insert = true; - if session.conn_type == AuthConnType::PortForward { - // port forward doesn't update last received time - let other_alive_conns = AUTHED_CONNS - .lock() - .unwrap() - .iter() - .filter(|c| { - c.2 == key && c.1 != AuthConnType::PortForward // port forward doesn't remove itself - }) - .map(|c| c.0) - .collect::>(); - let another = SESSIONS.lock().unwrap().get(&key).map(|s| { - other_alive_conns.contains(&s.conn_id) - && s.tfa == session.tfa - && s.conn_type != AuthConnType::PortForward - }) == Some(true); - if another { - insert = false; - log::info!("skip insert session for port forward"); - } - } - if insert { - log::info!("insert session for {:?}", session.conn_type); - SESSIONS.lock().unwrap().insert(key, session); + pub fn update_or_insert_session( + key: SessionKey, + password: Option, + tfa: Option, + ) { + let mut lock = SESSIONS.lock().unwrap(); + let session = lock.get_mut(&key); + if let Some(session) = session { + if let Some(password) = password { + session.random_password = password; + } + if let Some(tfa) = tfa { + session.tfa = tfa; + } + } else { + lock.insert( + key, + Session { + random_password: password.unwrap_or_default(), + tfa: tfa.unwrap_or_default(), + last_recv_time: Arc::new(Mutex::new(Instant::now())), + }, + ); + } + } + + pub fn set_session_2fa(key: SessionKey) { + let mut lock = SESSIONS.lock().unwrap(); + let session = lock.get_mut(&key); + if let Some(session) = session { + session.tfa = true; + } else { + lock.insert( + key, + Session { + last_recv_time: Arc::new(Mutex::new(Instant::now())), + random_password: "".to_owned(), + tfa: true, + }, + ); } } } From c51771c8548ddd0970337bb6a9c2fcbc0b9bb677 Mon Sep 17 00:00:00 2001 From: solokot Date: Fri, 25 Oct 2024 03:08:02 +0300 Subject: [PATCH 204/210] Update ru.rs (#9741) --- src/lang/ru.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 3fb856098c7..6d173f1097b 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -364,7 +364,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recording", "Запись"), ("Directory", "Папка"), ("Automatically record incoming sessions", "Автоматически записывать входящие сеансы"), - ("Automatically record outgoing sessions", ""), + ("Automatically record outgoing sessions", "Автоматически записывать исходящие сеансы"), ("Change", "Изменить"), ("Start session recording", "Начать запись сеанса"), ("Stop session recording", "Остановить запись сеанса"), From 924aa515c6977a6f4c31078fe6a86e2db61ab237 Mon Sep 17 00:00:00 2001 From: Tobias Degen <40177712+tobiasdegen@users.noreply.github.com> Date: Fri, 25 Oct 2024 02:21:42 +0200 Subject: [PATCH 205/210] fix german translation (#9742) --- src/lang/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index e59c3b5050c..a732213712a 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Qualitätsüberwachung anzeigen"), ("Disable clipboard", "Zwischenablage deaktivieren"), ("Lock after session end", "Nach Sitzungsende sperren"), - ("Insert Ctrl + Alt + Del", "Einfügen Ctrl + Alt + Del"), + ("Insert Ctrl + Alt + Del", "Strg + Alt + Entf senden"), ("Insert Lock", "Win+L (Sperren) senden"), ("Refresh", "Aktualisieren"), ("ID does not exist", "Diese ID existiert nicht."), From 129f6c869b2d836b829fc5939f4d783f6de08f45 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Sat, 26 Oct 2024 10:06:29 +0200 Subject: [PATCH 206/210] Update Italian language (#9752) --- src/lang/it.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index dd944af9f4d..e0dd83db6f7 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -130,7 +130,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show quality monitor", "Visualizza qualità video"), ("Disable clipboard", "Disabilita appunti"), ("Lock after session end", "Blocca al termine della sessione"), - ("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del inserimento"), + ("Insert Ctrl + Alt + Del", "Inserisci Ctrl + Alt + Del"), ("Insert Lock", "Blocco inserimento"), ("Refresh", "Aggiorna"), ("ID does not exist", "L'ID non esiste"), From 40e8f0d3076426f6bb5780f0bf21bd2509f5c17a Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 26 Oct 2024 22:05:54 +0800 Subject: [PATCH 207/210] revert missing retry and opt keep session (#9755) * Revert "fix missing retry (#8750)" If `hasRetry` is true: there is a retry timeout; If `hasRetry` is false: there is no retry button; In https://github.com/rustdesk/rustdesk/discussions/8748#discussioncomment-10081038,when doesn't want inactive to retry, https://github.com/rustdesk/rustdesk/blob/cf0e3ec303990a48e0b3a6beedd3587079a6526c/flutter/lib/models/model.dart#L444, 1.2.3 always show retry no matter what `hasRetry` is. This reverts commit c3c99ba10725158eaf37fb7fbf7665526138bb88. * not keep session if there is no remote connection left. Signed-off-by: 21pages --------- Signed-off-by: 21pages --- flutter/lib/common.dart | 38 ++++++++++++----------------------- flutter/lib/models/model.dart | 2 +- src/server/connection.rs | 34 +++++++++++-------------------- 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 099a04a1569..a2ad9677546 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1174,33 +1174,21 @@ void msgBox(SessionID sessionId, String type, String title, String text, dialogManager.dismissAll(); })); } - if (reconnect != null && title == "Connection Error") { + if (reconnect != null && + title == "Connection Error" && + reconnectTimeout != null) { // `enabled` is used to disable the dialog button once the button is clicked. final enabled = true.obs; - final button = reconnectTimeout != null - ? Obx(() => _ReconnectCountDownButton( - second: reconnectTimeout, - onPressed: enabled.isTrue - ? () { - // Disable the button - enabled.value = false; - reconnect(dialogManager, sessionId, false); - } - : null, - )) - : Obx( - () => dialogButton( - 'Reconnect', - isOutline: true, - onPressed: enabled.isTrue - ? () { - // Disable the button - enabled.value = false; - reconnect(dialogManager, sessionId, false); - } - : null, - ), - ); + final button = Obx(() => _ReconnectCountDownButton( + second: reconnectTimeout, + onPressed: enabled.isTrue + ? () { + // Disable the button + enabled.value = false; + reconnect(dialogManager, sessionId, false); + } + : null, + )); buttons.insert(0, button); } if (link.isNotEmpty) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 8bd0530f13c..bd91949843b 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -638,7 +638,7 @@ class FfiModel with ChangeNotifier { {bool? hasCancel}) { msgBox(sessionId, type, title, text, link, dialogManager, hasCancel: hasCancel, - reconnect: reconnect, + reconnect: hasRetry ? reconnect : null, reconnectTimeout: hasRetry ? _reconnects : null); _timer?.cancel(); if (hasRetry) { diff --git a/src/server/connection.rs b/src/server/connection.rs index 12157ddbbcc..c0cf8c784e6 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -760,10 +760,7 @@ impl Connection { } if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await { conn.on_close(&err.to_string(), false).await; - raii::AuthedConnID::remove_session_if_last_duplication( - conn.inner.id(), - conn.session_key(), - ); + raii::AuthedConnID::check_remove_session(conn.inner.id(), conn.session_key()); } conn.post_conn_audit(json!({ @@ -2377,7 +2374,7 @@ impl Connection { } Some(misc::Union::CloseReason(_)) => { self.on_close("Peer close", true).await; - raii::AuthedConnID::remove_session_if_last_duplication( + raii::AuthedConnID::check_remove_session( self.inner.id(), self.session_key(), ); @@ -3140,7 +3137,7 @@ impl Connection { let mut msg_out = Message::new(); msg_out.set_misc(misc); self.send(msg_out).await; - raii::AuthedConnID::remove_session_if_last_duplication(self.inner.id(), self.session_key()); + raii::AuthedConnID::check_remove_session(self.inner.id(), self.session_key()); } fn read_dir(&mut self, dir: &str, include_hidden: bool) { @@ -3295,17 +3292,6 @@ impl Connection { } } - #[inline] - fn conn_type(&self) -> AuthConnType { - if self.file_transfer.is_some() { - AuthConnType::FileTransfer - } else if self.port_forward_socket.is_some() { - AuthConnType::PortForward - } else { - AuthConnType::Remote - } - } - #[inline] fn session_key(&self) -> SessionKey { SessionKey { @@ -3848,20 +3834,24 @@ mod raii { .count() } - pub fn remove_session_if_last_duplication(conn_id: i32, key: SessionKey) { + pub fn check_remove_session(conn_id: i32, key: SessionKey) { let mut lock = SESSIONS.lock().unwrap(); let contains = lock.contains_key(&key); if contains { - let another = AUTHED_CONNS + // If there are 2 connections with the same peer_id and session_id, a remote connection and a file transfer or port forward connection, + // If any of the connections is closed allowing retry, this will not be called; + // If the file transfer/port forward connection is closed with no retry, the session should be kept for remote control menu action; + // If the remote connection is closed with no retry, keep the session is not reasonable in case there is a retry button in the remote side, and ignore network fluctuations. + let another_remote = AUTHED_CONNS .lock() .unwrap() .iter() - .any(|c| c.0 != conn_id && c.2 == key); - if !another { - // Keep the session if there is another connection with same peer_id and session_id. + .any(|c| c.0 != conn_id && c.2 == key && c.1 == AuthConnType::Remote); + if !another_remote { lock.remove(&key); log::info!("remove session"); } else { + // Keep the session if there is another remote connection with same peer_id and session_id. log::info!("skip remove session"); } } From c56584906276463f7a4c23e26fc6a1ad1da0252b Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 27 Oct 2024 20:36:21 +0800 Subject: [PATCH 208/210] fix: Function "LockScreen" on macOS since "ignore_flags" in enigo is introduced. (#9757) 1. LockScreen after connection is established. 2. LockScreen after "Map mode" or "Translate mode" keys are sent. Signed-off-by: fufesou --- src/server/input_service.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 3189520be0a..c7f651e9ac7 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -456,13 +456,22 @@ static RECORD_CURSOR_POS_RUNNING: AtomicBool = AtomicBool::new(false); // https://github.com/rustdesk/rustdesk/issues/9729 // We need to do some special handling for macOS when using the legacy mode. #[cfg(target_os = "macos")] -static LAST_KEY_LEGACY_MODE: AtomicBool = AtomicBool::new(false); -// We use enigo to simulate mouse events. Only the legacy mode uses the key flags. +static LAST_KEY_LEGACY_MODE: AtomicBool = AtomicBool::new(true); +// We use enigo to +// 1. Simulate mouse events +// 2. Simulate the legacy mode key events +// 3. Simulate the functioin key events, like LockScreen #[inline] #[cfg(target_os = "macos")] fn enigo_ignore_flags() -> bool { !LAST_KEY_LEGACY_MODE.load(Ordering::SeqCst) } +#[inline] +#[cfg(target_os = "macos")] +fn set_last_legacy_mode(v: bool) { + LAST_KEY_LEGACY_MODE.store(v, Ordering::SeqCst); + ENIGO.lock().unwrap().set_ignore_flags(!v); +} pub fn try_start_record_cursor_pos() -> Option> { if RECORD_CURSOR_POS_RUNNING.load(Ordering::SeqCst) { @@ -1698,17 +1707,19 @@ pub fn handle_key_(evt: &KeyEvent) { match evt.mode.enum_value() { Ok(KeyboardMode::Map) => { #[cfg(target_os = "macos")] - LAST_KEY_LEGACY_MODE.store(false, Ordering::SeqCst); + set_last_legacy_mode(false); map_keyboard_mode(evt); } Ok(KeyboardMode::Translate) => { #[cfg(target_os = "macos")] - LAST_KEY_LEGACY_MODE.store(false, Ordering::SeqCst); + set_last_legacy_mode(false); translate_keyboard_mode(evt); } _ => { + // All key down events are started from here, + // so we can reset the flag of last legacy mode here. #[cfg(target_os = "macos")] - LAST_KEY_LEGACY_MODE.store(true, Ordering::SeqCst); + set_last_legacy_mode(true); legacy_keyboard_mode(evt); } } From 3a7594755341f023f56fa4b6a43b60d6b47df88d Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 28 Oct 2024 09:40:16 +0800 Subject: [PATCH 209/210] add some missing web bridge (#9763) Signed-off-by: 21pages --- flutter/lib/web/bridge.dart | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index d38f0f9cf39..20891281455 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -82,6 +82,7 @@ class RustdeskImpl { required bool forceRelay, required String password, required bool isSharedPassword, + String? connToken, dynamic hint}) { return js.context.callMethod('setByName', [ 'session_add_sync', @@ -173,18 +174,12 @@ class RustdeskImpl { } Future sessionRecordScreen( - {required UuidValue sessionId, - required bool start, - required int display, - required int width, - required int height, - dynamic hint}) { + {required UuidValue sessionId, required bool start, dynamic hint}) { throw UnimplementedError("sessionRecordScreen"); } - Future sessionRecordStatus( - {required UuidValue sessionId, required bool status, dynamic hint}) { - throw UnimplementedError("sessionRecordStatus"); + bool sessionGetIsRecording({required UuidValue sessionId, dynamic hint}) { + return false; } Future sessionReconnect( @@ -707,7 +702,8 @@ class RustdeskImpl { Future sessionSendSelectedSessionId( {required UuidValue sessionId, required String sid, dynamic hint}) { - throw UnimplementedError("sessionSendSelectedSessionId"); + return Future( + () => js.context.callMethod('setByName', ['selected_sid', sid])); } Future> mainGetSoundInputs({dynamic hint}) { @@ -1828,5 +1824,9 @@ class RustdeskImpl { return Future(() => js.context.callMethod('setByName', ['select_files'])); } + String? sessionGetConnToken({required UuidValue sessionId, dynamic hint}) { + throw UnimplementedError("sessionGetConnToken"); + } + void dispose() {} } From f0450db2030f71df423b5c508b98b7332de0a355 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:49:04 +0800 Subject: [PATCH 210/210] refact: mobile reset canvas (#9766) Signed-off-by: fufesou --- flutter/lib/models/model.dart | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index bd91949843b..ecbfd6fa439 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -1659,11 +1659,25 @@ class CanvasModel with ChangeNotifier { notifyListeners(); } - clear([bool notify = false]) { + // For reset canvas to the last view style + reset() { + _scale = _lastViewStyle.scale; + _devicePixelRatio = ui.window.devicePixelRatio; + if (kIgnoreDpi && _lastViewStyle.style == kRemoteViewStyleOriginal) { + _scale = 1.0 / _devicePixelRatio; + } + final displayWidth = getDisplayWidth(); + final displayHeight = getDisplayHeight(); + _x = (size.width - displayWidth * _scale) / 2; + _y = (size.height - displayHeight * _scale) / 2; + bind.sessionSetViewStyle(sessionId: sessionId, value: _lastViewStyle.style); + notifyListeners(); + } + + clear() { _x = 0; _y = 0; _scale = 1.0; - if (notify) notifyListeners(); } updateScrollPercent() { @@ -1988,7 +2002,7 @@ class CursorModel with ChangeNotifier { _x = _displayOriginX; _y = _displayOriginY; parent.target?.inputModel.moveMouse(_x, _y); - parent.target?.canvasModel.clear(true); + parent.target?.canvasModel.reset(); notifyListeners(); }