From 8e1545b43231ecff341aeac86ca553ffdd003f3e Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 10 Nov 2022 10:27:13 +0800 Subject: [PATCH] portable service Signed-off-by: 21pages --- Cargo.lock | 75 +- Cargo.toml | 2 + flutter/lib/desktop/pages/server_page.dart | 88 ++- flutter/lib/models/model.dart | 3 + flutter/lib/models/server_model.dart | 10 + libs/scrap/src/common/dxgi.rs | 2 +- libs/scrap/src/common/vpxcodec.rs | 6 +- src/core_main.rs | 12 + src/flutter.rs | 6 +- src/flutter_ffi.rs | 8 + src/ipc.rs | 14 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/fr.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/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/template.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/ua.rs | 1 + src/lang/vn.rs | 1 + src/platform/windows.rs | 8 +- src/server.rs | 5 + src/server/connection.rs | 57 +- src/server/input_service.rs | 43 +- src/server/portable_service.rs | 779 +++++++++++++++++++++ src/server/video_qos.rs | 6 +- src/server/video_service.rs | 42 +- src/ui/cm.css | 2 +- src/ui/cm.rs | 14 + src/ui/cm.tis | 45 +- src/ui_cm_interface.rs | 34 + src/ui_interface.rs | 5 +- 46 files changed, 1217 insertions(+), 72 deletions(-) create mode 100644 src/server/portable_service.rs diff --git a/Cargo.lock b/Cargo.lock index 00916eabd..67a471cef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1253,7 +1253,7 @@ dependencies = [ "libc", "memalloc", "system-configuration", - "windows", + "windows 0.30.0", ] [[package]] @@ -4413,6 +4413,8 @@ dependencies = [ "serde_derive", "serde_json 1.0.85", "sha2", + "shared_memory", + "shutdown_hooks", "simple_rc", "sys-locale", "sysinfo", @@ -4761,12 +4763,31 @@ dependencies = [ "digest", ] +[[package]] +name = "shared_memory" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8593196da75d9dc4f69349682bd4c2099f8cde114257d1ef7ef1b33d1aba54" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "nix 0.23.1", + "rand 0.8.5", + "win-sys", +] + [[package]] name = "shlex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "shutdown_hooks" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6057adedbec913419c92996f395ba69931acbd50b7d56955394cd3f7bedbfa45" + [[package]] name = "signal-hook" version = "0.3.14" @@ -5844,6 +5865,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +[[package]] +name = "win-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7b128a98c1cfa201b09eb49ba285887deb3cbe7466a98850eb1adabb452be5" +dependencies = [ + "windows 0.34.0", +] + [[package]] name = "winapi" version = "0.2.8" @@ -5909,6 +5939,19 @@ dependencies = [ "windows_x86_64_msvc 0.30.0", ] +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + [[package]] name = "windows-service" version = "0.4.0" @@ -5959,6 +6002,12 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca" +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -5977,6 +6026,12 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8" +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -5995,6 +6050,12 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6" +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -6013,6 +6074,12 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -6031,6 +6098,12 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" diff --git a/Cargo.toml b/Cargo.toml index 5dc54d58b..689e1a989 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,8 @@ winreg = "0.10" windows-service = "0.4" virtual_display = { path = "libs/virtual_display" } impersonate_system = { git = "https://github.com/21pages/impersonate-system" } +shared_memory = "0.12.4" +shutdown_hooks = "0.1.0" [target.'cfg(target_os = "macos")'.dependencies] objc = "0.2" diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index e344575c7..aac6ee017 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -501,11 +501,46 @@ class _CmControlPanel extends StatelessWidget { } buildAuthorized(BuildContext context) { + final bool canElevate = bind.cmCanElevate(); + final model = Provider.of(context); + final offstage = !(canElevate && model.showElevation); + final width = offstage ? 200.0 : 100.0; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + Offstage( + offstage: offstage, + child: Ink( + width: width, + height: 40, + decoration: BoxDecoration( + color: Colors.green[700], + borderRadius: BorderRadius.circular(10)), + child: InkWell( + onTap: () => + checkClickTime(client.id, () => handleElevate(context)), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.security_sharp, + color: Colors.white, + ), + Text( + translate("Elevate"), + style: TextStyle(color: Colors.white), + ), + ], + )), + ), + ), + Offstage( + offstage: offstage, + child: SizedBox( + width: 30, + )), Ink( - width: 200, + width: width, height: 40, decoration: BoxDecoration( color: Colors.redAccent, borderRadius: BorderRadius.circular(10)), @@ -552,11 +587,50 @@ class _CmControlPanel extends StatelessWidget { } buildUnAuthorized(BuildContext context) { + final bool canElevate = bind.cmCanElevate(); + final model = Provider.of(context); + final offstage = !(canElevate && model.showElevation); + final width = offstage ? 100.0 : 85.0; + final spacerWidth = offstage ? 30.0 : 5.0; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + Offstage( + offstage: offstage, + child: Ink( + height: 40, + width: width, + decoration: BoxDecoration( + color: Colors.green[700], + borderRadius: BorderRadius.circular(10)), + child: InkWell( + onTap: () => checkClickTime(client.id, () { + handleAccept(context); + handleElevate(context); + windowManager.minimize(); + }), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.security_sharp, + color: Colors.white, + ), + Text( + translate("Accept"), + style: TextStyle(color: Colors.white), + ), + ], + )), + ), + ), + Offstage( + offstage: offstage, + child: SizedBox( + width: spacerWidth, + )), Ink( - width: 100, + width: width, height: 40, decoration: BoxDecoration( color: MyTheme.accent, borderRadius: BorderRadius.circular(10)), @@ -576,10 +650,10 @@ class _CmControlPanel extends StatelessWidget { )), ), SizedBox( - width: 30, + width: spacerWidth, ), Ink( - width: 100, + width: width, height: 40, decoration: BoxDecoration( color: Colors.transparent, @@ -611,6 +685,12 @@ class _CmControlPanel extends StatelessWidget { model.sendLoginResponse(client, true); } + void handleElevate(BuildContext context) { + final model = Provider.of(context, listen: false); + model.setShowElevation(false); + bind.cmElevatePortable(connId: client.id); + } + void handleClose(BuildContext context) async { await bind.cmRemoveDisconnectedConnection(connId: client.id); if (await bind.cmGetClientsLength() == 0) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index d7fc414d5..fb4f8b4f4 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -192,6 +192,9 @@ class FfiModel with ChangeNotifier { } } else if (name == 'alias') { handleAliasChanged(evt); + } else if (name == 'show_elevation') { + final show = evt['show'].toString() == 'true'; + parent.target?.serverModel.setShowElevation(show); } }; } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index d407ca51b..1c0d1cbdd 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -27,6 +27,7 @@ class ServerModel with ChangeNotifier { bool _inputOk = false; bool _audioOk = false; bool _fileOk = false; + bool _showElevation = true; int _connectStatus = 0; // Rendezvous Server status String _verificationMethod = ""; String _temporaryPasswordLength = ""; @@ -51,6 +52,8 @@ class ServerModel with ChangeNotifier { bool get fileOk => _fileOk; + bool get showElevation => _showElevation; + int get connectStatus => _connectStatus; String get verificationMethod { @@ -530,6 +533,13 @@ class ServerModel with ChangeNotifier { final index = _clients.indexWhere((client) => client.id == id); tabController.jumpTo(index); } + + void setShowElevation(bool show) { + if (_showElevation != show) { + _showElevation = show; + notifyListeners(); + } + } } class Client { diff --git a/libs/scrap/src/common/dxgi.rs b/libs/scrap/src/common/dxgi.rs index 963f39de1..287d85880 100644 --- a/libs/scrap/src/common/dxgi.rs +++ b/libs/scrap/src/common/dxgi.rs @@ -61,7 +61,7 @@ impl TraitCapturer for Capturer { } } -pub struct Frame<'a>(&'a [u8]); +pub struct Frame<'a>(pub &'a [u8]); impl<'a> ops::Deref for Frame<'a> { type Target = [u8]; diff --git a/libs/scrap/src/common/vpxcodec.rs b/libs/scrap/src/common/vpxcodec.rs index 47b3df3a6..5164886a1 100644 --- a/libs/scrap/src/common/vpxcodec.rs +++ b/libs/scrap/src/common/vpxcodec.rs @@ -4,7 +4,7 @@ use hbb_common::anyhow::{anyhow, Context}; use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame}; -use hbb_common::{ResultType, get_time}; +use hbb_common::{get_time, ResultType}; use crate::codec::EncoderApi; use crate::STRIDE_ALIGN; @@ -233,7 +233,9 @@ impl EncoderApi for VpxEncoder { impl VpxEncoder { pub fn encode(&mut self, pts: i64, data: &[u8], stride_align: usize) -> Result { - assert!(2 * data.len() >= 3 * self.width * self.height); + if 2 * data.len() < 3 * self.width * self.height { + return Err(Error::FailedCall("len not enough".to_string())); + } let mut image = Default::default(); call_vpx_ptr!(vpx_img_wrap( diff --git a/src/core_main.rs b/src/core_main.rs index 443ef92ea..889015c0d 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -80,6 +80,11 @@ pub fn core_main() -> Option> { .ok(); } } + #[cfg(windows)] + if !crate::platform::is_installed() && (_is_elevate || _is_run_as_system) { + crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system); + return None; + } if args.is_empty() { std::thread::spawn(move || crate::start_server(false)); } else { @@ -128,6 +133,13 @@ pub fn core_main() -> Option> { } else if args[0] == "--tray" { crate::tray::start_tray(); return None; + } else if args[0] == "--portable-service" { + crate::platform::elevate_or_run_as_system( + click_setup, + _is_elevate, + _is_run_as_system, + ); + return None; } } if args[0] == "--remove" { diff --git a/src/flutter.rs b/src/flutter.rs index 91b2ce7e5..a69473e5a 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -406,7 +406,7 @@ pub fn session_start_(id: &str, event_stream: StreamSink) -> ResultTy *session.event_stream.write().unwrap() = Some(event_stream); let session = session.clone(); std::thread::spawn(move || { - // if flutter : disable keyboard listen + // if flutter : disable keyboard listen crate::client::disable_keyboard_listening(); io_loop(session); }); @@ -469,6 +469,10 @@ pub mod connection_manager { fn change_language(&self) { self.push_event("language", vec![]); } + + fn show_elevation(&self, show: bool) { + self.push_event("show_elevation", vec![("show", &show.to_string())]); + } } impl FlutterHandler { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 3a2e3f58e..856c4ed21 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1010,6 +1010,14 @@ pub fn cm_switch_permission(conn_id: i32, name: String, enabled: bool) { crate::ui_cm_interface::switch_permission(conn_id, name, enabled) } +pub fn cm_can_elevate() -> SyncReturn { + SyncReturn(crate::ui_cm_interface::can_elevate()) +} + +pub fn cm_elevate_portable(conn_id: i32) { + crate::ui_cm_interface::elevate_portable(conn_id); +} + pub fn main_get_icon() -> String { #[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))] return ui_interface::get_icon(); diff --git a/src/ipc.rs b/src/ipc.rs index 229bcf166..d2d57f8c9 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -130,6 +130,19 @@ pub enum DataControl { }, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(tag = "t", content = "c")] +pub enum DataPortableService { + Ping, + Pong, + ConnCount(Option), + Mouse(Vec), + Key(Vec), + RequestStart, + WillClose, + CmShowElevation(bool), +} + #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(tag = "t", content = "c")] pub enum Data { @@ -187,6 +200,7 @@ pub enum Data { Language(String), Empty, Disconnected, + DataPortableService(DataPortableService), } #[tokio::main(flavor = "current_thread")] diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 7240f2a91..b622123a0 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "此电脑"), ("or", "或"), ("Continue with", "使用"), + ("Elevate", "提权"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index b51cb69e9..5c086bfb2 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index c4d633b9b..90670804a 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 9eb90ebcd..6ebea6b2e 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index e7a35d937..2dce72f6e 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 2eef088c3..2e70a5194 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "Este PC"), ("or", "o"), ("Continue with", "Continuar con"), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index fc738c2b6..811ec911d 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "Ce PC"), ("or", "ou"), ("Continue with", "Continuer avec"), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 6a75b6958..863fa3739 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "Ez a számítógép"), ("or", "vagy"), ("Continue with", "Folytatás a következővel"), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 6f328f127..78fccc9f3 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 75e7859ed..35c09a0b2 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 0e6931379..21344eb11 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 601db354d..3b7269023 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 359e14f55..1dc505807 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 382b254f0..7ed98913c 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 49679bfd4..8b4c980f6 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 3a60dc313..0d5594ea9 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "Este PC"), ("or", "ou"), ("Continue with", "Continuar com"), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 7d7b0c6ad..22c246f86 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "Этот компьютер"), ("or", "или"), ("Continue with", "Продолжить с"), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 4dfd8b02e..618ede5cd 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 7c1f18df3..4ff8f9b87 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index f856182f3..6fb89a09f 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 6a196feb7..1150199cd 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", "提權"), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 95d19b26b..cc0ef6536 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index c95498ca8..9773d7655 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -389,5 +389,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", ""), ("or", ""), ("Continue with", ""), + ("Elevate", ""), ].iter().cloned().collect(); } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 97dfbcc25..b418d0904 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -63,7 +63,7 @@ pub fn get_cursor() -> ResultType> { unsafe { let mut ci: CURSORINFO = mem::MaybeUninit::uninit().assume_init(); ci.cbSize = std::mem::size_of::() as _; - if GetCursorInfo(&mut ci) == FALSE { + if crate::portable_service::client::get_cursor_info(&mut ci) == FALSE { return Err(io::Error::last_os_error().into()); } if ci.flags & CURSOR_SHOWING == 0 { @@ -1542,9 +1542,11 @@ pub fn elevate_or_run_as_system(is_setup: bool, is_elevate: bool, is_run_as_syst } else { "--run-as-system" }; - if is_root() { - log::debug!("portable run as system user"); + if is_run_as_system { + log::info!("run portable service"); + crate::portable_service::server::run_portable_service(); + } } else { match is_elevated(None) { Ok(elevated) => { diff --git a/src/server.rs b/src/server.rs index 6e549c9f4..7e00532fe 100644 --- a/src/server.rs +++ b/src/server.rs @@ -49,6 +49,8 @@ pub const NAME_POS: &'static str = ""; } mod connection; +#[cfg(windows)] +pub mod portable_service; mod service; mod video_qos; pub mod video_service; @@ -60,6 +62,7 @@ type ConnMap = HashMap; lazy_static::lazy_static! { pub static ref CHILD_PROCESS: Childs = Default::default(); + pub static ref CONN_COUNT: Arc> = Default::default(); } pub struct Server { @@ -259,6 +262,7 @@ impl Server { } } self.connections.insert(conn.id(), conn); + *CONN_COUNT.lock().unwrap() = self.connections.len(); } pub fn remove_connection(&mut self, conn: &ConnInner) { @@ -266,6 +270,7 @@ impl Server { s.on_unsubscribe(conn.id()); } self.connections.remove(&conn.id()); + *CONN_COUNT.lock().unwrap() = self.connections.len(); } pub fn close_connections(&mut self) { diff --git a/src/server/connection.rs b/src/server/connection.rs index 891c48888..c77f6ebd7 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -236,8 +236,12 @@ impl Connection { #[cfg(not(any(target_os = "android", target_os = "ios")))] std::thread::spawn(move || Self::handle_input(rx_input, tx_cloned)); let mut second_timer = time::interval(Duration::from_secs(1)); + #[cfg(windows)] let mut last_uac = false; + #[cfg(windows)] let mut last_foreground_window_elevated = false; + #[cfg(windows)] + let is_installed = crate::platform::is_installed(); loop { tokio::select! { @@ -341,6 +345,12 @@ impl Connection { }; conn.send(msg_out).await; } + #[cfg(windows)] + ipc::Data::DataPortableService(ipc::DataPortableService::RequestStart) => { + if let Err(e) = crate::portable_service::client::start_portable_service() { + log::error!("Failed to start portable service from cm:{:?}", e); + } + } _ => {} } }, @@ -417,23 +427,36 @@ impl Connection { } }, _ = second_timer.tick() => { - let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone(); - if last_uac != uac { - last_uac = uac; - let mut misc = Misc::new(); - misc.set_uac(uac); - let mut msg = Message::new(); - msg.set_misc(misc); - conn.inner.send(msg.into()); - } - let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap().clone(); - if last_foreground_window_elevated != foreground_window_elevated { - last_foreground_window_elevated = foreground_window_elevated; - let mut misc = Misc::new(); - misc.set_foreground_window_elevated(foreground_window_elevated); - let mut msg = Message::new(); - msg.set_misc(misc); - conn.inner.send(msg.into()); + #[cfg(windows)] + { + use crate::portable_service::client::{PORTABLE_SERVICE_STATUS, PortableServiceStatus::*}; + let uac = crate::video_service::IS_UAC_RUNNING.lock().unwrap().clone(); + if last_uac != uac { + last_uac = uac; + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == NotStarted { + let mut misc = Misc::new(); + misc.set_uac(uac); + let mut msg = Message::new(); + msg.set_misc(misc); + conn.inner.send(msg.into()); + } + } + let foreground_window_elevated = crate::video_service::IS_FOREGROUND_WINDOW_ELEVATED.lock().unwrap().clone(); + if last_foreground_window_elevated != foreground_window_elevated { + last_foreground_window_elevated = foreground_window_elevated; + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == NotStarted { + let mut misc = Misc::new(); + misc.set_foreground_window_elevated(foreground_window_elevated); + let mut msg = Message::new(); + msg.set_misc(misc); + conn.inner.send(msg.into()); + } + } + if !is_installed { + let show_elevation = PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == NotStarted; + conn.send_to_cm(ipc::Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show_elevation))); + + } } } _ = test_delay_timer.tick() => { diff --git a/src/server/input_service.rs b/src/server/input_service.rs index f2591df0b..af4b7d853 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -287,7 +287,23 @@ pub fn handle_mouse(evt: &MouseEvent, conn: i32) { QUEUE.exec_async(move || handle_mouse_(&evt, conn)); return; } - handle_mouse_(evt, conn); + if !active_mouse_(conn) { + return; + } + let evt_type = evt.mask & 0x7; + if evt_type == 0 { + let time = get_time(); + *LATEST_PEER_INPUT_CURSOR.lock().unwrap() = Input { + time, + conn, + x: evt.x, + y: evt.y, + }; + } + #[cfg(windows)] + crate::portable_service::client::handle_mouse(evt); + #[cfg(not(windows))] + handle_mouse_(evt); } pub fn fix_key_down_timeout_loop() { @@ -415,8 +431,7 @@ fn active_mouse_(conn: i32) -> bool { let lock = LATEST_PEER_INPUT_CURSOR.lock().unwrap(); (lock.x, lock.y) }; - let mut can_active = - in_actived_dist(last_in_x, x) && in_actived_dist(last_in_y, y); + let mut can_active = in_actived_dist(last_in_x, x) && in_actived_dist(last_in_y, y); // The cursor may not have been moved to last input position if system is busy now. // While this is not a common case, we check it again after some time later. if !can_active { @@ -425,8 +440,7 @@ fn active_mouse_(conn: i32) -> bool { std::thread::sleep(std::time::Duration::from_micros(10)); // Sleep here can also somehow suppress delay accumulation. if let Some((x2, y2)) = crate::get_cursor_pos() { - can_active = - in_actived_dist(last_in_x, x2) && in_actived_dist(last_in_y, y2); + can_active = in_actived_dist(last_in_x, x2) && in_actived_dist(last_in_y, y2); } } if !can_active { @@ -440,15 +454,11 @@ fn active_mouse_(conn: i32) -> bool { } } -fn handle_mouse_(evt: &MouseEvent, conn: i32) { +pub fn handle_mouse_(evt: &MouseEvent) { if EXITING.load(Ordering::SeqCst) { return; } - if !active_mouse_(conn) { - return; - } - #[cfg(windows)] crate::platform::windows::try_change_desktop(); let buttons = evt.mask >> 3; @@ -477,14 +487,6 @@ fn handle_mouse_(evt: &MouseEvent, conn: i32) { } match evt_type { 0 => { - let time = get_time(); - *LATEST_PEER_INPUT_CURSOR.lock().unwrap() = Input { - time, - conn, - x: evt.x, - y: evt.y, - }; - en.mouse_move_to(evt.x, evt.y); } 1 => match buttons { @@ -698,6 +700,9 @@ pub fn handle_key(evt: &KeyEvent) { QUEUE.exec_async(move || handle_key_(&evt)); return; } + #[cfg(windows)] + crate::portable_service::client::handle_key(evt); + #[cfg(not(windows))] handle_key_(evt); } @@ -949,7 +954,7 @@ fn legacy_keyboard_mode(evt: &KeyEvent) { } } -fn handle_key_(evt: &KeyEvent) { +pub fn handle_key_(evt: &KeyEvent) { if EXITING.load(Ordering::SeqCst) { return; } diff --git a/src/server/portable_service.rs b/src/server/portable_service.rs new file mode 100644 index 000000000..1de2a1c8b --- /dev/null +++ b/src/server/portable_service.rs @@ -0,0 +1,779 @@ +use core::slice; +use hbb_common::{ + allow_err, + anyhow::anyhow, + bail, + config::Config, + log, + message_proto::{KeyEvent, MouseEvent}, + protobuf::Message, + sleep, + tokio::{self, sync::mpsc}, + ResultType, +}; +use scrap::{Capturer, Frame, TraitCapturer}; +use shared_memory::*; +use std::{ + mem::size_of, + ops::{Deref, DerefMut}, + sync::{Arc, Mutex}, + time::Duration, +}; +use winapi::{ + shared::minwindef::{BOOL, FALSE, TRUE}, + um::winuser::{self, CURSORINFO, PCURSORINFO}, +}; + +use crate::{ + ipc::{self, new_listener, Connection, Data, DataPortableService}, + video_service::get_current_display, +}; + +use super::video_qos; + +const SIZE_COUNTER: usize = size_of::() * 2; +const FRAME_ALIGN: usize = 64; + +const ADDR_CURSOR_PARA: usize = 0; +const ADDR_CURSOR_COUNTER: usize = ADDR_CURSOR_PARA + size_of::(); + +const ADDR_CAPTURER_PARA: usize = ADDR_CURSOR_COUNTER + SIZE_COUNTER; +const ADDR_CAPTURE_FRAME_SIZE: usize = ADDR_CAPTURER_PARA + size_of::(); +const ADDR_CAPTURE_WOULDBLOCK: usize = ADDR_CAPTURE_FRAME_SIZE + size_of::(); +const ADDR_CAPTURE_FRAME_COUNTER: usize = ADDR_CAPTURE_WOULDBLOCK + size_of::(); + +const ADDR_CAPTURE_FRAME: usize = + (ADDR_CAPTURE_FRAME_COUNTER + SIZE_COUNTER + FRAME_ALIGN - 1) / FRAME_ALIGN * FRAME_ALIGN; + +const IPC_PROFIX: &str = "_portable_service"; +pub const SHMEM_NAME: &str = "_portable_service"; +const MAX_NACK: usize = 3; +const IPC_CONN_TIMEOUT: Duration = Duration::from_secs(3); + +pub enum PortableServiceStatus { + NonStart, + Running, +} + +impl Default for PortableServiceStatus { + fn default() -> Self { + Self::NonStart + } +} + +pub struct SharedMemory { + inner: Shmem, +} + +unsafe impl Send for SharedMemory {} +unsafe impl Sync for SharedMemory {} + +impl Deref for SharedMemory { + type Target = Shmem; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for SharedMemory { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl SharedMemory { + pub fn create(name: &str, size: usize) -> ResultType { + let flink = Self::flink(name.to_string()); + let shmem = match ShmemConf::new() + .size(size) + .flink(&flink) + .force_create_flink() + .create() + { + Ok(m) => m, + Err(ShmemError::LinkExists) => { + bail!( + "Unable to force create shmem flink {}, which should not happen.", + flink + ) + } + Err(e) => { + bail!("Unable to create shmem flink {} : {}", flink, e); + } + }; + log::info!("Create shared memory, size:{}, flink:{}", size, flink); + Self::set_all_perm(&flink); + Ok(SharedMemory { inner: shmem }) + } + + pub fn open_existing(name: &str) -> ResultType { + let flink = Self::flink(name.to_string()); + let shmem = match ShmemConf::new().flink(&flink).allow_raw(true).open() { + Ok(m) => m, + Err(e) => { + bail!("Unable to open existing shmem flink {} : {}", flink, e); + } + }; + log::info!("open existing shared memory, flink:{:?}", flink); + Ok(SharedMemory { inner: shmem }) + } + + pub fn write(&self, addr: usize, data: &[u8]) { + unsafe { + assert!(addr + data.len() <= self.inner.len()); + let ptr = self.inner.as_ptr().add(addr); + let shared_mem_slice = slice::from_raw_parts_mut(ptr, data.len()); + shared_mem_slice.copy_from_slice(data); + } + } + + fn flink(name: String) -> String { + let mut shmem_flink = format!("shared_memory{}", name); + if cfg!(windows) { + let df = "C:\\ProgramData"; + let df = if std::path::Path::new(df).exists() { + df.to_owned() + } else { + std::env::var("TEMP").unwrap_or("C:\\Windows\\TEMP".to_owned()) + }; + let df = format!("{}\\{}", df, *hbb_common::config::APP_NAME.read().unwrap()); + std::fs::create_dir(&df).ok(); + shmem_flink = format!("{}\\{}", df, shmem_flink); + } else { + shmem_flink = Config::ipc_path("").replace("ipc", "") + &shmem_flink; + } + return shmem_flink; + } + + fn set_all_perm(_p: &str) { + #[cfg(not(windows))] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(_p, std::fs::Permissions::from_mode(0o0777)).ok(); + } + } +} + +mod utils { + use core::slice; + use std::mem::size_of; + + pub fn i32_to_vec(i: i32) -> Vec { + i.to_ne_bytes().to_vec() + } + + pub fn ptr_to_i32(ptr: *const u8) -> i32 { + unsafe { + let v = slice::from_raw_parts(ptr, size_of::()); + i32::from_ne_bytes([v[0], v[1], v[2], v[3]]) + } + } + + pub fn counter_ready(counter: *const u8) -> bool { + unsafe { + let wptr = counter; + let rptr = counter.add(size_of::()); + let iw = ptr_to_i32(wptr); + let ir = ptr_to_i32(rptr); + if ir != iw { + std::ptr::copy_nonoverlapping(wptr, rptr as *mut _, size_of::()); + true + } else { + false + } + } + } + + pub fn increase_counter(ptr: *mut u8) { + unsafe { + let i = ptr_to_i32(ptr); + let v = i32_to_vec(i + 1); + std::ptr::copy_nonoverlapping(v.as_ptr(), ptr, size_of::()); + } + } + + pub fn align(v: usize, align: usize) -> usize { + (v + align - 1) / align * align + } +} + +// functions called in seperate SYSTEM user process. +pub mod server { + use hbb_common::tokio::time::Instant; + + use super::*; + + lazy_static::lazy_static! { + static ref EXIT: Arc> = Default::default(); + } + + pub fn run_portable_service() { + let shmem = Arc::new(SharedMemory::open_existing(SHMEM_NAME).unwrap()); + let shmem1 = shmem.clone(); + let shmem2 = shmem.clone(); + let mut threads = vec![]; + threads.push(std::thread::spawn(|| { + run_get_cursor_info(shmem1); + })); + threads.push(std::thread::spawn(|| { + run_capture(shmem2); + })); + threads.push(std::thread::spawn(|| { + run_ipc_server(); + })); + threads.push(std::thread::spawn(|| { + run_exit_check(); + })); + for th in threads.drain(..) { + th.join().unwrap(); + log::info!("all thread joined"); + } + } + + fn run_exit_check() { + loop { + if EXIT.lock().unwrap().clone() { + std::thread::sleep(Duration::from_secs(1)); + log::info!("exit from seperate check thread"); + std::process::exit(0); + } + std::thread::sleep(Duration::from_secs(1)); + } + } + + fn run_get_cursor_info(shmem: Arc) { + loop { + if EXIT.lock().unwrap().clone() { + break; + } + unsafe { + let para = shmem.as_ptr().add(ADDR_CURSOR_PARA) as *mut CURSORINFO; + (*para).cbSize = size_of::() as _; + let result = winuser::GetCursorInfo(para); + if result == TRUE { + utils::increase_counter(shmem.as_ptr().add(ADDR_CURSOR_COUNTER)); + } + } + // more frequent in case of `Error of mouse_cursor service` + std::thread::sleep(Duration::from_millis(15)); + } + } + + fn run_capture(shmem: Arc) { + let mut c = None; + let mut last_current_display = usize::MAX; + let mut last_use_yuv = false; + let mut last_timeout_ms: i32 = 33; + let mut spf = Duration::from_millis(last_timeout_ms as _); + loop { + if EXIT.lock().unwrap().clone() { + break; + } + let start = std::time::Instant::now(); + unsafe { + let para_ptr = shmem.as_ptr().add(ADDR_CAPTURER_PARA); + let para = para_ptr as *const CapturerPara; + let current_display = (*para).current_display; + let use_yuv = (*para).use_yuv; + let timeout_ms = (*para).timeout_ms; + if c.is_none() { + let use_yuv = true; + *crate::video_service::CURRENT_DISPLAY.lock().unwrap() = current_display; + let (_, _current, display) = get_current_display().unwrap(); + match Capturer::new(display, use_yuv) { + Ok(mut v) => { + c = { + last_current_display = current_display; + last_use_yuv = use_yuv; + // dxgi failed at loadFrame on my PC. + // to-do: try dxgi on another PC. + v.set_gdi(); + Some(v) + } + } + Err(e) => { + log::error!("Failed to create gdi capturer:{:?}", e); + std::thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + } + } else { + if current_display != last_current_display || use_yuv != last_use_yuv { + log::info!( + "display:{}->{}, use_yuv:{}->{}", + last_current_display, + current_display, + last_use_yuv, + use_yuv + ); + c = None; + continue; + } + if timeout_ms != last_timeout_ms + && timeout_ms >= 1000 / video_qos::MAX_FPS as i32 + && timeout_ms <= 1000 / video_qos::MIN_FPS as i32 + { + last_timeout_ms = timeout_ms; + spf = Duration::from_millis(timeout_ms as _); + } + } + match c.as_mut().unwrap().frame(spf) { + Ok(f) => { + let len = f.0.len(); + let len_slice = utils::i32_to_vec(len as _); + shmem.write(ADDR_CAPTURE_FRAME_SIZE, &len_slice); + shmem.write(ADDR_CAPTURE_FRAME, f.0); + shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE)); + utils::increase_counter(shmem.as_ptr().add(ADDR_CAPTURE_FRAME_COUNTER)); + } + Err(e) => { + if e.kind() != std::io::ErrorKind::WouldBlock { + log::error!("capture frame failed:{:?}", e); + crate::platform::try_change_desktop(); + c = None; + shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(FALSE)); + continue; + } else { + shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE)); + } + } + } + } + let elapsed = start.elapsed(); + if elapsed < spf { + std::thread::sleep(spf - elapsed); + } + } + } + + #[tokio::main(flavor = "current_thread")] + async fn run_ipc_server() { + use DataPortableService::*; + + let postfix = IPC_PROFIX; + let last_recv_time = Arc::new(Mutex::new(Instant::now())); + let mut interval = tokio::time::interval(Duration::from_secs(1)); + + match new_listener(postfix).await { + Ok(mut incoming) => loop { + tokio::select! { + Some(result) = incoming.next() => { + match result { + Ok(stream) => { + log::info!("Got new connection"); + let last_recv_time_cloned = last_recv_time.clone(); + tokio::spawn(async move { + let mut stream = Connection::new(stream); + let postfix = postfix.to_owned(); + let mut timer = tokio::time::interval(Duration::from_secs(1)); + let mut nack = 0; + let mut old_conn_count = 0; + loop { + tokio::select! { + res = stream.next() => { + if res.is_ok() { + *last_recv_time_cloned.lock().unwrap() = Instant::now(); + } + match res { + Err(err) => { + log::error!( + "ipc{} connection closed: {}", + postfix, + err + ); + *EXIT.lock().unwrap() = true; + break; + } + Ok(Some(Data::DataPortableService(data))) => match data { + Ping => { + allow_err!( + stream + .send(&Data::DataPortableService(Pong)) + .await + ); + } + Pong => { + nack = 0; + } + ConnCount(Some(n)) => { + if old_conn_count != 0 && n == 0 { + log::info!("Connection count decrease to 0, exit"); + stream.send(&Data::DataPortableService(WillClose)).await.ok(); + *EXIT.lock().unwrap() = true; + break; + } + old_conn_count = n; + } + Mouse(v) => { + if let Ok(evt) = MouseEvent::parse_from_bytes(&v) { + crate::input_service::handle_mouse_(&evt); + } + } + Key(v) => { + if let Ok(evt) = KeyEvent::parse_from_bytes(&v) { + crate::input_service::handle_key_(&evt); + } + } + _ => {} + }, + _ => {} + } + } + _ = timer.tick() => { + nack+=1; + if nack > MAX_NACK { + log::info!("max ping nack, exit"); + *EXIT.lock().unwrap() = true; + break; + } + stream.send(&Data::DataPortableService(Ping)).await.ok(); + stream.send(&Data::DataPortableService(ConnCount(None))).await.ok(); + } + } + } + }); + } + Err(err) => { + log::error!("Couldn't get portable client: {:?}", err); + *EXIT.lock().unwrap() = true; + } + } + } + _ = interval.tick() => { + if last_recv_time.lock().unwrap().elapsed() > IPC_CONN_TIMEOUT { + log::error!("receive data timeout"); + *EXIT.lock().unwrap() = true; + } + if EXIT.lock().unwrap().clone() { + break; + } + } + } + }, + Err(err) => { + log::error!("Failed to start cm ipc server: {}", err); + *EXIT.lock().unwrap() = true; + } + } + } +} + +// functions called in main process. +pub mod client { + use hbb_common::anyhow::Context; + + use super::*; + + lazy_static::lazy_static! { + pub static ref SHMEM: Arc>> = Default::default(); + pub static ref PORTABLE_SERVICE_STATUS: Arc> = Default::default(); + static ref SENDER : Mutex> = Mutex::new(client::start_ipc_client()); + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum PortableServiceStatus { + NotStarted, + Starting, + Running, + } + + impl Default for PortableServiceStatus { + fn default() -> Self { + Self::NotStarted + } + } + + pub(crate) fn start_portable_service() -> ResultType<()> { + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == PortableServiceStatus::NotStarted { + if SHMEM.lock().unwrap().is_none() { + let displays = scrap::Display::all()?; + if displays.is_empty() { + bail!("no display available!"); + } + let mut max_pixel = 0; + let align = 64; + for d in displays { + let pixel = utils::align(d.width(), align) * utils::align(d.height(), align); + if max_pixel < pixel { + max_pixel = pixel; + } + } + let shmem_size = utils::align(ADDR_CAPTURE_FRAME + max_pixel * 4, align); + // os error 112, no enough space + *SHMEM.lock().unwrap() = Some(crate::portable_service::SharedMemory::create( + crate::portable_service::SHMEM_NAME, + shmem_size, + )?); + shutdown_hooks::add_shutdown_hook(drop_shmem); + } + if crate::common::run_me(vec!["--portable-service"]).is_err() { + *SHMEM.lock().unwrap() = None; + bail!("Failed to run portable service process"); + } + *PORTABLE_SERVICE_STATUS.lock().unwrap() = PortableServiceStatus::Starting; + let _sender = SENDER.lock().unwrap(); + } + Ok(()) + } + + extern "C" fn drop_shmem() { + log::info!("drop shared memory"); + *SHMEM.lock().unwrap() = None; + } + + pub struct CapturerPortable; + + impl CapturerPortable { + pub fn new(current_display: usize, use_yuv: bool) -> Self + where + Self: Sized, + { + Self::set_para(CapturerPara { + current_display, + use_yuv, + timeout_ms: 33, + }); + CapturerPortable {} + } + + fn set_para(para: CapturerPara) { + let mut option = SHMEM.lock().unwrap(); + let shmem = option.as_mut().unwrap(); + let para_ptr = ¶ as *const CapturerPara as *const u8; + let para_data; + unsafe { + para_data = slice::from_raw_parts(para_ptr, size_of::()); + } + shmem.write(ADDR_CAPTURER_PARA, para_data); + } + } + + impl TraitCapturer for CapturerPortable { + fn set_use_yuv(&mut self, use_yuv: bool) { + let mut option = SHMEM.lock().unwrap(); + let shmem = option.as_mut().unwrap(); + unsafe { + let para_ptr = shmem.as_ptr().add(ADDR_CAPTURER_PARA); + let para = para_ptr as *const CapturerPara; + if use_yuv != (*para).use_yuv { + Self::set_para(CapturerPara { + current_display: (*para).current_display, + use_yuv, + timeout_ms: (*para).timeout_ms, + }); + } + } + } + + fn frame<'a>(&'a mut self, timeout: Duration) -> std::io::Result> { + let mut option = SHMEM.lock().unwrap(); + let shmem = option.as_mut().unwrap(); + unsafe { + let base = shmem.as_ptr(); + let para_ptr = base.add(ADDR_CAPTURER_PARA); + let para = para_ptr as *const CapturerPara; + if timeout.as_millis() != (*para).timeout_ms as _ { + Self::set_para(CapturerPara { + current_display: (*para).current_display, + use_yuv: (*para).use_yuv, + timeout_ms: timeout.as_millis() as _, + }); + } + if utils::counter_ready(base.add(ADDR_CAPTURE_FRAME_COUNTER)) { + let frame_len_ptr = base.add(ADDR_CAPTURE_FRAME_SIZE); + let frame_len = utils::ptr_to_i32(frame_len_ptr); + let frame_ptr = base.add(ADDR_CAPTURE_FRAME); + let data = slice::from_raw_parts(frame_ptr, frame_len as usize); + Ok(Frame(data)) + } else { + let ptr = base.add(ADDR_CAPTURE_WOULDBLOCK); + let wouldblock = utils::ptr_to_i32(ptr); + if wouldblock == TRUE { + Err(std::io::Error::new( + std::io::ErrorKind::WouldBlock, + "wouldblock error".to_string(), + )) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "other error".to_string(), + )) + } + } + } + } + + fn is_gdi(&self) -> bool { + true + } + + fn set_gdi(&mut self) -> bool { + true + } + } + + pub(super) fn start_ipc_client() -> mpsc::UnboundedSender { + let (tx, rx) = mpsc::unbounded_channel::(); + std::thread::spawn(move || start_ipc_client_async(rx)); + tx + } + + #[tokio::main(flavor = "current_thread")] + async fn start_ipc_client_async(rx: mpsc::UnboundedReceiver) { + use DataPortableService::*; + let mut rx = rx; + let mut connect_failed = 0; + loop { + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == PortableServiceStatus::NotStarted + { + sleep(1.).await; + continue; + } + if let Ok(mut c) = ipc::connect(1000, IPC_PROFIX).await { + let mut nack = 0; + let mut timer = tokio::time::interval(Duration::from_secs(1)); + loop { + tokio::select! { + res = c.next() => { + match res { + Err(err) => { + log::error!("ipc connection closed: {}", err); + break; + } + Ok(Some(Data::DataPortableService(data))) => { + match data { + Ping => { + c.send(&Data::DataPortableService(Pong)).await.ok(); + } + Pong => { + nack = 0; + *PORTABLE_SERVICE_STATUS.lock().unwrap() = PortableServiceStatus::Running; + }, + ConnCount(None) => { + let cnt = crate::server::CONN_COUNT.lock().unwrap().clone(); + c.send(&Data::DataPortableService(ConnCount(Some(cnt)))).await.ok(); + }, + WillClose => { + log::info!("portable service will close, set status to not started"); + *PORTABLE_SERVICE_STATUS.lock().unwrap() = PortableServiceStatus::NotStarted; + break; + } + _=>{} + } + } + _ => {} + } + } + _ = timer.tick() => { + nack+=1; + if nack > MAX_NACK { + // In fact, this will not happen, ipc will be closed before max nack. + log::error!("max ipc nack, set status to not started"); + *PORTABLE_SERVICE_STATUS.lock().unwrap() = PortableServiceStatus::NotStarted; + break; + } + c.send(&Data::DataPortableService(Ping)).await.ok(); + } + Some(data) = rx.recv() => { + allow_err!(c.send(&data).await); + } + + } + } + } else { + connect_failed += 1; + if connect_failed > IPC_CONN_TIMEOUT.as_secs() { + connect_failed = 0; + *PORTABLE_SERVICE_STATUS.lock().unwrap() = PortableServiceStatus::NotStarted; + log::info!( + "connect failed {} times, set status to not started", + connect_failed + ); + } + log::info!( + "client ip connect failed, status:{:?}", + PORTABLE_SERVICE_STATUS.lock().unwrap().clone(), + ); + } + sleep(1.).await; + } + } + + fn client_ipc_send(data: Data) -> ResultType<()> { + let sender = SENDER.lock().unwrap(); + sender + .send(data) + .map_err(|e| anyhow!("ipc send error:{:?}", e)) + } + + fn get_cursor_info_(shmem: &mut SharedMemory, pci: PCURSORINFO) -> BOOL { + unsafe { + let shmem_addr_para = shmem.as_ptr().add(ADDR_CURSOR_PARA); + if utils::counter_ready(shmem.as_ptr().add(ADDR_CURSOR_COUNTER)) { + std::ptr::copy_nonoverlapping(shmem_addr_para, pci as _, size_of::()); + return TRUE; + } + FALSE + } + } + + fn handle_mouse_(evt: &MouseEvent) -> ResultType<()> { + let mut v = vec![]; + evt.write_to_vec(&mut v)?; + client_ipc_send(Data::DataPortableService(DataPortableService::Mouse(v))) + } + + fn handle_key_(evt: &KeyEvent) -> ResultType<()> { + let mut v = vec![]; + evt.write_to_vec(&mut v)?; + client_ipc_send(Data::DataPortableService(DataPortableService::Key(v))) + } + + pub fn create_capturer( + current_display: usize, + display: scrap::Display, + use_yuv: bool, + ) -> ResultType> { + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == PortableServiceStatus::Running { + log::info!("Create shared memeory capturer"); + return Ok(Box::new(CapturerPortable::new(current_display, use_yuv))); + } else { + log::debug!("Create capturer dxgi|gdi"); + return Ok(Box::new( + Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?, + )); + } + } + + pub fn get_cursor_info(pci: PCURSORINFO) -> BOOL { + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == PortableServiceStatus::Running { + get_cursor_info_(&mut SHMEM.lock().unwrap().as_mut().unwrap(), pci) + } else { + unsafe { winuser::GetCursorInfo(pci) } + } + } + + pub fn handle_mouse(evt: &MouseEvent) { + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == PortableServiceStatus::Running { + handle_mouse_(evt).ok(); + } else { + crate::input_service::handle_mouse_(evt); + } + } + + pub fn handle_key(evt: &KeyEvent) { + if PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == PortableServiceStatus::Running { + handle_key_(evt).ok(); + } else { + crate::input_service::handle_key_(evt); + } + } +} + +#[repr(C)] +struct CapturerPara { + current_display: usize, + use_yuv: bool, + timeout_ms: i32, +} diff --git a/src/server/video_qos.rs b/src/server/video_qos.rs index ba67d3fc4..d75596157 100644 --- a/src/server/video_qos.rs +++ b/src/server/video_qos.rs @@ -1,8 +1,8 @@ use super::*; use std::time::Duration; -const FPS: u8 = 30; -const MIN_FPS: u8 = 10; -const MAX_FPS: u8 = 120; +pub const FPS: u8 = 30; +pub const MIN_FPS: u8 = 10; +pub const MAX_FPS: u8 = 120; trait Percent { fn as_percent(&self) -> u32; } diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 81ab494e5..f48fefeec 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -19,6 +19,8 @@ // https://slhck.info/video/2017/03/01/rate-control.html use super::{video_qos::VideoQoS, *}; +#[cfg(windows)] +use crate::portable_service::client::{PortableServiceStatus, PORTABLE_SERVICE_STATUS}; use hbb_common::tokio::sync::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, Mutex as TokioMutex, @@ -49,7 +51,7 @@ pub const SCRAP_X11_REF_URL: &str = "https://rustdesk.com/docs/en/manual/linux/# pub const NAME: &'static str = "video"; lazy_static::lazy_static! { - static ref CURRENT_DISPLAY: Arc> = Arc::new(Mutex::new(usize::MAX)); + pub static ref CURRENT_DISPLAY: Arc> = Arc::new(Mutex::new(usize::MAX)); static ref LAST_ACTIVE: Arc> = Arc::new(Mutex::new(Instant::now())); static ref SWITCH: Arc> = Default::default(); static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option)>, Arc)>>>) = { @@ -188,6 +190,7 @@ fn create_capturer( privacy_mode_id: i32, display: Display, use_yuv: bool, + current: usize, ) -> ResultType> { #[cfg(not(windows))] let c: Option> = None; @@ -244,17 +247,18 @@ fn create_capturer( } } - let c = match c { - Some(c1) => c1, + match c { + Some(c1) => return Ok(c1), None => { - let c1 = - Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?; log::debug!("Create capturer dxgi|gdi"); - Box::new(c1) + #[cfg(windows)] + return crate::portable_service::client::create_capturer(current, display, use_yuv); + #[cfg(not(windows))] + return Ok(Box::new( + Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?, + )); } }; - - Ok(c) } #[cfg(windows)] @@ -277,8 +281,8 @@ fn ensure_close_virtual_device() -> ResultType<()> { pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool { let test_begin = Instant::now(); while test_begin.elapsed().as_millis() < timeout_millis as _ { - if let Ok((_, _, display)) = get_current_display() { - if let Ok(_) = create_capturer(privacy_mode_id, display, true) { + if let Ok((_, current, display)) = get_current_display() { + if let Ok(_) = create_capturer(privacy_mode_id, display, true, current) { return true; } } @@ -369,7 +373,7 @@ fn get_capturer(use_yuv: bool) -> ResultType { } else { log::info!("In privacy mode, the peer side cannot watch the screen"); } - let capturer = create_capturer(captuerer_privacy_mode_id, display, use_yuv)?; + let capturer = create_capturer(captuerer_privacy_mode_id, display, use_yuv, current)?; Ok(CapturerInfo { origin, width, @@ -468,6 +472,11 @@ fn run(sp: GenericService) -> ResultType<()> { let recorder: Arc>> = Default::default(); #[cfg(windows)] start_uac_elevation_check(); + #[cfg(windows)] + let portable_service_status = crate::portable_service::client::PORTABLE_SERVICE_STATUS + .lock() + .unwrap() + .clone(); #[cfg(target_os = "linux")] let mut would_block_count = 0u32; @@ -498,10 +507,17 @@ fn run(sp: GenericService) -> ResultType<()> { if codec_name != Encoder::current_hw_encoder_name() { bail!("SWITCH"); } + #[cfg(windows)] + if portable_service_status != PORTABLE_SERVICE_STATUS.lock().unwrap().clone() { + bail!("SWITCH"); + } check_privacy_mode_changed(&sp, c.privacy_mode_id)?; #[cfg(windows)] { - if crate::platform::windows::desktop_changed() { + if crate::platform::windows::desktop_changed() + && PORTABLE_SERVICE_STATUS.lock().unwrap().clone() + == PortableServiceStatus::NotStarted + { bail!("Desktop changed"); } } @@ -874,7 +890,7 @@ pub(super) fn get_current_display_2(mut all: Vec) -> ResultType<(usize, return Ok((n, current, all.remove(current))); } -fn get_current_display() -> ResultType<(usize, usize, Display)> { +pub fn get_current_display() -> ResultType<(usize, usize, Display)> { get_current_display_2(try_get_displays()?) } diff --git a/src/ui/cm.css b/src/ui/cm.css index fbbd58961..fccdb155f 100644 --- a/src/ui/cm.css +++ b/src/ui/cm.css @@ -68,7 +68,7 @@ div.permissions { } div.permissions > div { - size: 48px; + size: 42px; background: color(accent); } diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 3472a184e..7c0e3fe24 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -51,6 +51,10 @@ impl InvokeUiCM for SciterHandler { fn change_language(&self) { // TODO } + + fn show_elevation(&self, show: bool) { + self.call("showElevation", &make_args!(show)); + } } impl SciterHandler { @@ -123,6 +127,14 @@ impl SciterConnectionManager { fn t(&self, name: String) -> String { crate::client::translate(name) } + + fn can_elevate(&self) -> bool { + crate::ui_cm_interface::can_elevate() + } + + fn elevate_portable(&self, id: i32) { + crate::ui_cm_interface::elevate_portable(id); + } } impl sciter::EventHandler for SciterConnectionManager { @@ -141,5 +153,7 @@ impl sciter::EventHandler for SciterConnectionManager { fn authorize(i32); fn switch_permission(i32, String, bool); fn send_msg(i32, String); + fn can_elevate(); + fn elevate_portable(i32); } } diff --git a/src/ui/cm.tis b/src/ui/cm.tis index 2b42b719c..5238ab91a 100644 --- a/src/ui/cm.tis +++ b/src/ui/cm.tis @@ -3,6 +3,7 @@ view.windowFrame = is_osx ? #extended : #solid; var body; var connections = []; var show_chat = false; +var show_elevation = true; class Body: Reactor.Component { @@ -27,6 +28,7 @@ class Body: Reactor.Component }; var right_style = show_chat ? "" : "display: none"; var disconnected = c.disconnected; + var show_elevation_btn = handler.can_elevate() && show_elevation; // below size:* is work around for Linux, it alreayd set in css, but not work, shit sciter return
@@ -55,10 +57,12 @@ class Body: Reactor.Component {c.port_forward ?
Port Forwarding: {c.port_forward}
: ""}
- {auth ? "" : } - {auth ? "" : } - {auth && !disconnected ? : ""} - {auth && disconnected ? : ""} + {!auth && show_elevation_btn ? : "" } + {auth ? "" : } + {auth ? "" : } + {auth && !disconnected && show_elevation_btn ? : "" } + {auth && !disconnected ? : ""} + {auth && disconnected ? : ""}
{c.is_file_transfer || c.port_forward ? "" :
{svg_chat}
}
@@ -144,6 +148,32 @@ class Body: Reactor.Component }); } + event click $(button#elevate_accept) { + var { cid, connection } = this; + checkClickTime(function() { + connection.authorized = true; + show_elevation = false; + body.update(); + handler.elevate_portable(cid); + handler.authorize(cid); + self.timer(30ms, function() { + view.windowState = View.WINDOW_MINIMIZED; + }); + }); + } + + event click $(button#elevate) { + var { cid, connection } = this; + checkClickTime(function() { + show_elevation = false; + body.update(); + handler.elevate_portable(cid); + self.timer(30ms, function() { + view.windowState = View.WINDOW_MINIMIZED; + }); + }); + } + event click $(button#dismiss) { var cid = this.cid; checkClickTime(function() { @@ -386,6 +416,13 @@ handler.newMessage = function(id, text) { update(); } +handler.showElevation = function(show) { + if (show != show_elevation) { + show_elevation = show; + update(); + } +} + view << event statechange { adjustBorder(); } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 72225b3fb..b1e4db7f8 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -85,6 +85,8 @@ pub trait InvokeUiCM: Send + Clone + 'static + Sized { fn change_theme(&self, dark: String); fn change_language(&self); + + fn show_elevation(&self, show: bool); } impl Deref for ConnectionManager { @@ -171,6 +173,10 @@ impl ConnectionManager { self.ui_handler.remove_connection(id, close); } + + fn show_elevation(&self, show: bool) { + self.ui_handler.show_elevation(show); + } } #[inline] @@ -362,6 +368,9 @@ impl IpcTaskRunner { LocalConfig::set_option("lang".to_owned(), lang); self.cm.change_language(); } + Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show)) => { + self.cm.show_elevation(show); + } _ => { } @@ -757,3 +766,28 @@ fn cm_inner_send(id: i32, data: Data) { } } } + +pub fn can_elevate() -> bool { + #[cfg(windows)] + { + use crate::portable_service::client::{ + PortableServiceStatus::NotStarted, PORTABLE_SERVICE_STATUS, + }; + return !crate::platform::is_installed() + && PORTABLE_SERVICE_STATUS.lock().unwrap().clone() == NotStarted; + } + #[cfg(not(windows))] + return false; +} + +pub fn elevate_portable(id: i32) { + #[cfg(windows)] + { + let lock = CLIENTS.read().unwrap(); + if let Some(s) = lock.get(&id) { + allow_err!(s.tx.send(ipc::Data::DataPortableService( + ipc::DataPortableService::RequestStart + ))); + } + } +} diff --git a/src/ui_interface.rs b/src/ui_interface.rs index a334fb6fb..0e443ad61 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -10,8 +10,7 @@ use hbb_common::password_security; use hbb_common::{ allow_err, config::{self, Config, LocalConfig, PeerConfig}, - directories_next, log, - sleep, + directories_next, log, sleep, tokio::{self, sync::mpsc, time}, }; @@ -376,7 +375,7 @@ pub fn is_installed() -> bool { #[cfg(any(target_os = "android", target_os = "ios"))] #[inline] -pub fn is_installed() -> bool { +pub fn is_installed() -> bool { false }