22#include "../core/executor.hpp"
32namespace morph::exec {
52 AppContext() =
default;
53 virtual ~AppContext() =
default;
54 AppContext(AppContext
const&) =
delete;
55 AppContext& operator=(AppContext
const&) =
delete;
56 AppContext(AppContext&&) =
delete;
57 AppContext& operator=(AppContext&&) =
delete;
84 virtual void quit(
int exitCode = 0) = 0;
99 std::map<std::string, std::string, std::less<>>
documents;
102 std::map<std::string, std::string, std::less<>>
themes;
104 std::map<std::string, std::string, std::less<>>
icons;
110 enum class Kind : std::uint8_t {
139 AppSource() =
default;
140 virtual ~AppSource() =
default;
141 AppSource(AppSource
const&) =
delete;
142 AppSource& operator=(AppSource
const&) =
delete;
143 AppSource(AppSource&&) =
delete;
144 AppSource& operator=(AppSource&&) =
delete;
151 virtual void open(
AppContext& context, std::function<
void(std::expected<Bundle, ConnectError>)> ready) = 0;
171 virtual void onConnected(std::function<
void()>
const& listener) {
static_cast<void>(listener); }
177 Frontend() =
default;
178 virtual ~Frontend() =
default;
179 Frontend(Frontend
const&) =
delete;
180 Frontend& operator=(Frontend
const&) =
delete;
181 Frontend(Frontend&&) =
delete;
182 Frontend& operator=(Frontend&&) =
delete;
186 [[nodiscard]]
virtual std::string_view
name()
const = 0;
207 std::function<std::unique_ptr<Frontend>()>
make;
213 enum class Kind : std::uint8_t {
230 for (std::string
const& name :
built) {
231 names += names.empty() ? name :
", " + name;
238 return "morph::ui: --ui needs a frontend name (built: " + names +
")";
240 return "morph::ui: no frontend named '" +
requested +
"' is built (built: " + names +
")";
242 return "morph::ui: frontend '" +
requested +
"' could not be made";
245 return "morph::ui: no built frontend is usable here (built: " + names +
")";
256 return [](std::string_view name) -> std::optional<std::string> {
257 std::string
const key{name};
262#if defined(__clang__) && defined(_WIN32)
263#pragma clang diagnostic push
264#pragma clang diagnostic ignored "-Wdeprecated-declarations"
267#pragma warning(suppress : 4996)
269 char const*
const value = std::getenv(key.c_str());
270#if defined(__clang__) && defined(_WIN32)
271#pragma clang diagnostic pop
273 if (value ==
nullptr) {
276 return std::string{value};
285 std::optional<std::string>
name;
300 bool nameFollows =
false;
301 for (std::size_t index = 0; std::string
const& raw : args) {
302 std::string_view
const arg = raw;
305 if (arg.starts_with(
"--")) {
309 choice.
name = std::string{arg};
310 choice.
used.push_back(index);
311 }
else if (arg ==
"--") {
313 }
else if (arg ==
"--ui") {
315 choice.
used.push_back(index);
316 }
else if (arg.starts_with(
"--ui=")) {
317 choice.
name = std::string{arg.substr(std::string_view{
"--ui="}.size())};
318 choice.
used.push_back(index);
339 std::span<FrontendOption const> built, std::vector<std::string>& args,
341 std::vector<std::string> names;
342 names.reserve(built.size());
344 names.push_back(option.name);
347 return std::unexpected{
FrontendError{.kind = kind, .requested = std::move(requested), .built = names}};
353 std::optional<std::string> requested = choice.
name;
354 if (!requested.has_value() && env) {
355 if (std::optional<std::string> fromEnvironment = env(
"MORPH_UI");
356 fromEnvironment.has_value() && !fromEnvironment->empty()) {
357 requested = std::move(fromEnvironment);
361 if (requested.has_value()) {
363 if (found == built.end()) {
368 auto const found = std::ranges::find_if(
370 if (found == built.end()) {
375 std::unique_ptr<Frontend> frontend = chosen->
make ? chosen->
make() :
nullptr;
376 if (frontend ==
nullptr) {
379 for (std::size_t
const index : choice.
used | std::views::reverse) {
380 args.erase(args.begin() +
static_cast<std::ptrdiff_t
>(index));
389 MountedShell() =
default;
390 virtual ~MountedShell() =
default;
391 MountedShell(MountedShell
const&) =
delete;
392 MountedShell& operator=(MountedShell
const&) =
delete;
393 MountedShell(MountedShell&&) =
delete;
394 MountedShell& operator=(MountedShell&&) =
delete;
406 std::function<std::unique_ptr<MountedShell>(
AppContext& context, std::expected<Bundle, ConnectError> opened)>;
427 std::function<
int()>
const& loop) {
428 if (!mountShell || !loop) {
429 throw std::invalid_argument{
"morph::ui::runApp: the shell factory and the loop must not be empty"};
432 std::unique_ptr<MountedShell> shell;
434 std::exception_ptr mountFailure;
436 auto const run = std::make_shared<Run>();
437 std::exception_ptr failure;
441 [weak = std::weak_ptr{run}, &context, &mountShell](std::expected<Bundle, ConnectError> opened) {
442 std::shared_ptr<Run>
const live = weak.lock();
443 if (live ==
nullptr || live->opened) {
448 live->shell = mountShell(context, std::move(opened));
450 live->mountFailure = std::current_exception();
456 failure = std::current_exception();
460 std::exception_ptr
const mountFailure = run->mountFailure;
467 failure = std::current_exception();
471 failure = mountFailure;
474 std::rethrow_exception(failure);
Owns a core::net::PlatformLoop and, for IoLoopDriver::OwnThread natively, the one thread that runs it...
The owner of a signal graph: every node takes one, and every operation runs on its owner.
Runs callbacks after a delay or on a period, on the runtime's owner executor.
Owns one scheduled timer and cancels it when destroyed. Move-only.
What a frontend lends the source it opens and the screens it mounts.
virtual exec::IoLoop * ioLoop()=0
The I/O loop the frontend runs on, for sockets and timers that share its one thread.
virtual void quit(int exitCode=0)=0
Makes Frontend::run return once the current event has been handled.
virtual reactive::Runtime & runtime()=0
The runtime the screens are mounted in. Owned by the frontend.
virtual Scheduler & scheduler()=0
Timers that fire on the owner executor.
virtual exec::IExecutor & executor()=0
The runtime's owner, and the callback executor of every bridge handler the source and the screens mak...
Where the screens and the models are: a server (remote) or the application linked in-process (local).
virtual void open(AppContext &context, std::function< void(std::expected< Bundle, ConnectError >)> ready)=0
Connects (or, local, sets up the models), signs the user in natively if the application needs it,...
virtual void close()=0
Releases everything open made (connections, bridges, handlers) and drops a ready not yet delivered....
virtual void switchBackend(Backend backend, std::function< void(std::expected< void, std::string >)> done)=0
Replaces the dispatch target under the mounted screens: Bridge::switchBackend, posted to the owner.
virtual void onConnected(std::function< void()> const &listener)
Adds a listener called on the owner after each successful connect to the server, the first included (...
virtual std::string_view name() const =0
The name selectFrontend matches.
virtual int run(AppSource &source)=0
Builds the runtime and its executor, opens source, mounts the app shell and drives the loop until App...
The development inspector over one mounted screen: its mounted nodes, its queries and mutations,...
virtual void inspectFocused(std::function< void(Inspector &inspector)> const &visit)
Hands the development inspector over the focused screen to visit, before returning (spec 6 §21)....
@ Run
{"run": "m", "body": e?, "as": "x"?, "each": e?}
morph::reactive::Scheduler: timers a frontend runs on the runtime's owner executor.
morph::reactive::Runtime: the owner, batching and flush scheduling of a signal graph.
Abstract executor interface.
The application's opaque handle for one of its backends. Its name is what {"env": "backend"}...
bool operator==(Backend const &) const =default
Memberwise equality.
std::string name
The backend's name, such as primary, local or remote.
What an open source hands the frontend: the application's screens, verified.
std::map< std::string, std::string, std::less<> > documents
Every document of the bundle, by id, as its JSON text: the screens, the app shell,...
std::string applicationId
The application's id, as the source's handshake names it.
std::map< std::string, std::string, std::less<> > icons
The theme's icon set, by icon name, as SVG text; empty when the theme declares none.
std::string manifestDigest
The SHA-256 of the bundle's manifest, in lower-case hexadecimal; every document below was verified ag...
std::map< std::string, std::string, std::less<> > themes
The theme's files, by id, as their JSON text: the theme index and the token files it names; empty whe...
Why a source could not be opened.
@ Unreachable
The server could not be reached, or the connection broke during the handshake.
@ NoUiService
The peer answered, but serves no UI (unknown envelope kind: ui-hello).
@ Unsupported
The bundle needs a vocabulary this client does not speak; the message names it.
@ Unverified
A file or the manifest did not match its digest.
@ Refused
The application refused this client or this user: a sign-in, a licence, an authoriser.
Kind kind
What went wrong.
std::string message
What to tell the user, UTF-8.
Why no frontend was selected.
Kind kind
What went wrong.
std::string requested
The name asked for, or the option's name for NotMade; empty otherwise.
@ MissingName
A --ui without a name.
@ NotMade
The chosen option has no make, or its make returned null.
@ NoneUsable
Nothing was named, and no built frontend is usable here.
@ UnknownName
--ui or MORPH_UI names a frontend this binary was not built with.
std::vector< std::string > built
The names of the frontends this binary was built with, in order.
std::string message() const
A message for the user, naming the built frontends.
One frontend a binary was built with.
std::function< bool()> usable
Whether the frontend can run here (a terminal on stdin, a display); empty counts as usable.
std::function< std::unique_ptr< Frontend >()> make
Makes the frontend.
std::string name
The name --ui= and MORPH_UI match.
The command line's choice of frontend, and the arguments that made it.
std::optional< std::string > name
The last name given, or none.
std::vector< std::size_t > used
The positions in the arguments of every --ui=<name>, --ui and the name after it, ascending.
bool missingName
Whether a --ui was given without a name after it, or the last name given is empty.
int runApp(AppContext &context, AppSource &source, ShellFactory const &mountShell, std::function< int()> const &loop)
Runs one source inside a frontend: opens it, mounts the shell when it is ready, runs the loop,...
std::function< std::optional< std::string >(std::string_view name)> EnvironmentReader
Reads an environment variable: its value, or nullopt when it is unset.
reactive::TimerHandle TimerHandle
Owns one scheduled timer and cancels it when destroyed; see reactive::TimerHandle.
EnvironmentReader processEnvironment()
The process environment, read through std::getenv.
std::function< std::unique_ptr< MountedShell >(AppContext &context, std::expected< Bundle, ConnectError > opened)> ShellFactory
Mounts the shell for what the source delivered. Called once, on the owner; it may return null to moun...
CommandLineChoice readCommandLine(std::span< std::string const > args)
Reads --ui=<name> and --ui <name> from the arguments, up to a --.
std::expected< std::unique_ptr< Frontend >, FrontendError > selectFrontend(std::span< FrontendOption const > built, std::vector< std::string > &args, EnvironmentReader const &env=processEnvironment())
Picks the frontend to run: the last --ui=<name> (or --ui <name>) before a --, else a non-empty MORPH_...
reactive::Scheduler Scheduler
Timers on the frontend's owner executor; see reactive::Scheduler.