blob: 51f673e5d730adb44769132e3bda149784e3cfcc [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:ermine_utils/ermine_utils.dart';
import 'package:fidl_fuchsia_component/fidl_async.dart';
import 'package:fidl_fuchsia_component_decl/fidl_async.dart';
import 'package:fidl_fuchsia_io/fidl_async.dart';
import 'package:fidl_fuchsia_ui_app/fidl_async.dart';
import 'package:fidl_fuchsia_ui_scenic/fidl_async.dart';
import 'package:fidl_fuchsia_ui_views/fidl_async.dart' hide FocusState;
import 'package:fuchsia_logger/logger.dart';
import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
import 'package:fuchsia_services/services.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:zircon/zircon.dart';
/// Defines a service to launch and support Ermine user shell.
class ShellService {
late final StreamSubscription<bool> _focusSubscription;
late final VoidCallback onShellReady;
late final VoidCallback onShellExit;
late final bool _useFlatland;
_ErmineViewConnection? _ermine;
ShellService() {
ScenicProxy scenic = ScenicProxy();
Incoming.fromSvcPath().connectToService(scenic);
scenic.usesFlatland().then((scenicUsesFlatland) {
_useFlatland = scenicUsesFlatland;
runInAction(() => {_ready.value = true});
});
WidgetsFlutterBinding.ensureInitialized();
_focusSubscription = FocusState.instance.stream().listen(_onFocusChanged);
}
/// Returns [true] after call to [Scenic.usesFlatland] completes.
bool get ready => _ready.value;
final _ready = false.asObservable();
void dispose() {
_focusSubscription.cancel();
}
/// Launch Ermine shell and return [FuchsiaViewConnection].
FuchsiaViewConnection launchErmineShell() {
assert(_ermine == null, 'Instance of ermine shell already exists.');
_ermine = _ErmineViewConnection(
useFlatland: _useFlatland,
onReady: onShellReady,
onExit: onShellExit,
);
return _ermine!.fuchsiaViewConnection;
}
void disposeErmineShell() {
_ermine = null;
}
// Transfer focus to Ermine shell whenever login shell receives focus.
void _onFocusChanged(bool focused) {
if (focused) {
_ermine?.setFocus();
}
}
}
class _ErmineViewConnection {
final bool useFlatland;
final VoidCallback onReady;
final VoidCallback onExit;
late final FuchsiaViewConnection fuchsiaViewConnection;
bool _focusRequested = false;
_ErmineViewConnection({
required this.useFlatland,
required this.onReady,
required this.onExit,
}) {
// Connect to the Realm.
final realm = RealmProxy();
Incoming.fromSvcPath().connectToService(realm);
// Get the ermine shell's exposed /svc directory.
final exposedDir = DirectoryProxy();
realm.openExposedDir(
ChildRef(name: 'ermine_shell'), exposedDir.ctrl.request());
// Get the ermine shell's view provider.
final viewProvider = ViewProviderProxy();
Incoming.withDirectory(exposedDir).connectToService(viewProvider);
viewProvider.ctrl.whenClosed.then((_) => onExit());
fuchsiaViewConnection = _launch(viewProvider);
}
void setFocus() {
if (_focusRequested) {
fuchsiaViewConnection.requestFocus().catchError((e) {
log.shout(e);
});
}
}
FuchsiaViewConnection _launch(ViewProvider viewProvider) {
if (useFlatland) {
final viewTokens = ChannelPair();
assert(viewTokens.status == ZX.OK);
final viewportCreationToken =
ViewportCreationToken(value: viewTokens.first!);
final viewCreationToken = ViewCreationToken(value: viewTokens.second!);
final createViewArgs =
CreateView2Args(viewCreationToken: viewCreationToken);
viewProvider.createView2(createViewArgs);
return FuchsiaViewConnection.flatland(
viewportCreationToken,
onViewStateChanged: _onViewStateChanged,
);
} else {
final viewTokens = EventPairPair();
assert(viewTokens.status == ZX.OK);
final viewHolderToken = ViewHolderToken(value: viewTokens.first!);
final viewToken = ViewToken(value: viewTokens.second!);
final viewRefPair = EventPairPair();
final viewRef =
ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
final viewRefControl = ViewRefControl(
reference: viewRefPair.second!
.duplicate(ZX.DEFAULT_EVENTPAIR_RIGHTS & (~ZX.RIGHT_DUPLICATE)));
final viewRefInject =
ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
viewProvider.createViewWithViewRef(
viewToken.value, viewRefControl, viewRef);
return FuchsiaViewConnection(
viewHolderToken,
viewRef: viewRefInject,
onViewStateChanged: _onViewStateChanged,
);
}
}
void _onViewStateChanged(FuchsiaViewController _, bool? state) {
if (state == true && !_focusRequested) {
_focusRequested = true;
setFocus();
onReady();
}
}
}