always block desktop settings page if video connection exists ()

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

@ -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(

@ -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 (_) {}
} }
} }

@ -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(

@ -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),
]), ]),

@ -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

@ -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,

@ -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(),

@ -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;

@ -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()

@ -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;
} }