mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
window: add headless and Kitty terminal runtimes
Add process-level native, headless, and terminal runtime modes while keeping the SDL2/SDL3 window backends responsible for creating the graphics context. Use SDL's offscreen video driver with an eepp-owned logical root FBO for headless rendering. Add caller-owned RGB24/RGBA32 framebuffer readback through the renderer abstraction without leaking GL calls into the Window namespace. Introduce a generic FramePresenter boundary and a Kitty presenter that streams chunked direct RGB24 frames from a bounded worker queue. Optimize Base64 encoding for this streaming path. Add POSIX and Windows terminal lifecycle support, Kitty keyboard input, SGR-Pixels mouse input, focus handling, and terminal pixel-size resize events. Inject translated events through a thread-safe Input queue so applications continue using the normal input path. Make UIApplication independent of physical displays in offscreen modes, preserve native runtime behavior, and report the selected runtime and presentation mode during initialization. Add Sys::setEnv() for portable process environment configuration.
This commit is contained in:
@@ -21,7 +21,8 @@ FrameBufferFBO::FrameBufferFBO( EE::Window::Window* window ) :
|
||||
mLastFB( 0 ),
|
||||
mLastCB( 0 ),
|
||||
mLastDB( 0 ),
|
||||
mLastSB( 0 ) {}
|
||||
mLastSB( 0 ),
|
||||
mCreated( false ) {}
|
||||
|
||||
FrameBufferFBO::FrameBufferFBO( const Uint32& Width, const Uint32& Height, bool StencilBuffer,
|
||||
bool DepthBuffer, bool useColorBuffer, const Uint32& channels,
|
||||
@@ -34,8 +35,9 @@ FrameBufferFBO::FrameBufferFBO( const Uint32& Width, const Uint32& Height, bool
|
||||
mLastFB( 0 ),
|
||||
mLastCB( 0 ),
|
||||
mLastDB( 0 ),
|
||||
mLastSB( 0 ) {
|
||||
create( Width, Height, StencilBuffer, DepthBuffer, useColorBuffer, channels );
|
||||
mLastSB( 0 ),
|
||||
mCreated( false ) {
|
||||
mCreated = create( Width, Height, StencilBuffer, DepthBuffer, useColorBuffer, channels );
|
||||
}
|
||||
|
||||
FrameBufferFBO::~FrameBufferFBO() {
|
||||
@@ -70,7 +72,8 @@ FrameBufferFBO::~FrameBufferFBO() {
|
||||
}
|
||||
|
||||
bool FrameBufferFBO::create( const Uint32& Width, const Uint32& Height ) {
|
||||
return create( Width, Height, true, false, false, 4 );
|
||||
mCreated = create( Width, Height, true, false, false, 4 );
|
||||
return mCreated;
|
||||
}
|
||||
|
||||
bool FrameBufferFBO::create( const Uint32& Width, const Uint32& Height, bool StencilBuffer,
|
||||
@@ -200,6 +203,13 @@ void FrameBufferFBO::bind() {
|
||||
}
|
||||
}
|
||||
|
||||
void FrameBufferFBO::bindAsReadTarget() {
|
||||
if ( mFrameBuffer ) {
|
||||
GlobalBatchRenderer::instance()->draw();
|
||||
GLi->bindFramebuffer( GL_FRAMEBUFFER, mFrameBuffer );
|
||||
}
|
||||
}
|
||||
|
||||
void FrameBufferFBO::unbind() {
|
||||
if ( mFrameBuffer ) {
|
||||
recoverView();
|
||||
@@ -215,6 +225,9 @@ void FrameBufferFBO::resize( const Uint32& Width, const Uint32& Height ) {
|
||||
mSize.x = Width;
|
||||
mSize.y = Height;
|
||||
|
||||
// Resizing is not a bind-stack operation. Preserve the target to which unbind() must eventually
|
||||
// return, especially for the logical root FBO which remains bound across frames.
|
||||
const Int32 lastFB = mLastFB;
|
||||
bindFrameBuffer();
|
||||
|
||||
if ( mHasDepthBuffer ) {
|
||||
@@ -255,6 +268,7 @@ void FrameBufferFBO::resize( const Uint32& Width, const Uint32& Height ) {
|
||||
}
|
||||
|
||||
GLi->bindFramebuffer( GL_FRAMEBUFFER, mLastFB );
|
||||
mLastFB = lastFB;
|
||||
}
|
||||
|
||||
void FrameBufferFBO::draw( const Vector2f& position, const Sizef& size ) {
|
||||
@@ -289,7 +303,7 @@ void FrameBufferFBO::draw( Rect src, Rect dst ) {
|
||||
}
|
||||
|
||||
bool FrameBufferFBO::created() const {
|
||||
return mFrameBuffer != 0;
|
||||
return mCreated;
|
||||
}
|
||||
|
||||
const Int32& FrameBufferFBO::getFrameBufferId() const {
|
||||
|
||||
@@ -19,6 +19,8 @@ class EE_API FrameBufferFBO : public FrameBuffer {
|
||||
|
||||
void bind();
|
||||
|
||||
void bindAsReadTarget();
|
||||
|
||||
void unbind();
|
||||
|
||||
void resize( const Uint32& Width, const Uint32& Height );
|
||||
@@ -42,11 +44,12 @@ class EE_API FrameBufferFBO : public FrameBuffer {
|
||||
Int32 mLastCB;
|
||||
Int32 mLastDB;
|
||||
Int32 mLastSB;
|
||||
bool mCreated;
|
||||
|
||||
bool create( const Uint32& Width, const Uint32& Height );
|
||||
|
||||
bool create( const Uint32& Width, const Uint32& Height, bool StencilBuffer, bool useColorBuffer,
|
||||
bool DepthBuffer, const Uint32& channels );
|
||||
bool create( const Uint32& Width, const Uint32& Height, bool StencilBuffer, bool DepthBuffer,
|
||||
bool useColorBuffer, const Uint32& channels );
|
||||
|
||||
void bindFrameBuffer();
|
||||
|
||||
|
||||
@@ -397,6 +397,14 @@ bool Renderer::pointSpriteSupported() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void Renderer::configurePointSprite() {
|
||||
if ( GLv_3CP != version() && GLv_3 != version() && GLv_ES2 != version() ) {
|
||||
#if !defined( EE_GLES2 ) || defined( EE_GLES_BOTH )
|
||||
glTexEnvi( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool Renderer::shadersSupported() {
|
||||
#ifdef EE_GLES
|
||||
return ( GLv_ES2 == version() || GLv_3 == version() || GLv_3CP == version() );
|
||||
@@ -863,6 +871,29 @@ void Renderer::readPixels( int x, int y, unsigned int width, unsigned int height
|
||||
glReadPixels( x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels );
|
||||
}
|
||||
|
||||
bool Renderer::readPixels( int x, int y, unsigned int width, unsigned int height,
|
||||
PixelFormat pixelFormat, void* pixels, size_t stride ) {
|
||||
const unsigned int channels = pixelFormat == PixelFormat::RGB24 ? 3 : 4;
|
||||
const size_t rowBytes = static_cast<size_t>( width ) * channels;
|
||||
if ( nullptr == pixels || stride < rowBytes )
|
||||
return false;
|
||||
GLint packAlignment;
|
||||
glGetIntegerv( GL_PACK_ALIGNMENT, &packAlignment );
|
||||
if ( packAlignment != 1 )
|
||||
pixelStorei( GL_PACK_ALIGNMENT, 1 );
|
||||
const GLenum format = pixelFormat == PixelFormat::RGB24 ? GL_RGB : GL_RGBA;
|
||||
if ( stride == rowBytes ) {
|
||||
glReadPixels( x, y, width, height, format, GL_UNSIGNED_BYTE, pixels );
|
||||
} else {
|
||||
Uint8* output = static_cast<Uint8*>( pixels );
|
||||
for ( unsigned int row = 0; row < height; ++row )
|
||||
glReadPixels( x, y + row, width, 1, format, GL_UNSIGNED_BYTE, output + row * stride );
|
||||
}
|
||||
if ( packAlignment != 1 )
|
||||
pixelStorei( GL_PACK_ALIGNMENT, packAlignment );
|
||||
return true;
|
||||
}
|
||||
|
||||
Color Renderer::readPixel( int x, int y ) {
|
||||
Uint8 pixel[4];
|
||||
readPixels( x, y, 1, 1, pixel );
|
||||
@@ -1256,7 +1287,6 @@ void Renderer::genVertexArrays( int n, unsigned int* arrays ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void Renderer::waitForIdle() {
|
||||
glFinish();
|
||||
}
|
||||
|
||||
+19
-28
@@ -174,40 +174,31 @@ size_t Base64::decode( size_t in_len, const char* in, size_t out_len, unsigned c
|
||||
size_t Base64::encode( size_t in_len, const unsigned char* in, size_t out_len, char* out ) {
|
||||
static const Uint8 base64enc_tab[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const size_t encodedLen = ( ( in_len + 2 ) / 3 ) * 4;
|
||||
if ( out_len <= encodedLen )
|
||||
return static_cast<size_t>( -1 );
|
||||
|
||||
size_t ii, io;
|
||||
Uint32 v;
|
||||
unsigned rem;
|
||||
|
||||
for ( io = 0, ii = 0, v = 0, rem = 0; ii < in_len; ii++ ) {
|
||||
unsigned char ch;
|
||||
ch = in[ii];
|
||||
v = ( v << 8 ) | ch;
|
||||
rem += 8;
|
||||
while ( rem >= 6 ) {
|
||||
rem -= 6;
|
||||
if ( io >= out_len )
|
||||
return static_cast<size_t>( -1 ); /* truncation is failure */
|
||||
out[io++] = base64enc_tab[( v >> rem ) & 63];
|
||||
}
|
||||
size_t ii = 0;
|
||||
size_t io = 0;
|
||||
const size_t bulkEnd = in_len - in_len % 3;
|
||||
for ( ; ii < bulkEnd; ii += 3 ) {
|
||||
const Uint32 value = ( static_cast<Uint32>( in[ii] ) << 16 ) |
|
||||
( static_cast<Uint32>( in[ii + 1] ) << 8 ) | in[ii + 2];
|
||||
out[io++] = base64enc_tab[value >> 18];
|
||||
out[io++] = base64enc_tab[( value >> 12 ) & 63];
|
||||
out[io++] = base64enc_tab[( value >> 6 ) & 63];
|
||||
out[io++] = base64enc_tab[value & 63];
|
||||
}
|
||||
|
||||
if ( rem ) {
|
||||
v <<= ( 6 - rem );
|
||||
if ( io >= out_len )
|
||||
return static_cast<size_t>( -1 ); /* truncation is failure */
|
||||
out[io++] = base64enc_tab[v & 63];
|
||||
}
|
||||
|
||||
while ( io & 3 ) {
|
||||
if ( io >= out_len )
|
||||
return static_cast<size_t>( -1 ); /* truncation is failure */
|
||||
if ( ii < in_len ) {
|
||||
const Uint32 value = static_cast<Uint32>( in[ii] ) << 16 |
|
||||
( ii + 1 < in_len ? static_cast<Uint32>( in[ii + 1] ) << 8 : 0 );
|
||||
out[io++] = base64enc_tab[value >> 18];
|
||||
out[io++] = base64enc_tab[( value >> 12 ) & 63];
|
||||
out[io++] = ii + 1 < in_len ? base64enc_tab[( value >> 6 ) & 63] : '=';
|
||||
out[io++] = '=';
|
||||
}
|
||||
|
||||
if ( io >= out_len )
|
||||
return static_cast<size_t>( -1 ); /* no room for null terminator */
|
||||
|
||||
out[io] = 0;
|
||||
return io;
|
||||
}
|
||||
|
||||
@@ -1228,6 +1228,14 @@ std::string Sys::getEnv( const std::string& name ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Sys::setEnv( const char* name, const char* value ) {
|
||||
#if EE_PLATFORM == EE_PLATFORM_WIN
|
||||
return 0 == _putenv_s( name, value );
|
||||
#else
|
||||
return 0 == setenv( name, value, 1 );
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::string> Sys::getEnvSplit( const std::string& name ) {
|
||||
return String::split( getEnv( name.c_str() ), PATH_SEP_CHAR );
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <eepp/ui/uiwidget.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/input.hpp>
|
||||
#include <eepp/window/runtime.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
@@ -48,30 +49,37 @@ UIApplication::UIApplication( const WindowSettings& windowSettings, const Settin
|
||||
}
|
||||
|
||||
DisplayManager* displayManager = Engine::instance()->getDisplayManager();
|
||||
displayManager->enableScreenSaver();
|
||||
displayManager->enableMouseFocusClickThrough();
|
||||
displayManager->disableBypassCompositor();
|
||||
const bool offscreen = Runtime::isOffscreen();
|
||||
if ( !offscreen ) {
|
||||
displayManager->enableScreenSaver();
|
||||
displayManager->enableMouseFocusClickThrough();
|
||||
displayManager->disableBypassCompositor();
|
||||
}
|
||||
|
||||
if ( displayManager->getDisplayIndex( 0 ) == nullptr ) {
|
||||
if ( !offscreen && displayManager->getDisplayIndex( 0 ) == nullptr ) {
|
||||
std::cerr << "Display not found, exiting" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
mWindow = Engine::instance()->createWindow( windowSettings, contextSettings );
|
||||
|
||||
if ( !mWindow->isOpen() ) {
|
||||
if ( nullptr == mWindow || !mWindow->isOpen() ) {
|
||||
std::cerr << "Could not create window, exiting" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
mDidRun = true;
|
||||
|
||||
PixelDensity::setPixelDensity(
|
||||
appSettings.pixelDensity && *appSettings.pixelDensity > 0
|
||||
? *appSettings.pixelDensity
|
||||
: eemax( mWindow->getScale(),
|
||||
displayManager->getDisplayIndex( mWindow->getCurrentDisplayIndex() )
|
||||
->getPixelDensity() ) );
|
||||
if ( appSettings.pixelDensity && *appSettings.pixelDensity > 0 ) {
|
||||
PixelDensity::setPixelDensity( *appSettings.pixelDensity );
|
||||
} else if ( offscreen ) {
|
||||
PixelDensity::setPixelDensity( 1.f );
|
||||
} else {
|
||||
PixelDensity::setPixelDensity(
|
||||
eemax( mWindow->getScale(),
|
||||
displayManager->getDisplayIndex( mWindow->getCurrentDisplayIndex() )
|
||||
->getPixelDensity() ) );
|
||||
}
|
||||
|
||||
if ( !appSettings.basePath || appSettings.basePath->empty() ) {
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
@@ -154,10 +162,14 @@ UISceneNode* UIApplication::getUI() const {
|
||||
}
|
||||
|
||||
int UIApplication::run() {
|
||||
// Offscreen SDL windows do not receive an initial expose event. Ensure the first logical
|
||||
// framebuffer is rendered even when the scene was fully laid out before entering the loop.
|
||||
if ( Runtime::isOffscreen() )
|
||||
mUISceneNode->invalidate( nullptr );
|
||||
|
||||
mWindow->runMainLoop( [this]() {
|
||||
mWindow->getInput()->update();
|
||||
SceneManager::instance()->update();
|
||||
|
||||
if ( mUISceneNode->invalidated() ) {
|
||||
mWindow->clear();
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ void InputSDL::update() {
|
||||
if ( mEventsSentId == std::numeric_limits<Uint64>::max() )
|
||||
mEventsSentId = 0;
|
||||
|
||||
drainQueuedEvents();
|
||||
|
||||
if ( !mQueuedEvents.empty() ) {
|
||||
for ( const auto& prevEvent : mQueuedEvents )
|
||||
sendEvent( prevEvent );
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <eepp/window/backend/SDL2/wminfo.hpp>
|
||||
#include <eepp/window/backend/backendhelper.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/runtime.hpp>
|
||||
#include <eepp/window/terminal/terminalruntime.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@@ -45,6 +47,7 @@ WindowSDL::WindowSDL( WindowSettings Settings, ContextSettings Context ) :
|
||||
}
|
||||
|
||||
WindowSDL::~WindowSDL() {
|
||||
shutdownRuntimeRenderTarget();
|
||||
destroySDLResources();
|
||||
}
|
||||
|
||||
@@ -275,6 +278,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
|
||||
|
||||
setup2D( false );
|
||||
|
||||
if ( !initializeRuntimeRenderTarget() )
|
||||
return false;
|
||||
|
||||
mWindow.Created = true;
|
||||
|
||||
if ( "" != mWindow.WindowConfig.Icon ) {
|
||||
@@ -411,25 +417,35 @@ void WindowSDL::setTitle( const std::string& title ) {
|
||||
}
|
||||
|
||||
bool WindowSDL::isActive() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
Uint32 flags = SDL_GetWindowFlags( mSDLWindow );
|
||||
return 0 != ( ( flags & SDL_WINDOW_INPUT_FOCUS ) && ( flags & SDL_WINDOW_MOUSE_FOCUS ) );
|
||||
}
|
||||
|
||||
bool WindowSDL::isVisible() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return true;
|
||||
Uint32 flags = SDL_GetWindowFlags( mSDLWindow );
|
||||
return 0 != ( ( flags & SDL_WINDOW_SHOWN ) && !( flags & SDL_WINDOW_MINIMIZED ) );
|
||||
}
|
||||
|
||||
bool WindowSDL::hasFocus() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
return 0 != ( SDL_GetWindowFlags( mSDLWindow ) &
|
||||
( SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS ) );
|
||||
}
|
||||
|
||||
bool WindowSDL::hasInputFocus() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
return 0 != ( SDL_GetWindowFlags( mSDLWindow ) & ( SDL_WINDOW_INPUT_FOCUS ) );
|
||||
}
|
||||
|
||||
bool WindowSDL::hasMouseFocus() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
return 0 != ( SDL_GetWindowFlags( mSDLWindow ) & ( SDL_WINDOW_MOUSE_FOCUS ) );
|
||||
}
|
||||
|
||||
@@ -447,6 +463,7 @@ void WindowSDL::onWindowResize( Uint32 width, Uint32 height ) {
|
||||
mLastWindowedSize = Sizei( width, height );
|
||||
|
||||
mDefaultView.reset( Rectf( 0, 0, mWindow.WindowConfig.Width, mWindow.WindowConfig.Height ) );
|
||||
resizeRuntimeRenderTarget( width, height );
|
||||
|
||||
setup2D( false );
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ void InputSDL::update() {
|
||||
if ( mEventsSentId == std::numeric_limits<Uint64>::max() )
|
||||
mEventsSentId = 0;
|
||||
|
||||
drainQueuedEvents();
|
||||
|
||||
if ( !mQueuedEvents.empty() ) {
|
||||
for ( const auto& prevEvent : mQueuedEvents )
|
||||
sendEvent( prevEvent );
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <eepp/window/backend/SDL3/wminfo.hpp>
|
||||
#include <eepp/window/backend/backendhelper.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/runtime.hpp>
|
||||
#include <eepp/window/terminal/terminalruntime.hpp>
|
||||
|
||||
#if EE_PLATFORM == EE_PLATFORM_WIN
|
||||
#include <eepp/window/backend/SDL3/displaymanagersdl3.hpp>
|
||||
@@ -36,6 +38,7 @@ WindowSDL::WindowSDL( WindowSettings Settings, ContextSettings Context ) :
|
||||
}
|
||||
|
||||
WindowSDL::~WindowSDL() {
|
||||
shutdownRuntimeRenderTarget();
|
||||
destroySDLResources();
|
||||
}
|
||||
|
||||
@@ -252,6 +255,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
|
||||
|
||||
setup2D( false );
|
||||
|
||||
if ( !initializeRuntimeRenderTarget() )
|
||||
return false;
|
||||
|
||||
mWindow.Created = true;
|
||||
|
||||
if ( "" != mWindow.WindowConfig.Icon ) {
|
||||
@@ -382,26 +388,36 @@ void WindowSDL::setTitle( const std::string& title ) {
|
||||
}
|
||||
|
||||
bool WindowSDL::isActive() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
Uint64 flags = SDL_GetWindowFlags( mSDLWindow );
|
||||
return 0 != ( ( flags & SDL_WINDOW_INPUT_FOCUS ) && ( flags & SDL_WINDOW_MOUSE_FOCUS ) );
|
||||
}
|
||||
|
||||
bool WindowSDL::isVisible() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return true;
|
||||
Uint64 flags = SDL_GetWindowFlags( mSDLWindow );
|
||||
return 0 != ( !( flags & SDL_WINDOW_HIDDEN ) && !( flags & SDL_WINDOW_MINIMIZED ) );
|
||||
}
|
||||
|
||||
bool WindowSDL::hasFocus() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
Uint64 flags = SDL_GetWindowFlags( mSDLWindow );
|
||||
return 0 != ( flags & ( SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS ) );
|
||||
}
|
||||
|
||||
bool WindowSDL::hasInputFocus() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
Uint64 flags = SDL_GetWindowFlags( mSDLWindow );
|
||||
return 0 != ( flags & SDL_WINDOW_INPUT_FOCUS );
|
||||
}
|
||||
|
||||
bool WindowSDL::hasMouseFocus() const {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
return TerminalRuntime::instance().isFocused();
|
||||
Uint64 flags = SDL_GetWindowFlags( mSDLWindow );
|
||||
return 0 != ( flags & SDL_WINDOW_MOUSE_FOCUS );
|
||||
}
|
||||
@@ -420,6 +436,7 @@ void WindowSDL::onWindowResize( Uint32 width, Uint32 height ) {
|
||||
mLastWindowedSize = Sizei( width, height );
|
||||
|
||||
mDefaultView.reset( Rectf( 0, 0, mWindow.WindowConfig.Width, mWindow.WindowConfig.Height ) );
|
||||
resizeRuntimeRenderTarget( width, height );
|
||||
|
||||
setup2D( false );
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
#include <eepp/window/backend/SDL3/platformhelpersdl3.hpp>
|
||||
#endif
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/runtime.hpp>
|
||||
#include <eepp/window/terminal/terminalruntime.hpp>
|
||||
|
||||
#if EE_PLATFORM == EE_PLATFORM_ANDROID
|
||||
#include <eepp/system/zip.hpp>
|
||||
@@ -52,6 +54,17 @@ using namespace EE::Graphics;
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
namespace {
|
||||
|
||||
void configureRuntimeVideoDriver() {
|
||||
if ( !Runtime::isOffscreen() )
|
||||
return;
|
||||
Sys::setEnv( "SDL_VIDEODRIVER", "offscreen" );
|
||||
Sys::setEnv( "SDL_VIDEO_DRIVER", "offscreen" );
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static UintPtr sMainThreadId{ 0 };
|
||||
|
||||
SINGLETON_DECLARE_IMPLEMENTATION( Engine )
|
||||
@@ -65,6 +78,7 @@ Engine::Engine() :
|
||||
mDisplayManager( NULL ),
|
||||
mGlobalResourceCatalog( ResourceCatalog::New() ),
|
||||
mDefaultResourceScope( ResourceScope::New() ) {
|
||||
configureRuntimeVideoDriver();
|
||||
mDefaultResourceScope->importCatalog( mGlobalResourceCatalog );
|
||||
#if EE_PLATFORM == EE_PLATFORM_ANDROID
|
||||
mZip = Zip::New();
|
||||
@@ -104,6 +118,9 @@ Engine::~Engine() {
|
||||
|
||||
Doc::SyntaxDefinitionManager::destroySingleton();
|
||||
|
||||
for ( auto& window : mWindows )
|
||||
window.second->shutdownRuntimeRenderTarget();
|
||||
|
||||
Graphics::Private::FrameBufferRegistry::destroySingleton();
|
||||
|
||||
Graphics::Private::VertexBufferRegistry::destroySingleton();
|
||||
@@ -228,6 +245,20 @@ EE::Window::Window* Engine::createDefaultWindow( const WindowSettings& Settings,
|
||||
}
|
||||
|
||||
EE::Window::Window* Engine::createWindow( WindowSettings Settings, ContextSettings Context ) {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal && !mWindows.empty() ) {
|
||||
Log::error( "Terminal runtime currently supports one top-level Window" );
|
||||
return nullptr;
|
||||
}
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal ) {
|
||||
TerminalRuntime& terminal = TerminalRuntime::instance();
|
||||
if ( !terminal.initialize() )
|
||||
return nullptr;
|
||||
const Sizei terminalSize = terminal.pixelSize();
|
||||
if ( terminalSize.x > 0 && terminalSize.y > 0 ) {
|
||||
Settings.Width = terminalSize.x;
|
||||
Settings.Height = terminalSize.y;
|
||||
}
|
||||
}
|
||||
EE::Window::Window* window = NULL;
|
||||
|
||||
if ( NULL != mWindow ) {
|
||||
@@ -246,6 +277,11 @@ EE::Window::Window* Engine::createWindow( WindowSettings Settings, ContextSettin
|
||||
if ( NULL == window ) {
|
||||
window = createDefaultWindow( Settings, Context );
|
||||
}
|
||||
if ( NULL == window ) {
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal )
|
||||
TerminalRuntime::instance().shutdown();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
setCurrentWindow( window );
|
||||
|
||||
|
||||
@@ -229,6 +229,31 @@ void Input::processEvent( InputEvent* Event ) {
|
||||
sendEvent( Event );
|
||||
}
|
||||
|
||||
bool Input::enqueueEvent( InputEvent event ) {
|
||||
static constexpr size_t MaxInjectedEvents = 4096;
|
||||
std::lock_guard<std::mutex> lock( mInjectedEventsMutex );
|
||||
if ( mInjectedEvents.size() >= MaxInjectedEvents ) {
|
||||
auto motion = std::find_if(
|
||||
mInjectedEvents.begin(), mInjectedEvents.end(),
|
||||
[]( const InputEvent& queued ) { return queued.Type == InputEvent::MouseMotion; } );
|
||||
if ( motion == mInjectedEvents.end() )
|
||||
return false;
|
||||
mInjectedEvents.erase( motion );
|
||||
}
|
||||
mInjectedEvents.emplace_back( std::move( event ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
void Input::drainQueuedEvents() {
|
||||
std::deque<InputEvent> events;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( mInjectedEventsMutex );
|
||||
events.swap( mInjectedEvents );
|
||||
}
|
||||
for ( InputEvent& event : events )
|
||||
processEvent( &event );
|
||||
}
|
||||
|
||||
InputFinger* Input::getFingerId( const Int64& fingerId ) {
|
||||
Uint32 i;
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include <eepp/system/base64.hpp>
|
||||
#include <eepp/window/terminal/kittyframepresenter.hpp>
|
||||
#include <eepp/window/terminal/terminalruntime.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
using namespace EE::System;
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
KittyFramePresenter::~KittyFramePresenter() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( mMutex );
|
||||
mRunning = false;
|
||||
}
|
||||
mCondition.notify_one();
|
||||
if ( mWorker.joinable() )
|
||||
mWorker.join();
|
||||
}
|
||||
|
||||
bool KittyFramePresenter::initialize( Window& window ) {
|
||||
if ( !TerminalRuntime::instance().initialize() )
|
||||
return false;
|
||||
TerminalRuntime::instance().attach( window );
|
||||
mRunning = true;
|
||||
mWorker = std::thread( &KittyFramePresenter::run, this );
|
||||
return true;
|
||||
}
|
||||
|
||||
void KittyFramePresenter::present( Window& window ) {
|
||||
Frame frame;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( mMutex );
|
||||
frame = std::move( mRecycle );
|
||||
}
|
||||
frame.size = window.getSize();
|
||||
frame.pixels.resize( static_cast<size_t>( frame.size.x ) * frame.size.y * 3 );
|
||||
FrameReadback readback{ frame.pixels.data(), frame.size,
|
||||
static_cast<size_t>( frame.size.x ) * 3 };
|
||||
if ( !window.readFrameBuffer( readback ) )
|
||||
return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( mMutex );
|
||||
if ( mHasPending )
|
||||
mRecycle = std::move( mPending );
|
||||
mPending = std::move( frame );
|
||||
mHasPending = true;
|
||||
}
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
void KittyFramePresenter::shutdown( Window& ) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( mMutex );
|
||||
mRunning = false;
|
||||
}
|
||||
mCondition.notify_one();
|
||||
if ( mWorker.joinable() )
|
||||
mWorker.join();
|
||||
TerminalRuntime::instance().detach();
|
||||
TerminalRuntime::instance().shutdown();
|
||||
}
|
||||
|
||||
void KittyFramePresenter::run() {
|
||||
for ( ;; ) {
|
||||
Frame frame;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock( mMutex );
|
||||
mCondition.wait( lock, [this] { return !mRunning || mHasPending; } );
|
||||
if ( !mRunning && !mHasPending )
|
||||
return;
|
||||
frame = std::move( mPending );
|
||||
mHasPending = false;
|
||||
}
|
||||
sendFrame( frame );
|
||||
std::lock_guard<std::mutex> lock( mMutex );
|
||||
mRecycle = std::move( frame );
|
||||
}
|
||||
}
|
||||
|
||||
void KittyFramePresenter::sendFrame( const Frame& frame ) {
|
||||
std::array<unsigned char, 3072> binary;
|
||||
std::array<char, 4097> encoded;
|
||||
std::array<char, 128 + 4096 + 2> packet;
|
||||
const size_t rowBytes = static_cast<size_t>( frame.size.x ) * 3;
|
||||
// glReadPixels returns bottom-left-origin rows while Kitty consumes top-left-origin pixels.
|
||||
// Reverse row traversal during streaming so no full-frame flip buffer is needed.
|
||||
Int32 row = frame.size.y - 1;
|
||||
size_t column = 0;
|
||||
bool first = true;
|
||||
while ( row >= 0 ) {
|
||||
size_t count = 0;
|
||||
while ( count < binary.size() && row >= 0 ) {
|
||||
const size_t copy = eemin( binary.size() - count, rowBytes - column );
|
||||
std::memcpy( binary.data() + count,
|
||||
frame.pixels.data() + static_cast<size_t>( row ) * rowBytes + column,
|
||||
copy );
|
||||
count += copy;
|
||||
column += copy;
|
||||
if ( column == rowBytes ) {
|
||||
column = 0;
|
||||
--row;
|
||||
}
|
||||
}
|
||||
const size_t encodedSize =
|
||||
Base64::encode( count, binary.data(), encoded.size(), encoded.data() );
|
||||
char header[128];
|
||||
const int headerSize =
|
||||
first
|
||||
? std::snprintf( header, sizeof( header ), "\033_Gf=24,t=d,a=T,C=1,s=%d,v=%d,m=%d;",
|
||||
frame.size.x, frame.size.y, row >= 0 ? 1 : 0 )
|
||||
: std::snprintf( header, sizeof( header ), "\033_Gm=%d;", row >= 0 ? 1 : 0 );
|
||||
if ( encodedSize == static_cast<size_t>( -1 ) || headerSize <= 0 ||
|
||||
static_cast<size_t>( headerSize ) >= sizeof( header ) )
|
||||
return;
|
||||
std::memcpy( packet.data(), header, static_cast<size_t>( headerSize ) );
|
||||
std::memcpy( packet.data() + headerSize, encoded.data(), encodedSize );
|
||||
packet[static_cast<size_t>( headerSize ) + encodedSize] = '\033';
|
||||
packet[static_cast<size_t>( headerSize ) + encodedSize + 1] = '\\';
|
||||
if ( !TerminalRuntime::instance().write( packet.data(), static_cast<size_t>( headerSize ) +
|
||||
encodedSize + 2 ) )
|
||||
return;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}} // namespace EE::Window
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <eepp/window/runtime.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
namespace {
|
||||
|
||||
RuntimeMode resolveRuntimeMode() {
|
||||
const char* value = std::getenv( "EEPP_RUNTIME" );
|
||||
if ( nullptr == value )
|
||||
return RuntimeMode::Native;
|
||||
if ( 0 == std::strcmp( value, "headless" ) )
|
||||
return RuntimeMode::Headless;
|
||||
if ( 0 == std::strcmp( value, "terminal" ) )
|
||||
return RuntimeMode::Terminal;
|
||||
return RuntimeMode::Native;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RuntimeMode Runtime::mode() {
|
||||
static const RuntimeMode runtimeMode = resolveRuntimeMode();
|
||||
return runtimeMode;
|
||||
}
|
||||
|
||||
bool Runtime::isOffscreen() {
|
||||
return mode() != RuntimeMode::Native;
|
||||
}
|
||||
|
||||
const char* Runtime::modeName() {
|
||||
switch ( mode() ) {
|
||||
case RuntimeMode::Headless:
|
||||
return "headless";
|
||||
case RuntimeMode::Terminal:
|
||||
return "terminal";
|
||||
case RuntimeMode::Native:
|
||||
default:
|
||||
return "native";
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace EE::Window
|
||||
@@ -0,0 +1,436 @@
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <eepp/system/log.hpp>
|
||||
#include <eepp/window/input.hpp>
|
||||
#include <eepp/window/terminal/terminalruntime.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
#include <string>
|
||||
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#elif EE_PLATFORM == EE_PLATFORM_WIN
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
struct TerminalRuntime::State {
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
termios termiosState{};
|
||||
bool hasTermios{ false };
|
||||
#elif EE_PLATFORM == EE_PLATFORM_WIN
|
||||
HANDLE input{ INVALID_HANDLE_VALUE };
|
||||
HANDLE output{ INVALID_HANDLE_VALUE };
|
||||
DWORD inputMode{ 0 };
|
||||
DWORD outputMode{ 0 };
|
||||
bool hasInputMode{ false };
|
||||
bool hasOutputMode{ false };
|
||||
#endif
|
||||
};
|
||||
|
||||
TerminalRuntime& TerminalRuntime::instance() {
|
||||
static TerminalRuntime runtime;
|
||||
return runtime;
|
||||
}
|
||||
|
||||
TerminalRuntime::~TerminalRuntime() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool TerminalRuntime::initialize() {
|
||||
if ( mState )
|
||||
return true;
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
mFd = ::open( "/dev/tty", O_RDWR | O_CLOEXEC );
|
||||
if ( mFd < 0 ) {
|
||||
Log::error( "Terminal runtime could not open /dev/tty" );
|
||||
return false;
|
||||
}
|
||||
mState = new State;
|
||||
mState->hasTermios = 0 == tcgetattr( mFd, &mState->termiosState );
|
||||
if ( mState->hasTermios ) {
|
||||
termios raw = mState->termiosState;
|
||||
cfmakeraw( &raw );
|
||||
tcsetattr( mFd, TCSAFLUSH, &raw );
|
||||
}
|
||||
static constexpr char enter[] = "\033[?1049h\033[?25l\033[>31u\033[?1003h\033[?1006h"
|
||||
"\033[?1016h\033[?1004h\033[H";
|
||||
if ( write( enter, sizeof( enter ) - 1 ) )
|
||||
return true;
|
||||
if ( mState->hasTermios )
|
||||
tcsetattr( mFd, TCSAFLUSH, &mState->termiosState );
|
||||
::close( mFd );
|
||||
mFd = -1;
|
||||
delete mState;
|
||||
mState = nullptr;
|
||||
return false;
|
||||
#elif EE_PLATFORM == EE_PLATFORM_WIN
|
||||
mState = new State;
|
||||
mState->input = GetStdHandle( STD_INPUT_HANDLE );
|
||||
mState->output = GetStdHandle( STD_OUTPUT_HANDLE );
|
||||
if ( !mState->input || mState->input == INVALID_HANDLE_VALUE || !mState->output ||
|
||||
mState->output == INVALID_HANDLE_VALUE ) {
|
||||
Log::error( "Terminal runtime could not open the Windows standard handles" );
|
||||
delete mState;
|
||||
mState = nullptr;
|
||||
return false;
|
||||
}
|
||||
mState->hasInputMode = GetConsoleMode( mState->input, &mState->inputMode );
|
||||
mState->hasOutputMode = GetConsoleMode( mState->output, &mState->outputMode );
|
||||
if ( mState->hasInputMode ) {
|
||||
const DWORD mode = ( mState->inputMode | ENABLE_VIRTUAL_TERMINAL_INPUT ) &
|
||||
~( ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT );
|
||||
SetConsoleMode( mState->input, mode );
|
||||
}
|
||||
if ( mState->hasOutputMode ) {
|
||||
const DWORD mode =
|
||||
mState->outputMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
|
||||
SetConsoleMode( mState->output, mode );
|
||||
}
|
||||
static constexpr char enter[] = "\033[?1049h\033[?25l\033[>31u\033[?1003h\033[?1006h"
|
||||
"\033[?1016h\033[?1004h\033[H";
|
||||
if ( write( enter, sizeof( enter ) - 1 ) )
|
||||
return true;
|
||||
if ( mState->hasInputMode )
|
||||
SetConsoleMode( mState->input, mState->inputMode );
|
||||
if ( mState->hasOutputMode )
|
||||
SetConsoleMode( mState->output, mState->outputMode );
|
||||
delete mState;
|
||||
mState = nullptr;
|
||||
return false;
|
||||
#else
|
||||
Log::error( "Terminal runtime is not implemented on this platform" );
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void TerminalRuntime::shutdown() {
|
||||
detach();
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
if ( mFd < 0 )
|
||||
return;
|
||||
static constexpr char leave[] = "\033[?1004l\033[?1016l\033[?1006l\033[?1003l\033[<u"
|
||||
"\033[?25h\033[?1049l";
|
||||
write( leave, sizeof( leave ) - 1 );
|
||||
if ( mState && mState->hasTermios )
|
||||
tcsetattr( mFd, TCSAFLUSH, &mState->termiosState );
|
||||
::close( mFd );
|
||||
mFd = -1;
|
||||
delete mState;
|
||||
mState = nullptr;
|
||||
#elif EE_PLATFORM == EE_PLATFORM_WIN
|
||||
if ( !mState )
|
||||
return;
|
||||
static constexpr char leave[] = "\033[?1004l\033[?1016l\033[?1006l\033[?1003l\033[<u"
|
||||
"\033[?25h\033[?1049l";
|
||||
write( leave, sizeof( leave ) - 1 );
|
||||
if ( mState->hasInputMode )
|
||||
SetConsoleMode( mState->input, mState->inputMode );
|
||||
if ( mState->hasOutputMode )
|
||||
SetConsoleMode( mState->output, mState->outputMode );
|
||||
delete mState;
|
||||
mState = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TerminalRuntime::write( const char* data, size_t size ) {
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
std::lock_guard<std::mutex> lock( mWriteMutex );
|
||||
while ( size ) {
|
||||
const ssize_t written = ::write( mFd, data, size );
|
||||
if ( written < 0 && errno == EINTR )
|
||||
continue;
|
||||
if ( written <= 0 )
|
||||
return false;
|
||||
data += written;
|
||||
size -= static_cast<size_t>( written );
|
||||
}
|
||||
return true;
|
||||
#elif EE_PLATFORM == EE_PLATFORM_WIN
|
||||
std::lock_guard<std::mutex> lock( mWriteMutex );
|
||||
while ( size ) {
|
||||
DWORD written = 0;
|
||||
const DWORD chunk = static_cast<DWORD>( eemin<size_t>( size, MAXDWORD ) );
|
||||
if ( !WriteFile( mState->output, data, chunk, &written, nullptr ) || written == 0 )
|
||||
return false;
|
||||
data += written;
|
||||
size -= written;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
Math::Sizei TerminalRuntime::pixelSize() const {
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
winsize size{};
|
||||
if ( mFd >= 0 && 0 == ioctl( mFd, TIOCGWINSZ, &size ) && size.ws_xpixel && size.ws_ypixel )
|
||||
return { size.ws_xpixel, size.ws_ypixel };
|
||||
#elif EE_PLATFORM == EE_PLATFORM_WIN
|
||||
if ( mState ) {
|
||||
CONSOLE_SCREEN_BUFFER_INFO info{};
|
||||
CONSOLE_FONT_INFO font{};
|
||||
if ( GetConsoleScreenBufferInfo( mState->output, &info ) &&
|
||||
GetCurrentConsoleFont( mState->output, FALSE, &font ) ) {
|
||||
const Int32 columns = info.srWindow.Right - info.srWindow.Left + 1;
|
||||
const Int32 rows = info.srWindow.Bottom - info.srWindow.Top + 1;
|
||||
return { columns * font.dwFontSize.X, rows * font.dwFontSize.Y };
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
void TerminalRuntime::attach( Window& window ) {
|
||||
if ( mReading.exchange( true ) )
|
||||
return;
|
||||
mWindow = &window;
|
||||
mInputThread = std::thread( &TerminalRuntime::readInput, this );
|
||||
}
|
||||
|
||||
void TerminalRuntime::detach() {
|
||||
if ( !mReading.exchange( false ) )
|
||||
return;
|
||||
if ( mInputThread.joinable() )
|
||||
mInputThread.join();
|
||||
mWindow = nullptr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
Uint32 decodeModifiers( unsigned int value ) {
|
||||
const unsigned int bits = value ? value - 1 : 0;
|
||||
return ( bits & 1 ? KEYMOD_SHIFT : 0 ) | ( bits & 2 ? KEYMOD_ALT : 0 ) |
|
||||
( bits & 4 ? KEYMOD_CTRL : 0 ) | ( bits & 8 ? KEYMOD_META : 0 );
|
||||
}
|
||||
|
||||
Scancode decodeScancode( Uint32 code ) {
|
||||
if ( code >= 'a' && code <= 'z' )
|
||||
return static_cast<Scancode>( SCANCODE_A + code - 'a' );
|
||||
if ( code >= 'A' && code <= 'Z' )
|
||||
return static_cast<Scancode>( SCANCODE_A + code - 'A' );
|
||||
if ( code >= '1' && code <= '9' )
|
||||
return static_cast<Scancode>( SCANCODE_1 + code - '1' );
|
||||
if ( code == '0' )
|
||||
return SCANCODE_0;
|
||||
if ( code == static_cast<Uint32>( KEY_UP ) )
|
||||
return SCANCODE_UP;
|
||||
if ( code == static_cast<Uint32>( KEY_DOWN ) )
|
||||
return SCANCODE_DOWN;
|
||||
if ( code == static_cast<Uint32>( KEY_LEFT ) )
|
||||
return SCANCODE_LEFT;
|
||||
if ( code == static_cast<Uint32>( KEY_RIGHT ) )
|
||||
return SCANCODE_RIGHT;
|
||||
if ( code == static_cast<Uint32>( KEY_HOME ) )
|
||||
return SCANCODE_HOME;
|
||||
if ( code == static_cast<Uint32>( KEY_END ) )
|
||||
return SCANCODE_END;
|
||||
if ( code == static_cast<Uint32>( KEY_PAGEUP ) )
|
||||
return SCANCODE_PAGEUP;
|
||||
if ( code == static_cast<Uint32>( KEY_PAGEDOWN ) )
|
||||
return SCANCODE_PAGEDOWN;
|
||||
if ( code == static_cast<Uint32>( KEY_INSERT ) )
|
||||
return SCANCODE_INSERT;
|
||||
if ( code == static_cast<Uint32>( KEY_DELETE ) )
|
||||
return SCANCODE_DELETE;
|
||||
if ( code >= static_cast<Uint32>( KEY_F1 ) && code <= static_cast<Uint32>( KEY_F12 ) )
|
||||
return static_cast<Scancode>( SCANCODE_F1 + code - static_cast<Uint32>( KEY_F1 ) );
|
||||
switch ( code ) {
|
||||
case 9:
|
||||
return SCANCODE_TAB;
|
||||
case 13:
|
||||
return SCANCODE_RETURN;
|
||||
case 27:
|
||||
return SCANCODE_ESCAPE;
|
||||
case 32:
|
||||
return SCANCODE_SPACE;
|
||||
case 127:
|
||||
return SCANCODE_BACKSPACE;
|
||||
default:
|
||||
return SCANCODE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueueKey( Window& window, Uint32 code, Uint32 mods, Uint32 kind, Uint32 text ) {
|
||||
InputEvent event( kind == 3 ? InputEvent::KeyUp : InputEvent::KeyDown );
|
||||
event.key = { 0,
|
||||
static_cast<Uint8>( kind == 3 ? 0 : 1 ),
|
||||
static_cast<Uint8>( kind == 2 ),
|
||||
{ decodeScancode( code ), static_cast<Keycode>( code ), mods, text } };
|
||||
window.getInput()->enqueueEvent( event );
|
||||
if ( text && kind != 3 ) {
|
||||
InputEvent input( InputEvent::TextInput );
|
||||
input.text = { 0, static_cast<String::StringBaseType>( text ) };
|
||||
window.getInput()->enqueueEvent( input );
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 navigationCode( char final, unsigned int parameter ) {
|
||||
if ( parameter == 1 && final >= 'P' && final <= 'S' )
|
||||
return KEY_F1 + final - 'P';
|
||||
if ( final == 'A' )
|
||||
return KEY_UP;
|
||||
if ( final == 'B' )
|
||||
return KEY_DOWN;
|
||||
if ( final == 'C' )
|
||||
return KEY_RIGHT;
|
||||
if ( final == 'D' )
|
||||
return KEY_LEFT;
|
||||
if ( final == 'H' )
|
||||
return KEY_HOME;
|
||||
if ( final == 'F' )
|
||||
return KEY_END;
|
||||
if ( final == '~' ) {
|
||||
if ( parameter == 2 )
|
||||
return KEY_INSERT;
|
||||
if ( parameter == 3 )
|
||||
return KEY_DELETE;
|
||||
if ( parameter == 5 )
|
||||
return KEY_PAGEUP;
|
||||
if ( parameter == 6 )
|
||||
return KEY_PAGEDOWN;
|
||||
switch ( parameter ) {
|
||||
case 15:
|
||||
return KEY_F5;
|
||||
case 17:
|
||||
return KEY_F6;
|
||||
case 18:
|
||||
return KEY_F7;
|
||||
case 19:
|
||||
return KEY_F8;
|
||||
case 20:
|
||||
return KEY_F9;
|
||||
case 21:
|
||||
return KEY_F10;
|
||||
case 23:
|
||||
return KEY_F11;
|
||||
case 24:
|
||||
return KEY_F12;
|
||||
}
|
||||
}
|
||||
return KEY_UNKNOWN;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void TerminalRuntime::readInput() {
|
||||
std::string pending;
|
||||
pending.reserve( 256 );
|
||||
Math::Sizei lastSize = pixelSize();
|
||||
Vector2i lastMouse;
|
||||
while ( mReading.load( std::memory_order_relaxed ) ) {
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
pollfd descriptor{ mFd, POLLIN, 0 };
|
||||
if ( ::poll( &descriptor, 1, 50 ) > 0 && descriptor.revents & POLLIN ) {
|
||||
char data[256];
|
||||
const ssize_t count = ::read( mFd, data, sizeof( data ) );
|
||||
if ( count > 0 )
|
||||
pending.append( data, static_cast<size_t>( count ) );
|
||||
}
|
||||
#elif EE_PLATFORM == EE_PLATFORM_WIN
|
||||
if ( WAIT_OBJECT_0 == WaitForSingleObject( mState->input, 50 ) ) {
|
||||
char data[256];
|
||||
DWORD count = 0;
|
||||
if ( ReadFile( mState->input, data, sizeof( data ), &count, nullptr ) && count > 0 )
|
||||
pending.append( data, count );
|
||||
}
|
||||
#endif
|
||||
while ( pending.size() >= 3 && pending[0] == '\033' && pending[1] == '[' ) {
|
||||
size_t end = 2;
|
||||
while ( end < pending.size() && !( pending[end] >= '@' && pending[end] <= '~' ) )
|
||||
++end;
|
||||
if ( end == pending.size() )
|
||||
break;
|
||||
const char final = pending[end];
|
||||
const std::string params = pending.substr( 2, end - 2 );
|
||||
if ( params.empty() && ( final == 'I' || final == 'O' ) ) {
|
||||
mFocused.store( final == 'I', std::memory_order_relaxed );
|
||||
InputEvent event( InputEvent::Window );
|
||||
event.window = { static_cast<Uint8>( final == 'I' ),
|
||||
static_cast<Uint8>( final == 'I'
|
||||
? InputEvent::WindowKeyboardFocusGain
|
||||
: InputEvent::WindowKeyboardFocusLost ) };
|
||||
mWindow->getInput()->enqueueEvent( event );
|
||||
} else if ( !params.empty() && params[0] == '<' && ( final == 'M' || final == 'm' ) ) {
|
||||
unsigned int button, x, y;
|
||||
if ( 3 == std::sscanf( params.c_str(), "<%u;%u;%u", &button, &x, &y ) ) {
|
||||
InputEvent event;
|
||||
if ( button & 64 ) {
|
||||
const unsigned int wheelCode = button & 3;
|
||||
const Uint8 wheelButton = wheelCode == 0 ? EE_BUTTON_WHEELUP
|
||||
: wheelCode == 1 ? EE_BUTTON_WHEELDOWN
|
||||
: wheelCode == 2 ? EE_BUTTON_WHEELLEFT
|
||||
: EE_BUTTON_WHEELRIGHT;
|
||||
event.Type = InputEvent::MouseButtonDown;
|
||||
event.button = { 0, wheelButton, 1, static_cast<Int16>( x - 1 ),
|
||||
static_cast<Int16>( y - 1 ) };
|
||||
mWindow->getInput()->enqueueEvent( event );
|
||||
event.Type = InputEvent::MouseButtonUp;
|
||||
event.button.state = 0;
|
||||
mWindow->getInput()->enqueueEvent( event );
|
||||
event.Type = InputEvent::MouseWheel;
|
||||
event.wheel = { 0,
|
||||
wheelCode == 2 ? -1.f
|
||||
: wheelCode == 3 ? 1.f
|
||||
: 0.f,
|
||||
wheelCode == 0 ? 1.f
|
||||
: wheelCode == 1 ? -1.f
|
||||
: 0.f,
|
||||
InputEvent::WheelEvent::Normal };
|
||||
} else if ( button & 32 ) {
|
||||
event.Type = InputEvent::MouseMotion;
|
||||
event.motion = { 0,
|
||||
0,
|
||||
static_cast<Int16>( x - 1 ),
|
||||
static_cast<Int16>( y - 1 ),
|
||||
static_cast<Int16>( x - 1 - lastMouse.x ),
|
||||
static_cast<Int16>( y - 1 - lastMouse.y ) };
|
||||
lastMouse = { static_cast<Int32>( x - 1 ), static_cast<Int32>( y - 1 ) };
|
||||
} else {
|
||||
event.Type =
|
||||
final == 'm' ? InputEvent::MouseButtonUp : InputEvent::MouseButtonDown;
|
||||
event.button = { 0, static_cast<Uint8>( ( button & 3 ) + 1 ),
|
||||
static_cast<Uint8>( final != 'm' ),
|
||||
static_cast<Int16>( x - 1 ), static_cast<Int16>( y - 1 ) };
|
||||
}
|
||||
mWindow->getInput()->enqueueEvent( event );
|
||||
}
|
||||
} else if ( final == 'u' ) {
|
||||
unsigned int code = 0, mods = 1, kind = 1, text = 0;
|
||||
const size_t separator = params.find( ';' );
|
||||
if ( separator != std::string::npos ) {
|
||||
code = static_cast<unsigned int>( std::strtoul( params.c_str(), nullptr, 10 ) );
|
||||
std::sscanf( params.c_str() + separator + 1, "%u:%u;%u", &mods, &kind, &text );
|
||||
enqueueKey( *mWindow, code, decodeModifiers( mods ), kind, text );
|
||||
}
|
||||
} else if ( final == 'A' || final == 'B' || final == 'C' || final == 'D' ||
|
||||
final == 'H' || final == 'F' || final == 'P' || final == 'Q' ||
|
||||
final == 'R' || final == 'S' || final == '~' ) {
|
||||
unsigned int parameter = 1, mods = 1, kind = 1;
|
||||
std::sscanf( params.c_str(), "%u;%u:%u", ¶meter, &mods, &kind );
|
||||
const Uint32 code = navigationCode( final, parameter );
|
||||
if ( code != KEY_UNKNOWN )
|
||||
enqueueKey( *mWindow, code, decodeModifiers( mods ), kind, 0 );
|
||||
}
|
||||
pending.erase( 0, end + 1 );
|
||||
}
|
||||
if ( !pending.empty() && pending[0] != '\033' )
|
||||
pending.erase( 0, 1 );
|
||||
const Math::Sizei size = pixelSize();
|
||||
if ( size.x > 0 && size.y > 0 && size != lastSize ) {
|
||||
lastSize = size;
|
||||
InputEvent event( InputEvent::VideoResize );
|
||||
event.resize = { size.x, size.y };
|
||||
mWindow->getInput()->enqueueEvent( event );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace EE::Window
|
||||
+83
-33
@@ -1,4 +1,5 @@
|
||||
#include <SOIL2/src/SOIL2/SOIL2.h>
|
||||
#include <eepp/graphics/framebuffer.hpp>
|
||||
#include <eepp/graphics/globalbatchrenderer.hpp>
|
||||
#include <eepp/graphics/renderer/openglext.hpp>
|
||||
#include <eepp/graphics/renderer/renderer.hpp>
|
||||
@@ -9,23 +10,10 @@
|
||||
#include <eepp/window/cursormanager.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/input.hpp>
|
||||
#include <eepp/window/runtime.hpp>
|
||||
#include <eepp/window/terminal/kittyframepresenter.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
#ifdef EE_GLES1_LATE_INCLUDE
|
||||
#if EE_PLATFORM == EE_PLATFORM_IOS
|
||||
#include <OpenGLES/ES1/gl.h>
|
||||
#include <OpenGLES/ES1/glext.h>
|
||||
#else
|
||||
#include <GLES/gl.h>
|
||||
|
||||
#ifndef GL_GLEXT_PROTOTYPES
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#endif
|
||||
|
||||
#include <GLES/glext.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
@@ -221,11 +209,7 @@ void Window::setup2D( const bool& KeepView ) {
|
||||
|
||||
BlendMode::setMode( BlendMode::Alpha(), true );
|
||||
|
||||
if ( GLv_3CP != GLi->version() && GLv_3 != GLi->version() && GLv_ES2 != GLi->version() ) {
|
||||
#if !defined( EE_GLES2 ) || defined( EE_GLES_BOTH )
|
||||
glTexEnvi( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE );
|
||||
#endif
|
||||
}
|
||||
GLi->configurePointSprite();
|
||||
|
||||
if ( GLv_2 == GLi->version() || GLv_ES1 == GLi->version() ) {
|
||||
GLi->enableClientState( GL_VERTEX_ARRAY );
|
||||
@@ -307,17 +291,11 @@ Image Window::getFrontBufferImage() {
|
||||
GlobalBatchRenderer::instance()->draw();
|
||||
Image fb( mWindow.WindowConfig.Width, mWindow.WindowConfig.Height, 3 );
|
||||
|
||||
GLint pack_alignment;
|
||||
glGetIntegerv( GL_PACK_ALIGNMENT, &pack_alignment );
|
||||
if ( 1 != pack_alignment )
|
||||
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
|
||||
|
||||
Uint8* pixelData = fb.getPixels();
|
||||
glReadPixels( 0, 0, mWindow.WindowConfig.Width, mWindow.WindowConfig.Height, GL_RGB,
|
||||
GL_UNSIGNED_BYTE, pixelData );
|
||||
|
||||
if ( 1 != pack_alignment )
|
||||
glPixelStorei( GL_PACK_ALIGNMENT, pack_alignment );
|
||||
if ( !GLi->readPixels( 0, 0, mWindow.WindowConfig.Width, mWindow.WindowConfig.Height,
|
||||
Graphics::Renderer::PixelFormat::RGB24, pixelData,
|
||||
static_cast<size_t>( mWindow.WindowConfig.Width ) * 3 ) )
|
||||
return {};
|
||||
|
||||
for ( unsigned int j = 0; j * 2 < fb.getHeight(); ++j ) {
|
||||
int index1 = j * fb.getWidth() * fb.getChannels();
|
||||
@@ -334,6 +312,72 @@ Image Window::getFrontBufferImage() {
|
||||
return fb;
|
||||
}
|
||||
|
||||
bool Window::readFrameBuffer( FrameReadback& readback ) {
|
||||
const Uint32 channels = readback.format == FramePixelFormat::RGB24 ? 3 : 4;
|
||||
const size_t rowBytes = static_cast<size_t>( readback.size.getWidth() ) * channels;
|
||||
if ( nullptr == readback.pixels || readback.size.getWidth() <= 0 ||
|
||||
readback.size.getHeight() <= 0 || readback.stride < rowBytes || readback.position.x < 0 ||
|
||||
readback.position.y < 0 ||
|
||||
static_cast<Uint32>( readback.position.x + readback.size.getWidth() ) > getWidth() ||
|
||||
static_cast<Uint32>( readback.position.y + readback.size.getHeight() ) > getHeight() )
|
||||
return false;
|
||||
const auto format = readback.format == FramePixelFormat::RGB24
|
||||
? Graphics::Renderer::PixelFormat::RGB24
|
||||
: Graphics::Renderer::PixelFormat::RGBA32;
|
||||
if ( mLogicalFrameBuffer )
|
||||
mLogicalFrameBuffer->bindAsReadTarget();
|
||||
if ( !GLi->readPixels( readback.position.x, readback.position.y, readback.size.getWidth(),
|
||||
readback.size.getHeight(), format, readback.pixels, readback.stride ) )
|
||||
return false;
|
||||
readback.origin = FrameOrigin::BottomLeft;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Window::initializeRuntimeRenderTarget() {
|
||||
if ( !Runtime::isOffscreen() )
|
||||
return true;
|
||||
|
||||
if ( mLogicalFrameBuffer )
|
||||
return true;
|
||||
|
||||
mLogicalFrameBuffer =
|
||||
Graphics::FrameBuffer::New( getWidth(), getHeight(), true, false, true, 4, this );
|
||||
|
||||
if ( !mLogicalFrameBuffer || !mLogicalFrameBuffer->created() ) {
|
||||
Log::error( "Unable to create the %s runtime logical framebuffer", Runtime::modeName() );
|
||||
mLogicalFrameBuffer.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
mLogicalFrameBuffer->bind();
|
||||
|
||||
setup2D( false );
|
||||
|
||||
if ( Runtime::mode() == RuntimeMode::Terminal ) {
|
||||
mFramePresenter = std::make_unique<KittyFramePresenter>();
|
||||
if ( !mFramePresenter->initialize( *this ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::resizeRuntimeRenderTarget( Uint32 width, Uint32 height ) {
|
||||
if ( mLogicalFrameBuffer ) {
|
||||
mLogicalFrameBuffer->resize( width, height );
|
||||
mLogicalFrameBuffer->bind();
|
||||
}
|
||||
if ( mFramePresenter )
|
||||
mFramePresenter->resized( *this, Sizei( width, height ) );
|
||||
}
|
||||
|
||||
void Window::shutdownRuntimeRenderTarget() {
|
||||
if ( mFramePresenter )
|
||||
mFramePresenter->shutdown( *this );
|
||||
mFramePresenter.reset();
|
||||
mLogicalFrameBuffer.reset();
|
||||
}
|
||||
|
||||
bool Window::isRunning() const {
|
||||
return mWindow.Created;
|
||||
}
|
||||
@@ -461,6 +505,8 @@ void Window::clear() {
|
||||
|
||||
void Window::display( bool clear ) {
|
||||
GlobalBatchRenderer::instance()->draw();
|
||||
if ( mFramePresenter )
|
||||
mFramePresenter->present( *this );
|
||||
if ( TextureFactory* textureFactory = TextureFactory::existsSingleton() )
|
||||
textureFactory->collectReleasedTextures();
|
||||
mIME.updateLocation();
|
||||
@@ -526,17 +572,21 @@ void Window::sendVideoResizeCb() {
|
||||
}
|
||||
|
||||
void Window::logSuccessfulInit( const std::string& BackendName ) {
|
||||
const char* presenter =
|
||||
Runtime::mode() == RuntimeMode::Terminal ? "Kitty graphics protocol" : "None";
|
||||
std::string msg( String::format(
|
||||
"Engine Initialized Successfully.\n\tVersion: %s (codename: \"%s\")\n\tBuild time: "
|
||||
"%s\n\tPlatform: %s\n\tOS: %s\n\tArch: %s\n\tCPU Cores: %d\n\tProcess Path: %s\n\tCurrent "
|
||||
"Working Directory: %s\n\tHome Directory: %s\n\tDisk Free Space: %s\n\tWindow/Input "
|
||||
"Backend: %s\n\tGL Backend: %s\n\tGL Vendor: %s\n\tGL Renderer: %s\n\tGL Version: "
|
||||
"Working Directory: %s\n\tHome Directory: %s\n\tDisk Free Space: %s\n\tRuntime Mode: "
|
||||
"%s\n\tHeadless: %s\n\tFrame Presenter: %s\n\tWindow/Input Backend: %s\n\tGL Backend: "
|
||||
"%s\n\tGL Vendor: %s\n\tGL Renderer: %s\n\tGL Version: "
|
||||
"%s\n\tGL Shading Language Version: %s\n\tResolution: %dx%d\n\tWindow scale: %.2f",
|
||||
Version::getVersionName(), Version::getCodename(), Version::getBuildTime(),
|
||||
Sys::getPlatform(), Sys::getOSName( true ), Sys::getOSArchitecture(), Sys::getCPUCount(),
|
||||
Sys::getProcessPath(), FileSystem::getCurrentWorkingDirectory(), Sys::getUserDirectory(),
|
||||
FileSystem::sizeToString( FileSystem::getDiskFreeSpace( Sys::getProcessPath() ) ),
|
||||
BackendName, GLi->versionStr(), GLi->getVendor(), GLi->getRenderer(), GLi->getVersion(),
|
||||
Runtime::modeName(), Runtime::isOffscreen() ? "Yes" : "No", presenter, BackendName,
|
||||
GLi->versionStr(), GLi->getVendor(), GLi->getRenderer(), GLi->getVersion(),
|
||||
GLi->getShadingLanguageVersion(), getWidth(), getHeight(), getScale() ) );
|
||||
|
||||
#ifndef EE_SILENT
|
||||
|
||||
Reference in New Issue
Block a user