Merge remote-tracking branch 'rd/master' into feat/x11/clipboard-file/init

Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
ClSlaid
2023-10-28 19:56:19 +08:00
56 changed files with 450 additions and 136 deletions

View File

@@ -32,7 +32,7 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart';
for (var peer in peerData) {
if (peer is Map && peer.containsKey("id")) {
String id = peer["id"];
if (id != null && !combinedPeers.containsKey(id)) {
if (!combinedPeers.containsKey(id)) {
combinedPeers[id] = peer;
}
}
@@ -55,12 +55,12 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart';
}
class AutocompletePeerTile extends StatefulWidget {
final IDTextEditingController idController;
final VoidCallback onSelect;
final Peer peer;
const AutocompletePeerTile({
Key? key,
required this.idController,
required this.onSelect,
required this.peer,
}) : super(key: key);
@@ -85,12 +85,7 @@ class _AutocompletePeerTileState extends State<AutocompletePeerTile>{
fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
final child = GestureDetector(
onTap: () {
setState(() {
widget.idController.id = widget.peer.id;
FocusScope.of(context).unfocus();
});
},
onTap: () => widget.onSelect(),
child:
Container(
height: 42,

View File

@@ -190,14 +190,20 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
child: widget.peerCardBuilder(peer),
);
final windowWidth = MediaQuery.of(context).size.width;
final model = Provider.of<PeerTabModel>(context);
// `Provider.of<PeerTabModel>(context)` will causes infinete loop.
// Because `gFFI.peerTabModel.setCurrentTabCachedPeers(peers)` will trigger `notifyListeners()`.
//
// No need to listen the currentTab change event.
// Because the currentTab change event will trigger the peers change event,
// and the peers change event will trigger _buildPeersView().
final currentTab = Provider.of<PeerTabModel>(context, listen: false).currentTab;
final hideAbTagsPanel = bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
return isDesktop
? Obx(
() => SizedBox(
width: peerCardUiType.value != PeerUiType.list
? 220
: model.currentTab == PeerTabIndex.group.index || (model.currentTab == PeerTabIndex.ab.index && !hideAbTagsPanel)
: currentTab == PeerTabIndex.group.index || (currentTab == PeerTabIndex.ab.index && !hideAbTagsPanel)
? windowWidth - 390 :
windowWidth - 227,
height:

View File

@@ -5,6 +5,9 @@ import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart';
const int kMaxVirtualDisplayCount = 4;
const int kAllVirtualDisplay = -1;
const double kDesktopRemoteTabBarHeight = 28.0;
const int kInvalidWindowId = -1;
const int kMainWindowId = 0;
@@ -15,6 +18,11 @@ const kKeyLegacyMode = 'legacy';
const kKeyMapMode = 'map';
const kKeyTranslateMode = 'translate';
const String kPlatformAdditionsIsWayland = "is_wayland";
const String kPlatformAdditionsHeadless = "headless";
const String kPlatformAdditionsIsInstalled = "is_installed";
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS";

View File

@@ -277,6 +277,12 @@ class _ConnectionPageState extends State<ConnectionPage>
},
));
},
onSelected: (option) {
setState(() {
_idController.id = option.id;
FocusScope.of(context).unfocus();
});
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<Peer> onSelected, Iterable<Peer> options) {
double maxHeight = options.length * 50;
maxHeight = maxHeight > 200 ? 200 : maxHeight;
@@ -304,7 +310,7 @@ class _ConnectionPageState extends State<ConnectionPage>
: Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView(
children: options.map((peer) => AutocompletePeerTile(idController: _idController, peer: peer)).toList(),
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
),
),
),

View File

@@ -978,6 +978,10 @@ class _DisplayMenuState extends State<_DisplayMenu> {
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
_VirtualDisplayMenu(
id: widget.id,
ffi: widget.ffi,
),
Divider(),
toggles(),
widget.pluginItem,
@@ -1387,6 +1391,70 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
}
}
class _VirtualDisplayMenu extends StatefulWidget {
final String id;
final FFI ffi;
_VirtualDisplayMenu({
Key? key,
required this.id,
required this.ffi,
}) : super(key: key);
@override
State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState();
}
class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) {
return Offstage();
}
if (!widget.ffi.ffiModel.pi.isInstalled) {
return Offstage();
}
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
final children = <Widget>[];
for (var i = 0; i < kMaxVirtualDisplayCount; i++) {
children.add(CkbMenuButton(
value: virtualDisplays.contains(i + 1),
onChanged: (bool? value) async {
if (value != null) {
bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId, index: i + 1, on: value);
}
},
child: Text('${translate('Virtual display')} ${i + 1}'),
ffi: widget.ffi,
));
}
children.add(Divider());
children.add(MenuButton(
onPressed: () {
bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId,
index: kAllVirtualDisplay,
on: false);
},
ffi: widget.ffi,
child: Text(translate('Plug out all')),
));
return _SubmenuButton(
ffi: widget.ffi,
menuChildren: children,
child: Text(translate("Virtual display")),
);
}
}
class _KeyboardMenu extends StatelessWidget {
final String id;
final FFI ffi;

View File

@@ -245,6 +245,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
inputFormatters: [IDTextInputFormatter()],
);
},
onSelected: (option) {
setState(() {
_idController.id = option.id;
FocusScope.of(context).unfocus();
});
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<Peer> onSelected, Iterable<Peer> options) {
double maxHeight = options.length * 50;
maxHeight = maxHeight > 200 ? 200 : maxHeight;
@@ -268,7 +274,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
)))
: ListView(
padding: EdgeInsets.only(top: 5),
children: options.map((peer) => AutocompletePeerTile(idController: _idController, peer: peer)).toList(),
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
))))
);
},

View File

@@ -248,6 +248,8 @@ class FfiModel with ChangeNotifier {
handlePeerInfo(evt, peerId, false);
} else if (name == 'sync_peer_info') {
handleSyncPeerInfo(evt, sessionId, peerId);
} else if (name == 'sync_platform_additions') {
handlePlatformAdditions(evt, sessionId, peerId);
} else if (name == 'connection_ready') {
setConnectionType(
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
@@ -895,6 +897,33 @@ class FfiModel with ChangeNotifier {
notifyListeners();
}
handlePlatformAdditions(
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
final updateData = evt['platform_additions'] as String?;
if (updateData == null) {
return;
}
if (updateData.isEmpty) {
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
} else {
try {
final updateJson = json.decode(updateData);
for (final key in updateJson.keys) {
_pi.platformAdditions[key] = updateJson[key];
}
if (!updateJson.contains(kPlatformAdditionsVirtualDisplays)) {
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
}
} catch (e) {
debugPrint('Failed to decode platformAdditions $e');
}
}
cachedPeerData.peerInfo['platform_additions'] =
json.encode(_pi.platformAdditions);
}
// Directly switch to the new display without waiting for the response.
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
// VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays.
@@ -2300,8 +2329,13 @@ class PeerInfo with ChangeNotifier {
RxInt displaysCount = 0.obs;
RxBool isSet = false.obs;
bool get isWayland => platformAdditions['is_wayland'] == true;
bool get isHeadless => platformAdditions['headless'] == true;
bool get isWayland => platformAdditions[kPlatformAdditionsIsWayland] == true;
bool get isHeadless => platformAdditions[kPlatformAdditionsHeadless] == true;
bool get isInstalled =>
platform != kPeerPlatformWindows ||
platformAdditions[kPlatformAdditionsIsInstalled] == true;
List<int> get virtualDisplays => List<int>.from(
platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []);
bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession;