Fix eterm startup directories and clarify agent runtime guidance

- Add a default working directory setting, using home when empty.
- Keep command-line directory overrides local to the current launch.
- Remove the legacy setting that persisted one-off launch paths.
- Prefer native hidden windows for tests and benchmarks, and headless
  execution for ordinary applications and interactive inspector checks.
- Keep the secondary dialog test hidden and assert its visibility.
This commit is contained in:
Martín Lucas Golini
2026-10-17 14:32:02 -03:00
parent 64c75f101c
commit 3ca7ae7c7e
8 changed files with 105 additions and 33 deletions
+17
View File
@@ -48,6 +48,16 @@ optimization, benchmarking, or validating runtime speed. Do not reuse a gmake tr
The debug/unit-test workflow below is additional correctness validation. It does not replace the
release Ninja build required for performance work.
## Benchmark Runtime
Run the release benchmark harness with the native runtime on a usable desktop:
`EEPP_RUNTIME=native bin/benchmarks/eepp-benchmarks`.
The benchmarks that create windows explicitly request `WindowStyle::Hidden`; other benchmarks
do not create windows. They can use the native renderer without showing desktop windows or taking
focus. Do not switch this harness to headless merely to avoid interrupting the user. Verify the
renderer before reporting hardware rendering performance; headless and Xvfb runs can use software
rendering and measure a different execution path.
## Debug and Unit-Test Builds
All build commands must be executed from the **root project directory**. On macOS, follow the
@@ -117,6 +127,13 @@ Run the following command, replacing `<os_name>` with the correct environment:
## Running GUI Examples Under Xvfb
For ordinary application/example launches and UI inspector automation, try `EEPP_RUNTIME=headless`
first to avoid desktop windows and input interference without requiring Xvfb. This preference does
not apply to the unit-test and benchmark harnesses: their windows are already hidden, so use the
native runtime on a usable desktop. Use the native/Xvfb paths below for examples only when the
required check cannot run with the headless runtime. Verify the renderer before using an offscreen
run to assess hardware rendering or performance.
Xvfb does not support the multisampled OpenGL contexts requested by some eepp examples. In
particular, `src/examples/ui_html/ui_html.cpp` normally requests 4x MSAA. Launching that binary
through `xvfb-run` or `projects/scripts/xvfb-run-eepp` can therefore fail immediately with
+18
View File
@@ -23,6 +23,24 @@ not `eTerm`. Its implementation lives under `src/modules/eterm/`.
`eeiv` is the repository's image viewer. Its application entry point is
`src/tools/eeiv/eeiv.cpp`.
## AI Agent Runtime Checks
AI agents should prefer `EEPP_RUNTIME=headless` when launching ordinary applications or examples
and when automating their UI with the inspector. This avoids opening desktop windows or interrupting
the human's keyboard input and focus. Coordinate interactive checks that require visible native
windows with the user before driving them.
Unit tests and benchmarks are different: run their release binaries with the native runtime on a
usable desktop. The unit-test build defines `EE_UNIT_TESTS`, which adds `WindowStyle::Hidden` to
the default window style; benchmarks that create windows explicitly request `WindowStyle::Hidden`.
These hidden windows preserve native rendering without appearing or taking desktop focus. Keep
test and benchmark windows hidden when adding or changing cases. Do not select the headless runtime
solely to avoid desktop interruption for these harnesses.
Use headless tests when no usable display is available, when requested, or when testing the headless
runtime itself. Headless rendering can use a software renderer, so verify the renderer before
assessing GPU behavior or performance. See `.agents/rules/unit-tests.md` for commands and fallbacks.
## Documentation & Code References
When working on this project, rely on the following resources to understand existing implementations:
+30 -20
View File
@@ -8,44 +8,54 @@ The test binary manages its own current working directory, so you can execute it
The required default workflow is:
1. Build the release unit-test target (use ninja build).
2. Run `bin/unit_tests/eepp-unit_tests` directly, without Xvfb.
3. If sandbox restrictions prevent access to the host display, retry that same direct command with
elevated permissions.
4. Use Xvfb only after direct execution has failed outside the sandbox because no usable graphical
display is available. Xvfb is the last fallback, not the default headless convenience path.
2. On a usable desktop, run `EEPP_RUNTIME=native bin/unit_tests/eepp-unit_tests` directly, without
Xvfb. The unit-test build defines `EE_UNIT_TESTS`, making `WindowStyle::Default` include
`WindowStyle::Hidden`; native test windows remain hidden and do not take desktop focus.
3. If sandbox restrictions prevent access to the host display, retry the same native command with
elevated permissions. A sandbox display failure does not establish that the host lacks a display.
4. When no usable display is available, use `EEPP_RUNTIME=headless bin/unit_tests/eepp-unit_tests`.
Also use it when requested or when checking the headless runtime itself.
5. Use Xvfb only if there is no usable desktop display and the required check cannot run with the
headless runtime. Keep the explicit runtime selection in fallback commands below.
Do not infer that a session is headless merely because its first sandboxed command cannot connect to
the display. A sandbox can hide or deny access to an otherwise usable host display.
The preference for headless application launches and interactive UI inspector checks does not
change this harness workflow. Keep all native test windows hidden, including secondary dialog
hosts; showing a UI widget inside a hidden host does not require showing the native window.
* **Use the release test binary during normal development:**
When AddressSanitizer or other debug-only diagnostics are not required, build and run `bin/unit_tests/eepp-unit_tests`. The optimized release suite is substantially faster and is the required default for iterative testing. Use `bin/unit_tests/eepp-unit_tests-debug` only when investigating memory safety, assertions, or other behavior that specifically requires the debug configuration.
* **Default Execution on a Graphical Linux Desktop:**
Unit-test windows are created hidden, so run the release suite directly against the desktop:
`bin/unit_tests/eepp-unit_tests`
This keeps the windows invisible and unfocused while preserving hardware OpenGL acceleration.
* **Default Execution on a Graphical Desktop:**
`EEPP_RUNTIME=native bin/unit_tests/eepp-unit_tests`
Hidden windows preserve native rendering without appearing or taking desktop focus.
Confirm that the renderer log names the real GPU rather than llvmpipe when validating rendering
behavior or performance.
* **Filtered Tests on a Graphical Linux Desktop:**
Use the same direct hardware-backed command for focused runs:
`bin/unit_tests/eepp-unit_tests --filter="FontRendering.*Offset*"`
* **Last Fallback for Headless CI and Systems Confirmed to Lack a Usable Desktop Display:**
Keep `projects/scripts/xvfb-run-eepp` as the fallback when no desktop display is available:
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests`
* **Filtered Tests:**
Use the same native runtime for focused runs on a usable desktop:
`EEPP_RUNTIME=native bin/unit_tests/eepp-unit_tests --filter="FontRendering.*Offset*"`
* **Headless Execution:**
`EEPP_RUNTIME=headless bin/unit_tests/eepp-unit_tests`
Use this when no usable desktop display is available, when requested, or for headless runtime
validation. It renders offscreen without Xvfb. It can use a software renderer, so its timings
do not necessarily represent hardware GPU performance. Add `--filter` for focused headless runs.
* **Last Fallback When the Headless Runtime Cannot Support the Required Check:**
Keep `projects/scripts/xvfb-run-eepp` as the fallback when no usable desktop display is available:
`EEPP_RUNTIME=native projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests`
The wrapper provides a race-safe isolated display at `1280x1024x24` and injects
`ASAN_OPTIONS=detect_leaks=0`. Xvfb can use llvmpipe, so do not use wrapper timings to evaluate
hardware-rendered performance.
* **Wrapper Fallback:**
If `projects/scripts/xvfb-run-eepp` itself fails before launching the test binary, report that
failure and use:
`xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests`
`EEPP_RUNTIME=native xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests`
Do not use plain `xvfb-run` as the first headless attempt.
* **Non-window Tests:**
Tests known not to create windows can run without selecting a video driver:
`bin/unit_tests/eepp-unit_tests --filter="NonWindowTest.*"`
* **Filtering Tests:**
Use the `--filter` parameter to run specific tests (supports glob patterns).
Keep the same harness as the full suite: direct execution on a graphical Linux desktop, or the
wrapper in a genuinely headless environment.
Keep the same runtime as the full suite for the current environment: native on a usable desktop,
headless when no usable display is available, or the Xvfb fallback when headless cannot support
the required check.
## Cross-Platform Compatibility (Required)
+3 -3
View File
@@ -779,13 +779,13 @@ UTEST( UIApplication, CreatesSecondaryWindowWithoutChangingAmbientScene ) {
const Sizei primaryScreenSize = primaryWindow->getSizeInScreenCoordinates();
auto* fileDialog = UIFileDialog::NewInApplicationWindow(
app,
WindowSettings( 640, 400, "File Dialog UI Context",
WindowStyle::Titlebar | WindowStyle::Resize, WindowBackend::Default, 32, {},
1, false, true ),
WindowSettings( 640, 400, "File Dialog UI Context", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false, true ),
UIFileDialog::DefaultFlags, "*", FileSystem::getCurrentWorkingDirectory(),
ContextSettings( false, 0, 0, GLv_default, true, false ), true,
UIWindow::ApplicationWindowPosition::CenteredOnPrimary );
ASSERT_TRUE( fileDialog != nullptr );
EXPECT_FALSE( fileDialog->getUISceneNode()->getWindow()->isVisible() );
EXPECT_EQ( app.getWindowCount(), 3u );
EXPECT_NE( fileDialog->getUISceneNode(), app.getUI() );
EXPECT_NE( fileDialog->getUISceneNode(), secondaryUI );
+5 -3
View File
@@ -22,8 +22,9 @@ void AppConfig::load() {
terminal.shell = mIni.getValue( "terminal", "shell", terminal.shell );
terminal.shellArguments =
mIni.getValue( "terminal", "shell_arguments", terminal.shellArguments );
terminal.workingDirectory =
mIni.getValue( "terminal", "working_directory", terminal.workingDirectory );
// The legacy working_directory key also captured one-off command-line paths.
terminal.defaultWorkingDirectory =
mIni.getValue( "terminal", "default_working_directory", terminal.defaultWorkingDirectory );
terminal.executeInShell =
mIni.getValue( "terminal", "execute_in_shell", terminal.executeInShell );
terminal.historySize = mIni.getValueU( "terminal", "scrollback", terminal.historySize );
@@ -99,7 +100,8 @@ bool AppConfig::savePreferences() {
mIni.deleteValue( "window", "max_fps" );
mIni.setValue( "terminal", "shell", terminal.shell );
mIni.setValue( "terminal", "shell_arguments", terminal.shellArguments );
mIni.setValue( "terminal", "working_directory", terminal.workingDirectory );
mIni.deleteValue( "terminal", "working_directory" );
mIni.setValue( "terminal", "default_working_directory", terminal.defaultWorkingDirectory );
mIni.setValue( "terminal", "execute_in_shell", terminal.executeInShell );
mIni.setValueU( "terminal", "scrollback", terminal.historySize );
mIni.setValue( "terminal", "cursor_style",
+1 -1
View File
@@ -25,7 +25,7 @@ enum class NewTerminalBehavior { CurrentTabBar, VerticalSplit, HorizontalSplit }
struct TerminalConfig {
std::string shell;
std::string shellArguments;
std::string workingDirectory;
std::string defaultWorkingDirectory;
std::string executeInShell;
size_t historySize{ 10000 };
size_t initialTabs{ 1 };
+10 -6
View File
@@ -701,7 +701,8 @@ int App::run( int argc, char* argv[] ) {
parser, "prefers-color-scheme",
"Set the preferred color scheme (\"light\", \"dark\" or \"system\")",
{ 'c', "prefers-color-scheme" } );
args::Positional<std::string> wd( parser, "wording-dir", "Working Directory / executable" );
args::Positional<std::string> wd( parser, "working-dir",
"Working directory / executable for this launch only" );
args::Flag closeOnExit( parser, "close-on-exit",
"close the application when the executable exits",
{ "close", "close-on-exit" } );
@@ -770,8 +771,6 @@ int App::run( int argc, char* argv[] ) {
}
config->theme.uiColorScheme = ColorSchemePreferences::fromStringExt( scheme );
}
if ( wd )
config->terminal.workingDirectory = wd.Get();
config->terminal.executeInShell = executeInShell.Get();
config->theme.colorScheme = colorScheme.Get();
config->window.maxFPS = maxFPS.Get();
@@ -938,9 +937,14 @@ int App::run( int argc, char* argv[] ) {
resourceScope.getFontService().addFallbackFont( std::move( fallback ) );
}
const std::string launchPath = config->terminal.workingDirectory.empty()
? initialWorkingDirectory
: config->terminal.workingDirectory;
std::string launchPath = FileSystem::expandTilde( config->terminal.defaultWorkingDirectory );
if ( wd ) {
launchPath = wd.Get();
} else if ( launchPath.empty() || !FileSystem::isDirectory( launchPath ) ) {
if ( !launchPath.empty() )
Log::warning( "Default working directory does not exist: %s", launchPath );
launchPath = Sys::getUserDirectory();
}
FileInfo launchFile( launchPath );
const bool launchExecutable = launchFile.isRegularFile() && launchFile.isExecutable();
terminalConfig.program = launchExecutable ? launchFile.getFilepath() : config->terminal.shell;
+21
View File
@@ -307,6 +307,27 @@ UIWindow* SettingsPanel::create( App& app ) {
return true;
},
true );
panel->addText(
{ "terminalDefaultWorkingDirectory", "terminal.behavior",
app.i18n( "default_working_directory", "Default Working Directory" ),
app.i18n( "default_working_directory_desc",
"Set the starting directory for new terminals. Leave empty to use your home "
"directory. A command-line directory overrides it for that launch." ) },
[&app] { return app.config->terminal.defaultWorkingDirectory; },
[&app]( const std::string& value ) {
auto directory = FileSystem::expandTilde( value );
if ( !directory.empty() ) {
if ( !FileSystem::isDirectory( directory ) )
return false;
directory = FileSystem::getRealPath( directory );
}
app.config->terminal.defaultWorkingDirectory = directory;
app.terminalConfig.workingDirectory =
directory.empty() ? Sys::getUserDirectory() : std::move( directory );
app.savePreferences();
return true;
},
true );
panel->addInteger(
{ "terminalScrollback", "terminal.behavior",
app.i18n( "terminal_scrollback", "Terminal Scrollback" ),