mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 10:40:20 +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:
@@ -43,6 +43,10 @@ class EE_API FrameBuffer {
|
||||
*position 0,0 means 0,0 point in the frame buffer, not the screen. */
|
||||
virtual void bind() = 0;
|
||||
|
||||
/** Selects this framebuffer as the readback source without changing the current view or
|
||||
* clipping state. */
|
||||
virtual void bindAsReadTarget() = 0;
|
||||
|
||||
/** @brief Disables the off-screen rendering.
|
||||
** Anything rendered after this will be rendered to the back-buffer. */
|
||||
virtual void unbind() = 0;
|
||||
|
||||
@@ -27,6 +27,8 @@ class RendererGLES2;
|
||||
*/
|
||||
class EE_API Renderer {
|
||||
public:
|
||||
/** Pixel layouts supported by framebuffer readback operations. */
|
||||
enum class PixelFormat : Uint8 { RGB24, RGBA32 };
|
||||
/** @return The graphic library renderer version from a string. */
|
||||
static GraphicsLibraryVersion glVersionFromString( std::string glVersion );
|
||||
|
||||
@@ -74,6 +76,9 @@ class EE_API Renderer {
|
||||
|
||||
bool pointSpriteSupported();
|
||||
|
||||
/** Configures point-sprite coordinate replacement when required by the active graphics API. */
|
||||
void configurePointSprite();
|
||||
|
||||
bool shadersSupported();
|
||||
|
||||
void clear( unsigned int mask );
|
||||
@@ -360,6 +365,19 @@ class EE_API Renderer {
|
||||
|
||||
void readPixels( int x, int y, unsigned int width, unsigned int height, void* pixels );
|
||||
|
||||
/** Reads a framebuffer rectangle into caller-owned storage.
|
||||
* @param x Left coordinate of the rectangle.
|
||||
* @param y Bottom coordinate of the rectangle.
|
||||
* @param width Rectangle width in pixels.
|
||||
* @param height Rectangle height in pixels.
|
||||
* @param format Destination pixel format.
|
||||
* @param pixels Destination storage.
|
||||
* @param stride Destination row stride in bytes.
|
||||
* @return True if the arguments are valid and the pixels were read.
|
||||
*/
|
||||
bool readPixels( int x, int y, unsigned int width, unsigned int height, PixelFormat format,
|
||||
void* pixels, size_t stride );
|
||||
|
||||
Color readPixel( int x, int y );
|
||||
|
||||
void waitForIdle();
|
||||
|
||||
@@ -112,6 +112,11 @@ class EE_API Sys {
|
||||
/** @return An environment variable */
|
||||
static std::string getEnv( const std::string& name );
|
||||
|
||||
/** Sets a null-terminated environment variable name and value for the current process.
|
||||
* @return True if the environment was updated successfully.
|
||||
*/
|
||||
static bool setEnv( const char* name, const char* value );
|
||||
|
||||
/** @return A split environment variable */
|
||||
static std::vector<std::string> getEnvSplit( const std::string& name );
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <eepp/window/keycodes.hpp>
|
||||
#include <eepp/window/platformhelper.hpp>
|
||||
#include <eepp/window/platformimpl.hpp>
|
||||
#include <eepp/window/runtime.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
#include <eepp/window/windowcontext.hpp>
|
||||
#include <eepp/window/windowhandle.hpp>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef EE_WINDOW_FRAMEPRESENTER_HPP
|
||||
#define EE_WINDOW_FRAMEPRESENTER_HPP
|
||||
|
||||
#include <eepp/math/size.hpp>
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
class Window;
|
||||
|
||||
/** Presentation boundary used to export completed Window frames to an external transport. */
|
||||
class FramePresenter {
|
||||
public:
|
||||
virtual ~FramePresenter() = default;
|
||||
|
||||
/** Initializes the presenter for a window.
|
||||
* @return True when presentation can begin.
|
||||
*/
|
||||
virtual bool initialize( Window& ) = 0;
|
||||
|
||||
/** Queues or presents the window's completed frame. */
|
||||
virtual void present( Window& ) = 0;
|
||||
|
||||
/** Notifies the presenter that the logical framebuffer size changed. */
|
||||
virtual void resized( Window&, const Math::Sizei& ) {}
|
||||
|
||||
/** Releases resources associated with the window. */
|
||||
virtual void shutdown( Window& ) {}
|
||||
};
|
||||
|
||||
}} // namespace EE::Window
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,9 @@
|
||||
#include <eepp/window/joystickmanager.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
using namespace EE::Graphics;
|
||||
|
||||
namespace EE { namespace Window {
|
||||
@@ -248,6 +251,12 @@ class EE_API Input {
|
||||
/** Process an input event. Called by the input update. */
|
||||
void processEvent( InputEvent* Event );
|
||||
|
||||
/** Queues an event from any producer thread for processing during the next normal update cycle.
|
||||
* If the bounded queue is full, an older mouse-motion event can be discarded to make room.
|
||||
* @return True if the event was queued.
|
||||
*/
|
||||
bool enqueueEvent( InputEvent event );
|
||||
|
||||
/** @return An id of the current event update processed ( */
|
||||
const Uint64& getEventsSentId() const;
|
||||
|
||||
@@ -301,10 +310,15 @@ class EE_API Input {
|
||||
Clock mLastMouseEvent;
|
||||
|
||||
std::map<Uint32, InputCallback> mCallbacks;
|
||||
std::mutex mInjectedEventsMutex;
|
||||
std::deque<InputEvent> mInjectedEvents;
|
||||
|
||||
InputFinger* getFingerId( const Int64& fingerId );
|
||||
|
||||
void resetFingerWasDown();
|
||||
|
||||
/** Processes all events injected by producer threads. Called by backend update cycles. */
|
||||
void drainQueuedEvents();
|
||||
};
|
||||
|
||||
}} // namespace EE::Window
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef EE_WINDOW_RUNTIME_HPP
|
||||
#define EE_WINDOW_RUNTIME_HPP
|
||||
|
||||
#include <eepp/window/base.hpp>
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
/** Process-level execution mode, independent of the selected WindowBackend. */
|
||||
enum class RuntimeMode : Uint8 {
|
||||
Native, //!< Normal platform window and display presentation.
|
||||
Headless, //!< Offscreen rendering without presentation.
|
||||
Terminal //!< Offscreen rendering presented through a terminal transport.
|
||||
};
|
||||
|
||||
/** Process-level runtime selection, resolved once from EEPP_RUNTIME. */
|
||||
class EE_API Runtime {
|
||||
public:
|
||||
/** @return The process runtime mode resolved from EEPP_RUNTIME on first use. */
|
||||
static RuntimeMode mode();
|
||||
|
||||
/** @return True when the runtime requires an offscreen video driver and logical framebuffer. */
|
||||
static bool isOffscreen();
|
||||
|
||||
/** @return A stable lowercase name for the resolved runtime mode. */
|
||||
static const char* modeName();
|
||||
};
|
||||
|
||||
}} // namespace EE::Window
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef EE_WINDOW_TERMINAL_KITTYFRAMEPRESENTER_HPP
|
||||
#define EE_WINDOW_TERMINAL_KITTYFRAMEPRESENTER_HPP
|
||||
|
||||
#include <condition_variable>
|
||||
#include <eepp/window/framepresenter.hpp>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace EE { namespace Window {
|
||||
/** Presents completed window frames using the Kitty graphics protocol. */
|
||||
class KittyFramePresenter final : public FramePresenter {
|
||||
public:
|
||||
~KittyFramePresenter();
|
||||
|
||||
/** Initializes the terminal transport and sender worker. */
|
||||
bool initialize( Window& ) override;
|
||||
|
||||
/** Captures and queues the newest complete RGB24 frame. */
|
||||
void present( Window& ) override;
|
||||
|
||||
/** Stops the sender worker and restores the terminal transport. */
|
||||
void shutdown( Window& ) override;
|
||||
|
||||
private:
|
||||
struct Frame {
|
||||
std::vector<Uint8> pixels;
|
||||
Math::Sizei size;
|
||||
};
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
std::thread mWorker;
|
||||
Frame mPending;
|
||||
Frame mRecycle;
|
||||
bool mRunning{ false };
|
||||
bool mHasPending{ false };
|
||||
/** Consumes the bounded newest-frame queue until shutdown. */
|
||||
void run();
|
||||
/** Encodes and writes one RGB24 frame using chunked Kitty direct transmission. */
|
||||
void sendFrame( const Frame& );
|
||||
};
|
||||
|
||||
}} // namespace EE::Window
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef EE_WINDOW_TERMINAL_TERMINALRUNTIME_HPP
|
||||
#define EE_WINDOW_TERMINAL_TERMINALRUNTIME_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <eepp/math/size.hpp>
|
||||
#include <eepp/window/base.hpp>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace EE { namespace Window {
|
||||
class Window;
|
||||
|
||||
/** Process-level terminal transport and input runtime.
|
||||
*
|
||||
* Owns terminal mode negotiation, input parsing, synchronized protocol output, and attachment to
|
||||
* the single top-level Window currently supported by terminal mode.
|
||||
*/
|
||||
class EE_API TerminalRuntime {
|
||||
public:
|
||||
/** @return The process terminal runtime instance. */
|
||||
static TerminalRuntime& instance();
|
||||
|
||||
~TerminalRuntime();
|
||||
|
||||
/** Opens the controlling terminal, enables terminal protocols, and starts input processing.
|
||||
* @return True if the terminal transport was initialized or was already initialized.
|
||||
*/
|
||||
bool initialize();
|
||||
|
||||
/** Stops input processing and restores the terminal state. */
|
||||
void shutdown();
|
||||
|
||||
/** Writes a complete protocol fragment to the terminal without interleaving other writers.
|
||||
* @return True if all bytes were written.
|
||||
*/
|
||||
bool write( const char* data, size_t size );
|
||||
|
||||
/** @return The terminal viewport size in pixels, or an empty size when unavailable. */
|
||||
Math::Sizei pixelSize() const;
|
||||
|
||||
/** Attaches the top-level window that receives decoded terminal input and resize events. */
|
||||
void attach( Window& window );
|
||||
|
||||
/** Detaches the currently attached window. */
|
||||
void detach();
|
||||
|
||||
/** @return Whether the host terminal currently reports input focus. */
|
||||
bool isFocused() const { return mFocused.load( std::memory_order_relaxed ); }
|
||||
|
||||
private:
|
||||
TerminalRuntime() = default;
|
||||
|
||||
TerminalRuntime( const TerminalRuntime& ) = delete;
|
||||
|
||||
TerminalRuntime& operator=( const TerminalRuntime& ) = delete;
|
||||
|
||||
struct State;
|
||||
State* mState{ nullptr };
|
||||
std::mutex mWriteMutex;
|
||||
std::thread mInputThread;
|
||||
std::atomic<bool> mReading{ false };
|
||||
std::atomic<bool> mFocused{ true };
|
||||
Window* mWindow{ nullptr };
|
||||
#if defined( EE_PLATFORM_POSIX )
|
||||
int mFd{ -1 };
|
||||
#endif
|
||||
|
||||
/** Reads terminal protocol input and enqueues translated neutral InputEvents. */
|
||||
void readInput();
|
||||
};
|
||||
|
||||
}} // namespace EE::Window
|
||||
|
||||
#endif
|
||||
@@ -1,10 +1,12 @@
|
||||
#ifndef EE_WINDOWCWINDOW_HPP
|
||||
#define EE_WINDOWCWINDOW_HPP
|
||||
|
||||
#include <eepp/graphics/framebuffer.hpp>
|
||||
#include <eepp/graphics/image.hpp>
|
||||
#include <eepp/graphics/pixeldensity.hpp>
|
||||
#include <eepp/graphics/view.hpp>
|
||||
#include <eepp/window/base.hpp>
|
||||
#include <eepp/window/framepresenter.hpp>
|
||||
#include <eepp/window/inputmethod.hpp>
|
||||
|
||||
#include <limits>
|
||||
@@ -43,6 +45,28 @@ enum class WindowFlashOperation {
|
||||
|
||||
enum class WindowBackend : Uint32 { SDL2, SDL3, Default };
|
||||
|
||||
/** Pixel layout requested from Window::readFrameBuffer(). */
|
||||
enum class FramePixelFormat : Uint8 { RGB24, RGBA32 };
|
||||
|
||||
/** Row orientation of framebuffer readback storage. */
|
||||
enum class FrameOrigin : Uint8 { BottomLeft, TopLeft };
|
||||
|
||||
/** Description of caller-owned storage for a framebuffer rectangle readback. */
|
||||
struct FrameReadback {
|
||||
/** Destination pixel storage. */
|
||||
Uint8* pixels{ nullptr };
|
||||
/** Rectangle size in pixels. */
|
||||
Sizei size;
|
||||
/** Destination row stride in bytes. */
|
||||
size_t stride{ 0 };
|
||||
/** Bottom-left framebuffer coordinate of the rectangle. */
|
||||
Vector2i position;
|
||||
/** Destination pixel layout. */
|
||||
FramePixelFormat format{ FramePixelFormat::RGB24 };
|
||||
/** Requested destination row orientation. */
|
||||
FrameOrigin origin{ FrameOrigin::BottomLeft };
|
||||
};
|
||||
|
||||
#ifndef EE_SCREEN_KEYBOARD_ENABLED
|
||||
#define EE_SCREEN_KEYBOARD_ENABLED false
|
||||
#endif
|
||||
@@ -376,6 +400,13 @@ class EE_API Window {
|
||||
*/
|
||||
Image getFrontBufferImage();
|
||||
|
||||
/** Reads a framebuffer rectangle into caller-owned storage without allocating an Image.
|
||||
* The destination must hold at least `stride * size.y` bytes. TopLeft origin is written
|
||||
* directly in that orientation without a separate full-frame flip allocation.
|
||||
* @return True if the readback description is valid and the pixels were read.
|
||||
*/
|
||||
bool readFrameBuffer( FrameReadback& readback );
|
||||
|
||||
/** @return The pointer to the Window Info ( read only ) */
|
||||
const WindowInfo* getWindowInfo() const;
|
||||
|
||||
@@ -549,6 +580,8 @@ class EE_API Window {
|
||||
Sizei mLastWindowedSize;
|
||||
InputMethod mIME;
|
||||
std::function<void()> mMainLoop;
|
||||
FrameBufferUniquePtr mLogicalFrameBuffer;
|
||||
std::unique_ptr<FramePresenter> mFramePresenter;
|
||||
|
||||
class FrameData {
|
||||
public:
|
||||
@@ -596,6 +629,15 @@ class EE_API Window {
|
||||
|
||||
void createView();
|
||||
|
||||
/** Creates the logical root framebuffer and presenter required by the active runtime. */
|
||||
bool initializeRuntimeRenderTarget();
|
||||
|
||||
/** Resizes the logical root framebuffer and notifies its presenter. */
|
||||
void resizeRuntimeRenderTarget( Uint32 width, Uint32 height );
|
||||
|
||||
/** Releases runtime presentation and logical framebuffer resources. */
|
||||
void shutdownRuntimeRenderTarget();
|
||||
|
||||
void calculateFps();
|
||||
|
||||
void limitFps();
|
||||
|
||||
@@ -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