always block desktop settings page if video connection exists (#10224)

1. Always block desktop settings page if video connection exists, both mouse event and key event are blocked..
2. Server control page always block key event.

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages 2024-12-08 18:26:55 +08:00 committed by GitHub
parent 1c17fddf51
commit d4a712bb32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 126 additions and 53 deletions

View File

@ -2809,7 +2809,7 @@ Widget buildRemoteBlock(
onExit: (event) => block.value = false, onExit: (event) => block.value = false,
child: Stack(children: [ child: Stack(children: [
// scope block tab // scope block tab
FocusScope(child: child, canRequestFocus: !block.value), preventMouseKeyBuilder(child: child, block: block.value),
// mask block click, cm not block click and still use check_click_time to avoid block local click // mask block click, cm not block click and still use check_click_time to avoid block local click
if (mask) if (mask)
Offstage( Offstage(
@ -2821,6 +2821,11 @@ Widget buildRemoteBlock(
)); ));
} }
Widget preventMouseKeyBuilder({required Widget child, required bool block}) {
return ExcludeFocus(
excluding: block, child: AbsorbPointer(child: child, absorbing: block));
}
Widget unreadMessageCountBuilder(RxInt? count, Widget unreadMessageCountBuilder(RxInt? count,
{double? size, double? fontSize}) { {double? size, double? fontSize}) {
return Obx(() => Offstage( return Obx(() => Offstage(

View File

@ -179,6 +179,9 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
stateGlobal.svcStatus.value = SvcStatus.notReady; stateGlobal.svcStatus.value = SvcStatus.notReady;
} }
_svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer(); _svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer();
try {
stateGlobal.videoConnCount.value = status['video_conn_count'] as int;
} catch (_) {}
} }
} }
@ -359,7 +362,7 @@ class _ConnectionPageState extends State<ConnectionPage>
); );
} }
String textToFind = textEditingValue.text.toLowerCase(); String textToFind = textEditingValue.text.toLowerCase();
_autocompleteOpts = peers _autocompleteOpts = peers
.where((peer) => .where((peer) =>
peer.id.toLowerCase().contains(textToFind) || peer.id.toLowerCase().contains(textToFind) ||
peer.username peer.username

View File

@ -35,7 +35,7 @@ class DesktopHomePage extends StatefulWidget {
const borderColor = Color(0xFF2F65BA); const borderColor = Color(0xFF2F65BA);
class _DesktopHomePageState extends State<DesktopHomePage> class _DesktopHomePageState extends State<DesktopHomePage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
final _leftPaneScrollController = ScrollController(); final _leftPaneScrollController = ScrollController();
@override @override
@ -51,6 +51,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
bool isCardClosed = false; bool isCardClosed = false;
final RxBool _editHover = false.obs; final RxBool _editHover = false.obs;
final RxBool _block = false.obs;
final GlobalKey _childKey = GlobalKey(); final GlobalKey _childKey = GlobalKey();
@ -58,14 +59,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
final isIncomingOnly = bind.isIncomingOnly(); final isIncomingOnly = bind.isIncomingOnly();
return Row( return _buildBlock(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildLeftPane(context), buildLeftPane(context),
if (!isIncomingOnly) const VerticalDivider(width: 1), if (!isIncomingOnly) const VerticalDivider(width: 1),
if (!isIncomingOnly) Expanded(child: buildRightPane(context)), if (!isIncomingOnly) Expanded(child: buildRightPane(context)),
], ],
); ));
}
Widget _buildBlock({required Widget child}) {
return buildRemoteBlock(
block: _block, mask: true, use: canBeBlocked, child: child);
} }
Widget buildLeftPane(BuildContext context) { Widget buildLeftPane(BuildContext context) {
@ -805,6 +812,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
_updateWindowSize(); _updateWindowSize();
}); });
} }
WidgetsBinding.instance.addObserver(this);
} }
_updateWindowSize() { _updateWindowSize() {
@ -830,9 +838,18 @@ class _DesktopHomePageState extends State<DesktopHomePage>
platformFFI.unregisterEventHandler( platformFFI.unregisterEventHandler(
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish); kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish);
} }
WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
shouldBeBlocked(_block, canBeBlocked);
}
}
Widget buildPluginEntry() { Widget buildPluginEntry() {
final entries = PluginUiManager.instance.entries.entries; final entries = PluginUiManager.instance.entries.entries;
return Offstage( return Offstage(

View File

@ -107,13 +107,20 @@ class DesktopSettingPage extends StatefulWidget {
} }
class _DesktopSettingPageState extends State<DesktopSettingPage> class _DesktopSettingPageState extends State<DesktopSettingPage>
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { with
TickerProviderStateMixin,
AutomaticKeepAliveClientMixin,
WidgetsBindingObserver {
late PageController controller; late PageController controller;
late Rx<SettingsTabKey> selectedTab; late Rx<SettingsTabKey> selectedTab;
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
final RxBool _block = false.obs;
final RxBool _canBeBlocked = false.obs;
Timer? _videoConnTimer;
_DesktopSettingPageState(SettingsTabKey initialTabkey) { _DesktopSettingPageState(SettingsTabKey initialTabkey) {
var initialIndex = DesktopSettingPage.tabKeys.indexOf(initialTabkey); var initialIndex = DesktopSettingPage.tabKeys.indexOf(initialTabkey);
if (initialIndex == -1) { if (initialIndex == -1) {
@ -133,11 +140,34 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
}); });
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
shouldBeBlocked(_block, canBeBlocked);
}
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_videoConnTimer =
periodic_immediate(Duration(milliseconds: 1000), () async {
if (!mounted) {
return;
}
_canBeBlocked.value = await canBeBlocked();
});
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
Get.delete<PageController>(tag: _kSettingPageControllerTag); Get.delete<PageController>(tag: _kSettingPageControllerTag);
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag); Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
WidgetsBinding.instance.removeObserver(this);
_videoConnTimer?.cancel();
} }
List<_TabInfo> _settingTabs() { List<_TabInfo> _settingTabs() {
@ -207,12 +237,35 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
return children; return children;
} }
Widget _buildBlock({required List<Widget> children}) {
// check both mouseMoveTime and videoConnCount
return Obx(() {
final videoConnBlock =
_canBeBlocked.value && stateGlobal.videoConnCount > 0;
return Stack(children: [
buildRemoteBlock(
block: _block,
mask: false,
use: canBeBlocked,
child: preventMouseKeyBuilder(
child: Row(children: children),
block: videoConnBlock,
),
),
if (videoConnBlock)
Container(
color: Colors.black.withOpacity(0.5),
)
]);
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
body: Row( body: _buildBlock(
children: <Widget>[ children: <Widget>[
SizedBox( SizedBox(
width: _kTabWidth, width: _kTabWidth,
@ -706,8 +759,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
locked = false; locked = false;
setState(() => {}); setState(() => {});
}), }),
AbsorbPointer( preventMouseKeyBuilder(
absorbing: locked, block: locked,
child: Column(children: [ child: Column(children: [
permissions(context), permissions(context),
password(context), password(context),
@ -1374,8 +1427,8 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
locked = false; locked = false;
setState(() => {}); setState(() => {});
}), }),
AbsorbPointer( preventMouseKeyBuilder(
absorbing: locked, block: locked,
child: Column(children: [ child: Column(children: [
network(context), network(context),
]), ]),

View File

@ -37,13 +37,9 @@ class DesktopTabPage extends StatefulWidget {
} }
} }
class _DesktopTabPageState extends State<DesktopTabPage> class _DesktopTabPageState extends State<DesktopTabPage> {
with WidgetsBindingObserver {
final tabController = DesktopTabController(tabType: DesktopTabType.main); final tabController = DesktopTabController(tabType: DesktopTabType.main);
final RxBool _block = false.obs;
// bool mouseIn = false;
_DesktopTabPageState() { _DesktopTabPageState() {
RemoteCountState.init(); RemoteCountState.init();
Get.put<DesktopTabController>(tabController); Get.put<DesktopTabController>(tabController);
@ -69,19 +65,10 @@ class _DesktopTabPageState extends State<DesktopTabPage>
} }
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
shouldBeBlocked(_block, canBeBlocked);
} else if (state == AppLifecycleState.inactive) {}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// HardwareKeyboard.instance.addHandler(_handleKeyEvent); // HardwareKeyboard.instance.addHandler(_handleKeyEvent);
WidgetsBinding.instance.addObserver(this);
} }
/* /*
@ -97,7 +84,6 @@ class _DesktopTabPageState extends State<DesktopTabPage>
@override @override
void dispose() { void dispose() {
// HardwareKeyboard.instance.removeHandler(_handleKeyEvent); // HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
WidgetsBinding.instance.removeObserver(this);
Get.delete<DesktopTabController>(); Get.delete<DesktopTabController>();
super.dispose(); super.dispose();
@ -119,7 +105,6 @@ class _DesktopTabPageState extends State<DesktopTabPage>
isClose: false, isClose: false,
), ),
), ),
blockTab: _block,
))); )));
return isMacOS || kUseCompatibleUiMode return isMacOS || kUseCompatibleUiMode
? tabWidget ? tabWidget

View File

@ -110,7 +110,8 @@ class ConnectionManager extends StatefulWidget {
class ConnectionManagerState extends State<ConnectionManager> class ConnectionManagerState extends State<ConnectionManager>
with WidgetsBindingObserver { with WidgetsBindingObserver {
final RxBool _block = false.obs; final RxBool _controlPageBlock = false.obs;
final RxBool _sidePageBlock = false.obs;
ConnectionManagerState() { ConnectionManagerState() {
gFFI.serverModel.tabController.onSelected = (client_id_str) { gFFI.serverModel.tabController.onSelected = (client_id_str) {
@ -139,7 +140,8 @@ class ConnectionManagerState extends State<ConnectionManager>
super.didChangeAppLifecycleState(state); super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
if (!allowRemoteCMModification()) { if (!allowRemoteCMModification()) {
shouldBeBlocked(_block, null); shouldBeBlocked(_controlPageBlock, null);
shouldBeBlocked(_sidePageBlock, null);
} }
} }
} }
@ -192,7 +194,6 @@ class ConnectionManagerState extends State<ConnectionManager>
selectedBorderColor: MyTheme.accent, selectedBorderColor: MyTheme.accent,
maxLabelWidth: 100, maxLabelWidth: 100,
tail: null, //buildScrollJumper(), tail: null, //buildScrollJumper(),
blockTab: allowRemoteCMModification() ? null : _block,
tabBuilder: (key, icon, label, themeConf) { tabBuilder: (key, icon, label, themeConf) {
final client = serverModel.clients final client = serverModel.clients
.firstWhereOrNull((client) => client.id.toString() == key); .firstWhereOrNull((client) => client.id.toString() == key);
@ -237,13 +238,20 @@ class ConnectionManagerState extends State<ConnectionManager>
? buildSidePage() ? buildSidePage()
: buildRemoteBlock( : buildRemoteBlock(
child: buildSidePage(), child: buildSidePage(),
block: _block, block: _sidePageBlock,
mask: true), mask: true),
)), )),
SizedBox( SizedBox(
width: realClosedWidth, width: realClosedWidth,
child: child: SizedBox(
SizedBox(width: realClosedWidth, child: pageView)), width: realClosedWidth,
child: allowRemoteCMModification()
? pageView
: buildRemoteBlock(
child: _buildKeyEventBlock(pageView),
block: _controlPageBlock,
mask: false,
))),
]); ]);
return Container( return Container(
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
@ -268,6 +276,10 @@ class ConnectionManagerState extends State<ConnectionManager>
} }
} }
Widget _buildKeyEventBlock(Widget child) {
return ExcludeFocus(child: child, excluding: true);
}
Widget buildTitleBar() { Widget buildTitleBar() {
return SizedBox( return SizedBox(
height: kDesktopRemoteTabBarHeight, height: kDesktopRemoteTabBarHeight,

View File

@ -246,7 +246,6 @@ class DesktopTab extends StatefulWidget {
final Color? selectedTabBackgroundColor; final Color? selectedTabBackgroundColor;
final Color? unSelectedTabBackgroundColor; final Color? unSelectedTabBackgroundColor;
final Color? selectedBorderColor; final Color? selectedBorderColor;
final RxBool? blockTab;
final DesktopTabController controller; final DesktopTabController controller;
@ -272,7 +271,6 @@ class DesktopTab extends StatefulWidget {
this.selectedTabBackgroundColor, this.selectedTabBackgroundColor,
this.unSelectedTabBackgroundColor, this.unSelectedTabBackgroundColor,
this.selectedBorderColor, this.selectedBorderColor,
this.blockTab,
}) : super(key: key); }) : super(key: key);
static RxString tablabelGetter(String peerId) { static RxString tablabelGetter(String peerId) {
@ -311,7 +309,6 @@ class _DesktopTabState extends State<DesktopTab>
Color? get unSelectedTabBackgroundColor => Color? get unSelectedTabBackgroundColor =>
widget.unSelectedTabBackgroundColor; widget.unSelectedTabBackgroundColor;
Color? get selectedBorderColor => widget.selectedBorderColor; Color? get selectedBorderColor => widget.selectedBorderColor;
RxBool? get blockTab => widget.blockTab;
DesktopTabController get controller => widget.controller; DesktopTabController get controller => widget.controller;
RxList<String> get invisibleTabKeys => widget.invisibleTabKeys; RxList<String> get invisibleTabKeys => widget.invisibleTabKeys;
Debouncer get _scrollDebounce => widget._scrollDebounce; Debouncer get _scrollDebounce => widget._scrollDebounce;
@ -533,21 +530,9 @@ class _DesktopTabState extends State<DesktopTab>
]); ]);
} }
Widget _buildBlock({required Widget child}) {
if (blockTab != null) {
return buildRemoteBlock(
child: child,
block: blockTab!,
use: canBeBlocked,
mask: tabType == DesktopTabType.main);
} else {
return child;
}
}
List<Widget> _tabWidgets = []; List<Widget> _tabWidgets = [];
Widget _buildPageView() { Widget _buildPageView() {
final child = _buildBlock( final child = Container(
child: Obx(() => PageView( child: Obx(() => PageView(
controller: state.value.pageController, controller: state.value.pageController,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),

View File

@ -18,6 +18,7 @@ class StateGlobal {
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth); final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
final RxBool showRemoteToolBar = false.obs; final RxBool showRemoteToolBar = false.obs;
final svcStatus = SvcStatus.notReady.obs; final svcStatus = SvcStatus.notReady.obs;
final RxInt videoConnCount = 0.obs;
final RxBool isFocused = false.obs; final RxBool isFocused = false.obs;
// for mobile and web // for mobile and web
bool isInMainPage = true; bool isInMainPage = true;

View File

@ -266,6 +266,7 @@ pub enum Data {
ControlledSessionCount(usize), ControlledSessionCount(usize),
CmErr(String), CmErr(String),
CheckHwcodec, CheckHwcodec,
#[cfg(feature = "flutter")]
VideoConnCount(Option<usize>), VideoConnCount(Option<usize>),
// Although the key is not neccessary, it is used to avoid hardcoding the key. // Although the key is not neccessary, it is used to avoid hardcoding the key.
WaylandScreencastRestoreToken((String, String)), WaylandScreencastRestoreToken((String, String)),
@ -455,6 +456,7 @@ async fn handle(data: Data, stream: &mut Connection) {
log::info!("socks updated"); log::info!("socks updated");
} }
}, },
#[cfg(feature = "flutter")]
Data::VideoConnCount(None) => { Data::VideoConnCount(None) => {
let n = crate::server::AUTHED_CONNS let n = crate::server::AUTHED_CONNS
.lock() .lock()

View File

@ -47,6 +47,8 @@ pub struct UiStatus {
pub mouse_time: i64, pub mouse_time: i64,
#[cfg(not(feature = "flutter"))] #[cfg(not(feature = "flutter"))]
pub id: String, pub id: String,
#[cfg(feature = "flutter")]
pub video_conn_count: usize,
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
@ -65,14 +67,14 @@ lazy_static::lazy_static! {
mouse_time: 0, mouse_time: 0,
#[cfg(not(feature = "flutter"))] #[cfg(not(feature = "flutter"))]
id: "".to_owned(), id: "".to_owned(),
#[cfg(feature = "flutter")]
video_conn_count: 0,
})); }));
static ref ASYNC_JOB_STATUS : Arc<Mutex<String>> = Default::default(); static ref ASYNC_JOB_STATUS : Arc<Mutex<String>> = Default::default();
static ref ASYNC_HTTP_STATUS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(HashMap::new())); static ref ASYNC_HTTP_STATUS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(HashMap::new()));
static ref TEMPORARY_PASSWD : Arc<Mutex<String>> = Arc::new(Mutex::new("".to_owned())); static ref TEMPORARY_PASSWD : Arc<Mutex<String>> = Arc::new(Mutex::new("".to_owned()));
} }
pub static VIDEO_CONN_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref OPTION_SYNCED: Arc<Mutex<bool>> = Default::default(); static ref OPTION_SYNCED: Arc<Mutex<bool>> = Default::default();
@ -1144,6 +1146,8 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
let mut key_confirmed = false; let mut key_confirmed = false;
let mut rx = rx; let mut rx = rx;
let mut mouse_time = 0; let mut mouse_time = 0;
#[cfg(feature = "flutter")]
let mut video_conn_count = 0;
#[cfg(not(feature = "flutter"))] #[cfg(not(feature = "flutter"))]
let mut id = "".to_owned(); let mut id = "".to_owned();
#[cfg(any( #[cfg(any(
@ -1204,8 +1208,9 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
*TEMPORARY_PASSWD.lock().unwrap() = value; *TEMPORARY_PASSWD.lock().unwrap() = value;
} }
} }
#[cfg(feature = "flutter")]
Ok(Some(ipc::Data::VideoConnCount(Some(n)))) => { Ok(Some(ipc::Data::VideoConnCount(Some(n)))) => {
VIDEO_CONN_COUNT.store(n, Ordering::Relaxed); video_conn_count = n;
} }
Ok(Some(ipc::Data::OnlineStatus(Some((mut x, _c))))) => { Ok(Some(ipc::Data::OnlineStatus(Some((mut x, _c))))) => {
if x > 0 { if x > 0 {
@ -1223,6 +1228,8 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
mouse_time, mouse_time,
#[cfg(not(feature = "flutter"))] #[cfg(not(feature = "flutter"))]
id: id.clone(), id: id.clone(),
#[cfg(feature = "flutter")]
video_conn_count,
}; };
} }
_ => {} _ => {}
@ -1236,6 +1243,7 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
c.send(&ipc::Data::Options(None)).await.ok(); c.send(&ipc::Data::Options(None)).await.ok();
c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok(); c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok();
c.send(&ipc::Data::Config(("temporary-password".to_owned(), None))).await.ok(); c.send(&ipc::Data::Config(("temporary-password".to_owned(), None))).await.ok();
#[cfg(feature = "flutter")]
c.send(&ipc::Data::VideoConnCount(None)).await.ok(); c.send(&ipc::Data::VideoConnCount(None)).await.ok();
} }
} }
@ -1256,6 +1264,8 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
mouse_time, mouse_time,
#[cfg(not(feature = "flutter"))] #[cfg(not(feature = "flutter"))]
id: id.clone(), id: id.clone(),
#[cfg(feature = "flutter")]
video_conn_count,
}; };
sleep(1.).await; sleep(1.).await;
} }