morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
frontend.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4#include <algorithm>
5#include <cstddef>
6#include <cstdint>
7#include <cstdlib>
8#include <exception>
9#include <expected>
10#include <functional>
11#include <map>
12#include <memory>
13#include <optional>
14#include <ranges>
15#include <span>
16#include <stdexcept>
17#include <string>
18#include <string_view>
19#include <utility>
20#include <vector>
21
22#include "../core/executor.hpp"
25
26/// @file
27/// @brief The frontend seam: `main` picks a frontend at runtime, and the frontend runs whatever an `AppSource`
28/// opens.
29///
30/// Specified in `docs/spec/ui/frontend.md`.
31
32namespace morph::exec {
33class IoLoop;
34} // namespace morph::exec
35
36namespace morph::ui {
37
38class Inspector;
39
40/// @brief Timers on the frontend's owner executor; see `reactive::Scheduler`.
42
43/// @brief Owns one scheduled timer and cancels it when destroyed; see `reactive::TimerHandle`.
45
46/// @brief What a frontend lends the source it opens and the screens it mounts.
47///
48/// Everything it lends (the runtime, the executor, the scheduler, the loop) outlives the screens and the
49/// connections made with it: a frontend destroys those, through `runApp`, before its runtime.
50class AppContext {
51public:
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;
58
59 /// @brief The runtime the screens are mounted in. Owned by the frontend.
60 /// @return The runtime.
61 virtual reactive::Runtime& runtime() = 0;
62
63 /// @brief The runtime's owner, and the callback executor of every bridge handler the source and the screens
64 /// make.
65 /// @return The executor; the same one as `runtime().owner()`.
66 virtual exec::IExecutor& executor() = 0;
67
68 /// @brief Timers that fire on the owner executor.
69 ///
70 /// It honours every guarantee of `reactive::Scheduler`, as `docs/spec/reactive/control.md` ("The `Scheduler`
71 /// contract") states them.
72 /// @return The scheduler.
73 virtual Scheduler& scheduler() = 0;
74
75 /// @brief The I/O loop the frontend runs on, for sockets and timers that share its one thread.
76 /// @return The loop, or null when the frontend has none (Qt Quick).
77 virtual exec::IoLoop* ioLoop() = 0;
78
79 /// @brief Makes `Frontend::run` return once the current event has been handled.
80 ///
81 /// Called before the loop runs (from `AppSource::open`, or from mounting the shell), it still ends the run once
82 /// the loop starts.
83 /// @param exitCode What `run` returns.
84 virtual void quit(int exitCode = 0) = 0;
85};
86
87/// @brief What an open source hands the frontend: the application's screens, verified.
88///
89/// The document model and the interpreter read it (`docs/spec/ui/frontend.md`, "Bundle"); the frontend seam only
90/// carries it from the source to the shell.
91struct Bundle {
92 /// @brief The application's id, as the source's handshake names it.
93 std::string applicationId;
94 /// @brief The SHA-256 of the bundle's manifest, in lower-case hexadecimal; every document below was verified
95 /// against the manifest, and the manifest against this digest.
96 std::string manifestDigest;
97 /// @brief Every document of the bundle, by id, as its JSON text: the screens, the app shell, and the app document
98 /// (`app`) when the bundle has one.
99 std::map<std::string, std::string, std::less<>> documents;
100 /// @brief The theme's files, by id, as their JSON text: the theme index and the token files it names; empty when
101 /// the bundle has no theme.
102 std::map<std::string, std::string, std::less<>> themes;
103 /// @brief The theme's icon set, by icon name, as SVG text; empty when the theme declares none.
104 std::map<std::string, std::string, std::less<>> icons;
105};
106
107/// @brief Why a source could not be opened.
109 /// @brief What went wrong.
110 enum class Kind : std::uint8_t {
111 Unreachable, ///< The server could not be reached, or the connection broke during the handshake.
112 NoUiService, ///< The peer answered, but serves no UI (`unknown envelope kind: ui-hello`).
113 Unsupported, ///< The bundle needs a vocabulary this client does not speak; the message names it.
114 Unverified, ///< A file or the manifest did not match its digest.
115 Refused, ///< The application refused this client or this user: a sign-in, a licence, an authoriser.
116 };
117 /// @brief What went wrong.
119 /// @brief What to tell the user, UTF-8.
120 std::string message;
121};
122
123/// @brief The application's opaque handle for one of its backends. Its name is what `{"env": "backend"}` reads.
124struct Backend {
125 /// @brief The backend's name, such as `primary`, `local` or `remote`.
126 std::string name;
127
128 /// @brief Memberwise equality.
129 /// @return Whether the names match.
130 bool operator==(Backend const&) const = default;
131};
132
133/// @brief Where the screens and the models are: a server (remote) or the application linked in-process (local).
134///
135/// `main` builds one from its arguments and hands it to `Frontend::run`. Every call is made on the context's owner
136/// executor, and every callback it is given must be called there too.
137class AppSource {
138public:
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;
145
146 /// @brief Connects (or, local, sets up the models), signs the user in natively if the application needs it, and
147 /// delivers the bundle.
148 /// @param context The running frontend's context; its executor is every bridge's callback executor.
149 /// @param ready Called once, on the owner, now or later, with the bundle or why there is none. A source that is
150 /// closed first never calls it.
151 virtual void open(AppContext& context, std::function<void(std::expected<Bundle, ConnectError>)> ready) = 0;
152
153 /// @brief Replaces the dispatch target under the mounted screens: `Bridge::switchBackend`, posted to the owner.
154 /// @param backend One of the application's backends.
155 /// @param done Called once, on the owner, when the switch committed, or with why it did not; a switch that
156 /// fails leaves the old backend in place.
157 virtual void switchBackend(Backend backend, std::function<void(std::expected<void, std::string>)> done) = 0;
158
159 /// @brief Releases everything `open` made (connections, bridges, handlers) and drops a `ready` not yet
160 /// delivered. Called by `runApp` once the screens are gone and before the frontend's runtime is; the
161 /// source may be opened again afterwards.
162 virtual void close() = 0;
163
164 /// @brief Adds a listener called on the owner after each successful connect to the server, the first included
165 /// (spec 8 §7, "Reconnect"). A shell registers `Session::reconnected` here, so the session re-sends its
166 /// watch set and re-reads what it shows after an outage. The source owns the backend's single connect
167 /// hook, so anything else that wants connects subscribes here too.
168 ///
169 /// The default does nothing: a local source has no connection to lose.
170 /// @param listener Called on the owner after each connect; it must stay callable while the source is open.
171 virtual void onConnected(std::function<void()> const& listener) { static_cast<void>(listener); }
172};
173
174/// @brief A frontend: owns an event loop and a renderer, and runs one source's screens.
175class Frontend {
176public:
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;
183
184 /// @brief The name `selectFrontend` matches.
185 /// @return The name.
186 [[nodiscard]] virtual std::string_view name() const = 0;
187
188 /// @brief Builds the runtime and its executor, opens @p source, mounts the app shell and drives the loop until
189 /// `AppContext::quit`.
190 ///
191 /// Tears down in reverse: the screens, then the source's connections, then the runtime, then the loop and the
192 /// toolkit. A frontend leaves the first two to `runApp`, which fixes that order, and keeps the rest by
193 /// declaring its members or locals in construction order.
194 /// @param source Where the screens and the models are; it outlives the call.
195 /// @return The exit code passed to `quit`; a frontend may also end the run on its own (end of input, an
196 /// interrupt) with a code it documents.
197 virtual int run(AppSource& source) = 0;
198};
199
200/// @brief One frontend a binary was built with.
202 /// @brief The name `--ui=` and `MORPH_UI` match.
203 std::string name;
204 /// @brief Whether the frontend can run here (a terminal on stdin, a display); empty counts as usable.
205 std::function<bool()> usable;
206 /// @brief Makes the frontend.
207 std::function<std::unique_ptr<Frontend>()> make;
208};
209
210/// @brief Why no frontend was selected.
212 /// @brief What went wrong.
213 enum class Kind : std::uint8_t {
214 MissingName, ///< A `--ui` without a name.
215 UnknownName, ///< `--ui` or `MORPH_UI` names a frontend this binary was not built with.
216 NoneUsable, ///< Nothing was named, and no built frontend is usable here.
217 NotMade, ///< The chosen option has no `make`, or its `make` returned null.
218 };
219 /// @brief What went wrong.
221 /// @brief The name asked for, or the option's name for `NotMade`; empty otherwise.
222 std::string requested;
223 /// @brief The names of the frontends this binary was built with, in order.
224 std::vector<std::string> built;
225
226 /// @brief A message for the user, naming the built frontends.
227 /// @return The message.
228 [[nodiscard]] std::string message() const {
229 std::string names;
230 for (std::string const& name : built) {
231 names += names.empty() ? name : ", " + name;
232 }
233 if (names.empty()) {
234 names = "none";
235 }
236 switch (kind) {
238 return "morph::ui: --ui needs a frontend name (built: " + names + ")";
240 return "morph::ui: no frontend named '" + requested + "' is built (built: " + names + ")";
241 case Kind::NotMade:
242 return "morph::ui: frontend '" + requested + "' could not be made";
243 case Kind::NoneUsable:
244 default:
245 return "morph::ui: no built frontend is usable here (built: " + names + ")";
246 }
247 }
248};
249
250/// @brief Reads an environment variable: its value, or `nullopt` when it is unset.
251using EnvironmentReader = std::function<std::optional<std::string>(std::string_view name)>;
252
253/// @brief The process environment, read through `std::getenv`.
254/// @return A reader over the process environment.
255[[nodiscard]] inline EnvironmentReader processEnvironment() {
256 return [](std::string_view name) -> std::optional<std::string> {
257 std::string const key{name};
258 // getenv races only with a concurrent setenv; frontend selection runs in main before any thread exists.
259 // MSVC deprecates getenv (C4996) in favour of _dupenv_s; a consumer built with /WX must not fail on it.
260 // clang-cl reports the same deprecation as -Wdeprecated-declarations, which the MSVC pragma does not reach.
261 // The MSVC pragma stays on the line just before the call, the only line it covers.
262#if defined(__clang__) && defined(_WIN32)
263#pragma clang diagnostic push
264#pragma clang diagnostic ignored "-Wdeprecated-declarations"
265#endif
266#ifdef _MSC_VER
267#pragma warning(suppress : 4996)
268#endif
269 char const* const value = std::getenv(key.c_str()); // NOLINT(concurrency-mt-unsafe)
270#if defined(__clang__) && defined(_WIN32)
271#pragma clang diagnostic pop
272#endif
273 if (value == nullptr) {
274 return std::nullopt;
275 }
276 return std::string{value};
277 };
278}
279
280namespace detail {
281
282/// @brief The command line's choice of frontend, and the arguments that made it.
284 /// @brief The last name given, or none.
285 std::optional<std::string> name;
286 /// @brief Whether a `--ui` was given without a name after it, or the last name given is empty.
287 bool missingName = false;
288 /// @brief The positions in the arguments of every `--ui=<name>`, `--ui` and the name after it, ascending.
289 std::vector<std::size_t> used;
290};
291
292/// @brief Reads `--ui=<name>` and `--ui <name>` from the arguments, up to a `--`.
293///
294/// The last one wins. An argument starting with `--` is never taken as the name after `--ui`: it is the next option,
295/// and the `--ui` before it has no name.
296/// @param args The arguments; a program name among them is never a `--ui` flag.
297/// @return The choice.
298[[nodiscard]] inline CommandLineChoice readCommandLine(std::span<std::string const> args) {
299 CommandLineChoice choice;
300 bool nameFollows = false;
301 for (std::size_t index = 0; std::string const& raw : args) {
302 std::string_view const arg = raw;
303 if (nameFollows) {
304 nameFollows = false;
305 if (arg.starts_with("--")) {
306 choice.missingName = true;
307 break;
308 }
309 choice.name = std::string{arg};
310 choice.used.push_back(index);
311 } else if (arg == "--") {
312 break;
313 } else if (arg == "--ui") {
314 nameFollows = true;
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);
319 }
320 ++index;
321 }
322 choice.missingName = choice.missingName || nameFollows || (choice.name.has_value() && choice.name->empty());
323 return choice;
324}
325
326} // namespace detail
327
328/// @brief Picks the frontend to run: the last `--ui=<name>` (or `--ui <name>`) before a `--`, else a non-empty
329/// `MORPH_UI`, else the first option in the given order whose `usable()` holds.
330///
331/// A frontend named on the command line or in `MORPH_UI` is used even when its `usable()` is false: the user asked
332/// for it. On success every `--ui` argument (with the name after a bare `--ui`) is removed from @p args, and the rest
333/// keep their order, for the caller to pass on; on an error @p args is left as it was.
334/// @param built The frontends this binary was built with, in order of preference.
335/// @param args The command-line arguments, with or without the program name first.
336/// @param env Reads `MORPH_UI`; the process environment by default. An empty reader reads nothing.
337/// @return The frontend, made; or why there is none.
338[[nodiscard]] inline std::expected<std::unique_ptr<Frontend>, FrontendError> selectFrontend(
339 std::span<FrontendOption const> built, std::vector<std::string>& args,
340 EnvironmentReader const& env = processEnvironment()) {
341 std::vector<std::string> names;
342 names.reserve(built.size());
343 for (FrontendOption const& option : built) {
344 names.push_back(option.name);
345 }
346 auto const failure = [&names](FrontendError::Kind kind, std::string requested) {
347 return std::unexpected{FrontendError{.kind = kind, .requested = std::move(requested), .built = names}};
348 };
350 if (choice.missingName) {
351 return failure(FrontendError::Kind::MissingName, {});
352 }
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);
358 }
359 }
360 FrontendOption const* chosen = nullptr;
361 if (requested.has_value()) {
362 auto const found = std::ranges::find(built, *requested, &FrontendOption::name);
363 if (found == built.end()) {
364 return failure(FrontendError::Kind::UnknownName, *requested);
365 }
366 chosen = &*found;
367 } else {
368 auto const found = std::ranges::find_if(
369 built, [](FrontendOption const& candidate) { return !candidate.usable || candidate.usable(); });
370 if (found == built.end()) {
371 return failure(FrontendError::Kind::NoneUsable, {});
372 }
373 chosen = &*found;
374 }
375 std::unique_ptr<Frontend> frontend = chosen->make ? chosen->make() : nullptr;
376 if (frontend == nullptr) {
377 return failure(FrontendError::Kind::NotMade, chosen->name);
378 }
379 for (std::size_t const index : choice.used | std::views::reverse) {
380 args.erase(args.begin() + static_cast<std::ptrdiff_t>(index));
381 }
382 return frontend;
383}
384
385/// @brief What a frontend mounts once its source is open: the app shell and its screens, or a view of why the source
386/// did not open. Destroying it unmounts all of it.
387class MountedShell {
388public:
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;
395
396 /// @brief Hands the development inspector over the focused screen to @p visit, before returning (spec 6 §21).
397 /// A frontend's inspector window calls it each time it shows the screen. The default has no screen to
398 /// inspect and calls nothing.
399 /// @param visit Called at most once, with the inspector; it must not keep it past the call.
400 virtual void inspectFocused(std::function<void(Inspector& inspector)> const& visit) { static_cast<void>(visit); }
401};
402
403/// @brief Mounts the shell for what the source delivered. Called once, on the owner; it may return null to mount
404/// nothing (after calling `AppContext::quit`, for example).
406 std::function<std::unique_ptr<MountedShell>(AppContext& context, std::expected<Bundle, ConnectError> opened)>;
407
408/// @brief Runs one source inside a frontend: opens it, mounts the shell when it is ready, runs the loop, and tears
409/// down in order.
410///
411/// The order is fixed here so that every frontend keeps it. When the loop returns, or throws, the shell is destroyed
412/// first, while the source's connections that its screens use are still open, and the source is closed next, while
413/// the runtime and the executor its handlers were made with are still alive; the frontend destroys those after this
414/// returns.
415///
416/// The source's `ready` may come during `open` (a local source) or later from the loop (a remote one). A second
417/// `ready` is ignored, and one that comes after the run has ended does nothing. A @p mountShell that throws quits the
418/// context with exit code 1, and its exception leaves this call once everything is torn down.
419/// @param context The frontend's context; its runtime must outlive this call.
420/// @param source The source to open; closed before this returns.
421/// @param mountShell Mounts the shell for what the source delivered.
422/// @param loop Runs the frontend's event loop and returns the exit code once `AppContext::quit` ends it.
423/// @return What @p loop returns.
424/// @throws std::invalid_argument when @p mountShell or @p loop is empty; whatever `open`, @p mountShell, @p loop or
425/// `close` throws, the first of them.
426[[nodiscard]] inline int runApp(AppContext& context, AppSource& source, ShellFactory const& mountShell,
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"};
430 }
431 struct Run {
432 std::unique_ptr<MountedShell> shell;
433 bool opened = false;
434 std::exception_ptr mountFailure;
435 };
436 auto const run = std::make_shared<Run>();
437 std::exception_ptr failure;
438 int exitCode = 0;
439 try {
440 source.open(context,
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) {
444 return;
445 }
446 live->opened = true;
447 try {
448 live->shell = mountShell(context, std::move(opened));
449 } catch (...) {
450 live->mountFailure = std::current_exception();
451 context.quit(1);
452 }
453 });
454 exitCode = loop();
455 } catch (...) {
456 failure = std::current_exception();
457 }
458 // The shell goes first, and the run counts as opened from here on, so a `ready` that arrives while the source
459 // closes mounts nothing; one that arrives after this returns finds the run gone.
460 std::exception_ptr const mountFailure = run->mountFailure;
461 run->shell.reset();
462 run->opened = true;
463 try {
464 source.close();
465 } catch (...) {
466 if (!failure) {
467 failure = std::current_exception();
468 }
469 }
470 if (!failure) {
471 failure = mountFailure;
472 }
473 if (failure) {
474 std::rethrow_exception(failure);
475 }
476 return exitCode;
477}
478
479} // namespace morph::ui
Owns a core::net::PlatformLoop and, for IoLoopDriver::OwnThread natively, the one thread that runs it...
Definition io_loop.hpp:74
The owner of a signal graph: every node takes one, and every operation runs on its owner.
Definition runtime.hpp:70
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.
Definition scheduler.hpp:19
What a frontend lends the source it opens and the screens it mounts.
Definition frontend.hpp:50
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).
Definition frontend.hpp:137
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 (...
Definition frontend.hpp:171
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,...
Definition inspector.hpp:70
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)....
Definition frontend.hpp:400
@ Run
{"run": "m", "body": e?, "as": "x"?, "each": e?}
Definition document.hpp:245
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.
Definition executor.hpp:70
The application's opaque handle for one of its backends. Its name is what {"env": "backend"}...
Definition frontend.hpp:124
bool operator==(Backend const &) const =default
Memberwise equality.
std::string name
The backend's name, such as primary, local or remote.
Definition frontend.hpp:126
What an open source hands the frontend: the application's screens, verified.
Definition frontend.hpp:91
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,...
Definition frontend.hpp:99
std::string applicationId
The application's id, as the source's handshake names it.
Definition frontend.hpp:93
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.
Definition frontend.hpp:104
std::string manifestDigest
The SHA-256 of the bundle's manifest, in lower-case hexadecimal; every document below was verified ag...
Definition frontend.hpp:96
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...
Definition frontend.hpp:102
Why a source could not be opened.
Definition frontend.hpp:108
Kind
What went wrong.
Definition frontend.hpp:110
@ Unreachable
The server could not be reached, or the connection broke during the handshake.
Definition frontend.hpp:111
@ NoUiService
The peer answered, but serves no UI (unknown envelope kind: ui-hello).
Definition frontend.hpp:112
@ Unsupported
The bundle needs a vocabulary this client does not speak; the message names it.
Definition frontend.hpp:113
@ Unverified
A file or the manifest did not match its digest.
Definition frontend.hpp:114
@ Refused
The application refused this client or this user: a sign-in, a licence, an authoriser.
Definition frontend.hpp:115
Kind kind
What went wrong.
Definition frontend.hpp:118
std::string message
What to tell the user, UTF-8.
Definition frontend.hpp:120
Why no frontend was selected.
Definition frontend.hpp:211
Kind kind
What went wrong.
Definition frontend.hpp:220
std::string requested
The name asked for, or the option's name for NotMade; empty otherwise.
Definition frontend.hpp:222
Kind
What went wrong.
Definition frontend.hpp:213
@ MissingName
A --ui without a name.
Definition frontend.hpp:214
@ NotMade
The chosen option has no make, or its make returned null.
Definition frontend.hpp:217
@ NoneUsable
Nothing was named, and no built frontend is usable here.
Definition frontend.hpp:216
@ UnknownName
--ui or MORPH_UI names a frontend this binary was not built with.
Definition frontend.hpp:215
std::vector< std::string > built
The names of the frontends this binary was built with, in order.
Definition frontend.hpp:224
std::string message() const
A message for the user, naming the built frontends.
Definition frontend.hpp:228
One frontend a binary was built with.
Definition frontend.hpp:201
std::function< bool()> usable
Whether the frontend can run here (a terminal on stdin, a display); empty counts as usable.
Definition frontend.hpp:205
std::function< std::unique_ptr< Frontend >()> make
Makes the frontend.
Definition frontend.hpp:207
std::string name
The name --ui= and MORPH_UI match.
Definition frontend.hpp:203
The command line's choice of frontend, and the arguments that made it.
Definition frontend.hpp:283
std::optional< std::string > name
The last name given, or none.
Definition frontend.hpp:285
std::vector< std::size_t > used
The positions in the arguments of every --ui=<name>, --ui and the name after it, ascending.
Definition frontend.hpp:289
bool missingName
Whether a --ui was given without a name after it, or the last name given is empty.
Definition frontend.hpp:287
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,...
Definition frontend.hpp:426
std::function< std::optional< std::string >(std::string_view name)> EnvironmentReader
Reads an environment variable: its value, or nullopt when it is unset.
Definition frontend.hpp:251
reactive::TimerHandle TimerHandle
Owns one scheduled timer and cancels it when destroyed; see reactive::TimerHandle.
Definition frontend.hpp:44
EnvironmentReader processEnvironment()
The process environment, read through std::getenv.
Definition frontend.hpp:255
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...
Definition frontend.hpp:405
CommandLineChoice readCommandLine(std::span< std::string const > args)
Reads --ui=<name> and --ui <name> from the arguments, up to a --.
Definition frontend.hpp:298
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_...
Definition frontend.hpp:338
reactive::Scheduler Scheduler
Timers on the frontend's owner executor; see reactive::Scheduler.
Definition frontend.hpp:41