Generated reference › API — ICoreBlocks/Shell
kind: generated#api#icoreblocks-shell

API — ICoreBlocks/Shell

The public contract of 4 header(s) under src/ICoreBlocks/Shell — 4 class/struct definition(s), 111 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.

EditorWindowImpl.h#

src/ICoreBlocks/Shell/EditorWindowImpl.h

EditorWindowImpl.h -- the definition of icore::EditorWindow::Impl.

INTERNAL. Under src/, and it stays there: it names ICorePrimaryWindow, and include/icore/ is toolkit-free by contract (DEVELOPER_GUIDELINES.md). Nothing under include/ may include this file, and tools/check_sdk_boundary.sh is what notices if that ever changes.

Why a header at all, when the whole point of Impl is that it lives in one translation unit:

Application::createEditorWindow() has to CONSTRUCT an Impl and hand it to an EditorWindow built through EditorWindow's private constructor. Only Application may call that constructor -- it is EditorWindow's sole friend -- so the factory cannot be a free function in EditorWindow.cpp. And a

EditorWindow#

EditorWindowImpl.h:44 · class · bases :Impl · 18 declaration(s)

class EditorWindow : :Impl {
public:
    // Builds the widget, hidden. Throws whatever the widget's construction
    // throws; there is no failed-but-constructed state, which is what lets
    // createEditorWindow() promise a never-null handle.
    //
    // @pre Initialization::initializeServices() has run. ICorePrimaryWindow's
    //      constructor reaches for the theme manager, the studio window
    //      registry, the studio registry and the subsystem tree node registry.
    //      icore::Application's Impl is what guarantees this.
    Impl();
    ~Impl();

    Impl(const Impl&) = delete;
    Impl& operator=(const Impl&) = delete;
    Impl(Impl&&) = delete;
    Impl& operator=(Impl&&) = delete;

    // --- what the public surface forwards to -------------------------------

    void setTitle(std::string_view title);
    void show();

    // Takes the window off screen if it is on it. The startup hold's
    // (Application::holdWindowsUntilReady): nothing may show the window
    // before the ready report does.
    void hideIfShown();

    void close();
    bool openProject(std::string_view utf8Path);

    // The CALLER's close hook. One hook, replaced rather than accumulated --
    // the public signature returns no subscription token, so there would be no
    // way to detach one of several.
    void onCloseRequested(std::function<void()> fn);

    // --- lifetime bookkeeping ----------------------------------------------

    // The SDK's OWN close hook, kept in a separate slot from the caller's.
    //
    // Application::createEditorWindow() installs one here to notice that a
    // window has left the screen, which is what ends the session when the last
    // one goes. If it shared the slot above, a caller doing the perfectly
    // ordinary thing -- asking to be told when its window closes -- would
    // silently overwrite it, and the application would stop quitting when its
    // last window was closed. Two slots, both fired, neither able to displace
    // the other.
    void setInternalCloseHook(std::function<void()> fn);

    // Run from ~Impl. Application::createEditorWindow() installs one to strike
    // the window off the Application's live-window list, which is the list
    // ~Application::Impl asserts is empty.
    void setDestroyedHook(std::function<void()> fn);

    // --- the Impl's own state ----------------------------------------------
    //
    // Public, and deliberately so. This class IS the Impl the header surface
    // rule moves state INTO -- the rule's own recipe spells one
    // `class ICoreFoo::Impl { public: /* every field */ };`
    // (DEVELOPER_GUIDELINES.md). It is reachable from exactly two translation
    // units, both listed at the top of this file, and `private:` here bought
    // nothing: the only code that could have been kept out is EditorWindow's
    // own, which owns it.

    // Fires both close hooks, internal first. Installed on the window as its
    // close-requested hook (ICoreWindow::setCloseRequestedHook), which is the
    // Qt-free replacement for the QObject event filter this class used to
    // carry -- the reason EditorWindow.cpp names no toolkit type at all now.
    void notifyCloseRequested();

    std::unique_ptr<ICorePrimaryWindow> m_window;

    std::function<void()> m_onCloseInternal;
    std::function<void()> m_onCloseRequested;
    std::function<void()> m_onDestroyed;
};
};

ICoreAPIs.h#

src/ICoreBlocks/Shell/ICoreAPIs.h

Explicit-model forms of the three above, for callers not working on the focused canvas (mirrors the moveObjectsToTrash / permanentlyDeleteObjects convention).

ICoreAPIs#

ICoreAPIs.h:19 · class · 75 declaration(s)

class ICoreAPIs {
public:
    static void closeSoftware();
    static void forceCloseSoftware();

    static void migrateSelection(ICoreSubsystemTreeNode* destination, const ICorePoint& positionOffset);

    static void cutSelection();
    static void copySelection();
    static void paste(ICoreSubsystemTreeNode* destination, const ICorePoint& newPosOnCanvas);

    // Explicit-model forms of the three above, for callers not working on the focused canvas
    // (mirrors the moveObjectsToTrash / permanentlyDeleteObjects convention).
    static void cutSelection(ICoreCanvasSelectionModel* selectionModel);
    static void copySelection(ICoreCanvasSelectionModel* selectionModel);
    static void migrateObjects(ICoreCanvasSelectionModel* selectionModel, ICoreSubsystemTreeNode* destination, const ICorePoint& positionOffset);

    static void moveSelectionToTrash();
    static void moveObjectsToTrash(ICoreCanvasSelectionModel* selectionModel);

    static void permanentlyDeleteObjects(ICoreCanvasSelectionModel *selectionModel);

    static void permanentlyDeleteSelectedObjects();

    // Both return the created subsystem's tree node, or nullptr when creation failed
    // (invalid parent path, name collision, ...). Callers may ignore the return.
    static ICoreSubsystemTreeNode* createNewEmptySubsystem(const std::string& parentPath);
    static ICoreSubsystemTreeNode* createSubsystemAndMigrateSelection();

    // Explicit form: arbitrary parent path and selection model, plus the position the new
    // subsystem block takes when the selection is empty (the no-arg overload leaves it to
    // the backend default).
    static ICoreSubsystemTreeNode* createSubsystemAndMigrateSelection(const std::string& parentPath, ICoreCanvasSelectionModel* selectionModel, const ICorePoint& subsystemPosIfSelectionEmpty);

    // Re-positions the blocks of `treeNode` into a clean left-to-right layered diagram,
    // ordered by execution (solver) order. No-op for a null node.
    static void autoArrange(ICoreSubsystemTreeNode* treeNode);

    // Series-reduces the two selected SISO blocks into one State Space block. See
    // ICoreSeriesReduction::reduceSeriesSelection for the exact preconditions; logs and leaves the
    // model untouched if the current selection doesn't satisfy them.
    static bool reduceSeriesSelection(ICoreCanvasSelectionModel* selectionModel);

    // Parallel-reduces the two selected SISO blocks (same input source, outputs summed by a shared
    // Sum block) into one State Space block. See ICoreParallelReduction::reduceParallelSelection for
    // the exact preconditions; logs and leaves the model untouched if the current selection doesn't
    // satisfy them.
    static bool reduceParallelSelection(ICoreCanvasSelectionModel* selectionModel);

    // Reduces the selected plant/feedback/Sum-block closed loop into one State Space block. See
    // ICoreFeedbackReduction::reduceFeedbackSelection for the exact preconditions; logs and leaves
    // the model untouched if the current selection doesn't satisfy them.
    static bool reduceFeedbackSelection(ICoreCanvasSelectionModel* selectionModel);

    // Auto reduction over `treeNode` (only the selected blocks, when two or more are
    // selected): one step tries a series reduction, then parallel, then feedback, and
    // applies the first that passes; full repeats until none is left. Every reduction is
    // verified whatever the preference says, and the run is one undo step. Returns how
    // many reductions were applied. See ICoreAutoReduction.
    static int autoReduceOneStep(ICoreSubsystemTreeNode* treeNode, ICoreCanvasSelectionModel* selectionModel);
    static int autoReduceFully(ICoreSubsystemTreeNode* treeNode, ICoreCanvasSelectionModel* selectionModel);

    // Shows/hides the name label of every selected block. Per-block model state, so it is
    // captured in the recipe and survives undo/redo and a save/reload. See ICoreBlockRename.
    static void setSelectionNameLabelsVisible(ICoreCanvasSelectionModel* selectionModel, bool visible);

    // Hides the labels when any selected block still shows one, shows them all otherwise.
    static void toggleSelectionNameLabels(ICoreCanvasSelectionModel* selectionModel);

    // Saves the whole project to disk immediately (the Ctrl+S write path).
    static void saveProject();

    // Discards the in-memory model and reloads the project from what is on disk.
    static void reloadProjectFromDisk();

    static void undo();
    static void redo();
    // A console / script recipe statement just ran and may have edited the diagram
    // without logging. See ICoreStudioStateMachine::notePossibleUnloggedEdit().
    static void notePossibleUnloggedDiagramEdit();
    // ICoreStudioStateMachine's logging switch, for layers that may not name it:
    // a caller that turns it off reads it first and restores only what it found.
    static bool isLoggingDiagramEdits();
    static void setLoggingDiagramEdits(bool enabled);

    // Builds the project. On failure, raises the "Build Failed" warning notification
    // (details go to the run diagnosis panel) and returns false.
    static bool buildProject();

    static void runProjectSimulator();
    static void runProjectSimulatorInDebugMode();
    static void stepProjectSimulator();
    static void pauseProjectSimulator();
    static void stopProjectSimulator();

    // Comments out (or uncomments) every selected block. Per-block model state, so it is
    // captured in the recipe and survives undo/redo and a save/reload. See ICoreBlockComment.
    static void setSelectionCommentedOut(ICoreCanvasSelectionModel* selectionModel, bool commentedOut);
    // Uncomments when every selected block is commented out, comments all out otherwise.
    static void toggleSelectionCommentedOut(ICoreCanvasSelectionModel* selectionModel);

    // Turns every selected block by `degrees`, clockwise on screen. One undo step. See ICoreBlockRotate.
    static void rotateSelection(ICoreCanvasSelectionModel* selectionModel, double degrees);

    // Opens the one selected block's name for typing; false when there is not exactly one
    // block selected with its name showing. See ICoreBlockRename.
    static bool beginRenamingSelection(ICoreCanvasSelectionModel* selectionModel);

    // Clones the selected objects (blocks, links, areas, notes, images) into `destination`,
    // re-establishes the clones' connections among themselves, and applies `positionOffset`.
    // See ICoreObjectsDuplication.
    static void duplicateSelection(ICoreCanvasSelectionModel* selectionModel, ICoreSubsystemTreeNode* destination, const ICorePoint& positionOffset);

    // Sets / scales the focused canvas's zoom. Clamping happens in the canvas parent.
    static void setFocusedCanvasZoom(double zoomLevel);
    static void zoomFocusedCanvasBy(double factor);

    // Focused-tab navigation. Both no-op quietly when there is nowhere to go.
    static void navigateBack();
    static void navigateUp();

    // Loads the subsystem at `treeNodePath` into the focused tab.
    static void navigateToPath(const std::string& treeNodePath);

    // Opens the subsystem at `treeNodePath` in a new tab of the focused window, or in a
    // new editor window. A tree node lives in exactly one canvas, so if it is already
    // loaded somewhere these focus the tab that holds it instead of opening a blank one.
    static void openSubsystemInNewTab(const std::string& treeNodePath);
    static void openSubsystemInNewWindow(const std::string& treeNodePath);

    static void closeFocusedTab();

    // Selects every child of the tree node loaded on the focused canvas.
    static void selectAllOnFocusedCanvas();

    // Releases any in-flight mouse grabs, then restores the focused canvas's pan and
    // zoom to their defaults.
    static void resetFocusedCanvasPanAndZoom();

    // Handle lookups (re-exported from ICoreSubsystemTreeNodeRegistry). Paths accept both
    // display and canonical forms; nullptr when nothing matches.
    static ICoreSubsystemTreeNode* findTreeNodeByPath(const std::string& path);
    static ICoreBlock* findBlockByPath(const std::string& path);
    static ICorePort* findPortByPath(const std::string& path);

    // Object creation. Each creates into `treeNode` at `position` (canvas scene
    // coordinates), logs one undo step, and returns the created object; nullptr and
    // no-op for a null node. `type` for blocks is the library path, e.g.
    // "Control_Systems/Continuous/State_Space".
    static ICoreBlock* createBlock(ICoreSubsystemTreeNode* treeNode, const std::string& type, const ICorePoint& position);
    static ICoreCanvasArea* createCanvasArea(ICoreSubsystemTreeNode* treeNode, const ICorePoint& position);
    static ICoreCanvasTextBox* createNote(ICoreSubsystemTreeNode* treeNode, const ICorePoint& position);
    static ICoreImage* createImage(ICoreSubsystemTreeNode* treeNode, const ICorePoint& position);

    // Link wiring (re-exported from ICoreAutoLinkConnector). All validate their inputs,
    // log-and-return-null on an invalid connection, and log one undo step on success.
    // `linkType` names the link class, e.g. "Signal".
    static ICoreLink* generateLink_Port_Port(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, ICorePort* tailPort, ICorePort* headPort);
    static ICoreLink* generateLink_Port_Null(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, ICorePort* tailPort, const ICorePoint& headCoordination);
    static ICoreLink* generateLink_Null_Port(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, const ICorePoint& tailCoordination, ICorePort* headPort);
    static ICoreLink* generateLink_Null_Null(ICoreSubsystemTreeNode* treeNode, const std::string& linkType, const ICorePoint& tailCoordination, const ICorePoint& headCoordination);
    static ICoreLinkBranch* generateLinkBranch_Link_Port(ICoreLink* link, ICorePort* headPort);

    // Recipe / Simulink-script transfer. Return false on failure; per-line warnings and
    // the failure reason go to the transfer Result, which the notification log carries.
    static bool importRecipe(ICoreSubsystemTreeNode* node, const std::string& filePath);
    static bool exportRecipe(ICoreSubsystemTreeNode* node, const std::string& filePath);
    static bool importSimulinkScript(ICoreSubsystemTreeNode* node, const std::string& filePath);
    static bool exportSimulinkScript(ICoreSubsystemTreeNode* node, const std::string& filePath);

    // MATLAB .m -> .icore: the command bridge's import half, the mirror of
    // ICoreMatlabCommandBridge::exportToMatlabScript.
    // Unlike the four above this one touches no diagram: it writes a script into
    // <project>/scripts, so it takes no node. `scriptName` empty means the .m file's own
    // stem. False when the .m could not be read, when nothing in it crossed, or when the
    // target exists and `overwrite` is false -- the import never overwrites silently.
    // The per-statement refusals are warnings on the bridge Result; call
    // ICoreMatlabCommandBridge::importFromMatlabScript directly to read them.
    static bool importMatlabScript(const std::string& filePath, const std::string& scriptName,
                                   bool overwrite = false);

    // Writes the canvas's diagram to a PDF at `filePath` — no dialog. False on failure.
    static bool exportCanvasAsPDF(ICoreCanvas* canvas, const std::string& filePath);
    static bool exportFocusedCanvasAsPDF(const std::string& filePath);

    // Code deploy (re-exported from ICoreCodeEngine).
    static ICoreCodeExportTarget* createCodeExportTarget();
    static void deleteCodeExportTarget(const ICoreCodeExportTarget* target);
    static bool deployTarget(const ICoreCodeExportTarget* target);
    static bool deployAllTargets();
    static void cancelDeploy();

};
};

ICoreOtherObjectsRegistry.h#

src/ICoreBlocks/Shell/ICoreOtherObjectsRegistry.h

ICoreOtherObjectsRegistry#

ICoreOtherObjectsRegistry.h:6 · class · 2 declaration(s)

class ICoreOtherObjectsRegistry {
public:
    // ----- Global Variables Space (single, app-lifetime) -----
    static ICoreVariablesSpace* getGlobalVariablesSpace();
    static void clearGlobalVariables();

};
};

Initialization.h#

src/ICoreBlocks/Shell/Initialization.h

Everything the application object itself has to be told: what the app calls itself to the window system, the icon every window is marked with, and the palette all widgets inherit. Runs against an already-constructed application (nothing here can precede it) and before any widget exists, so it belongs between the application's construction and initializeApp().

⚠ TAKES THE APPLICATION, and used to take nothing. The old spelling reached the toolkit's statics directly, which is exactly what kept a QApplication include in this module; the properties it sets are ICoreApplication's now, and they are instance methods there on purpose. The reference is forward-declared, so this header still names no toolkit type.

Initialization#

Initialization.h:12 · class · 16 declaration(s)

class Initialization {
public:
    // Everything the application object itself has to be told: what the app
    // calls itself to the window system, the icon every window is marked with,
    // and the palette all widgets inherit. Runs against an already-constructed
    // application (nothing here can precede it) and before any widget exists,
    // so it belongs between the application's construction and
    // initializeApp().
    //
    // ⚠ TAKES THE APPLICATION, and used to take nothing. The old spelling
    // reached the toolkit's statics directly, which is exactly what kept a
    // QApplication include in this module; the properties it sets are
    // ICoreApplication's now, and they are instance methods there on purpose.
    // The reference is forward-declared, so this header still names no
    // toolkit type.
    static void configureApplication(ICoreApplication& app);

    // Everything an editor window is built on, and nothing that builds one:
    // loggers, communications, preferences, theme, the four registries, the
    // state machine, and the console command bindings. Split out of
    // initializeApp() because ICorePrimaryWindow's constructor reaches for the
    // theme manager and three of those registries, so ANY caller that wants a
    // primary window has to have run this first -- including
    // icore::Application, which is a lifetime scope and creates no window of
    // its own (see src/ICoreBlocks/Shell/Application.cpp).
    //
    // Idempotent: the second and later calls do nothing, so the SDK path and
    // the Main.cpp path can both ask for services without either having to
    // know whether the other already ran.
    //
    // `reportStep`, when given, hears what is about to happen and how far
    // through startup it is (0..1) -- icore::Application forwards it to a
    // startup screen (Application.h, StartupProgress). Its first call comes
    // AFTER the user's theme is active and not before, so a screen that draws
    // itself on that call draws in the right theme.
    static void initializeServices(
        const std::function<void(std::string_view status, double fraction)>& reportStep = {});

    // ---- the wall (A3.1) --------------------------------------------------
    //
    // Resolves the licence ONCE, binds it so `icore::License` and every
    // ICoreFeatureGate can answer, and hands the entitlements to the update
    // service. Returns whether this session may open an editor window.
    //
    // ⚠ THIS FUNCTION NO LONGER SHOWS A DIALOG (owner, 2026-08-24). It used to
    // raise the wall itself, parentless, before any window existed. The
    // instruction is that THE PRIMARY WINDOW ALWAYS OPENS AND THE WALL ARRIVES
    // ON TOP OF IT, so the dialog is showSignInWallIfUnlicensed()'s, called
    // once the window is on screen. What did NOT move is the enforcing half:
    // the gate is bound here, before any window is constructed, so
    // `icore::License` and every ICoreFeatureGate already answer Unlicensed by
    // the time one exists. The wall was never what refused a feature.
    //
    // ⚠ A WINDOWED SESSION ALWAYS MAY, SINCE A3.5 (2026-08-24). Resolving
    // nothing no longer ends the process -- the session opens in no-licence
    // mode instead. The gate stays Unlicensed
    // and stays bound, so nothing is granted: every paid feature is refused by
    // ICoreFeatureGate off the empty entitlements, and ICoreLicenseBanner shows
    // its non-dismissible Unlicensed row. Only a HEADLESS session can still be
    // refused, and it must stay that way -- a --console run that proceeded
    // unlicensed would report passes for features the gates had refused, so CI
    // would go quiet instead of red.
    //
    // ⚠ HEADLESS IS A DIFFERENT SHAPE, NOT A WEAKER ONE. A `--console` session
    // resolves the same way through the same verifier, but over an IN-MEMORY
    // vault: the OS keychain's first read from a freshly relinked binary raises
    // an authorisation prompt that a headless run waits on FOREVER (measured in
    // this tree, in the boundaries suite), and `resolveOnLaunch()` also WRITES
    // the clock anchor. Handing the headless gate a memory vault makes the
    // keychain unreachable by construction rather than by care, and costs
    // nothing real: an environment token is never persisted anyway (A3.2), so
    // there was never anything in the vault for CI to read. The escape hatch is
    // ICORE_LICENSE_TOKEN, verified identically — never a bypass.
    //
    // ⚠ AND IT NEVER RAISES A DIALOG HEADLESS. A wall with no one to click it
    // is a hang, and a hang is how this fails for everybody's suites at once
    // (ACCOUNT_MANAGER N3). Unlicensed headless is reported by the caller as a
    // message and a non-zero exit; only a windowed session shows the dialog.
    //
    // Idempotent: the first call resolves, the rest report what it decided.
    static bool resolveLicense();

    // ---- Usage telemetry (ACCOUNT_MANAGER A6.6) -------------------------
    //
    // ICoreTelemetrySession was built and tested at A6.1-A6.5 and then never
    // constructed, so for six weeks no session left any copy of the app. These
    // three are the shell's half: resolveLicense() starts it once the gate is
    // bound, the shell's timer ticks it, and the quit path ends it.
    //
    // ⚠ NEVER IN A --console SESSION. The suites run inside the real
    // application; a session started there would file every regression run
    // as a customer using the product.
    //
    // ⚠ OPT-OUT IS READ BEFORE ANYTHING IS SENT. startTelemetry() binds the
    // preference (which pushes the user's saved choice into the session)
    // before it calls start(), so an opted-out user sends nothing at all.
    static void startTelemetry();

    // Every few seconds from the shell's timer. Samples whether one of the
    // app's windows is in front and whether the pointer moved -- the activity
    // signal ICoreActiveTime counts against its five-minute idle rule -- then
    // lets the session send whatever heartbeat or flush is due. Cheap when
    // nothing is due; a no-op when telemetry never started.
    static void tickTelemetry();

    // The clean_exit row. Fire-and-forget: the app never waits for telemetry
    // on the way out, and a row that does not arrive is reaped server-side
    // (close_stale_sessions) with at most one heartbeat of active time lost.
    static void endTelemetry();

    // The wall itself, raised ONCE, over a window that is already on screen.
    //
    // Does nothing at all unless every one of these holds: this is a windowed
    // session, resolveLicense() has run, it resolved to Unlicensed, and no
    // startup wall has been raised yet. So the licensed case costs a comparison
    // and a console session can never reach a dialog.
    //
    // ⚠ CALL IT AFTER THE PRIMARY WINDOW IS SHOWN, AND AFTER
    // applyStartupBehavior(). That ordering is the owner's instruction of
    // 2026-08-24 -- the primary window opens whatever the licence says, and the
    // wall arrives on top of it -- and it is also what puts the dialog in
    // front: whichever window goes up last is the one the window system leaves
    // there. Both entry paths do this: initializeApp() as its last statement,
    // and icore::Application through installStartupBehaviorHook().
    //
    // ⚠ IT GOES THROUGH ICorePrimaryWindow::raiseSignInWall() WHEN THERE IS A
    // PRIMARY WINDOW, and that is not an implementation detail:
    // ICoreLicenseBanner is built during the window's construction, which now
    // happens while the session is still Unlicensed, so it comes up saying so
    // and something must refresh it when the sign-in succeeds. The window's own
    // path is the one place that does -- and it holds the one-wall-at-a-time
    // guard that keeps this from stacking with the wall a sign-out raises.
    //
    // Cancelling does not quit (A3.5). A REFUSAL still explains itself
    // afterwards (A5.5); a cancellation deliberately does not.
    static void showSignInWallIfUnlicensed();

    // Whether this session is allowed to put an editor window on screen.
    // Safe before resolveLicense() runs (answers false) and after.
    //
    // ⚠ THIS IS NOT "IS LICENSED", AND THE OLD NAME SAID IT WAS. Since A3.5 a
    // windowed session that resolves nothing opens anyway in no-licence mode,
    // so this answers TRUE while the gate is Unlicensed and ICoreFeatureGate
    // refuses simulation, code generation and every other paid feature. Only a
    // headless session can answer false now. ⚠ If your question is "may the
    // user do X", ask ICoreFeatureGate or icore::License -- a caller that
    // reaches for this one is asking whether a WINDOW is allowed to exist.
    [[nodiscard]] static bool sessionMayOpenAnEditor();

    // Whether this process was launched with --console. Recorded by
    // configureApplication(), which is the first thing holding the argument
    // vector; both entry paths call it.
    [[nodiscard]] static bool isConsoleSession();

    // The sentence a refused headless session prints. Public so the console
    // dispatch can print it and a suite can assert it names the variable and
    // the diagnostic command rather than saying "unlicensed".
    [[nodiscard]] static std::string headlessRefusalMessage();

    // Console commands a refused session may still run. `licenseStatus` has to
    // work on the machine where verification is FAILING -- that is the whole
    // reason it exists -- so the wall must not be what stops you diagnosing the
    // wall.
    [[nodiscard]] static bool isLicenseDiagnosticCommand(const std::string& commandLine);

    // initializeServices(), then THE primary window -- created, shown, and
    // handed to the startup behavior. This is Main.cpp's entry point and the
    // reason initializeServices() exists separately: the SDK wants the first
    // half without the second.
    static void initializeApp();

    // Opens what the user launches into, once the primary window exists. Nothing
    // else loads a project at startup, so this always ends with either a project
    // open or the launcher up: it acts on the startup preference (Settings ->
    // Startup) to either reopen the most recent project or go straight to the
    // launcher, and falls back to the New Project form when there is no recent
    // project to reopen or the most recent one cannot be read.
    //
    // Public rather than a private step of initializeApp(), because there are
    // now two ways the primary window comes into being and only one of them is
    // initializeApp(). icore::Application creates no window -- the SDK caller
    // does, through createEditorWindow() -- so the SDK arms this on the event
    // loop and it fires once that window is up. See src/ICoreBlocks/Shell/Application.cpp.
    //
    // @pre A primary window exists and is on screen. It is the parent of the
    //      prompts raised here, and the window the launcher has to land in
    //      front of.
    //
    // ⚠ EXCEPT WHEN A STARTUP SCREEN IS UP. Then the window exists but is
    // still hidden behind it, and the two callbacks are how the caller keeps
    // the rest of that precondition true:
    //   * `beforeOpening(name)` runs just before the most recent project is
    //     reopened -- the step a startup screen names ("Opening <name>").
    //   * `beforeLauncher()` runs just before the launcher is raised, so the
    //     caller can put the primary window on screen FIRST and the launcher
    //     still lands in front of it.
    // Both are optional; with neither, this is exactly the old behaviour.
    static void applyStartupBehavior(
        const std::function<void(const std::string& projectName)>& beforeOpening = {},
        const std::function<void()>& beforeLauncher = {});

    // --- who the primary window is -----------------------------------------
    //
    // Two paths reach this: initializeApp(), which creates the window itself,
    // and icore::EditorWindow's Impl, which is what creates it on the SDK path.
    // Registering from both is what keeps getPrimaryWindow() -- and the dialog
    // parent applyStartupBehavior() uses -- answering the same question no
    // matter which path built the app.
    //
    // First non-null registration wins: an Application may hand out more than
    // one editor window, and the app's primary window is the first of them.
    static void setPrimaryWindow(ICorePrimaryWindow* window);

    // Withdraws `window` if it is the one currently registered, and does
    // nothing otherwise. Called from the window's own destructor, so a second
    // editor window closing can never blank a pointer that belongs to the
    // first.
    static void clearPrimaryWindow(const ICorePrimaryWindow* window);

    static ICorePrimaryWindow* getPrimaryWindow();

};
};