diff --git a/SConstruct b/SConstruct index f4500d752522..d9afd096eb17 100644 --- a/SConstruct +++ b/SConstruct @@ -1,7 +1,7 @@ #!/usr/bin/env python from misc.utility.scons_hints import * -EnsureSConsVersion(4, 0) +EnsureSConsVersion(3, 0, 5) EnsurePythonVersion(3, 8) # System diff --git a/drivers/egl/egl_manager.cpp b/drivers/egl/egl_manager.cpp index c545de39a630..3e4b51226f68 100644 --- a/drivers/egl/egl_manager.cpp +++ b/drivers/egl/egl_manager.cpp @@ -95,8 +95,8 @@ int EGLManager::_get_gldisplay_id(void *p_display) { new_gldisplay.egl_display = eglGetPlatformDisplayEXT(_get_platform_extension_enum(), new_gldisplay.display, (attribs.size() > 0) ? attribs.ptr() : nullptr); #endif // EGL_EXT_platform_base } else { - NativeDisplayType *native_display_type = (NativeDisplayType *)new_gldisplay.display; - new_gldisplay.egl_display = eglGetDisplay(*native_display_type); + NativeDisplayType native_display_type = (NativeDisplayType)new_gldisplay.display; + new_gldisplay.egl_display = eglGetDisplay(native_display_type); } ERR_FAIL_COND_V(eglGetError() != EGL_SUCCESS, -1); @@ -287,8 +287,8 @@ Error EGLManager::window_create(DisplayServer::WindowID p_window_id, void *p_dis if (GLAD_EGL_VERSION_1_5) { glwindow.egl_surface = eglCreatePlatformWindowSurface(gldisplay.egl_display, gldisplay.egl_config, p_native_window, egl_attribs.ptr()); } else { - EGLNativeWindowType *native_window_type = (EGLNativeWindowType *)p_native_window; - glwindow.egl_surface = eglCreateWindowSurface(gldisplay.egl_display, gldisplay.egl_config, *native_window_type, nullptr); + EGLNativeWindowType native_window_type = (EGLNativeWindowType)p_native_window; + glwindow.egl_surface = eglCreateWindowSurface(gldisplay.egl_display, gldisplay.egl_config, native_window_type, nullptr); } if (glwindow.egl_surface == EGL_NO_SURFACE) { @@ -462,8 +462,8 @@ Error EGLManager::initialize(void *p_native_display) { #endif // EGL_EXT_platform_base } else { WARN_PRINT("EGL: EGL_EXT_platform_base not found during init, using default platform."); - EGLNativeDisplayType *native_display_type = (EGLNativeDisplayType *)p_native_display; - tmp_display = eglGetDisplay(*native_display_type); + EGLNativeDisplayType native_display_type = (EGLNativeDisplayType)p_native_display; + tmp_display = eglGetDisplay(native_display_type); } if (tmp_display == EGL_NO_DISPLAY) { @@ -519,7 +519,11 @@ Error EGLManager::initialize(void *p_native_display) { if (eglGetError() == EGL_SUCCESS) { const char *platform = _get_platform_extension_name(); if (!client_extensions_string.split(" ").has(platform)) { +#ifdef AURORAOS_ENABLED + WARN_PRINT(vformat("EGL platform extension \"%s\" not found. But it not need on AuroraOS.", platform)); +#else ERR_FAIL_V_MSG(ERR_UNAVAILABLE, vformat("EGL platform extension \"%s\" not found.", platform)); +#endif } } diff --git a/drivers/gles3/effects/feed_effects.cpp b/drivers/gles3/effects/feed_effects.cpp index 8ca88da6623b..9af25a50beee 100644 --- a/drivers/gles3/effects/feed_effects.cpp +++ b/drivers/gles3/effects/feed_effects.cpp @@ -32,7 +32,7 @@ #include "feed_effects.h" -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) #include #endif diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 8b389f733db0..b3e3cd1d69af 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -38,6 +38,7 @@ #include "core/io/image.h" #include "core/os/os.h" #include "storage/texture_storage.h" +#include #define _EXT_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 #define _EXT_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 @@ -382,6 +383,258 @@ RasterizerGLES3::RasterizerGLES3() { RasterizerGLES3::~RasterizerGLES3() { } +#ifdef AURORAOS_ENABLED +#define LOG(...) fprintf(stderr, "INFO: " __VA_ARGS__) + +static int loadShader(int shaderType, const char * source) +{ + int shader = glCreateShader(shaderType); + + if(shader != 0) + { + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + + GLint length; + + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); + + if(length) + { + char* buffer = new char [ length ]; + glGetShaderInfoLog(shader, length, NULL, buffer); + LOG("shader = %s\n", buffer); + delete [] buffer; + + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if(success != GL_TRUE) + { + ERR_PRINT("ERROR compiling shader\n"); + } + } + } + else + { + ERR_PRINT("FAILED to create shader\n"); + } + + return shader; +} + +static int createProgram(const char * vertexSource, const char * fragmentSource) +{ + int vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource); + int pixelShader = loadShader(GL_FRAGMENT_SHADER, fragmentSource); + + int program = glCreateProgram(); + + if(program != 0) + { + glAttachShader(program, vertexShader); + // checkGlError("glAttachShader"); + glAttachShader(program, pixelShader); + // checkGlError("glAttachShader"); + glLinkProgram(program); + int linkStatus[1]; + glGetProgramiv(program, GL_LINK_STATUS, linkStatus); + + if(linkStatus[0] != GL_TRUE) + { + char log[256]; + GLsizei size; + glGetProgramInfoLog(program, 256, &size, log); + ERR_PRINT(vformat("Could not link program: %s", log)); + glDeleteProgram(program); + program = 0; + } + + } + else + { + ERR_PRINT("FAILED to create program"); + } + + LOG("Program linked OK %d\n", program); + return program; +} + +#define GL_CHECK_ERROR() \ + while (true) { \ + GLenum error = glGetError(); \ + if (error != GL_NO_ERROR) { \ + if (error == GL_INVALID_ENUM) \ + ERR_PRINT("GL_INVALID_ENUM"); \ + else if (error == GL_INVALID_OPERATION) \ + ERR_PRINT("GL_INVALID_OPERATION"); \ + else \ + ERR_PRINT(vformat("glError = %d", error)); \ + }\ + break;\ + } + +static void create_vbo(GLuint &vbo, GLuint &vao, const GLuint &shader, const GLint &a_pos, const GLint &a_uv) { + glGenBuffers(1, &vbo); GL_CHECK_ERROR() + glBindBuffer(GL_ARRAY_BUFFER, vbo); GL_CHECK_ERROR() + LOG("Vertex buffer is: %d\n", vbo); + + const GLfloat vertices[] = { + -1.0f, -1.0f, 0.0, 1.0, + 1.0f, -1.0f, 1.0, 1.0, + -1.0f, 1.0f, 0.0, 0.0, + -1.0f, 1.0f, 0.0, 0.0, + 1.0f, -1.0f, 1.0, 1.0, + 1.0f, 1.0f, 1.0, 0.0, + }; + + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); GL_CHECK_ERROR() + // LOG("Size of vertices is %d\n", sizeof(vertices)); + + glGenVertexArrays(1, &vao); GL_CHECK_ERROR() + glBindVertexArray(vao); GL_CHECK_ERROR() + + glVertexAttribPointer(a_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, 0); GL_CHECK_ERROR() + glEnableVertexAttribArray(a_pos); GL_CHECK_ERROR() + glVertexAttribPointer(a_uv, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (void*)(sizeof(GLfloat) * 2)); GL_CHECK_ERROR() + glEnableVertexAttribArray(a_uv); GL_CHECK_ERROR() + glBindVertexArray(GL_NONE); GL_CHECK_ERROR() + glBindBuffer(GL_ARRAY_BUFFER, GL_NONE); GL_CHECK_ERROR() +} + +static void draw_vbo(GLES3::RenderTarget *rt, const bool &gles_over_gl, const Size2i &native_size) { + static bool initialized = false; + static GLint m_positionLoc = -1; + static GLint m_uvLoc = -1; + static GLint m_colorLoc = -1; + static GLint m_rotationLoc = -1; + static GLuint r_program; + + static GLuint m_vbo = 0; + static GLuint m_vao = 0; + + if (!initialized) { + LOG("System FBO is: %d\n", GLES3::TextureStorage::system_fbo); + initialized = true; + + const GLchar *vertSource = "" + "attribute vec2 a_position; \n" + "attribute vec2 a_uv; \n" + "uniform mat2 u_rotation; \n" + "varying vec2 v_uv; \n" + "void main() \n" + "{ \n" + " v_uv = a_uv; \n" + " gl_Position = vec4(a_position * u_rotation, 0.0, 1.0); \n" + "} \n" + ; + + // GLchar *fragSource = nullptr; + std::string fragSource; + if (gles_over_gl) { + fragSource = + "uniform sampler2D u_color; \n" + "varying vec2 v_uv; \n" + "void main() \n" + "{ \n" + " gl_FragColor = texture2D(u_color, vec2(v_uv.y, 1.0 - v_uv.x)); \n" + "} \n" + ; + } else { + // #if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__arm__) || defined(_M_ARM) || defined(__riscv) + // #endif + fragSource = + "precision highp float; \n " + "uniform sampler2D u_color; \n" + "varying vec2 v_uv; \n" + "void main() \n" + "{ \n" + // " gl_FragColor = texture2D(u_color, vec2(v_uv.y, 1.0 - v_uv.x)); \n" + " gl_FragColor = texture2D(u_color, v_uv); \n" + "} \n" + ; + } + + r_program = createProgram(vertSource, fragSource.c_str()); + glUseProgram(r_program); GL_CHECK_ERROR() + + m_positionLoc = glGetAttribLocation(r_program, "a_position"); GL_CHECK_ERROR() + m_uvLoc = glGetAttribLocation(r_program, "a_uv"); GL_CHECK_ERROR() + m_colorLoc = glGetUniformLocation(r_program, "u_color"); GL_CHECK_ERROR() + m_rotationLoc = glGetUniformLocation(r_program, "u_rotation"); GL_CHECK_ERROR() + glUniform1i(m_colorLoc, 0); GL_CHECK_ERROR() + GLfloat w = Math_PI; + GLfloat mat[4] = { + cos(w), sin(w), + -sin(w), cos(w) + }; + glUniformMatrix2fv(m_rotationLoc, 1, GL_FALSE, mat); GL_CHECK_ERROR() + create_vbo(m_vbo, m_vao, r_program, m_positionLoc, m_uvLoc); + } + + glUseProgram(r_program); GL_CHECK_ERROR() + + glBindBuffer(GL_ARRAY_BUFFER, m_vbo); GL_CHECK_ERROR() + + glBindVertexArray(m_vao); GL_CHECK_ERROR() + glEnableVertexAttribArray(m_positionLoc); GL_CHECK_ERROR() + glVertexAttribPointer(m_positionLoc, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (void*)0); GL_CHECK_ERROR() + glEnableVertexAttribArray(m_uvLoc); GL_CHECK_ERROR() + glVertexAttribPointer(m_uvLoc, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (void*)(sizeof(GLfloat) * 2)); GL_CHECK_ERROR() + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, rt->color); GL_CHECK_ERROR() + + GLfloat w = 0; + GLfloat scale[2] = + { + 1.0, + 1.0 + }; + DisplayServer::ScreenOrientation orientation = DisplayServer::get_singleton()->screen_get_orientation(); + if (orientation >= DisplayServer::SCREEN_SENSOR_LANDSCAPE) { + orientation = DisplayServer::get_singleton()->screen_get_sensor_orientation(); + } + switch(orientation) { + case DisplayServer::SCREEN_LANDSCAPE: + w = Math_PI * 0.5; + scale[0] = (GLfloat)rt->size.x / (GLfloat)native_size.y; + scale[1] = (GLfloat)rt->size.y / (GLfloat)native_size.x; + break; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + w = Math_PI * 1.5; + scale[0] = (GLfloat)rt->size.x / (GLfloat)native_size.y; + scale[1] = (GLfloat)rt->size.y / (GLfloat)native_size.x; + break; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + w = Math_PI; + default: + scale[0] = (GLfloat)rt->size.x / (GLfloat)native_size.x; + scale[1] = (GLfloat)rt->size.y / (GLfloat)native_size.y; + } + + if (scale[0] > scale[1]) { + scale[1] = scale[1] / scale[0]; + scale[0] = 1.0; + } else { + scale[0] = scale[0] / scale[1]; + scale[1] = 1.0; + } + + GLfloat mat[4] = { + cos(w) * scale[0], sin(w) * scale[1], + -sin(w) * scale[0], cos(w) * scale[1] + }; + + glUniformMatrix2fv(m_rotationLoc, 1, GL_FALSE, mat); GL_CHECK_ERROR() + + glDrawArrays(GL_TRIANGLES, 0, 6); GL_CHECK_ERROR() + + glBindBuffer(GL_ARRAY_BUFFER, GL_NONE); GL_CHECK_ERROR() + glBindVertexArray(GL_NONE); GL_CHECK_ERROR() +} +#endif + void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, DisplayServer::WindowID p_screen, const Rect2 &p_screen_rect, uint32_t p_layer, bool p_first) { GLES3::RenderTarget *rt = GLES3::TextureStorage::get_singleton()->get_render_target(p_render_target); @@ -415,37 +668,56 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display glReadBuffer(GL_COLOR_ATTACHMENT0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); + Vector2i screen_rect_end = p_screen_rect.get_end(); + +#ifdef AURORAOS_ENABLED + Size2i native_size = DisplayServer::get_singleton()->screen_get_native_size(); //TODO: fix to use window size instead + if (native_size.x > 1 || native_size.y > 1) + glViewport(0, 0, native_size.x, native_size.y); +#endif + // TODO AuroraOS if (p_first) { if (p_screen_rect.position != Vector2() || p_screen_rect.size != rt->size) { // Viewport doesn't cover entire window so clear window to black before blitting. // Querying the actual window size from the DisplayServer would deadlock in separate render thread mode, // so let's set the biggest viewport the implementation supports, to be sure the window is fully covered. +#ifndef AURORAOS_ENABLED Size2i max_vp = GLES3::Utilities::get_singleton()->get_maximum_viewport_size(); glViewport(0, 0, max_vp[0], max_vp[1]); +#endif glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); } } - Vector2i screen_rect_end = p_screen_rect.get_end(); - // Adreno (TM) 3xx devices have a bug that create wrong Landscape rotation of 180 degree - // Reversing both the X and Y axis is equivalent to rotating 180 degrees + // Reversing both the X and Y axis is eqivalent to rotating 180 degrees bool flip_x = false; if (flip_xy_workaround && screen_rect_end.x > screen_rect_end.y) { flip_y = !flip_y; flip_x = !flip_x; } - glBlitFramebuffer(0, 0, rt->size.x, rt->size.y, + // flip 180 + // flip_x = true; + // flip_y = false; + +#ifndef AURORAOS_ENABLED + glBlitFramebuffer (0, 0, rt->size.x, rt->size.y, flip_x ? screen_rect_end.x : p_screen_rect.position.x, flip_y ? screen_rect_end.y : p_screen_rect.position.y, flip_x ? p_screen_rect.position.x : screen_rect_end.x, flip_y ? p_screen_rect.position.y : screen_rect_end.y, GL_COLOR_BUFFER_BIT, GL_NEAREST); +#endif if (read_fbo != 0) { glBindFramebuffer(GL_READ_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); glDeleteFramebuffers(1, &read_fbo); } + +#ifdef AURORAOS_ENABLED + draw_vbo(rt, gles_over_gl, native_size); + glViewport(0, 0, screen_rect_end.x, screen_rect_end.y); +#endif } // is this p_screen useless in a multi window environment? diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 1537035c07bf..07fbc8844c88 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -551,7 +551,7 @@ bool ShaderGLES3::_load_from_cache(Version *p_version) { #ifdef WEB_ENABLED // not supported in webgl return false; #else -#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) +#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) && !defined(AURORAOS_ENABLED) if (RasterizerGLES3::is_gles_over_gl() && (glProgramBinary == nullptr)) { // ARB_get_program_binary extension not available. return false; } @@ -638,7 +638,7 @@ void ShaderGLES3::_save_to_cache(Version *p_version) { return; #else ERR_FAIL_COND(!shader_cache_dir_valid); -#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) +#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) && !defined(AURORAOS_ENABLED) if (RasterizerGLES3::is_gles_over_gl() && (glGetProgramBinary == nullptr)) { // ARB_get_program_binary extension not available. return; } diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index f561cdac073f..379c75546d6b 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -142,7 +142,7 @@ Config::Config() { multiview_supported = extensions.has("OCULUS_multiview") || extensions.has("GL_OVR_multiview2") || extensions.has("GL_OVR_multiview"); #endif -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) // These are GLES only rt_msaa_supported = extensions.has("GL_EXT_multisampled_render_to_texture"); rt_msaa_multiview_supported = extensions.has("GL_OVR_multiview_multisampled_render_to_texture"); diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h index 9727346cf60d..7cc5ed7ab7d9 100644 --- a/drivers/gles3/storage/config.h +++ b/drivers/gles3/storage/config.h @@ -39,7 +39,7 @@ #include "platform_gl.h" -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) typedef void (*PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei); typedef void (*PFNGLTEXSTORAGE3DMULTISAMPLEPROC)(GLenum, GLsizei, GLenum, GLsizei, GLsizei, GLsizei, GLboolean); typedef void (*PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)(GLenum, GLenum, GLenum, GLuint, GLint, GLsizei); @@ -106,7 +106,7 @@ class Config { // ANGLE shader workaround. bool polyfill_half2float = true; -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC eglFramebufferTextureMultiviewOVR = nullptr; PFNGLTEXSTORAGE3DMULTISAMPLEPROC eglTexStorage3DMultisample = nullptr; PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC eglFramebufferTexture2DMultisampleEXT = nullptr; diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp index 7d899fad7789..7541e25da8fa 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp +++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp @@ -35,7 +35,7 @@ #include "texture_storage.h" #include "utilities.h" -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) #define glFramebufferTextureMultiviewOVR GLES3::Config::get_singleton()->eglFramebufferTextureMultiviewOVR #define glTexStorage3DMultisample GLES3::Config::get_singleton()->eglTexStorage3DMultisample #define glFramebufferTexture2DMultisampleEXT GLES3::Config::get_singleton()->eglFramebufferTexture2DMultisampleEXT @@ -68,7 +68,7 @@ void RenderSceneBuffersGLES3::_rt_attach_textures(GLuint p_color, GLuint p_depth ERR_PRINT_ONCE("Multiview MSAA isn't supported on this platform."); #endif } else { -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(AURORAOS_ENABLED) glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, 0, p_view_count); glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, 0, p_view_count); #else @@ -77,7 +77,7 @@ void RenderSceneBuffersGLES3::_rt_attach_textures(GLuint p_color, GLuint p_depth } } else { if (p_samples > 1) { -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_color, 0, p_samples); glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0, p_samples); #else @@ -93,7 +93,7 @@ void RenderSceneBuffersGLES3::_rt_attach_textures(GLuint p_color, GLuint p_depth GLuint RenderSceneBuffersGLES3::_rt_get_cached_fbo(GLuint p_color, GLuint p_depth, GLsizei p_samples, uint32_t p_view_count) { FBDEF new_fbo; -#if defined(ANDROID_ENABLED) || defined(WEB_ENABLED) +#if defined(ANDROID_ENABLED) || defined(WEB_ENABLED) || defined(AURORAOS_ENABLED) // There shouldn't be more then 3 entries in this... for (const FBDEF &cached_fbo : msaa3d.cached_fbos) { if (cached_fbo.color == p_color && cached_fbo.depth == p_depth) { @@ -328,7 +328,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() { glGenTextures(1, &msaa3d.color); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.color); -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, color_internal_format, internal_size.x, internal_size.y, view_count, GL_TRUE); #else glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, color_internal_format, internal_size.x, internal_size.y, view_count, GL_TRUE); @@ -340,7 +340,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() { glGenTextures(1, &msaa3d.depth); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.depth); -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, GL_TRUE); #else glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, GL_TRUE); @@ -365,7 +365,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() { glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, 0); glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); #endif -#if defined(ANDROID_ENABLED) || defined(WEB_ENABLED) // Only supported on OpenGLES! +#if defined(ANDROID_ENABLED) || defined(WEB_ENABLED) || defined(AURORAOS_ENABLED)// Only supported on OpenGLES! } else if (!use_internal_buffer) { // We are going to render directly into our render target textures, // these can change from frame to frame as we cycle through swapchains, diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index de5938176857..589aaaad2cc1 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -832,7 +832,7 @@ void TextureStorage::texture_external_initialize(RID p_texture, int p_width, int glGenTextures(1, &texture.tex_id); glBindTexture(texture.target, texture.tex_id); -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) if (texture.target == _GL_TEXTURE_EXTERNAL_OES) { if (p_external_buffer) { GLES3::Config::get_singleton()->eglEGLImageTargetTexture2DOES(_GL_TEXTURE_EXTERNAL_OES, reinterpret_cast(p_external_buffer)); @@ -1025,7 +1025,7 @@ void TextureStorage::texture_external_update(RID p_texture, int p_width, int p_h tex->alloc_width = tex->width = p_width; tex->alloc_height = tex->height = p_height; -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) if (tex->target == _GL_TEXTURE_EXTERNAL_OES && p_external_buffer) { glBindTexture(_GL_TEXTURE_EXTERNAL_OES, tex->tex_id); GLES3::Config::get_singleton()->eglEGLImageTargetTexture2DOES(_GL_TEXTURE_EXTERNAL_OES, reinterpret_cast(p_external_buffer)); @@ -2164,7 +2164,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) { GLES3::Utilities::get_singleton()->texture_allocated_data(rt->color, rt->size.x * rt->size.y * rt->view_count * rt->color_format_size, "Render target color texture"); } -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(AURORAOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->color, 0, 0, rt->view_count); } else { @@ -2197,7 +2197,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) { GLES3::Utilities::get_singleton()->texture_allocated_data(rt->depth, rt->size.x * rt->size.y * rt->view_count * 3, "Render target depth texture"); } -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(AURORAOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count); } else { @@ -2338,7 +2338,7 @@ void GLES3::TextureStorage::check_backbuffer(RenderTarget *rt, const bool uses_s glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(AURORAOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->backbuffer, 0, 0, rt->view_count); } else { @@ -2362,7 +2362,7 @@ void GLES3::TextureStorage::check_backbuffer(RenderTarget *rt, const bool uses_s glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(AURORAOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->backbuffer_depth, 0, 0, rt->view_count); } else { diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp index 7198f4dd0d08..d2ee37098421 100644 --- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp +++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp @@ -839,6 +839,9 @@ void AudioDriverPulseAudio::set_input_device(const String &p_name) { } AudioDriverPulseAudio::AudioDriverPulseAudio() { +#if defined(AURORAOS_ENABLED) + setenv("PULSE_PROP_media.role", "x-maemo", 1); +#endif samples_in.clear(); samples_out.clear(); } diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index a0baad7a1410..9c081beaed3c 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -239,6 +239,14 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["loongarch64"] = "loongarch64"; capitalize_string_remaps["lowpass"] = "Low-pass"; capitalize_string_remaps["macos"] = "macOS"; + capitalize_string_remaps["auroraos"] = "AuroraOS"; + capitalize_string_remaps["armv7hl"] = "armv7hl"; + capitalize_string_remaps["aarch64"] = "aarch64"; + capitalize_string_remaps["x86_64"] = "x86_64"; + capitalize_string_remaps["86x86"] = "86x86"; + capitalize_string_remaps["108x108"] = "108x108"; + capitalize_string_remaps["128x128"] = "128x128"; + capitalize_string_remaps["172x172"] = "172x172"; capitalize_string_remaps["mb"] = "(MB)"; // Unit. capitalize_string_remaps["mjpeg"] = "MJPEG"; capitalize_string_remaps["mms"] = "MMS"; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index a246249cfe33..e54c28ed5cac 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -469,7 +469,11 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/code_font", "", "*.ttf,*.otf,*.woff,*.woff2,*.pfb,*.pfm") _initial_set("interface/editor/separate_distraction_mode", false, true); _initial_set("interface/editor/automatically_open_screenshots", true, true); +#ifdef AURORAOS_ENABLED + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/single_window_mode", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING) +#else EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/single_window_mode", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING) +#endif _initial_set("interface/editor/mouse_extra_buttons_navigate_history", true); _initial_set("interface/editor/save_each_scene_on_quit", true, true); // Regression EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/save_on_focus_loss", false, "") diff --git a/modules/openxr/openxr_platform_inc.h b/modules/openxr/openxr_platform_inc.h index d2268efc3cb6..3fd9656e58d3 100644 --- a/modules/openxr/openxr_platform_inc.h +++ b/modules/openxr/openxr_platform_inc.h @@ -45,7 +45,7 @@ #endif // METAL_ENABLED #if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) -#ifdef ANDROID_ENABLED +#if defined(ANDROID_ENABLED) || defined(AURORAOS_ENABLED) #define XR_USE_GRAPHICS_API_OPENGL_ES #include #include diff --git a/platform/auroraos/detect.py b/platform/auroraos/detect.py new file mode 100644 index 000000000000..71c5de3a8f05 --- /dev/null +++ b/platform/auroraos/detect.py @@ -0,0 +1,306 @@ +import os +import platform +import sys +from platform_methods import validate_arch + + +def is_active(): + return True + + +def get_name(): + return "AuroraOS" + + +def can_build(): + + if (os.name != "posix" or sys.platform == "darwin"): + return False + + # Check the minimal dependencies + sdl_error = os.system("pkg-config --version > /dev/null") + if (sdl_error): + print("pkg-config not found...") + return False + + + sdl_error = os.system("pkg-config sdl2 --modversion > /dev/null ") + if (sdl_error): + print("SDL2 not found. AuroraOS build disabled. Install SDL2-devel for all your targets in MerSDK ") + return False + else: + print("SDL2-devel is found") + + glib_error = os.system("pkg-config glib-2.0 --modversion > /dev/null") + if(glib_error): + print("glib2-devel not found. Install glib2-devel for all your targets in MerSDK") + return False; + else: + print("glib2-devel is found") + + udev_error = os.system("pkg-config libudev --modversion > /dev/null") + if(udev_error): + print("libudev.pc not found. Install systemd-devel for all your targets in MerSDK. Joypad support is disabled.") + # return False; # not critical + else: + print("libudev.pc is found") + + # webp_error = os.system("pkg-config libwebp --modversion > /dev/null") + # if(webp_error): + # print("libwebp-devel not found. Install libwebp-devel for all your targets in MerSDK\n") + # return False; + + return True + +def get_opts(): + from SCons.Variables import BoolVariable, EnumVariable, PathVariable + + return [ + BoolVariable('use_llvm', 'Use the LLVM compiler', False), + BoolVariable('use_static_cpp', 'Link stdc++ statically', False), + BoolVariable('use_sanitizer', 'Use LLVM compiler address sanitizer', False), + BoolVariable('use_leak_sanitizer', 'Use LLVM compiler memory leaks sanitizer (implies use_sanitizer)', False), + BoolVariable('pulseaudio', 'Detect & use pulseaudio', True), + BoolVariable('udev', 'Use udev for gamepad connection callbacks', False), + EnumVariable('debug_symbols', 'Add debug symbols to release version', 'no', ('yes', 'no', 'full')), + BoolVariable('separate_debug_symbols', 'Create a separate file with the debug symbols', False), + BoolVariable('touch', 'Enable touch events', True), + BoolVariable('tools', 'Enable editor tools', False), + # PathVariable('static_sdl', 'Enable static link of SDL2', ""), + # PathVariable('sdl_path', 'Custom SDL2 path', "") + ] + + +def get_flags(): + + return [ + # ('builtin_freetype', True), + ('builtin_libpng', False), + ('builtin_openssl', False), + ('builtin_zlib', False), + # ('builtin_libvpx', False), + # ('builtin_libwebp', False) + ] + + +def configure(env): + ## Build type + + if (env["target"] == "release"): + # -O3 -ffast-math is identical to -Ofast. We need to split it out so we can selectively disable + # -ffast-math in code for which it generates wrong results. + env.Prepend(CCFLAGS=['-O3', '-ffast-math', '-DGLES_ENABLED']) + if (env["debug_symbols"] == "yes"): + env.Prepend(CCFLAGS=['-g1']) + if (env["debug_symbols"] == "full"): + env.Prepend(CCFLAGS=['-g2']) + + elif (env["target"] == "release_debug"): + env.Prepend(CCFLAGS=['-O2', '-ffast-math', '-DDEBUG_ENABLED', '-DGLES_ENABLED']) + if (env["debug_symbols"] == "yes"): + env.Prepend(CCFLAGS=['-g1']) + if (env["debug_symbols"] == "full"): + env.Prepend(CCFLAGS=['-g2']) + + elif (env["target"] == "debug"): + env.Prepend(CCFLAGS=['-g3', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED', '-DGLES_ENABLED']) + env.Append(LINKFLAGS=['-rdynamic']) + env.Prepend(CCFLAGS=['-DMESA_EGL_NO_X11_HEADERS']) + ## Architecture + + is64 = sys.maxsize > 2**32 + if (env["bits"] == "default"): + env["bits"] = "64" if is64 else "32" + + ## Compiler configuration + + if 'CXX' in env and 'clang' in env['CXX']: + # Convenience check to enforce the use_llvm overrides when CXX is clang(++) + env['use_llvm'] = True + + if env['use_llvm']: + if ('clang++' not in env['CXX']): + env["CC"] = "clang" + env["CXX"] = "clang++" + env["LINK"] = "clang++" + env.Append(CPPFLAGS=['-DTYPED_METHOD_BIND', '-DGLES_ENABLED']) + env.extra_suffix = ".llvm" + env.extra_suffix + + # leak sanitizer requires (address) sanitizer + if env['use_sanitizer'] or env['use_leak_sanitizer']: + env.Append(CCFLAGS=['-fsanitize=address', '-fno-omit-frame-pointer']) + env.Append(LINKFLAGS=['-fsanitize=address']) + env.extra_suffix += "s" + if env['use_leak_sanitizer']: + env.Append(CCFLAGS=['-fsanitize=leak']) + env.Append(LINKFLAGS=['-fsanitize=leak']) + + if env['lto'] == 'full': + env.Append(CCFLAGS=['-flto']) + if not env['use_llvm'] and env.GetOption("num_jobs") > 1: + env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))]) + else: + env.Append(LINKFLAGS=['-flto']) + if not env['use_llvm']: + env['RANLIB'] = 'gcc-ranlib' + env['AR'] = 'gcc-ar' + + env.Append(CCFLAGS=['-pipe']) + env.Append(LINKFLAGS=['-pipe']) + + ## Dependencies + # try use static SDL2 + if not 'static_sdl' in env or not "sdl_path" in env: + env.ParseConfig('pkg-config sdl2 --cflags --libs') + else: + env.Append(CCFLAGS=["-I" + env["sdl_path"]] ) + + env.Append(CPPPATH=['#platform/auroraos/SDL2-2.0.9/src']) + env.Append(CPPFLAGS=['-D__AURORAOS_PLATFORM__']) + env.ParseConfig('pkg-config wayland-client --cflags --libs') + # ar_error = os.system("pkg-config audioresource --modversion > /dev/null") + # if(ar_error): + env.Prepend(CCFLAGS=['-DDISABLE_LIBAUDIORESOURCE']) + # else: + # env.ParseConfig("pkg-config audioresource --cflags --libs") + # env.ParseConfig("pkg-config glib-2.0 --cflags --libs") + + if (env['touch']): + env.Append(CPPFLAGS=['-DTOUCH_ENABLED']) + + # FIXME: Check for existence of the libs before parsing their flags with pkg-config + + if not env['builtin_openssl']: + env.ParseConfig('pkg-config openssl --cflags --libs') + + if not env['builtin_libwebp']: + env.ParseConfig('pkg-config libwebp --cflags --libs') + + + # freetype depends on libpng and zlib, so bundling one of them while keeping others + # as shared libraries leads to weird issues + if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']: + env['builtin_freetype'] = True + env['builtin_libpng'] = True + env['builtin_zlib'] = True + + if not env['builtin_freetype']: + env.ParseConfig('pkg-config freetype2 --cflags --libs') + + if not env['builtin_libpng']: + env.ParseConfig('pkg-config libpng --cflags --libs') + + if not env['builtin_bullet']: + # We need at least version 2.88 + import subprocess + bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip() + if bullet_version < "2.88": + # Abort as system bullet was requested but too old + print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.88")) + sys.exit(255) + env.ParseConfig('pkg-config bullet --cflags --libs') + + if not env['builtin_enet']: + env.ParseConfig('pkg-config libenet --cflags --libs') + + if not env['builtin_squish'] and env['tools']: + env.ParseConfig('pkg-config libsquish --cflags --libs') + + if not env['builtin_zstd']: + env.ParseConfig('pkg-config libzstd --cflags --libs') + + # Sound and video libraries + # Keep the order as it triggers chained dependencies (ogg needed by others, etc.) + + if not env['builtin_libtheora']: + env['builtin_libogg'] = False # Needed to link against system libtheora + env['builtin_libvorbis'] = False # Needed to link against system libtheora + env.ParseConfig('pkg-config theora theoradec --cflags --libs') + + if not env['builtin_libvpx']: + env.ParseConfig('pkg-config vpx --cflags --libs') + + if not env['builtin_libvorbis']: + env['builtin_libogg'] = False # Needed to link against system libvorbis + env.ParseConfig('pkg-config vorbis vorbisfile --cflags --libs') + + if not env['builtin_opus']: + env['builtin_libogg'] = False # Needed to link against system opus + env.ParseConfig('pkg-config opus opusfile --cflags --libs') + + if not env['builtin_libogg']: + env.ParseConfig('pkg-config ogg --cflags --libs') + + if env['builtin_libtheora']: + list_of_x86 = ['x86_64', 'x86', 'i386', 'i586'] + if any(platform.machine() in s for s in list_of_x86): + env["x86_libtheora_opt_gcc"] = True + + # On Linux wchar_t should be 32-bits + # 16-bit library shouldn't be required due to compiler optimisations + if not env['builtin_pcre2']: + env.ParseConfig('pkg-config libpcre2-32 --cflags --libs') + + ## Flags + # if env['alsa']: + # if (os.system("pkg-config --exists alsa") == 0): # 0 means found + # print("Enabling ALSA") + # env.Append(CPPFLAGS=["-DALSA_ENABLED"]) + # env.ParseConfig('pkg-config alsa --cflags --libs') + # else: + # print("ALSA libraries not found, disabling driver") + + if env['pulseaudio']: + if (os.system("pkg-config --exists libpulse-simple") == 0): # 0 means found + print("Enabling PulseAudio") + env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"]) + env.ParseConfig('pkg-config --cflags --libs libpulse-simple') + else: + print("PulseAudio development libraries not found, disabling driver") + + if (platform.system() == "Linux"): + env.Append(CPPFLAGS=["-DJOYDEV_ENABLED", '-DGLES_ENABLED']) + + if env['udev']: + if (os.system("pkg-config --exists libudev") == 0): # 0 means found + print("Enabling udev support") + env.Append(CPPFLAGS=["-DUDEV_ENABLED"]) + env.ParseConfig('pkg-config libudev --cflags --libs') + else: + print("libudev development libraries not found, disabling udev support") + else: + print('udev support disabled with flag "udev=no"') + + # Linkflags below this line should typically stay the last ones + if not env['builtin_zlib']: + env.ParseConfig('pkg-config zlib --cflags --libs') + + env.Append(CPPPATH=['#platform/auroraos','#core', '#thirdparty/glad', '#platform/auroraos/outputsdl/include', '#platform/auroraos/SDL_src/src']) + env.Append(CPPFLAGS=['-DSDL_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DGLES2_ENABLED', '-Wno-strict-aliasing']) + env.Append(CPPFLAGS=['-DAURORAOS_ENABLED']) + env.Append(CPPFLAGS=['-DAURORAOS_FORCE_LANDSCAPE']) + # include paths for different versions of AuroraSDK width different SDL2 version + env.Append(LIBS=['GLESv2', 'EGL', 'pthread']) + + # Architecture-specific flags + if env['arch'] == "x86": + env.Append(CPPFLAGS=['-DAURORAOS_i486_GLES2']) + + if (platform.system() == "Linux"): + env.Append(LIBS=['dl']) + + if (platform.system().find("BSD") >= 0): + env.Append(LIBS=['execinfo']) + + ## Cross-compilation + + if (is64 and env["bits"] == "32"): + env.Append(CPPFLAGS=['-m32']) + env.Append(LINKFLAGS=['-m32', '-L/usr/lib/i386-linux-gnu']) + elif (not is64 and env["bits"] == "64"): + env.Append(CPPFLAGS=['-m64']) + env.Append(LINKFLAGS=['-m64', '-L/usr/lib/i686-linux-gnu']) + + + if env['use_static_cpp']: + env.Append(LINKFLAGS=['-static-libstdc++']) diff --git a/platform/auroraos/export/export.cpp b/platform/auroraos/export/export.cpp new file mode 100644 index 000000000000..6421f96fe8eb --- /dev/null +++ b/platform/auroraos/export/export.cpp @@ -0,0 +1,2017 @@ +/*************************************************************************/ +/* export.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Author: 2025 sashikknox */ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-present Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "export.h" + +#include "core/variant/dictionary.h" +#include "core/io/marshalls.h" +#include "core/io/xml_parser.h" +#include "core/io/zip_io.h" +#include "drivers/unix/os_unix.h" +#include "core/os/os.h" +#include "core/os/thread.h" +#include "core/config/project_settings.h" +#include "core/version.h" +#include "editor/export/editor_export.h" +#include "editor/themes/editor_scale.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_paths.h" +#include "main/main.h" +#include "modules/regex/regex.h" +#include "modules/svg/image_loader_svg.h" +#include "platform/auroraos/export/logo_svg.gen.h" +#include "scene/resources/texture.h" + +#define prop_editor_sdk_path "export/auroraos/aurora_sdk/sdk_path" +#define prop_editor_tool "export/auroraos/aurora_sdk/tool" +#define prop_editor_ssh_tool_path "export/auroraos/aurora_sdk/ssh_tool_path" +#define prop_editor_ssh_port "export/auroraos/aurora_sdk/ssh_port" +#define prop_editor_binary_arm "export/auroraos/export_tempaltes/arm32" +#define prop_editor_binary_arm64 "export/auroraos/export_tempaltes/arm64" +#define prop_editor_binary_x86_64 "export/auroraos/export_tempaltes/x86_64" + +#define prop_project_launcher_name "application/config/name" +#define prop_project_package_version "application/config/version" + +#define prop_export_application_launcher_name "application/launcher_name" +#define prop_export_application_organization "application/organization" +#define prop_export_application_name "application/name" +#define prop_export_package_version "application/version" +#define prop_export_release_version "application/release_version" +#define prop_export_icon_config_path "icons/" +#define prop_export_icon_86 "icons/86x86" +#define prop_export_icon_108 "icons/108x108" +#define prop_export_icon_128 "icons/128x128" +#define prop_export_icon_172 "icons/172x172" +#define prop_export_sign_key "sign/key" +#define prop_export_sign_cert "sign/cert" +#define prop_export_sign_password "sign/password" + +#define prop_export_binary_arch "architecture" +#define prop_export_binary_arch_armv7hl "architecture/armv7hl" +#define prop_export_binary_arch_aarch64 "architecture/aarch64" +#define prop_export_binary_arch_x86_64 "architecture/x86_64" +// firejail / sailjail properties +#define prop_sailjail_permissions "permissions/" +#define prop_validator_enable "rpm_validator/enable" + +#define mersdk_rsa_key "/vmshare/ssh/private_keys/engine/mersdk" + +// Add missing TTR macro +#ifndef TTR +#define TTR(m_text) (m_text) +#endif + +const String sailjail_minimum_required_permissions[] = { + // required in any case + // "Internet", // looks like its too required FIXME: debug godot and made it useable without internet permission on device + String() +}; + +const String sailjail_permissions[] = { + "Audio", + "Bluetooth", + "Camera", + "Internet", + "Location", + "MediaIndexing", + "Microphone", + "NFC", + "RemovableMedia", + "UserDirs", + "WebView", + "Documents", + "Downloads", + "Music", + "Pictures", + "PublicDir", + "Videos", + "Compatibility", + String() +}; + +// #ifdef WINDOWS_ENABLED +// const String separator("\\"); +// #else +const String separator("/"); +// #endif + +const String spec_file_tempalte = + "Name: %{_gd_application_name}\n" + "# >> macros\n" + "%define __requires_exclude ^libfreetype\\.so.*|.*libxkbcommon\\.so.*$\n" + "%define __provides_exclude_from ^%{_datadir}/%{name}/lib/.*\\.so.*$\n" + "%define debug_package %{nil}\n" + "# << macros\n" + "Summary: %{_gd_launcher_name}\n" + "Version: %{_gd_version}\n" + "Release: %{_gd_release}\n" + "Group: Game\n" + "License: Proprietary\n" + "BuildArch: %{_gd_architecture}\n" + "BuildRequires: patchelf\n" + "\n" + "%define _topdir %{_gd_shared_path}%{_gd_export_path}\n" + "\n" + "%description\n" + "%{_gd_description}\n" + "\n" + "%prep\n" + "echo \"Nothing to do here. Skip this step\"\n" + "\n" + "%build\n" + "echo \"Nothing to do here. Skip this step\"\n" + "\n" + "%install\n" + "rm -rf %{buildroot}\n" + "mkdir -p \"%{buildroot}\"\n" + "mkdir -p \"%{buildroot}%{_bindir}\"\n" + "rm -fr \"%{buildroot}%{_bindir}\"\n" + "mv \"%{_topdir}/BUILD%{_bindir}\" \"%{buildroot}%{_bindir}\"\n" + // "cp -r \"%{_topdir}/%{_datadir}/%{name}/\"* \"%{buildroot}%{_datadir}/%{name}/\"\n" + "mv \"%{_topdir}/BUILD%{_datadir}\" \"%{buildroot}%{_datadir}\"\n" + "mkdir -p \"%{buildroot}/usr/share/applications\"\n" + "[ -f \"%{_topdir}/BUILD/usr/share/applications/%{name}.desktop\" ] && mv -f \"%{_topdir}/BUILD/usr/share/applications/%{name}.desktop\" \"%{buildroot}/usr/share/applications/%{name}.desktop\"||echo \"File moved already\"\n" + "chmod 755 %{buildroot}/usr/share/icons/hicolor/*\n" + "chmod 755 %{buildroot}/usr/share/icons/hicolor/*/apps\n" + "chmod -R 755 %{buildroot}%{_datadir}/%{name}\n" + // TODO: add use patchelf + "patchelf --force-rpath --set-rpath /usr/share/%{name}/lib %{buildroot}%{_bindir}/%{name}\n" + "# dependencies\n" + "install -D %{_libdir}/libfreetype.so.* -t %{buildroot}%{_datadir}/%{name}/lib/\n" + "install -D %{_libdir}/libxkbcommon.so.* -t %{buildroot}%{_datadir}/%{name}/lib/\n" + //libudev.so.1 - not allowed dependency.. + "\n" + "%files\n" + "%defattr(644,root,root,-)\n" + "%attr(755,root,root) %{_bindir}/%{name}\n" + // "%attr(644,root,root) %{_datadir}/%{name}/%{name}.png\n" // FIXME: add icons for all resolutions, as AuroraOS specification needed + "%{_datadir}/icons/hicolor/86x86/apps/%{name}.png\n" + "%{_datadir}/icons/hicolor/108x108/apps/%{name}.png\n" + "%{_datadir}/icons/hicolor/128x128/apps/%{name}.png\n" + "%{_datadir}/icons/hicolor/172x172/apps/%{name}.png\n" + "%attr(644,root,root) %{_datadir}/%{name}/%{name}.pck\n" + "%{_datadir}/%{name}/lib\n" + // "%attr(755,root,root) %{_datadir}/%{name}/lib/*\n" // FIXME: add this as optional string, if we use native extensions + "%attr(644,root,root) %{_datadir}/applications/%{name}.desktop\n" + "%changelog\n" + "* %{_gd_date} Godot Game Engine\n" + "- application %{name} packed to RPM\n" + "#$changelog$"; + +// --define "_datadir /home/nemo/.local/share" if need install to user folder +const String desktop_file_template = + "[Desktop Entry]\n" + "Type=Application\n" + "X-Nemo-Application-Type=no-invoker\n" + "Icon=%{name}\n" + "Exec=%{name} --main-pack %{_datadir}/%{name}/%{name}.pck\n" + "Name=%{_gd_launcher_name}\n" + "Name[en]=%{_gd_launcher_name}\n" + "Categories=Game"; + +const String desktop_file_sailjail = + "\n" + "[X-Application]\n" + "Permissions=%{permissions}\n" + "OrganizationName=%{organization}\n" + "ApplicationName=%{appname}\n"; + +static void _execute_thread(void *p_ud) { + EditorNode::ExecuteThreadArgs *eta = (EditorNode::ExecuteThreadArgs *)p_ud; + // Error err = OS_Unix::execute(eta->path, eta->args, true, NULL, &eta->output, &eta->exitcode, true, &eta->execute_output_mutex); + Error err = OS::get_singleton()->execute(eta->path, eta->args, &eta->output, &eta->exitcode, true, &eta->execute_output_mutex, false); + // Error err = OS_Unix::execute(eta->path, eta->args, &eta->output, &eta->exitcode, true, &eta->execute_output_mutex, false); + print_verbose("Thread exit status: " + itos(eta->exitcode)); + if (err != OK) { + eta->exitcode = err; + } + + eta->done.set(); +} + +class EditorExportPlatformAuroraOS : public EditorExportPlatform { + GDCLASS(EditorExportPlatformAuroraOS, EditorExportPlatform) + + /** On Windows, the sfdk tool works worst + * with the @godot implementation of the + * execution command in windows port, it does + * not work at all. + * Ok, try use just ssh + */ + enum SDKConnectType { + tool_sfdk, + tool_ssh + }; + + enum TargetArch { + arch_armv7hl, + arch_aarch64, + arch_x86_64, + arch_MAX, + arch_MIN = arch_armv7hl, + arch_x86 = arch_x86_64, + arch_unkown + }; + + struct Device { + String address; + String name; + TargetArch arch; + }; + + struct MerTarget { + MerTarget() { + arch = arch_unkown; + name = "AuroraOS"; + target_template = ""; + addversion = ""; + version[0] = 4; + version[1] = 3; + version[2] = 0; + version[3] = 12; + } + + String target_template; + String name; + String addversion; // in AuroraOS we have additions name "-base" after version + int version[4]; // array of 4 integers + TargetArch arch; + }; + + struct NativePackage { + MerTarget target; // AuroraOS build target + String name; // package rpm name (lowercase, without special symbols) + String launcher_name; // button name in launcher menu + String version; // game/application version + String release; // build number/release version + String description; // package desciption + }; + +protected: + String get_current_date() const { + // TODO implement get date function + return String("Thu Dec 19 2019"); + } + + String mertarget_to_text(const MerTarget &target) const { + Array args; + for (int i = 0; i < 4; i++) + args.push_back(target.version[i]); + bool error; + String addversion = "-"; + if( !target.addversion.is_empty() ) + addversion = target.addversion + addversion; + return target.name + String("-%d.%d.%d.%d").sprintf(args, &error) + addversion + arch_to_text(target.arch) + String(".default"); + } + + String arch_to_text(TargetArch arch) const { + switch (arch) { + case arch_armv7hl: + return "armv7hl"; + break; + case arch_aarch64: + return "aarch64"; + break; + case arch_x86_64: + return "x86_64"; + break; + default: + print_error("Cant use this architecture"); + return "noarch"; + break; + } + return "noarch"; + } + + String get_sdk_config_path(const Ref &p_preset) const { + String sdk_configs_path = OS::get_singleton()->get_config_path(); +#ifdef OSX_ENABLED + sdk_configs_path = OS::get_singleton()->get_environment("HOME") + String("/.config"); +#elif WINDOWS_ENABLED + sdk_configs_path = String(EDITOR_GET(prop_editor_sdk_path)) + separator + String("settings"); +#endif + sdk_configs_path += separator + sdk_config_dir; + return sdk_configs_path; + } + + String get_absolute_export_path(const String &realitive_export_path) const { + String export_path = realitive_export_path; + String project_path = ProjectSettings::get_singleton()->get_resource_path(); + + // if (project_path.rfind(separator) == project_path.length() - 1) + if (project_path.right(1) == separator) + project_path = project_path.left(project_path.length() - 1); + // project_path = project_path.left(project_path.rfind(separator)); + // make from realitive path an absolute path + if (export_path.find(String(".") + separator) == 0) { + export_path = project_path + separator + export_path.substr(2, export_path.length() - 2); + } else { + int count_out_dir = 0; + while (export_path.find(String("..") + separator) == 0) { + count_out_dir++; + export_path = export_path.substr(3, export_path.length() - 3); + } + for (int i = 0; i < count_out_dir; i++) { + int pos = project_path.rfind(separator); + if (pos >= 0) { + project_path = project_path.left(pos); + } + } + export_path = project_path + separator + export_path; + } + return export_path; + } + + String get_sfdk_path(const Ref &p_preset) const { + String sfdk_path = String(EDITOR_GET(prop_editor_sdk_path)); +#ifdef WINDOWS_ENABLED + sfdk_path += String("\\bin\\sfdk.exe"); +#else + sfdk_path += String("/bin/sfdk"); +#endif + return sfdk_path; + } + + int execute_task(const String &p_path, const List &p_arguments, List &r_output) { + EditorNode::ExecuteThreadArgs eta; + eta.path = p_path; + eta.args = p_arguments; + eta.exitcode = 255; + eta.done.set_to(false); + + int prev_len = 0; + eta.execute_output_thread.start(_execute_thread, &eta); + + while (!eta.done.is_set()) { + eta.execute_output_mutex.lock(); + if (prev_len != eta.output.length()) { + String to_add = eta.output.substr(prev_len, eta.output.length()); + prev_len = eta.output.length(); + r_output.push_back(to_add); + //print_verbose(to_add); + Main::iteration(); + } + eta.execute_output_mutex.unlock(); + OS::get_singleton()->delay_usec(1000); + } + + eta.execute_output_thread.wait_to_finish(); + + return eta.exitcode; + } + + Error build_package(const NativePackage &package, const Ref &p_preset, const bool &p_debug, const String &sfdk_tool, EditorProgress &ep, int progress_from, int progress_full) { + int steps = 9; // if add some step to build process, need change it + const bool validate_rpm = p_preset->get(prop_validator_enable); + const bool sign_rpm = true; //p_preset->get(prop_aurora_sign_enable); + const bool sailjail_enabled = true; + int current_step = 0; + int progress_step = progress_full / steps; + + if (validate_rpm) { + steps++; //another step to validate PRM + } + + if (sign_rpm) { + steps++; //another step to sign PRM + } + + // Check if template file exists and is readable + if (!FileAccess::exists(package.target.target_template)) { + print_error("AuroraOS Export: Template file does not exist: " + package.target.target_template); + return ERR_FILE_NOT_FOUND; + } + + SDKConnectType sdk_tool = SDKConnectType::tool_sfdk; + String tool = EDITOR_GET(prop_editor_tool); + + if (tool == String("ssh")) + sdk_tool = SDKConnectType::tool_ssh; + + List args; + List pre_args; + String export_template; + + if (sdk_tool == SDKConnectType::tool_ssh) { + // here we neet to know where is RSA keys for buildengine + String rsa_key_path = EDITOR_GET(prop_editor_sdk_path); + rsa_key_path += String(mersdk_rsa_key); + String ssh_port = EDITOR_GET(prop_editor_ssh_port); + pre_args.push_back("-o"); + pre_args.push_back("\"IdentitiesOnly=yes\""); + pre_args.push_back("-i"); + pre_args.push_back(String("\"") + rsa_key_path + String("\"")); + pre_args.push_back("-p"); + pre_args.push_back(ssh_port); // default is 2222 port + pre_args.push_back("mersdk@localhost"); + } else { // SFDK tool + pre_args.push_back("engine"); + pre_args.push_back("exec"); + } + + switch (package.target.arch) { + case arch_armv7hl: + export_template = EDITOR_GET(prop_editor_binary_arm); + break; + case arch_aarch64: + export_template = EDITOR_GET(prop_editor_binary_arm64); + break; + case arch_x86: + export_template = EDITOR_GET(prop_editor_binary_x86_64); + break; + default: + print_error("Wrong architecture of package"); + return ERR_CANT_CREATE; + } + + if (export_template.is_empty()) { + print_error(String("No ") + arch_to_text(package.target.arch) + String(" template setuped")); + return ERR_CANT_CREATE; + } + + // Validate template architecture compatibility + print_verbose("AuroraOS Export: Validating template architecture..."); + Error template_check = validate_template_architecture(export_template, package.target.arch); + if (template_check != OK) { + print_error("AuroraOS Export: Template architecture validation failed"); + return template_check; + } + + String target_string = mertarget_to_text(package.target); + String export_path = get_absolute_export_path(p_preset->get_export_path()); + String broot_path = export_path + String("_buildroot"); + String build_folder = separator + ("BUILD") + separator; + String rpm_prefix_path = broot_path.left(broot_path.rfind(separator)); + String export_path_part; + String sdk_shared_path; + String rpm_dir_path = broot_path + separator + String("rpm"); + String pck_path = broot_path + separator + package.name + String(".pck"); + String template_path = broot_path + separator; + // String spec_file_path = rpm_dir_path + separator+ package.name + String(".spec"); + String spec_file_path = broot_path + separator + String("SPECS") + separator + package.name + String(".spec"); + String desktop_file_path = broot_path + build_folder + String("/usr/share/applications/").replace("/", separator) + package.name + String(".desktop"); + String package_prefix = "/usr"; //p_preset->get(prop_package_prefix); + String data_dir = package_prefix + String("/share"); + String bin_dir = package_prefix + String("/bin"); +#ifdef WINDOWS_ENABLED + String data_dir_native = data_dir.replace("/", "\\"); + String bin_dir_native = bin_dir.replace("/", "\\"); +#else + String &data_dir_native = data_dir; + String &bin_dir_native = bin_dir; +#endif + // String sailjail_organization_name = GLOBAL_GET(prop_project_organization); + String sailjail_organization_name = p_preset->get(prop_export_application_organization); + + Error err; + bool is_export_path_exits_in_sdk = false; + { + // check avaliable folders in build engine ( from some AuroraSDK version, + // in buildengine has no /home/metsdk/share folder, just /home/username + // folder (in unix systems, with docker) ) + // that mean, expart path in build engine should be same as on host system + // List args; + args.clear(); + String pipe; + int exitcode = 0; + for (List::Element *a = pre_args.front(); a != nullptr; a = a->next()) { + args.push_back(a->get()); + } + args.push_back("bash"); + args.push_back("-c"); + if (export_path.find(shared_home) == 0) + args.push_back(String("if [ -d \"") + shared_home + String("\" ]; then echo returncode_true; exit 0; else echo returncode_false; exit 1; fi")); + else if (export_path.find(shared_src) == 0) + args.push_back(String("if [ -d \"") + shared_src + String("\" ]; then echo returncode_true; exit 0; else echo returncode_false; exit 1; fi")); + else + args.push_back(String("if [ -d \"") + export_path + String("\" ]; then echo returncode_true; exit 0; else echo returncode_false; exit 1; fi")); + + // err = OS::get_singleton()->execute(sfdk_tool, args, true, nullptr, &pipe, &exitcode, false /*read stderr*/); + err = OS::get_singleton()->execute(sfdk_tool, args, &pipe, &exitcode, false); + + if (err == OK) { + if (exitcode == 0 || String("returncode_true") == pipe) { + print_line("Use export path without any changes, its same in host and in build engine"); + is_export_path_exits_in_sdk = true; + } + } else { + print_line(pipe); + } + } + if (!shared_home.is_empty() && export_path.find(shared_home) == 0) { + // print_verbose(String("sfdk") + result_string); + // String execute_binary = sfdk_tool; + export_path_part = export_path.substr(shared_home.length(), export_path.length() - shared_home.length()).replace(separator, "/") + String("_buildroot"); + if (is_export_path_exits_in_sdk) + sdk_shared_path = shared_home; + else + sdk_shared_path = String("/home/mersdk/share"); + } else if (!shared_src.is_empty() && export_path.find(shared_src) == 0) { + export_path_part = export_path.substr(shared_src.length(), export_path.length() - shared_src.length()).replace(separator, "/") + String("_buildroot"); + if (is_export_path_exits_in_sdk) + sdk_shared_path = shared_src; + else + sdk_shared_path = String("/home/src1"); + } else { + print_error(String("Export path outside of SharedHome and SharedSrc:\nSharedHome: ") + shared_home + String("\nSharedSrc: ") + shared_src); + return ERR_CANT_CREATE; + } + + Ref broot = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + const String directories[] = { + separator + String("SPECS"), + build_folder + data_dir_native + separator + package.name + separator + String("lib"), + build_folder + bin_dir_native, + build_folder + String("/usr/share/applications/").replace("/", separator), + build_folder + String("/usr/share/icons/hicolor/86x86/").replace("/", separator) + String("apps"), + build_folder + String("/usr/share/icons/hicolor/108x108/").replace("/", separator) + String("apps"), + build_folder + String("/usr/share/icons/hicolor/128x128/").replace("/", separator) + String("apps"), + build_folder + String("/usr/share/icons/hicolor/172x172/").replace("/", separator) + String("apps"), + String("") + }; + + int path_num = 0; + while (directories[path_num].length() > 0) { + err = broot->make_dir_recursive(broot_path + directories[path_num]); + if (err != Error::OK) { + print_error(String("Cant create directory: ") + broot_path + directories[path_num]); + return ERR_CANT_CREATE; + } + path_num++; + } + + ep.step("copy export tempalte to buildroot", progress_from + (++current_step) * progress_step); + String binary_result_path = broot_path + build_folder + bin_dir_native + separator + package.name; + if (broot->copy(export_template, binary_result_path) != Error::OK) { + print_verbose(String("Template path: ") + export_template); + print_error(String("Cant copy ") + arch_to_text(package.target.arch) + String(" template binary to: ") + binary_result_path); + return ERR_CANT_CREATE; + } + + ep.step(String("create name.pck file.").replace("name", package.name), progress_from + (++current_step) * progress_step); + pck_path = broot_path + build_folder + data_dir_native + separator + package.name + separator + package.name + String(".pck"); + + // Modify GDExtension files for AuroraOS before packing + Error gdext_err = modify_gdextension_files(package.name, p_preset, p_debug); + if (gdext_err != OK) { + print_error("AuroraOS Export: Failed to modify GDExtension files"); + return gdext_err; + } + + // use custom data path + bool use_custom_dir = ProjectSettings::get_singleton()->get("application/config/use_custom_user_dir"); + String custom_path = String(ProjectSettings::get_singleton()->get("application/config/custom_user_dir_name")); + ProjectSettings::get_singleton()->set("application/config/use_custom_user_dir",true); + ProjectSettings::get_singleton()->set( + "application/config/custom_user_dir_name", + package.name + .replace("harbour-", sailjail_organization_name + String("/")) + .replace(sailjail_organization_name + String("."), sailjail_organization_name + String("/")) + ); + err = export_pack(p_preset, p_debug, pck_path); + + // Restore GDExtension files from backup + Error restore_err = restore_gdextension_files(p_preset); + if (restore_err != OK) { + print_error("AuroraOS Export: Failed to restore GDExtension files from backup"); + } + + ProjectSettings::get_singleton()->set("application/config/use_custom_user_dir",use_custom_dir); + ProjectSettings::get_singleton()->set("application/config/custom_user_dir_name", custom_path); + + if (err != Error::OK) { + print_error(String("Cant create *.pck: ") + pck_path); + return err; + } + + ep.step(String("generate ") + package.name + String(".spec file"), progress_from + (++current_step) * progress_step); + { + Ref spec_file = FileAccess::open(spec_file_path, FileAccess::WRITE, &err); + if (err != Error::OK) { + print_error(String("Cant create *.spec: ") + spec_file_path); + return ERR_CANT_CREATE; + } + String spec_text = spec_file_tempalte.replace("%{_gd_application_name}", package.name); + spec_text = spec_text.replace("%{_gd_launcher_name}", package.launcher_name); + spec_text = spec_text.replace("%{_gd_version}", package.version); + spec_text = spec_text.replace("%{_gd_release}", package.release); + spec_text = spec_text.replace("%{_gd_architecture}", arch_to_text(package.target.arch)); + spec_text = spec_text.replace("%{_gd_description}", package.description); + spec_text = spec_text.replace("%{_gd_shared_path}", sdk_shared_path); + spec_text = spec_text.replace("%{_gd_export_path}", export_path_part); + spec_text = spec_text.replace("%{_gd_date}", get_current_date()); + spec_text = spec_text.replace("%{_datadir}", data_dir); + spec_text = spec_text.replace("%{_bindir}", bin_dir); + // spec_text = spec_text.replace("%{_set_topdir}", sdk_shared_path + export_path_part); + + spec_file->store_string(spec_text); + spec_file->flush(); + spec_file->close(); + } + + ep.step(String("generate ") + package.name + String(".desktop file"), progress_from + (++current_step) * progress_step); + { + //desktop_file_path = broot_path + desktop_file_path; + Ref desktop_file = FileAccess::open(desktop_file_path, FileAccess::WRITE, &err); + if (err != Error::OK) { + print_error(String("Cant create *.desktop: ") + desktop_file_path); + return ERR_CANT_CREATE; + } + String file_text = desktop_file_template.replace("%{_gd_launcher_name}", package.launcher_name); + file_text = file_text.replace("%{name}", package.name); + file_text = file_text.replace("%{_datadir}", data_dir); + file_text = file_text.replace("%{_bindir}", bin_dir); + if (sailjail_enabled) { + file_text = file_text + desktop_file_sailjail.replace("%{name}", package.name); + file_text = file_text.replace("%{organization}", sailjail_organization_name); + file_text = file_text.replace("%{appname}", package.name.replace("harbour-","").replace(sailjail_organization_name + String("."),"")); + // collect sailjail permissions + int permission_num = 0; + String permissions; + // minimum required permission + while (!sailjail_minimum_required_permissions[permission_num].is_empty()) { + if (permissions.is_empty()) + permissions = sailjail_minimum_required_permissions[permission_num]; + else + permissions = permissions + String(";") + sailjail_minimum_required_permissions[permission_num]; + permission_num++; + } + // all other permissions + permission_num = 0; + while (!sailjail_permissions[permission_num].is_empty()) { + if (!p_preset->get(String(prop_sailjail_permissions) + sailjail_permissions[permission_num++])) + continue; + if (permissions.is_empty()) + permissions = sailjail_permissions[permission_num - 1]; + else + permissions = permissions + String(";") + sailjail_permissions[permission_num - 1]; + } + file_text = file_text.replace("%{permissions}", permissions); + } + desktop_file->store_string(file_text); + desktop_file->flush(); + desktop_file->close(); + } + + ep.step(String("copy project icons"), progress_from + (++current_step) * progress_step); + int icon_number = 0; + const String icons[] = { + String(p_preset->get(prop_export_icon_86)), "86x86", + String(p_preset->get(prop_export_icon_108)), "108x108", + String(p_preset->get(prop_export_icon_128)), "128x128", + String(p_preset->get(prop_export_icon_172)), "172x172", + // String(GLOBAL_GET(prop_project_icon_86)), "86x86", + // String(GLOBAL_GET(prop_project_icon_108)), "108x108", + // String(GLOBAL_GET(prop_project_icon_128)), "128x128", + // String(GLOBAL_GET(prop_project_icon_172)), "172x172", + String() + }; + + while (icons[icon_number].length() > 0) { + String icon = icons[icon_number]; + // //usr/share/icons/hicolor/128x128/apps/ + String icon_file_path = broot_path + build_folder + String("/usr/share/icons/hicolor/").replace("/", separator) + icons[icon_number + 1] + separator + String("apps") + separator + package.name + String(".png"); + if (broot->copy(icon, icon_file_path) != Error::OK) { + print_error(String("Cant copy icon file \"") + icon + String("\" to \"") + icon_file_path + String("\"")); + return ERR_CANT_CREATE; + } + icon_number += 2; + } + + // Copy GDExtension libraries + ep.step(String("copy GDExtension libraries"), progress_from + (++current_step) * progress_step); + + // Add diagnostic logging before copying libraries + log_gdextension_diagnostics(package.name, p_preset); + + err = copy_gdextension_libraries(broot_path, build_folder, data_dir_native, package.name, p_preset, p_debug); + if (err != Error::OK) { + print_error(String("Failed to copy GDExtension libraries")); + return err; + } + + ep.step(String("setup SDK tool for ") + arch_to_text(package.target.arch) + String(" package build"), progress_from + (++current_step) * progress_step); + { + String build_script; + String buid_script_path = broot_path + separator + String("buildscript.sh"); + { // + Ref script_file = FileAccess::open(buid_script_path, FileAccess::WRITE, &err); + if (err != Error::OK) { + print_error(String("Cant create : ") + buid_script_path); + return ERR_CANT_CREATE; + } + String file_text; + file_text += String("rpmbuild "); + file_text += String("--define "); + file_text += String(String("'_topdir ") + sdk_shared_path + export_path_part + String("'")); + file_text += String(" -ba "); + file_text += String("--without debug --without debuginfo "); + file_text += String("\"") + sdk_shared_path + export_path_part + String("/SPECS/") + package.name + String(".spec\""); + build_script = file_text; + script_file->store_string(file_text); + script_file->flush(); + script_file->close(); + } + + // try use shell ------------------------------- + + // --------------------------------------------- + String result_string; + for (List::Element *a = args.front(); a != nullptr; a = a->next()) { + result_string += String(" ") + a->get(); + } + print_verbose(String("sfdk") + result_string); + String execute_binary = sfdk_tool; + String long_parameter; + + // install deps + args.clear(); + for (List::Element *a = pre_args.front(); a != nullptr; a = a->next()) { + args.push_back(a->get()); + } + args.push_back("sb2"); + args.push_back("-t"); + args.push_back(target_string); + args.push_back("-m"); + args.push_back("sdk-install"); + args.push_back("-R"); + args.push_back("zypper"); + args.push_back("in"); + args.push_back("-y"); + args.push_back("patchelf"); + + int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Install dependencies for ") + target_string, execute_binary, args, true, false); + if (result != 0) { + return ERR_CANT_CREATE; + } + + args.clear(); + for (List::Element *a = pre_args.front(); a != nullptr; a = a->next()) { + args.push_back(a->get()); + } + + if (sdk_tool == SDKConnectType::tool_ssh) { + buid_script_path = sdk_shared_path + export_path_part + separator + String("buildscript.sh"); + } + + args.push_back("sb2"); + args.push_back("-t"); + args.push_back(target_string); + args.push_back("bash"); + args.push_back("-c"); + args.push_back(build_script); + // args.push_back(buid_script_path); + + EditorNode::get_singleton()->execute_and_show_output(TTR("Build rpm package for ") + target_string, execute_binary, args, true, false); + if (result != 0) { + return ERR_CANT_CREATE; + } + print_verbose("Move result RPMS to outside folder"); + { + String build_result_dir = broot_path + separator + String("RPMS") + separator + arch_to_text(package.target.arch); + Ref move_result = DirAccess::create_for_path(build_result_dir); + String result_rpm_name = package.name + String("-") + package.version + String("-") + package.release + String(".") + arch_to_text(package.target.arch) + String(".rpm"); + if (move_result.is_null()) + return ERR_CANT_CREATE; + if (move_result->exists(rpm_prefix_path + separator + result_rpm_name)) + move_result->remove(rpm_prefix_path + separator + result_rpm_name); + err = move_result->rename(build_result_dir + separator + result_rpm_name, rpm_prefix_path + separator + result_rpm_name); + + if (err != Error::OK) { + print_error(TTR("Cant move result of build, please check it by yourself: ") + broot_path + separator + String("RPMS")); + return ERR_CANT_CREATE; + } + + if (sign_rpm) { + String key_path = p_preset->get(prop_export_sign_key); + String cert_path = p_preset->get(prop_export_sign_cert); + String cert_password = p_preset->get(prop_export_sign_password); + ep.step(String("Signing RPM file: ") + result_rpm_name, progress_from + (++current_step) * progress_step); + String rpm_path = rpm_prefix_path + separator + result_rpm_name; + + args.clear(); + for(List::Element *a = pre_args.front(); a != nullptr; a = a->next()) { + args.push_back( a->get() ); + } + args.push_back("sb2"); + args.push_back("-t"); + args.push_back(target_string); + if (cert_password.is_empty()) + cert_password = "nopassword"; // to prevent wait stdin if password not set by user + args.push_back("env"); + args.push_back(String("KEY_PASSPHRASE=") + cert_password); + args.push_back("rpmsign-external"); + args.push_back("sign"); + args.push_back("--key"); + args.push_back(key_path); + args.push_back("--cert"); + args.push_back(cert_path); + args.push_back(rpm_path); + + result = EditorNode::get_singleton()->execute_and_show_output(TTR("Signing RPM package ") + result_rpm_name, execute_binary, args, true, false); + if (result != 0) { + return ERR_CANT_CREATE; + } + } + + if (validate_rpm) { + if (sdk_tool == SDKConnectType::tool_sfdk) { + ep.step(String("Validate RPM file: ") + result_rpm_name, progress_from + (++current_step) * progress_step); + String rpm_path = rpm_prefix_path + separator + result_rpm_name; + + args.clear(); + args.push_back("-c"); + args.push_back(String("target=") + target_string.replace(".default", "")); + args.push_back("check"); + args.push_back(rpm_path); + + result = EditorNode::get_singleton()->execute_and_show_output(TTR("Validate RPM package ") + result_rpm_name, execute_binary, args, true, false); + if (result != 0) { + return ERR_CANT_CREATE; + } + } else { + ep.step(String("Cant Validate RPM file: ") + result_rpm_name, progress_from + (++current_step) * progress_step); + } + } + } + } + ep.step(String("remove temp directory"), progress_from + (++current_step) * progress_step); + { + Ref rmdir = DirAccess::open(broot_path, &err); + if (err != Error::OK) { + print_error("cant open dir"); + } + rmdir->erase_contents_recursive(); //rmdir->remove(<#String p_name#>) + rmdir->remove(broot_path); + } + + ep.step(String("build ") + arch_to_text(package.target.arch) + String(" target success"), progress_from + (++current_step) * progress_step); + return Error::OK; + } + + // Copy GDExtension libraries to buildroot + Error copy_gdextension_libraries(const String &broot_path, const String &build_folder, const String &data_dir_native, const String &package_name, const Ref &p_preset, bool p_debug) { + Vector so_files; + print_verbose("AuroraOS Export: Copying GDExtension libraries"); + + // Use save_pack to collect shared objects without writing to file + print_verbose("AuroraOS Export: Collecting GDExtension libraries via save_pack"); + + // Create a temporary file path for the pack (we won't actually use it) + String temp_pack_path = EditorPaths::get_singleton()->get_temp_dir().path_join("temp_collect_libs.pck"); + + // Call save_pack to collect shared objects + Error err = save_pack(p_preset, p_debug, temp_pack_path, &so_files, nullptr, nullptr, false); + + // Clean up the temporary file if it was created + if (FileAccess::exists(temp_pack_path)) { + DirAccess::remove_file_or_error(temp_pack_path); + } + + if (err != OK) { + print_verbose("AuroraOS Export: Could not collect GDExtension libraries via save_pack, continuing without them"); + return OK; // Continue without libraries - they're optional + } + + if (so_files.size() > 0) { + print_verbose("AuroraOS Export: Found " + String::num_int64(so_files.size()) + " GDExtension libraries"); + + // Copy each library to the lib directory + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (const SharedObject &so : so_files) { + print_verbose("AuroraOS Export: Copying GDExtension library: " + so.path); + String source_path = so.path; + String lib_name = source_path.get_file(); + String dest_path = broot_path + build_folder + data_dir_native + separator + package_name + separator + String("lib") + separator + lib_name; + + print_verbose("AuroraOS Export: Copying GDExtension library: " + source_path + " -> " + dest_path); + + if (da->copy(source_path, dest_path) != Error::OK) { + print_error("AuroraOS Export: Failed to copy GDExtension library: " + source_path); + // Continue with other libraries even if one fails + } + } + } else { + print_verbose("AuroraOS Export: No GDExtension libraries found"); + } + + return OK; + } + + // Modify GDExtension files to use correct paths for AuroraOS + Error modify_gdextension_files(const String &package_name, const Ref &p_preset, bool p_debug) { + print_verbose("AuroraOS Export: Modifying GDExtension files for AuroraOS paths"); + + // Get project resource path + String project_path = ProjectSettings::get_singleton()->get_resource_path(); + + // Find all .gdextension files in the project + Vector gdextension_files; + find_gdextension_files(project_path, gdextension_files); + + if (gdextension_files.size() == 0) { + print_verbose("AuroraOS Export: No GDExtension files found"); + return OK; + } + + print_verbose("AuroraOS Export: Found " + String::num_int64(gdextension_files.size()) + " GDExtension files"); + + // Backup and modify each .gdextension file + for (const String &gdext_path : gdextension_files) { + print_verbose("AuroraOS Export: Processing GDExtension file: " + gdext_path); + + // Create backup + String backup_path = gdext_path + ".auroraos_backup"; + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da->copy(gdext_path, backup_path) != OK) { + print_error("AuroraOS Export: Failed to backup GDExtension file: " + gdext_path); + continue; + } + + // Read original file + Ref file = FileAccess::open(gdext_path, FileAccess::READ); + if (!file.is_valid()) { + print_error("AuroraOS Export: Failed to read GDExtension file: " + gdext_path); + continue; + } + + String content = file->get_as_text(); + file->close(); + + // Modify library paths for AuroraOS + String modified_content = modify_gdextension_content(content, package_name, p_preset, p_debug); + + // Write modified file + file = FileAccess::open(gdext_path, FileAccess::WRITE); + if (!file.is_valid()) { + print_error("AuroraOS Export: Failed to write modified GDExtension file: " + gdext_path); + continue; + } + + file->store_string(modified_content); + file->close(); + + print_verbose("AuroraOS Export: Modified GDExtension file: " + gdext_path); + } + + return OK; + } + + // Restore GDExtension files from backup + Error restore_gdextension_files(const Ref &p_preset) { + print_verbose("AuroraOS Export: Restoring GDExtension files from backup"); + + // Get project resource path + String project_path = ProjectSettings::get_singleton()->get_resource_path(); + + // Find all .gdextension files in the project + Vector gdextension_files; + find_gdextension_files(project_path, gdextension_files); + + // Restore each .gdextension file from backup + for (const String &gdext_path : gdextension_files) { + String backup_path = gdext_path + ".auroraos_backup"; + + if (FileAccess::exists(backup_path)) { + print_verbose("AuroraOS Export: Restoring GDExtension file: " + gdext_path); + + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da->copy(backup_path, gdext_path) != OK) { + print_error("AuroraOS Export: Failed to restore GDExtension file: " + gdext_path); + continue; + } + + // Remove backup file + da->remove(backup_path); + } + } + + return OK; + } + + // Find all .gdextension files in project + void find_gdextension_files(const String &path, Vector &gdextension_files) { + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + da->change_dir(path); + da->list_dir_begin(); + + String file_name = da->get_next(); + while (file_name != "") { + String full_path = path + "/" + file_name; + + if (da->current_is_dir() && file_name != "." && file_name != "..") { + find_gdextension_files(full_path, gdextension_files); + } else if (file_name.ends_with(".gdextension")) { + gdextension_files.push_back(full_path); + } + + file_name = da->get_next(); + } + + da->list_dir_end(); + } + + // Modify GDExtension content to use AuroraOS paths + String modify_gdextension_content(const String &content, const String &package_name, const Ref &p_preset, bool p_debug) { + Vector lines = content.split("\n"); + String modified_content; + bool in_libraries_section = false; + + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + String trimmed_line = line.strip_edges(); + + // Check if we're entering libraries section + if (trimmed_line == "[libraries]") { + in_libraries_section = true; + modified_content += line + "\n"; + continue; + } + + // Check if we're leaving libraries section + if (in_libraries_section && trimmed_line.begins_with("[") && trimmed_line.ends_with("]") && trimmed_line != "[libraries]") { + in_libraries_section = false; + } + + // Modify library paths if we're in libraries section + if (in_libraries_section && trimmed_line.contains("=")) { + String key = trimmed_line.get_slice("=", 0).strip_edges(); + String value = trimmed_line.get_slice("=", 1).strip_edges(); + + // Remove quotes from value if present + if (value.begins_with("\"") && value.ends_with("\"")) { + value = value.substr(1, value.length() - 2); + } + + // Check if this is a target we should modify + bool should_modify = false; + + // Check for AuroraOS-specific targets + if (key.contains("auroraos.")) { + // Only modify if the debug/release type matches + if (p_debug && key.contains("debug")) { + should_modify = true; + } else if (!p_debug && key.contains("release")) { + should_modify = true; + } + } + + if (should_modify) { + // Extract library filename from path + String lib_name = value.get_file(); + // Replace with AuroraOS path + String new_value = String("/usr/share/") + package_name + String("/lib/") + lib_name; + modified_content += key + " = \"" + new_value + "\"\n"; + print_verbose("AuroraOS Export: Modified library path: " + value + " -> " + new_value); + } else { + modified_content += line + "\n"; + } + } else { + modified_content += line + "\n"; + } + } + + return modified_content; + } + + // Validate template architecture compatibility + Error validate_template_architecture(const String &template_path, TargetArch expected_arch) { + print_verbose("AuroraOS Export: Validating template architecture for: " + template_path); + + // Check if file exists + if (!FileAccess::exists(template_path)) { + print_error("AuroraOS Export: Template file does not exist: " + template_path); + return ERR_FILE_NOT_FOUND; + } + + // Try to run 'file' command to check architecture + List args; + args.push_back(template_path); + + String output; + int exit_code; + Error err = OS::get_singleton()->execute("file", args, &output, &exit_code, true); + + if (err == OK && exit_code == 0) { + print_verbose("AuroraOS Export: File command output: " + output); + + // Check architecture in output + String arch_expected = arch_to_text(expected_arch); + bool arch_match = false; + + if (expected_arch == arch_armv7hl) { + arch_match = output.contains("ARM") && (output.contains("32-bit") || output.contains("armv7")); + } else if (expected_arch == arch_aarch64) { + arch_match = output.contains("ARM") && (output.contains("64-bit") || output.contains("aarch64")); + } else if (expected_arch == arch_x86_64) { + arch_match = output.contains("x86-64") || output.contains("x86_64"); + } + + if (!arch_match) { + print_error("AuroraOS Export: Template architecture mismatch!"); + print_error("AuroraOS Export: Expected: " + arch_expected); + print_error("AuroraOS Export: Template file info: " + output); + return ERR_INVALID_DATA; + } else { + print_verbose("AuroraOS Export: Template architecture validated successfully"); + } + } else { + print_verbose("AuroraOS Export: Could not run 'file' command to check architecture (this is not critical)"); + } + + return OK; + } + + // Add diagnostic logging for GDExtension libraries + void log_gdextension_diagnostics(const String &package_name, const Ref &p_preset) { + print_verbose("AuroraOS Export: === GDExtension Diagnostics ==="); + + // Get project resource path + String project_path = ProjectSettings::get_singleton()->get_resource_path(); + + // Find all .gdextension files + Vector gdextension_files; + find_gdextension_files(project_path, gdextension_files); + + print_verbose("AuroraOS Export: Found " + String::num_int64(gdextension_files.size()) + " GDExtension files"); + + for (const String &gdext_path : gdextension_files) { + print_verbose("AuroraOS Export: Processing: " + gdext_path); + + // Read and analyze the file + Ref file = FileAccess::open(gdext_path, FileAccess::READ); + if (file.is_valid()) { + String content = file->get_as_text(); + file->close(); + + // Check for architecture-specific entries + Vector lines = content.split("\n"); + bool in_libraries_section = false; + + for (const String &line : lines) { + String trimmed = line.strip_edges(); + + if (trimmed == "[libraries]") { + in_libraries_section = true; + continue; + } + + if (in_libraries_section && trimmed.begins_with("[") && trimmed.ends_with("]")) { + in_libraries_section = false; + } + + if (in_libraries_section && trimmed.contains("=")) { + String key = trimmed.get_slice("=", 0).strip_edges(); + String value = trimmed.get_slice("=", 1).strip_edges(); + + if (key.contains("armv7hl") || key.contains("aarch64") || key.contains("x86_64") || + key.contains("auroraos") || key.contains("linux")) { + print_verbose("AuroraOS Export: Found library entry: " + key + " = " + value); + } + } + } + } + } + } + +public: + EditorExportPlatformAuroraOS() { + // Ref img = memnew(Image(_auroraos_logo)); + // logo.instance(); + // logo->create_from_image(img); + } + + void get_preset_features(const Ref &p_preset, List *r_features) const override { + r_features->push_back("etc2"); + r_features->push_back("astc"); + + // Add architecture features based on enabled architectures + if (p_preset->get(prop_export_binary_arch_armv7hl).operator bool()) { + r_features->push_back("arm32"); + r_features->push_back("armv7hl"); + } + if (p_preset->get(prop_export_binary_arch_aarch64).operator bool()) { + r_features->push_back("arm64"); + r_features->push_back("aarch64"); + } + if (p_preset->get(prop_export_binary_arch_x86_64).operator bool()) { + r_features->push_back("x86_64"); + } + } + + virtual void get_platform_features(List *r_features) const override { + r_features->push_back("mobile"); + r_features->push_back(get_os_name().to_lower()); + } + + String get_os_name() const override { + return "AuroraOS"; + } + + String get_name() const override { + return "AuroraOS"; + } + + void set_logo(Ref logo) { + this->logo = logo; + } + + Ref get_logo() const override { + return logo; + } + + void get_export_options(List *r_options) const override { + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, prop_editor_sdk_path, PROPERTY_HINT_GLOBAL_DIR)); + bool global_valid = false; + String global_sdk_path = EditorSettings::get_singleton()->get(prop_editor_sdk_path, &global_valid); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_auroraos_sdk_path, PROPERTY_HINT_GLOBAL_DIR), (global_valid) ? global_sdk_path : "")); + + // r_options->push_back(ExportOption(PropertyInfo(Variant::INT, prop_export_binary_arch, PROPERTY_HINT_ENUM, "arm32:0,arm64:1,x86_64:2"), TargetArch::arch_armv7hl)); + for (int i = TargetArch::arch_MIN; i < TargetArch::arch_MAX; i++) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, String(prop_export_binary_arch) + String("/") + arch_to_text((TargetArch)i)), false)); + } + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_application_launcher_name, PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use name from project application/config/name"), GLOBAL_GET(prop_project_launcher_name))); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_application_organization, PROPERTY_HINT_PLACEHOLDER_TEXT, "org.godot"), ""/*GLOBAL_GET(prop_project_organization)*/)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_application_name, PROPERTY_HINT_PLACEHOLDER_TEXT, "example"), ""/*GLOBAL_GET(prop_project_application_name)*/)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_package_version, PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use version from project config: application/config/version"), GLOBAL_GET(prop_project_package_version))); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, prop_export_release_version, PROPERTY_HINT_RANGE, "1,40096,1,or_greater"), 1)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_icon_86, PROPERTY_HINT_GLOBAL_FILE, "*.png"), "res://icons/86.png")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_icon_108, PROPERTY_HINT_GLOBAL_FILE, "*.png"), "res://icons/108.png")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_icon_128, PROPERTY_HINT_GLOBAL_FILE, "*.png"), "res://icons/128.png")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_icon_172, PROPERTY_HINT_GLOBAL_FILE, "*.png"), "res://icons/172.png")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_sign_key, PROPERTY_HINT_GLOBAL_FILE, "*.pem"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_sign_cert, PROPERTY_HINT_GLOBAL_FILE, "*.pem"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_export_sign_password, PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_release, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_debug, PROPERTY_HINT_GLOBAL_FILE), "")); + + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_arm, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_arm_debug, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_aarch64, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_aarch64_debug, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_x86, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_custom_binary_x86_debug, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_package_prefix, PROPERTY_HINT_ENUM, "/usr,/home/nemo/.local"), "/usr")); + + // r_options->push_back(ExportOption(PropertyInfo(Variant::INT, prop_version_release, PROPERTY_HINT_RANGE, "1,40096,1,or_greater"), 1)); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_version_string, PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "1.0.0")); + + // String gename = + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_package_name, PROPERTY_HINT_PLACEHOLDER_TEXT, "harbour-$gename", PROPERTY_USAGE_READ_ONLY), "harbour-$gename")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_package_launcher_name, PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), "")); + + // String global_icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_launcher_icons_86, PROPERTY_HINT_GLOBAL_FILE), "res://icons/86.png")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_launcher_icons_108, PROPERTY_HINT_GLOBAL_FILE), "res://icons/108.png")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_launcher_icons_128, PROPERTY_HINT_GLOBAL_FILE), "res://icons/128.png")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_launcher_icons_172, PROPERTY_HINT_GLOBAL_FILE), "res://icons/172.png")); + + // r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, prop_sailjail_enabled), true)); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_package_organization, PROPERTY_HINT_PLACEHOLDER_TEXT, "org.godot"), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_package_application_name, PROPERTY_HINT_PLACEHOLDER_TEXT, "mygame"), "")); + int permission_num = 0; + while (!sailjail_permissions[permission_num].is_empty()) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, String(prop_sailjail_permissions) + sailjail_permissions[permission_num++]), false)); + } + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, prop_validator_enable), false)); + + // r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, prop_aurora_sign_enable), false)); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_aurora_sign_key, PROPERTY_HINT_GLOBAL_FILE), "")); + // r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, prop_aurora_sign_cert, PROPERTY_HINT_GLOBAL_FILE), "")); + } + + virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override + { + // String binary_template; + String binary_arch; + + Error err; + + String driver = ProjectSettings::get_singleton()->get("rendering/renderer/rendering_method"); + SDKConnectType sdk_tool = SDKConnectType::tool_sfdk; + String tool = EDITOR_GET(prop_editor_tool); + + if (tool == String("ssh")) + sdk_tool = SDKConnectType::tool_ssh; + + if (driver != "gl_compability") { + print_line(TTR("Vulkan render not finished in AuroraOS port! Driver is:") + driver); + //return false; + } + + r_missing_templates = false; + int package_count = 0; + if (p_preset->get(prop_export_binary_arch_armv7hl).operator bool()) { + String template_path = EDITOR_GET(prop_editor_binary_arm); + r_missing_templates = r_missing_templates || template_path.is_empty(); + if (r_missing_templates) { + r_error = TTR(String("Can`t export without AuroraOS export templates: ") + arch_to_text(TargetArch::arch_armv7hl) + template_path); + return false; + } + FileAccess::open(template_path, FileAccess::READ, &err); + if (err != Error::OK) { + r_error = vformat(TTR("Template file not found: \"%s\"."), template_path); + return false; + } + package_count++; + } + if (p_preset->get(prop_export_binary_arch_aarch64).operator bool()) { + String template_path = EDITOR_GET(prop_editor_binary_arm64); + r_missing_templates = r_missing_templates || template_path.is_empty(); + if (r_missing_templates) { + r_error = TTR(String("Can`t export without AuroraOS export templates: ") + arch_to_text(TargetArch::arch_aarch64) ); + return false; + } + FileAccess::open(template_path, FileAccess::READ, &err); + if (err != Error::OK) { + r_error = vformat(TTR("Template file not found: \"%s\"."), template_path); + return false; + } + package_count++; + } + if (p_preset->get(prop_export_binary_arch_x86_64).operator bool()) { + String template_path = EDITOR_GET(prop_editor_binary_x86_64); + r_missing_templates = r_missing_templates || template_path.is_empty(); + if (r_missing_templates) { + r_error = TTR(String("Can`t export without AuroraOS export templates: ") + arch_to_text(TargetArch::arch_x86_64)); + return false; + } + FileAccess::open(template_path, FileAccess::READ, &err); + if (err != Error::OK) { + r_error = vformat(TTR("Template file not found: \"%s\"."), template_path); + return false; + } + package_count++; + } + + if (package_count == 0) { + r_error = TTR("Please select one or more export architectures."); + return false; + } + + if (r_missing_templates) { + r_error = TTR("Can`t export without AuroraOS export templates: "); + r_missing_templates = true; + return false; + } + + // print_verbose(binary_arch + String(" binary: ") + binary_template); + + // Ref template_file = FileAccess::open(binary_template, FileAccess::READ, &err); + // if (err != Error::OK) { + // binary_template.clear(); + // r_error = TTR("Template file not exists!\n"); + // return false; + // } + + // here need check if SDK is exists + // String sfdk_path = String(EDITOR_GET(prop_editor_sdk_path)); + sdk_path = EDITOR_GET(prop_editor_sdk_path); + if (!DirAccess::exists(sdk_path)) { + sdk_path = String(EDITOR_GET(prop_editor_sdk_path)); + if (!DirAccess::exists(sdk_path)) { + r_error = TTR("Wrong AuroraSDK path. Setup it in \nEditor->Settings->Export->AuroraOS->SDK Path,\nor setup it for current project\n"); + return false; + } + } + + // ---- if we use ssh, we need RSA keys for build engine + if (sdk_tool == SDKConnectType::tool_ssh) { + String rsa_key_path = sdk_path; + rsa_key_path += String("/vmshare/ssh/private_keys/engine/mersdk"); + Ref da = DirAccess::open(sdk_path, &err); + if (da.is_null() || !da->file_exists(rsa_key_path)) { + r_error = TTR("Cant find RSA key for access to build engine:\n") + rsa_key_path; + return false; + } + } + + // check SDK version, minimum is 3.0.7 + Ref sdk_release_file = FileAccess::open(sdk_path + separator + String("sdk-release"), FileAccess::READ, &err); + + if (err != Error::OK) { + r_error = TTR("Wrong AuroraSDK path: cant find \"sdk-release\" file\n"); + return false; + } + bool wrong_sdk_version = false; + String current_line; + Vector sdk_version; + sdk_version.resize(3); + while (!sdk_release_file->eof_reached()) { + current_line = sdk_release_file->get_line(); + Vector splitted = current_line.split("="); + if (splitted.size() < 2) + continue; + + if (splitted[0] == String("SDK_RELEASE")) { + RegEx regex("([0-9]+)\\.([0-9]+)\\.([0-9]+)"); + Array matches = regex.search_all(splitted[1]); + // print_verbose( String("Matches size: ") + Variant(matches.size()) ); + if (matches.size() == 1) { + Ref rem = ((Ref)matches[0]); + PackedStringArray names = rem->get_strings(); + if (names.size() >= 4) { + if (names[1].to_int() > 3) { + wrong_sdk_version = false; + } else if (names[1].to_int() < 3) { + r_error = TTR("Minimum AuroraSDK version is 3.0.7, current is ") + current_line.split("=")[1]; + wrong_sdk_version = true; + } else if (names[2].to_int() > 0) { + wrong_sdk_version = false; + } else if (names[3].to_int() < 7) { + r_error = TTR("Minimum AuroraSDK version is 3.0.7, current is ") + current_line.split("=")[1]; + wrong_sdk_version = true; + } + sdk_version.set(0, names[1].to_int()); + sdk_version.set(1, names[2].to_int()); + sdk_version.set(2, names[3].to_int()); + } + } else { + r_error = TTR("Cant parse \"sdk-release\" file in AuroraSDK directory"); + wrong_sdk_version = true; + } + } else if (splitted[0] == String("SDK_CONFIG_DIR")) { + sdk_config_dir = splitted[1]; + } + } + sdk_release_file->close(); + if (wrong_sdk_version) { + //r_error = TTR("Wrong AuroraSDK path: cant find \"sdk-release\" file"); + return false; + } + + String sfdk_path; + + // Chek if tool exists -------------------------------- + Ref da = DirAccess::open(sdk_path, &err); + + if (sdk_tool == SDKConnectType::tool_sfdk) { + sfdk_path = sdk_path + String("/bin/sfdk"); + #ifdef WINDOWS_ENABLED + sfdk_path = sdk_path + String("/bin/sfdk.exe"); + #endif + + if (err != Error::OK || da.is_null() || !da->file_exists(sfdk_path)) { + r_error = TTR("Wrong AuroraSDK path or sfdk tool not exists"); + return false; + } + } else { + sfdk_path = EDITOR_GET(prop_editor_ssh_tool_path); + + if (err != Error::OK || da.is_null() || !da->file_exists(sfdk_path)) { + r_error = TTR("Wrong SSH tool path. Setup it in Editor->Settings->Export->AuroraOS"); + return false; + } + + String rsa_key = sdk_path + String(mersdk_rsa_key); + if (!da->file_exists(rsa_key)) { + r_error = TTR("Cant find RSA key for acces to build engine. Try use AuroraOSIDE for generate keys."); + return false; + } + } + + /// PARSE XML ------------------------------------------ + String xml_path; + String sdk_configs_path = get_sdk_config_path(p_preset); + xml_path = sdk_configs_path + String("/libsfdk/"); + xml_path += String("buildengines.xml"); + + XMLParser *xml_parser = memnew(XMLParser); + if (xml_parser->open(xml_path) != Error::OK) { + memdelete(xml_parser); + r_error = TTR("Cant open XML file: ") + xml_path; + return false; + } + + while (xml_parser->read() == Error::OK) { + if (xml_parser->get_node_type() == XMLParser::NodeType::NODE_ELEMENT) { + if (xml_parser->get_node_name() != String("value")) { + String debug_string = xml_parser->get_node_name(); + print_verbose(String("Node skipping is: ") + debug_string); + //xml_parser->skip_section(); + continue; + } + if (xml_parser->has_attribute("key")) { + if (xml_parser->get_named_attribute_value("key") == String("SharedHome")) { + xml_parser->read(); + shared_home = xml_parser->get_node_data(); + } else if (xml_parser->get_named_attribute_value("key") == String("SharedSrc")) { + xml_parser->read(); + shared_src = xml_parser->get_node_data(); + } + } + } + } + xml_parser->close(); + memdelete(xml_parser); + + bool result = true; + //---- checkl App Icon ------------------------------------------------ + String sizes[] = { + String("86x86"), + String("108x108"), + String("128x128"), + String("172x172") + }; + { // DirAccess + Ref icon_path_check = FileAccess::create(FileAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < 4; i++) { + String current_icon_path = p_preset->get(String(prop_export_icon_config_path) + sizes[i]); + print_verbose(String("Check if icon exists") + current_icon_path); + if (!icon_path_check->exists(current_icon_path)) { + r_error = String("Icon path not exists: ") + current_icon_path; + return false; + } + } + } + // --- Package name ------------------------------------------ + String packname = p_preset->get(prop_export_application_organization).operator String() + String(".") + p_preset->get(prop_export_application_name).operator String(); + print_verbose(vformat("Genrated package name: %s", packname)); + // check packagename (if its set by user) + if (packname.find("$genname") >= 0) { + packname = packname.replace("$genname", ""); + } + // check name by regex + { + String name; // = packname; + RegEx regex("([a-z_\\-0-9\\.]+)"); + Array matches = regex.search_all(packname); + // name.clear(); + for (int mi = 0; mi < matches.size(); mi++) { + Ref rem = ((Ref)matches[mi]); + PackedStringArray names = rem->get_strings(); + for (int n = 1; n < names.size(); n++) { + name += names[n]; + } + } + if (packname != name) { + r_error += TTR("Package name should be in lowercase, only 'a-z,_,-,0-9' symbols"); + return false; + } + } + + String orgname = p_preset->get(prop_export_application_organization); + if (orgname.is_empty()) { + r_error += TTR("Organization name should be reverse domain name, like: org.godot."); + return false; + } + + String appname = p_preset->get(prop_export_application_name); + if (appname.is_empty()) { + r_error += TTR("Application name is empty"); + return false; + } + + String export_path = get_absolute_export_path(p_preset->get_export_path()); + print_verbose(String("Export path: ") + export_path); + if ( shared_home.is_empty() || shared_src.is_empty() || + (export_path.find(shared_home) < 0 && export_path.find(shared_src) < 0) ) { + result = false; + r_error += TTR("Export path is outside of Shared Home in AuroraSDK (choose export path inside shared home):\nSharedHome: ") + shared_home + String("\nShareedSource: ") + shared_src; + } + + // if (p_preset->get(prop_aurora_sign_enable)) { + if (true) { + String key_path = p_preset->get(prop_export_sign_key); + String cert_path =p_preset->get(prop_export_sign_cert); + if (key_path.is_empty() || cert_path.is_empty()) { + r_error += TTR("Signing enabled, but has empty paths for signing key and cert."); + result = false; + } else if (!da->file_exists(key_path) || !da->file_exists(cert_path)) { + r_error += TTR("Key or cert file not exists. Check path to key and cert files."); + result = false; + } + } + + return result; + } + + virtual bool has_valid_project_configuration(const Ref &p_preset, String &r_error) const override + { + // TODO: FIXME + return true; + } + + List get_binary_extensions(const Ref &p_preset) const override { + List ext; + ext.push_back("rpm"); + return ext; + } + + Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags = 0) override { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + EditorProgress ep("export", "Exporting for AuroraOS", 100, true); + ep.step("=== Starting AuroraOS export process ===", 1); + + String sdk_path = EDITOR_GET(prop_editor_sdk_path); + String sdk_configs_path = OS::get_singleton()->get_config_path(); + String sfdk_tool = get_sfdk_path(p_preset); + + print_verbose("AuroraOS Export: SDK path: " + sdk_path); + print_verbose("AuroraOS Export: SFDK tool path: " + sfdk_tool); + + SDKConnectType sdk_tool = SDKConnectType::tool_sfdk; + String tool = EDITOR_GET(prop_editor_tool); + + if (tool == String("ssh")) { + sdk_tool = SDKConnectType::tool_ssh; + print_verbose("AuroraOS Export: Using SSH connection to SDK"); + } else { + print_verbose("AuroraOS Export: Using SFDK tool connection"); + } + + String accept_templates; + + ep.step("checking export template binaries.", 5); + print_verbose("AuroraOS Export: === Phase 1: Checking export template binaries ==="); + + for (int i = TargetArch::arch_MIN; i < TargetArch::arch_MAX; i++) { + String binary_template; + String arch_name = arch_to_text((TargetArch)i); + + print_verbose("AuroraOS Export: Checking architecture: " + arch_name); + + bool arch_enabled = p_preset->get(String(prop_export_binary_arch) + String("/") + arch_name); + print_verbose("AuroraOS Export: Architecture " + arch_name + " enabled in preset: " + (arch_enabled ? "YES" : "NO")); + + if (arch_enabled) { + print_verbose("AuroraOS Export: Architecture " + arch_name + " is enabled in preset"); + switch(i) { + case TargetArch::arch_armv7hl: + binary_template = EDITOR_GET(prop_editor_binary_arm); + print_verbose("AuroraOS Export: ARM32 template path from settings: " + binary_template); + break; + case TargetArch::arch_aarch64: + binary_template = EDITOR_GET(prop_editor_binary_arm64); + print_verbose("AuroraOS Export: ARM64 template path from settings: " + binary_template); + break; + case TargetArch::arch_x86_64: + binary_template = EDITOR_GET(prop_editor_binary_x86_64); + print_verbose("AuroraOS Export: x86_64 template path from settings: " + binary_template); + break; + } + + if (binary_template.is_empty()) { + print_verbose("AuroraOS Export: No " + arch_name + " template setuped - skipping architecture"); + // binary_template = String(p_preset->get(prop_custom_binary_release)); + } else { + if (accept_templates.is_empty()) { + accept_templates = arch_name; + } else { + accept_templates += String(" ") + arch_name; + } + print_verbose("AuroraOS Export: Added " + arch_name + " to accept_templates"); + } + } else { + print_verbose("AuroraOS Export: Architecture " + arch_name + " is disabled in preset"); + } + } + + print_verbose("AuroraOS Export: Final accept_templates string: '" + accept_templates + "'"); + + if (accept_templates.is_empty() /*&& aarch64_template.is_empty() && x86_template.is_empty()*/) { + print_error("AuroraOS Export: No export templates found"); + return ERR_CANT_CREATE; + } + + ep.step("found export template binaries.", 10); + print_verbose("AuroraOS Export: === Phase 2: Getting SDK targets ==="); + List args; + List output_list; + + if (sdk_tool == SDKConnectType::tool_sfdk) { + args.push_back("engine"); + args.push_back("exec"); + } else { // use SSH + String rsa_key_path = sdk_path; + rsa_key_path += String("/vmshare/ssh/private_keys/engine/mersdk"); + sfdk_tool = EDITOR_GET(prop_editor_ssh_tool_path); + String ssh_port = EDITOR_GET(prop_editor_ssh_port); + args.push_back("-o"); + args.push_back("\"IdentitiesOnly=yes\""); + args.push_back("-i"); + args.push_back(String("\"") + rsa_key_path + String("\"")); + args.push_back("-p"); + args.push_back(ssh_port); // default is 2222 port + args.push_back("mersdk@localhost"); + } + args.push_back("sb2-config"); + args.push_back("-l"); + + ep.step("check build targets", 20); + List targets; + { // echo verbose + String result_cmd = sfdk_tool; + for (int i = 0; i < args.size(); i++) + result_cmd += String(" ") + args.get(i); + print_verbose("AuroraOS Export: Executing command: " + result_cmd); + } + //int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Run sfdk tool"), sfdk_tool, args, true, false); + int result = execute_task(sfdk_tool, args, output_list); + if (result != 0) { + String result_cmd; + List::Element *e = output_list.front(); + while (e) { + result_cmd += String("\n") + e->get(); + e = e->next(); + } + EditorNode::get_singleton()->show_warning(TTR("Building of AuroraOS RPM failed, check output for the error.\nAlternatively visit docs.godotengine.org for AuroraOS build documentation.\n Output: ") + result_cmd); + return ERR_CANT_CREATE; + } else { + print_verbose("AuroraOS Export: === Phase 3: Parsing SDK targets ==="); + // parse export targets, and choose two latest targets + List::Element *e = output_list.front(); + while (e) { + String entry = e->get(); + print_verbose("AuroraOS Export: Processing SDK target line: " + entry); + RegEx regex(".*AuroraOS-([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)(-base|-MB2)?-(armv7hl|x86_64|aarch64).*"); + Array matches = regex.search_all(entry); + // print_verbose( String("Matches size: ") + Variant(matches.size()) ); + for (int mi = 0; mi < matches.size(); mi++) { + MerTarget target; + Ref rem = ((Ref)matches[mi]); + PackedStringArray names = rem->get_strings(); + Dictionary formats; + formats["size"] = names.size(); + print_verbose( String("AuroraOS Export: Regex match strings size: {size}").format(formats)); + if (names.size() < 7) { + print_verbose("AuroraOS Export: Wrong match - insufficient groups"); + for (int d = 0; d < names.size(); d++) { + print_verbose(String("AuroraOS Export: match[0].strings[") + String::num_int64(d) + String("]: ") + String(names[d])); + } + target.arch = arch_unkown; + } else { + target.name = "AuroraOS"; // no more SailfishOS support + target.version[0] = names[1].to_int(); + target.version[1] = names[2].to_int(); + target.version[2] = names[3].to_int(); + target.version[3] = names[4].to_int(); + target.addversion = names[5]; + String target_arch = names[6]; + + print_verbose("AuroraOS Export: Parsed target: " + target.name + "-" + names[1] + "." + names[2] + "." + names[3] + "." + names[4] + target.addversion + "-" + target_arch); + + // First, determine the architecture from the string + if (target_arch == String("armv7hl")) { + target.arch = arch_armv7hl; + } else if (target_arch == String("x86_64")) { + target.arch = arch_x86_64; + } else if (target_arch == String("aarch64")) { + target.arch = arch_aarch64; + } else { + target.arch = arch_unkown; + } + + // Then check if this architecture is enabled + bool arch_is_enabled = accept_templates.contains(target_arch); + print_verbose("AuroraOS Export: Target architecture '" + target_arch + "' enabled: " + (arch_is_enabled ? "YES" : "NO")); + print_verbose("AuroraOS Export: accept_templates contains: '" + accept_templates + "'"); + + // Only set template path if architecture is enabled + if (arch_is_enabled) { + if (target.arch == arch_armv7hl) { + target.target_template = EDITOR_GET(prop_editor_binary_arm); + print_verbose("AuroraOS Export: Set ARM32 template: " + target.target_template); + } else if (target.arch == arch_x86_64) { + target.target_template = EDITOR_GET(prop_editor_binary_x86_64); + print_verbose("AuroraOS Export: Set x86_64 template: " + target.target_template); + } else if (target.arch == arch_aarch64) { + target.target_template = EDITOR_GET(prop_editor_binary_arm64); + print_verbose("AuroraOS Export: Set ARM64 template: " + target.target_template); + } + } else { + continue; + } + + print_verbose(String("AuroraOS Export: Target template path: ") + target.target_template); + print_verbose(String("AuroraOS Export: Found target ") + mertarget_to_text(target) + String(" with arch: ") + target_arch); + + bool need_add_to_list = true; + List::Element *it = targets.front(); + for (; it != nullptr; it = it->next()) { + MerTarget current = it->get(); + if (current.arch != target.arch) + continue; + int is_equal = 0; + // check if target is more than added to list + for (int v = 0; v < 4; v++) { + if (current.version[v] > target.version[v]) { + need_add_to_list = false; + it->set(current); + break; + } else if (current.version[v] == target.version[v]) + is_equal++; + } + if (is_equal == 4) + need_add_to_list = false; + if (!need_add_to_list) + continue; + } + if (need_add_to_list) { + print_verbose(String("AuroraOS Export: Target added ") + mertarget_to_text(target) + String(" to export list")); + targets.push_back(target); + } else { + print_verbose(String("AuroraOS Export: Target skipped ") + mertarget_to_text(target) + String(" (duplicate or older version)")); + } + } + } + + e = e->next(); + } + + if (targets.size() == 0) { + print_error("AuroraOS Export: No targets found."); + return ERR_CANT_CREATE; + } + + print_verbose("AuroraOS Export: === Phase 4: Building packages ==="); + print_verbose("AuroraOS Export: Total targets to build: " + String::num_int64(targets.size())); + int one_target_progress_length = (90 - 20) / targets.size(); + int targets_succes = 0, target_num = 0; + for (List::Element *it = targets.front(); it != nullptr; it = it->next(), target_num++) { + print_verbose("AuroraOS Export: === Building target " + String::num_int64(target_num + 1) + " of " + String::num_int64(targets.size()) + " ==="); + NativePackage pack; + + pack.release = vformat("%d", p_preset->get(prop_export_release_version).operator signed int()); + print_verbose(String("AuroraOS Export: Release version tag: ") + pack.release); + pack.description = ProjectSettings::get_singleton()->get("application/config/description"); + pack.launcher_name = p_preset->get(prop_export_application_launcher_name); + if (pack.launcher_name.is_empty()) { + print_verbose("AuroraOS Export: Launcher name is empty, getting from project settings"); + pack.launcher_name = GLOBAL_GET(prop_project_launcher_name); + } + if (pack.launcher_name.is_empty()) + pack.launcher_name = ProjectSettings::get_singleton()->get("application/config/name"); + pack.name = p_preset->get(prop_export_application_organization).operator String() + String(".") + p_preset->get(prop_export_application_name).operator String(); + print_verbose(vformat("AuroraOS Export: Generated package name: %s", pack.name)); + print_verbose(vformat("AuroraOS Export: Launcher name: %s", pack.launcher_name)); + + pack.version = p_preset->get(prop_export_package_version); + if (pack.version.is_empty()) + pack.version = GLOBAL_GET(prop_project_package_version); + print_verbose(vformat("AuroraOS Export: Package version: %s", pack.version)); + // TODO arch should be generated from current MerTarget + pack.target = it->get(); + print_verbose("AuroraOS Export: Target: " + mertarget_to_text(pack.target)); + print_verbose("AuroraOS Export: Target template path: " + pack.target.target_template); + + if (pack.target.target_template.is_empty()) { + print_error(String("AuroraOS Export: Target ") + mertarget_to_text(it->get()) + String(" template path is empty. Skip")); + print_verbose("AuroraOS Export: REASON: Template path is empty - this means the architecture was not properly enabled or template not configured"); + continue; + } + + print_verbose("AuroraOS Export: Starting build_package for target: " + mertarget_to_text(pack.target)); + if (build_package(pack, p_preset, p_debug, sfdk_tool, ep, 20 + one_target_progress_length * target_num, one_target_progress_length) != Error::OK) { + // TODO Warning mesasgebox + print_error(String("AuroraOS Export: Target ") + mertarget_to_text(it->get()) + String(" not exported succesfully")); + } else { + targets_succes++; + print_verbose("AuroraOS Export: Target " + mertarget_to_text(it->get()) + " exported successfully"); + } + } + if (targets_succes == targets.size()) { + ep.step("all targets build succes", 100); + print_verbose("AuroraOS Export: === All targets built successfully ==="); + } else { + // TODO add Warning messagebox + ep.step("Not all targets builded", 100); + print_verbose("AuroraOS Export: === Not all targets built successfully ==="); + print_verbose("AuroraOS Export: Successful targets: " + String::num_int64(targets_succes) + " / " + String::num_int64(targets.size())); + } + } + + print_verbose("AuroraOS Export: === AuroraOS export process completed ==="); + return Error::OK; + } + + void resolve_platform_feature_priorities(const Ref &p_preset, HashSet &p_features) override { + } + +protected: + Ref logo; + mutable String shared_home; + mutable String shared_src; + mutable String sdk_config_dir; + mutable String sdk_path; + mutable SDKConnectType sdk_connection_type; +}; + +void register_auroraos_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformAuroraOS); +} + +void register_auroraos_exporter() { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = "*.exe"; + } + + // Ref platform; + // platform.instance(); + Ref platform = Ref(memnew(EditorExportPlatformAuroraOS)); + + if (EditorNode::get_singleton()) { + Ref img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + ImageLoaderSVG::create_image_from_string(img, _auroraos_logo_svg, EDSCALE, upsample, false); + Ref logo = ImageTexture::create_from_image(img); + platform->set_logo(logo); + } + + EDITOR_DEF_BASIC(prop_editor_sdk_path, ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, prop_editor_sdk_path, PROPERTY_HINT_GLOBAL_DIR)); + +// #ifdef WINDOWS_ENABLED +// EDITOR_DEF(prop_editor_tool, "ssh"); +// #else + EDITOR_DEF_BASIC(prop_editor_tool, "sfdk"); +// #endif + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, prop_editor_tool, PROPERTY_HINT_ENUM, "sfdk,ssh")); + +#ifndef WINDOWS_ENABLED + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da->file_exists("/usr/bin/ssh")) + EDITOR_DEF_BASIC(prop_editor_ssh_tool_path, "/usr/bin/ssh"); + else + EDITOR_DEF_BASIC(prop_editor_ssh_tool_path, ""); + // da.reset() +#else + EDITOR_DEF_BASIC(prop_editor_ssh_tool_path, ""); +#endif + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, prop_editor_ssh_tool_path, PROPERTY_HINT_GLOBAL_FILE, exe_ext)); + + EDITOR_DEF_BASIC(prop_editor_ssh_port, "2222"); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, prop_editor_ssh_port, PROPERTY_HINT_RANGE, "1,40096,1,false")); + + EDITOR_DEF_BASIC(prop_editor_binary_arm, ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, prop_editor_binary_arm, PROPERTY_HINT_GLOBAL_FILE, "")); + + EDITOR_DEF_BASIC(prop_editor_binary_arm64, ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, prop_editor_binary_arm64, PROPERTY_HINT_GLOBAL_FILE, "")); + + EDITOR_DEF_BASIC(prop_editor_binary_x86_64, ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, prop_editor_binary_x86_64, PROPERTY_HINT_GLOBAL_FILE, "")); + + // GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, prop_project_application_name, PROPERTY_HINT_TYPE_STRING, ""), ""); + // GLOBAL_DEF_BASIC(prop_project_organization, ""); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_organization, PROPERTY_HINT_TYPE_STRING, "")); + + // GLOBAL_DEF_BASIC(prop_project_application_name, ""); + // ProjectSettings::get_singleton()->set(prop_project_application_name, ""); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_application_name, PROPERTY_HINT_TYPE_STRING, "")); + + // GLOBAL_DEF_BASIC(prop_project_release_version, ""); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::INT, prop_project_release_version, PROPERTY_HINT_RANGE, "1,40096,1,or_greater", 1)); + // GLOBAL_DEF_BASIC(prop_project_icon_86, "res://icons/86.png"); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_icon_86, PROPERTY_HINT_GLOBAL_FILE, "*.png")); + // GLOBAL_DEF_BASIC(prop_project_icon_108, "res://icons/108.png"); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_icon_108, PROPERTY_HINT_GLOBAL_FILE, "*.png")); + // GLOBAL_DEF_BASIC(prop_project_icon_128, "res://icons/128.png"); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_icon_128, PROPERTY_HINT_GLOBAL_FILE, "*.png")); + // GLOBAL_DEF_BASIC(prop_project_icon_172, "res://icons/172.png"); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_icon_172, PROPERTY_HINT_GLOBAL_FILE, "*.png")); + // GLOBAL_DEF_BASIC(prop_project_sign_key, ""); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_sign_key, PROPERTY_HINT_GLOBAL_FILE, "")); + // GLOBAL_DEF_BASIC(prop_project_sign_cert, ""); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_sign_cert, PROPERTY_HINT_GLOBAL_FILE, "")); + // GLOBAL_DEF_BASIC(prop_project_sign_password_file, ""); + // ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, prop_project_sign_password_file, PROPERTY_HINT_GLOBAL_FILE, "")); + + EditorExport::get_singleton()->add_export_platform(platform); +} diff --git a/platform/auroraos/export/export.h b/platform/auroraos/export/export.h new file mode 100644 index 000000000000..914a9a115656 --- /dev/null +++ b/platform/auroraos/export/export.h @@ -0,0 +1,34 @@ +/*************************************************************************/ +/* export.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include + +void register_auroraos_exporter(); +void register_auroraos_exporter_types(); + diff --git a/platform/auroraos/export/logo.svg b/platform/auroraos/export/logo.svg new file mode 100644 index 000000000000..cfbdbec523e4 --- /dev/null +++ b/platform/auroraos/export/logo.svg @@ -0,0 +1,74 @@ + + + + diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index b5e80f4a4dbc..f3eb18d5b472 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -51,6 +51,7 @@ def get_opts(): BoolVariable("libdecor", "Enable libdecor support", True), BoolVariable("touch", "Enable touch events", True), BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), + BoolVariable("auroraos", "Build for AuroraOS", False), ] @@ -76,6 +77,14 @@ def configure(env: "SConsEnvironment"): supported_arches = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "loongarch64"] validate_arch(env["arch"], get_name(), supported_arches) + # Check if we build for AuroraOS + if env["auroraos"]: + env.Append(CPPDEFINES=["AURORAOS_ENABLED"]) + env["x11"] = False + env["libdecor"] = False + env["wayland"] = True + env["alsa"] = False + ## Build type if env.dev_build: diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 9fba97d552a0..9254a93b4c3a 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -251,7 +251,9 @@ String OS_LinuxBSD::get_identifier() const { } String OS_LinuxBSD::get_name() const { -#ifdef __linux__ +#ifdef AURORAOS_ENABLED + return "AuroraOS"; +#elif defined(__linux__) return "Linux"; #elif defined(__FreeBSD__) return "FreeBSD"; @@ -547,6 +549,12 @@ bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { } #endif +#ifdef AURORAOS_ENABLED + if (p_feature == "auroraos") { + return true; + } +#endif + #ifndef __linux__ // `bsd` includes **all** BSD, not only "other BSD" (see `get_name()`). if (p_feature == "bsd") { @@ -558,7 +566,7 @@ bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { return true; } - // Match against the specific OS (`linux`, `freebsd`, `netbsd`, `openbsd`). + // Match against the specific OS (`linux`, `freebsd`, `netbsd`, `openbsd`, `auroraos`). if (p_feature == get_name().to_lower()) { return true; } diff --git a/platform/linuxbsd/platform_gl.h b/platform/linuxbsd/platform_gl.h index 5d4c2d897860..dc139205d372 100644 --- a/platform/linuxbsd/platform_gl.h +++ b/platform/linuxbsd/platform_gl.h @@ -31,7 +31,7 @@ #ifndef PLATFORM_GL_H #define PLATFORM_GL_H -#ifndef GL_API_ENABLED +#if !defined(GL_API_ENABLED) && !defined(AURORAOS_ENABLED) #define GL_API_ENABLED // Allow using desktop GL. #endif @@ -39,7 +39,17 @@ #define GLES_API_ENABLED // Allow using GLES. #endif +#ifdef GLAD_ENABLED #include "thirdparty/glad/glad/egl.h" #include "thirdparty/glad/glad/gl.h" +#else +#include +#include +#include +#include +#include +#include +#include +#endif #endif // PLATFORM_GL_H diff --git a/platform/linuxbsd/wayland/SCsub b/platform/linuxbsd/wayland/SCsub index 4cceb49977a5..83de249853e2 100644 --- a/platform/linuxbsd/wayland/SCsub +++ b/platform/linuxbsd/wayland/SCsub @@ -152,9 +152,16 @@ env.NoCache( "protocol/xdg_system_bell.gen.c", "#thirdparty/wayland-protocols/staging/xdg-system-bell/xdg-system-bell-v1.xml", ), + env.WAYLAND_API_HEADER( + "protocol/surface-extension.gen.h", + "#thirdparty/wayland-protocols/stable/surface-extension/surface-extension.xml" + ), + env.WAYLAND_API_CODE( + "protocol/surface-extension.gen.c", + "#thirdparty/wayland-protocols/stable/surface-extension/surface-extension.xml" + ) ) - source_files = [ "protocol/wayland.gen.c", "protocol/viewporter.gen.c", @@ -172,6 +179,7 @@ source_files = [ "protocol/idle_inhibit.gen.c", "protocol/tablet.gen.c", "protocol/text_input.gen.c", + "protocol/surface-extension.gen.c", "display_server_wayland.cpp", "wayland_thread.cpp", "key_mapping_xkb.cpp", diff --git a/platform/linuxbsd/wayland/detect_prime_egl.cpp b/platform/linuxbsd/wayland/detect_prime_egl.cpp index e24c03c869d4..0fa892a3234c 100644 --- a/platform/linuxbsd/wayland/detect_prime_egl.cpp +++ b/platform/linuxbsd/wayland/detect_prime_egl.cpp @@ -99,7 +99,11 @@ void DetectPrimeEGL::create_context(EGLenum p_platform_enum) { EGLint context_attribs[] = { EGL_CONTEXT_MAJOR_VERSION, 3, +#if defined(AURORAOS_ENABLED) + EGL_CONTEXT_MINOR_VERSION, 0, +#else EGL_CONTEXT_MINOR_VERSION, 3, +#endif EGL_NONE }; diff --git a/platform/linuxbsd/wayland/detect_prime_egl.h b/platform/linuxbsd/wayland/detect_prime_egl.h index 3391e020d8b2..497251dac9ad 100644 --- a/platform/linuxbsd/wayland/detect_prime_egl.h +++ b/platform/linuxbsd/wayland/detect_prime_egl.h @@ -40,9 +40,19 @@ #else #include #include +#if !defined(AURORAOS_ENABLED) #include #define GLAD_EGL_VERSION_1_5 1 +#else +#include +#include +#include +#include +#include + +#define GLAD_EGL_VERSION_1_5 0 +#endif #ifdef EGL_EXT_platform_base #define GLAD_EGL_EXT_platform_base 1 diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 5334446b2248..a97c2147a9d7 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -96,6 +96,105 @@ void DisplayServerWayland::_dispatch_input_event(const Ref &p_event) } } +#ifdef AURORAOS_ENABLED +void DisplayServerWayland::_sensor_orientation_changed(DisplayServer::ScreenOrientation _orientation, const Size2i &size) { + DisplayServer::ScreenOrientation new_orientation; + + device_orientation = _orientation; + native_size = size; + print_verbose(vformat("Native size: %dx%d", native_size.x, native_size.y)); + + if (orientation < SCREEN_SENSOR_LANDSCAPE) { + new_orientation = orientation; + } else if (orientation >= SCREEN_SENSOR_LANDSCAPE) { + switch(orientation) { + case SCREEN_SENSOR_LANDSCAPE: + if (_orientation == SCREEN_LANDSCAPE || _orientation == SCREEN_REVERSE_LANDSCAPE) + new_orientation = _orientation; + else if (_orientation == SCREEN_PORTRAIT) + new_orientation = sensor_orientation == SCREEN_REVERSE_LANDSCAPE ? SCREEN_REVERSE_LANDSCAPE : SCREEN_LANDSCAPE; + else if (_orientation == SCREEN_REVERSE_PORTRAIT) + new_orientation = sensor_orientation == SCREEN_LANDSCAPE ? SCREEN_LANDSCAPE : SCREEN_REVERSE_LANDSCAPE; + break; + case SCREEN_SENSOR_PORTRAIT: + if (_orientation == SCREEN_PORTRAIT || _orientation == SCREEN_REVERSE_PORTRAIT) + new_orientation = _orientation; + else if (_orientation == SCREEN_LANDSCAPE) + new_orientation = sensor_orientation == SCREEN_REVERSE_PORTRAIT ? SCREEN_REVERSE_PORTRAIT : SCREEN_PORTRAIT; + else if (_orientation == SCREEN_REVERSE_LANDSCAPE) + new_orientation = sensor_orientation == SCREEN_PORTRAIT ? SCREEN_PORTRAIT : SCREEN_REVERSE_PORTRAIT; + break; + default: + new_orientation = _orientation; + } + } else { + return; + } + + if (new_orientation == sensor_orientation) { + return; + } + sensor_orientation = new_orientation; + + WindowData &wd = main_window; + + struct wl_surface *wl_surface = wayland_thread.window_get_wl_surface(wd.id); + int32_t buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; + + switch(sensor_orientation) { + case SCREEN_REVERSE_LANDSCAPE: + buffer_transform = WL_OUTPUT_TRANSFORM_90; + break; + case SCREEN_REVERSE_PORTRAIT: + buffer_transform = WL_OUTPUT_TRANSFORM_180; + break; + case SCREEN_LANDSCAPE: + buffer_transform = WL_OUTPUT_TRANSFORM_270; + break; + } + wl_surface_set_buffer_transform(wl_surface, buffer_transform); + + if (native_size.x == 1 && native_size.y == 1) { + native_size = wd.rect.size; + print_verbose(vformat("Use window size as native size for first start %dx%d", native_size.x, native_size.y)); + } + + Size2i new_size = size; + switch(sensor_orientation) { + case DisplayServer::SCREEN_PORTRAIT: + print_verbose("Message incoming sensor orientation:SCREEN_PORTRAIT"); + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + if (size.width < size.height) { + new_size = size; + } else { + new_size = {size.y, size.x}; + } + print_verbose("Message incoming sensor orientation:SCREEN_REVERSE_PORTRAIT"); + break; + case DisplayServer::SCREEN_LANDSCAPE: + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + if (size.width < size.height) { + new_size = {size.y, size.x}; + } else { + new_size = size; + } + print_verbose("Message incoming sensor orientation:SCREEN_LANDSCAPE or REVERSE"); + break; + } + + main_window.rect.size = new_size; +#ifdef RD_ENABLED + if (wd.visible && rendering_context) { + print_verbose(vformat("DEBUG: rendering_context->window_set_size %dx%d", wd.rect.size.width, wd.rect.size.height)); + rendering_context->window_set_size(MAIN_WINDOW_ID, wd.rect.size.width, wd.rect.size.height); + } +#endif + if (wd.rect_changed_callback.is_valid()) { + wd.rect_changed_callback.call(wd.rect); + } +} +#endif + void DisplayServerWayland::_resize_window(const Size2i &p_size) { WindowData &wd = main_window; @@ -157,6 +256,9 @@ void DisplayServerWayland::_show_window() { Error err = rendering_context->window_create(wd.id, &wpd); ERR_FAIL_COND_MSG(err != OK, vformat("Can't create a %s window", rendering_driver)); + // TODO: AuroraOS made window fullscreen size by default (and use safezonerect in future) + DEBUG_LOG_WAYLAND(vformat("Set window size: %dx%d", wd.rect.size.width, wd.rect.size.height)); + rendering_context->window_set_size(wd.id, wd.rect.size.width, wd.rect.size.height); rendering_context->window_set_vsync_mode(wd.id, wd.vsync_mode); @@ -172,6 +274,13 @@ void DisplayServerWayland::_show_window() { #ifdef GLES3_ENABLED if (egl_manager) { struct wl_surface *wl_surface = wayland_thread.window_get_wl_surface(wd.id); +#if defined(AURORAOS_ENABLED) + WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wl_surface); + if (ws) { + wd.rect.set_size(ws->rect.size); + DEBUG_LOG_WAYLAND(vformat("Set window size to screen size: %dx%d", wd.rect.size.width, wd.rect.size.height)); + } +#endif wd.wl_egl_window = wl_egl_window_create(wl_surface, wd.rect.size.width, wd.rect.size.height); Error err = egl_manager->window_create(MAIN_WINDOW_ID, wayland_thread.get_wl_display(), wd.wl_egl_window, wd.rect.size.width, wd.rect.size.height); @@ -202,8 +311,12 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const { case FEATURE_MOUSE: case FEATURE_MOUSE_WARP: case FEATURE_CLIPBOARD: +#ifndef AURORAOS_ENABLED case FEATURE_CURSOR_SHAPE: case FEATURE_CUSTOM_CURSOR_SHAPE: +#else + case FEATURE_ORIENTATION: +#endif case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_HIDPI: case FEATURE_SWAP_BUFFERS: @@ -615,6 +728,43 @@ float DisplayServerWayland::screen_get_refresh_rate(int p_screen) const { return wayland_thread.screen_get_data(p_screen).refresh_rate; } +bool DisplayServerWayland::is_touchscreen_available() const { + // MutexLock mutex_lock(wayland_thread.mutex); + return wayland_thread.get_touch_avaliable(); +} + +void DisplayServerWayland::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { + orientation = p_orientation; +#ifdef AURORAOS_ENABLED + switch(p_orientation) { + case SCREEN_LANDSCAPE: DEBUG_LOG_WAYLAND("Set allowed orientation to SCREEN_LANDSCAPE;"); break; + case SCREEN_PORTRAIT: DEBUG_LOG_WAYLAND("Set allowed orientation to SCREEN_PORTRAIT;"); break; + case SCREEN_REVERSE_LANDSCAPE: DEBUG_LOG_WAYLAND("Set allowed orientation to SCREEN_REVERSE_LANDSCAPE;"); break; + case SCREEN_REVERSE_PORTRAIT: DEBUG_LOG_WAYLAND("Set allowed orientation to SCREEN_REVERSE_PORTRAIT;"); break; + case SCREEN_SENSOR_LANDSCAPE: DEBUG_LOG_WAYLAND("Set allowed orientation to SCREEN_SENSOR_LANDSCAPE;"); break; + case SCREEN_SENSOR_PORTRAIT: DEBUG_LOG_WAYLAND("Set allowed orientation to SCREEN_SENSOR_PORTRAIT;"); break; + case SCREEN_SENSOR: DEBUG_LOG_WAYLAND("Set allowed orientation to SCREEN_SENSOR;"); break; + } + _sensor_orientation_changed(device_orientation, native_size); +#endif +} + +DisplayServer::ScreenOrientation DisplayServerWayland::screen_get_orientation(int p_screen) const { + return orientation; +} + +#ifdef AURORAOS_ENABLED +DisplayServer::ScreenOrientation DisplayServerWayland::screen_get_sensor_orientation(int p_screen) const { + if (orientation < SCREEN_SENSOR_LANDSCAPE) + return orientation; + return sensor_orientation; +} + +Size2i DisplayServerWayland::screen_get_native_size() const { + return native_size; +} +#endif + void DisplayServerWayland::screen_set_keep_on(bool p_enable) { MutexLock mutex_lock(wayland_thread.mutex); @@ -1192,12 +1342,79 @@ void DisplayServerWayland::try_suspend() { } } +static void _transform_vector( + const DisplayServer::ScreenOrientation &native_orientation, + const DisplayServer::ScreenOrientation &orientation, + const Size2i &native_size, + Vector2 &pos, + Vector2 &relative) { + real_t tmp; + switch(orientation) { + case DisplayServer::SCREEN_PORTRAIT: + if (native_orientation == DisplayServer::SCREEN_LANDSCAPE) { + tmp = native_size.x - pos.x; + pos.x = pos.y; + pos.y = tmp; + + tmp = relative.x; + relative.x = relative.y; + relative.y = tmp; + } + break; + case DisplayServer::SCREEN_LANDSCAPE: + if (native_orientation == DisplayServer::SCREEN_PORTRAIT) { + tmp = native_size.x - pos.x; + pos.x = pos.y; + pos.y = tmp; + + tmp = -relative.x; + relative.x = relative.y; + relative.y = tmp; + } + break; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + if (native_orientation == DisplayServer::SCREEN_PORTRAIT) { + tmp = pos.x; + pos.x = native_size.y - pos.y; + pos.y = tmp; + + tmp = relative.x; + relative.x = -relative.y; + relative.y = tmp; + } else { + tmp = native_size.x - pos.x; + pos.x = native_size.y - pos.y; + pos.y = tmp; + + relative.x = -relative.x; + relative.y = -relative.y; + } + break; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + if (native_orientation == DisplayServer::SCREEN_PORTRAIT) { + pos.x = native_size.x - pos.x; + pos.y = native_size.y - pos.y; + + relative.x = -relative.x; + relative.y = -relative.y; + } + break; + } +} + void DisplayServerWayland::process_events() { wayland_thread.mutex.lock(); while (wayland_thread.has_message()) { Ref msg = wayland_thread.pop_message(); +#ifdef AURORAOS_ENABLED + Ref orientation_msg = msg; + if (orientation_msg.is_valid()) { + _sensor_orientation_changed(orientation_msg->orientation, orientation_msg->real_size); + } +#endif + Ref winrect_msg = msg; if (winrect_msg.is_valid()) { _resize_window(winrect_msg->rect.size); @@ -1221,6 +1438,32 @@ void DisplayServerWayland::process_events() { Ref inputev_msg = msg; if (inputev_msg.is_valid()) { +#ifdef AURORAOS_ENABLED + Ref touch_event = inputev_msg->event; + if (touch_event.is_valid()) { + Vector2 pos = touch_event->get_position(); + Vector2 relative {1, 1}; + _transform_vector(native_size.x > native_size.y ? SCREEN_LANDSCAPE : SCREEN_PORTRAIT, + screen_get_sensor_orientation(), + native_size, + pos, + relative); + touch_event->set_position(pos); + } + + Ref drag_event = inputev_msg->event; + if (drag_event.is_valid()) { + Vector2 pos = drag_event->get_position(); + Vector2 relative = drag_event->get_relative(); + _transform_vector(native_size.x > native_size.y ? SCREEN_LANDSCAPE : SCREEN_PORTRAIT, + screen_get_sensor_orientation(), + native_size, + pos, + relative); + drag_event->set_position(pos); + drag_event->set_relative(relative); + } +#endif Input::get_singleton()->parse_input_event(inputev_msg->event); } @@ -1483,6 +1726,7 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win } #endif // SOWRAP_ENABLED +#ifndef AURORAOS_ENABLED if (getenv("DRI_PRIME") == nullptr) { int prime_idx = -1; @@ -1524,6 +1768,7 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win setenv("DRI_PRIME", itos(prime_idx).utf8().ptr(), 1); } } +#endif if (rendering_driver == "opengl3") { egl_manager = memnew(EGLManagerWayland); diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index 4e21aea6a553..e42fd9c24156 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -123,6 +123,13 @@ class DisplayServerWayland : public DisplayServer { WindowData main_window; WaylandThread wayland_thread; + DisplayServer::ScreenOrientation orientation = DisplayServer::SCREEN_PORTRAIT; +#ifdef AURORAOS_ENABLED + DisplayServer::ScreenOrientation sensor_orientation = DisplayServer::SCREEN_PORTRAIT; + DisplayServer::ScreenOrientation device_orientation = DisplayServer::SCREEN_PORTRAIT; + Size2i native_size{1, 1}; +#endif + Context context; String ime_text; @@ -159,7 +166,9 @@ class DisplayServerWayland : public DisplayServer { static void dispatch_input_events(const Ref &p_event); void _dispatch_input_event(const Ref &p_event); - +#ifdef AURORAOS_ENABLED + void _sensor_orientation_changed(DisplayServer::ScreenOrientation _orientation, const Size2i &size); +#endif void _resize_window(const Size2i &p_size); virtual void _show_window(); @@ -219,6 +228,15 @@ class DisplayServerWayland : public DisplayServer { virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual bool is_touchscreen_available() const override; + + virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override; + virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +#ifdef AURORAOS_ENABLED + virtual ScreenOrientation screen_get_sensor_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Size2i screen_get_native_size() const override; +#endif + virtual void screen_set_keep_on(bool p_enable) override; virtual bool screen_is_kept_on() const override; diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index e34807763b2a..120906f31fb7 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -36,8 +36,12 @@ #include #else // Assume Linux. +#ifdef AURORAOS_ENABLED +#include +#else #include #endif +#endif // For the actual polling thread. #include @@ -325,6 +329,14 @@ bool WaylandThread::_load_cursor_theme(int p_cursor_size) { cursor_theme_name = "default"; } + +#if !defined(AURORAOS_ENABLED) + for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) { + wl_cursors[i] = nullptr; + } + + return true; + print_verbose(vformat("Loading cursor theme \"%s\" size %d.", cursor_theme_name, p_cursor_size)); wl_cursor_theme = wl_cursor_theme_load(cursor_theme_name.utf8().get_data(), p_cursor_size, registry.wl_shm); @@ -386,7 +398,14 @@ bool WaylandThread::_load_cursor_theme(int p_cursor_size) { } } + return false; +#else + for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) { + wl_cursors[i] = nullptr; + } + return true; +#endif } void WaylandThread::_update_scale(int p_scale) { @@ -418,6 +437,17 @@ void WaylandThread::_update_scale(int p_scale) { } } +void WaylandThread::_wl_display_on_error(void *data, struct wl_display *wl_display, void *object_id, uint32_t code, const char *message) { + RegistryState *registry = (RegistryState *)data; + ERR_FAIL_NULL(registry); + + WARN_PRINT(vformat("wl_display error [code: %i, object_id: %l]:.%s", (uint64_t)object_id, code, message)); +} + +void WaylandThread::_wl_display_on_delete_id(void *data, struct wl_display *wl_display, uint32_t id) { + DEBUG_LOG_WAYLAND_THREAD(vformat("delete_id: %i", id)); +} + void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) { RegistryState *registry = (RegistryState *)data; ERR_FAIL_NULL(registry); @@ -530,6 +560,20 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re return; } + if (strcmp(interface, wl_shell_interface.name) == 0) { + registry->wl_shell = (struct wl_shell*)wl_registry_bind(wl_registry, name, &wl_shell_interface, 1); + registry->wl_shell_name = name; + return; + } + + if (strcmp(interface, qt_surface_extension_interface.name) == 0) + { //TODO" qtwayland compability + registry->qt_surface_extension = (struct qt_surface_extension*)wl_registry_bind(wl_registry, name, + &qt_surface_extension_interface, 1); + registry->qt_surface_extension_name = name; + return; + } + if (strcmp(interface, xdg_wm_base_interface.name) == 0) { registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, CLAMP((int)version, 1, 6)); registry->xdg_wm_base_name = name; @@ -726,6 +770,28 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry return; } + if (name == registry->wl_shell_name) { + if (registry->wl_shell) { + wl_shell_destroy(registry->wl_shell); + registry->wl_shell = nullptr; + } + + registry->wl_shell_name = 0; + + return; + } + + if (name == registry->qt_surface_extension_name) { + if (registry->qt_surface_extension) { + qt_surface_extension_destroy(registry->qt_surface_extension); + registry->qt_surface_extension = nullptr; + } + + registry->qt_surface_extension_name = 0; + + return; + } + if (name == registry->wp_viewporter_name) { WindowState *ws = ®istry->wayland_thread->main_window; @@ -1113,10 +1179,31 @@ void WaylandThread::_wl_output_on_geometry(void *data, struct wl_output *wl_outp ss->pending_data.make.parse_utf8(make); ss->pending_data.model.parse_utf8(model); + switch(transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + ss->pending_data.orientation = DisplayServer::SCREEN_PORTRAIT; + break; + case WL_OUTPUT_TRANSFORM_90: + ss->pending_data.orientation = DisplayServer::SCREEN_REVERSE_LANDSCAPE; + break; + case WL_OUTPUT_TRANSFORM_180: + ss->pending_data.orientation = DisplayServer::SCREEN_REVERSE_PORTRAIT; + break; + case WL_OUTPUT_TRANSFORM_270: + ss->pending_data.orientation = DisplayServer::SCREEN_LANDSCAPE; + break; + } + // `wl_output::done` is a version 2 addition. We'll directly update the data // for compatibility. if (wl_output_get_version(wl_output) == 1) { ss->data = ss->pending_data; + print_verbose("Send message from wl_output_on_geometry"); + Ref orientation_msg; + orientation_msg.instantiate(); + orientation_msg->orientation = ss->data.orientation; + orientation_msg->real_size = ss->data.real_size; + ss->wayland_thread->push_message(orientation_msg); } } @@ -1124,6 +1211,9 @@ void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, ScreenState *ss = (ScreenState *)data; ERR_FAIL_NULL(ss); + ss->pending_data.real_size.width = width; + ss->pending_data.real_size.height = height; + ss->pending_data.size.width = width; ss->pending_data.size.height = height; @@ -1133,7 +1223,19 @@ void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, // for compatibility. if (wl_output_get_version(wl_output) == 1) { ss->data = ss->pending_data; - } + print_verbose("Send message from wl_output_on_mode"); + Ref orientation_msg; + orientation_msg.instantiate(); + orientation_msg->orientation = ss->data.orientation; + orientation_msg->real_size = ss->data.real_size; + ss->wayland_thread->push_message(orientation_msg); + } + +#ifdef AURORAOS_ENABLED + DEBUG_LOG_WAYLAND_THREAD(vformat("AuroraOS display size is %dx%d", width, height));; + ss->wayland_thread->main_window.rect.size.width = width; + ss->wayland_thread->main_window.rect.size.height = height; +#endif } // NOTE: The following `wl_output` events are only for version 2 onwards, so we @@ -1147,6 +1249,13 @@ void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) ss->wayland_thread->_update_scale(ss->data.scale); + print_verbose("Send message from wl_output_on_done"); + Ref orientation_msg; + orientation_msg.instantiate(); + orientation_msg->orientation = ss->data.orientation; + orientation_msg->real_size = ss->data.real_size; + ss->wayland_thread->push_message(orientation_msg); + DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output)); } @@ -1165,6 +1274,50 @@ void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) { } +void WaylandThread::_wl_shell_surface_on_ping(void *data, struct wl_shell_surface *wl_shell_surface, uint32_t serial) { + // DEBUG_LOG_WAYLAND_THREAD(vformat("_wl_shell_surface_on_ping: send pong")); + wl_shell_surface_pong(wl_shell_surface, serial); +} + +void WaylandThread::_wl_shell_surface_on_configure(void *data, struct wl_shell_surface *wl_shell_surface, uint32_t edges, int32_t width, int32_t height) { + DEBUG_LOG_WAYLAND_THREAD(vformat("_wl_shell_surface_on_configure")); + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + ws->rect.size.width = width; + ws->rect.size.height = height; + + window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height); + DEBUG_LOG_WAYLAND_THREAD(vformat("wl_shell surface on configure width %d height %d", ws->rect.size.width, ws->rect.size.height)); +} + +void WaylandThread::_wl_shell_surface_on_popup_done(void *data, struct wl_shell_surface *wl_shell_surface) { + DEBUG_LOG_WAYLAND_THREAD("_wl_shell_surface_on_popup_done"); +} + +void WaylandThread::_qt_extended_surface_onscreen_visibility(void *data, + struct qt_extended_surface *qt_extended_surface, + int32_t visible) { + DEBUG_LOG_WAYLAND_THREAD(vformat("_qt_extended_surface_onscreen_visibility: visible = %s", visible == 1 ? "true" : "false")); +} + +void WaylandThread::_qt_extended_surface_set_generic_property(void *data, + struct qt_extended_surface *qt_extended_surface, + const char *name, + struct wl_array *value) { + DEBUG_LOG_WAYLAND_THREAD(vformat("_qt_extended_surface_set_generic_property handler: %s", name)); +} + +void WaylandThread::_qt_extended_surface_close(void *data, struct qt_extended_surface *qt_extended_surface) { + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST; + ws->wayland_thread->push_message(msg); +} + void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } @@ -1361,7 +1514,24 @@ void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat ERR_FAIL_NULL(ss); - // TODO: Handle touch. + // Handle touch. + if (capabilities & WL_SEAT_CAPABILITY_TOUCH) { + if (!ss->wl_touch) { + ss->wl_touch = wl_seat_get_touch(wl_seat); + ss->touch_data_buffer.reserve(10); + ss->touch_data.reserve(10); + wl_touch_add_listener(ss->wl_touch, &wl_touch_listener, ss); + ss->wayland_thread->touch_avaliable = true; + } + } else { + if (ss->wl_touch) { + wl_touch_destroy(ss->wl_touch); + ss->wl_touch = nullptr; + ss->touch_data_buffer.clear(); + ss->touch_data.clear(); + ss->wayland_thread->touch_avaliable = false; + } + } // Pointer handling. if (capabilities & WL_SEAT_CAPABILITY_POINTER) { @@ -1457,6 +1627,167 @@ void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callbac seat_state_update_cursor(ss); } +void WaylandThread::_wl_touch_listener_on_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { + if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { + return; + } + + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + TouchData touch; + touch.id = id; + touch.state = TouchState::DOWN; + touch.pressed = true; + touch.down_time = time; + touch.motion_time = time; + touch.relative_motion_time = 0; + touch.position = { + real_t(wl_fixed_to_double(x)), + real_t(wl_fixed_to_double(y)), + }; + touch.relative_motion = {0, 0}; + + for (auto i = 0; i < ss->touch_data_buffer.size(); i++) { + if (ss->touch_data_buffer[i].id == id || ss->touch_data_buffer[i].id == -1) { + ss->touch_data_buffer[i] = touch; + return; + } + } + + #define MAX_TOUCH_ERROR 100 + if (ss->touch_data_buffer.size() >= MAX_TOUCH_ERROR) { + ERR_PRINT(vformat("Too match touch id's! More than %d. Clear touch buffer.", MAX_TOUCH_ERROR)); + ss->touch_data_buffer.clear(); + } + + ss->touch_data_buffer.push_back(touch); +} + +void WaylandThread::_wl_touch_listener_on_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { + + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + for (auto i = 0; i < ss->touch_data_buffer.size(); i++) { + if (ss->touch_data_buffer[i].id == id) { + TouchData &touch = ss->touch_data_buffer[i]; + touch.state = TouchState::UP; + touch.pressed = false; + touch.motion_time = touch.down_time + 1; + ss->touch_data_buffer[i].id = -1; + return; + } + } + + ERR_PRINT(vformat("Touch with id %d not found! But FINGER_UP event was recieved!", id)); +} + +void WaylandThread::_wl_touch_listener_on_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + static int error_count = 0; + + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + for (auto i = 0; i < ss->touch_data_buffer.size(); i++) { + if (ss->touch_data_buffer[i].id == id) { + TouchData &touch = ss->touch_data_buffer[i]; + touch.position = { + real_t(wl_fixed_to_double(x)), + real_t(wl_fixed_to_double(y)) + }; + touch.motion_time = time; + touch.state = TouchState::MOTION; + return; + } + } + + if (error_count == 0) { + ERR_PRINT(vformat("Touch with id %d not found! But FINGER_MOTION event was recieved!", id)); + } + + error_count++; + if (error_count == 100) { + error_count = 0; + } +} + +void WaylandThread::_wl_touch_listener_on_frame(void *data, struct wl_touch *wl_touch) { + + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + wayland_thread->_set_current_seat(ss->wl_seat); + + // copy buffered touches to thread + for (auto buffer_i = 0; buffer_i < ss->touch_data_buffer.size(); buffer_i++) { + TouchData &touch = ss->touch_data_buffer[buffer_i]; + + auto old_i = 0; + for (; old_i < ss->touch_data.size(); old_i++) { + if (touch.state == TouchState::MOTION && ss->touch_data[old_i].id == touch.id) { + touch.relative_motion = { + touch.position.x - ss->touch_data[old_i].position.x, + touch.position.y - ss->touch_data[old_i].position.y, + }; + touch.relative_motion_time = touch.motion_time - ss->touch_data[old_i].motion_time; + break; + } + } + if (old_i == ss->touch_data.size()) { + ss->touch_data.push_back(touch); + } + + if (touch.state == TouchState::DOWN || touch.state == TouchState::UP) { + Ref st; + st.instantiate(); + st->set_index(buffer_i); + st->set_pressed(touch.state == TouchState::DOWN); + st->set_position(touch.position); + st->set_double_tap(false); // TODO: calculate double tap : 400ms and distance lower than 5 (or in pysichal 3 mm) + + Ref msg; + msg.instantiate(); + msg->event = st; + + ss->wayland_thread->push_message(msg); + } + else if (touch.state == TouchState::MOTION) { + Ref st; + st.instantiate(); + st->set_index(buffer_i); + st->set_position(touch.position); + st->set_relative(touch.relative_motion); + st->set_velocity(touch.relative_motion / touch.relative_motion_time); + + Ref msg; + msg.instantiate(); + msg->event = st; + + ss->wayland_thread->push_message(msg); + } + + touch.state = TouchState::NONE; + } + + ss->touch_data = ss->touch_data_buffer; +} + +void WaylandThread::_wl_touch_listener_on_cancel(void *data, struct wl_touch *wl_touch) { + // DEBUG_LOG_WAYLAND_THREAD("Handle _wl_touch_listener_on_cancel") +} + +void WaylandThread::_wl_touch_listener_on_shape(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + DEBUG_LOG_WAYLAND_THREAD("Handle _wl_touch_listener_on_shape") +} + +void WaylandThread::_wl_touch_listener_on_orientation(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t orientation) { + DEBUG_LOG_WAYLAND_THREAD("Handle _wl_touch_listener_on_orientation") +} + void WaylandThread::_wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { return; @@ -3056,7 +3387,7 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int if (p_ws->xdg_surface) { xdg_surface_set_window_geometry(p_ws->xdg_surface, 0, 0, p_width, p_height); - } + } } #ifdef LIBDECOR_ENABLED @@ -3181,6 +3512,10 @@ void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) { void WaylandThread::seat_state_update_cursor(SeatState *p_ss) { ERR_FAIL_NULL(p_ss); +#if defined(AURORAOS_ENABLED) + WARN_PRINT_ONCE("Disable set cursor on AuroraOS lower than 5.2"); + return; +#endif WaylandThread *thread = p_ss->wayland_thread; ERR_FAIL_NULL(p_ss->wayland_thread); @@ -3323,13 +3658,36 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid ws.registry = ®istry; ws.wayland_thread = this; +#if !defined(AURORAOS_ENABLED) ws.rect.size.width = p_width; ws.rect.size.height = p_height; +#else +// ws.rect.size.width = registry.pen; +// ws.rect.size.height = p_height; + DEBUG_LOG_WAYLAND_THREAD(vformat("Set window size from pending data: %dx%d", ws.rect.size.width, ws.rect.size.height)); +#endif ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor); wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface); wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws); + if (!registry.xdg_wm_base) { + if (registry.wl_shell) { + WARN_PRINT(vformat("Fallback to wl_shell_surface: add listener.")); + // fallback to wl_shell + ws.wl_shell_surface = wl_shell_get_shell_surface(registry.wl_shell, ws.wl_surface); + wl_shell_surface_add_listener(ws.wl_shell_surface, &wl_shell_surface_listener, &ws); + wl_shell_surface_set_maximized(ws.wl_shell_surface, nullptr); // ask compsitor configure us + } + + if (registry.qt_surface_extension) { + ws.qt_extended_surface = qt_surface_extension_get_extended_surface(registry.qt_surface_extension, ws.wl_surface); + qt_extended_surface_add_listener(ws.qt_extended_surface, &qt_extended_surface_listener, &ws); + } else { + WARN_PRINT("Cant add qt_surface_extension listener."); + } + } + if (registry.wp_viewporter) { ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface); @@ -3350,7 +3708,7 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid } #endif - if (!decorated) { + if (!decorated && registry.xdg_wm_base) { // libdecor has failed loading or is disabled, we shall handle xdg_toplevel // creation and decoration ourselves (and by decorating for now I just mean // asking for SSDs and hoping for the best). @@ -3809,6 +4167,10 @@ int WaylandThread::get_screen_count() const { return registry.wl_outputs.size(); } +bool WaylandThread::get_touch_avaliable() const { + return touch_avaliable; +} + DisplayServer::WindowID WaylandThread::pointer_get_pointed_window_id() const { SeatState *ss = wl_seat_get_seat_state(wl_seat_current); @@ -3908,6 +4270,8 @@ Error WaylandThread::init() { wl_display = wl_display_connect(nullptr); ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display."); + wl_display_add_listener(wl_display, &wl_display_listener, ®istry); + thread_data.wl_display = wl_display; events_thread.start(_poll_events_thread, &thread_data); @@ -3925,7 +4289,7 @@ Error WaylandThread::init() { ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global."); ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global."); - ERR_FAIL_NULL_V_MSG(registry.xdg_wm_base, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG shell global."); + ERR_FAIL_COND_V_MSG(!registry.xdg_wm_base && !registry.wl_shell, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG or WL shell global."); if (!registry.xdg_decoration_manager) { #ifdef LIBDECOR_ENABLED @@ -3976,7 +4340,11 @@ Error WaylandThread::init() { bool cursor_theme_loaded = _load_cursor_theme(unscaled_cursor_size * cursor_scale); if (!cursor_theme_loaded) { +#if !defined(AURORAOS_ENABLED) return ERR_CANT_CREATE; +#else + WARN_PRINT("Fail to load cursor theme. Its OK on AuroraOS for now."); +#endif } // Update the cursor. @@ -4483,6 +4851,10 @@ void WaylandThread::destroy() { xdg_surface_destroy(main_window.xdg_surface); } + if (main_window.wl_shell_surface) { + wl_shell_surface_destroy(main_window.wl_shell_surface); + } + if (main_window.wl_surface) { wl_surface_destroy(main_window.wl_surface); } diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index 053dac35e6fd..8d55d6349027 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -42,6 +42,7 @@ #include "xkbcommon-so_wrap.h" #else #include +#include #include #ifdef GLES3_ENABLED #include @@ -61,6 +62,7 @@ #include "wayland/protocol/relative_pointer.gen.h" #undef pointer #include "wayland/protocol/fractional_scale.gen.h" +#include "wayland/protocol/surface-extension.gen.h" #include "wayland/protocol/tablet.gen.h" #include "wayland/protocol/text_input.gen.h" #include "wayland/protocol/viewporter.gen.h" @@ -85,6 +87,8 @@ #include "core/os/thread.h" #include "servers/display_server.h" +#include + class WaylandThread { public: // Messages used for exchanging information between Godot's and Wayland's thread. @@ -102,6 +106,12 @@ class WaylandThread { Rect2i rect; }; + class OrientationMessage : public Message { + public: + DisplayServer::ScreenOrientation orientation; + Size2i real_size; + }; + class WindowEventMessage : public Message { public: DisplayServer::WindowEvent event; @@ -147,6 +157,14 @@ class WaylandThread { List wl_outputs; List wl_seats; + // wl_shell back compability + struct wl_shell *wl_shell = nullptr; + uint32_t wl_shell_name = 0; + + //qt_surface_extension (qtwayland compability) + struct qt_surface_extension *qt_surface_extension = nullptr; + uint32_t qt_surface_extension_name = 0; + // xdg-shell globals. struct xdg_wm_base *xdg_wm_base = nullptr; @@ -204,6 +222,7 @@ class WaylandThread { struct WindowState { DisplayServer::WindowID id; + Size2i real_size; Rect2i rect; DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED; bool suspended = false; @@ -226,6 +245,8 @@ class WaylandThread { struct wl_callback *frame_callback = nullptr; struct wl_surface *wl_surface = nullptr; + struct wl_shell_surface *wl_shell_surface = nullptr; + struct qt_extended_surface *qt_extended_surface = nullptr; struct xdg_surface *xdg_surface = nullptr; struct xdg_toplevel *xdg_toplevel = nullptr; @@ -283,10 +304,12 @@ class WaylandThread { String model; Size2i size; + Size2i real_size; Size2i physical_size; float refresh_rate = -1; int scale = 1; + DisplayServer::ScreenOrientation orientation = DisplayServer::SCREEN_LANDSCAPE; }; struct ScreenState { @@ -309,6 +332,36 @@ class WaylandThread { CONFINED, }; + enum class TouchState { + NONE, + UP, + DOWN, + MOTION + }; + + struct TouchData { + int32_t id = -1; + bool pressed = false; + uint32_t down_time = 0; + uint32_t motion_time = 0; + Point2 position; + Point2 relative_motion; + uint32_t relative_motion_time = 0; + TouchState state = TouchState::NONE; + + TouchData &operator=(const TouchData &other) { + id = other.id; + pressed = other.pressed; + down_time = other.down_time; + motion_time = other.motion_time; + position = other.position; + relative_motion = other.relative_motion; + relative_motion_time = other.relative_motion_time; + state = other.state; + return *this; + } + }; + struct PointerData { Point2 position; uint32_t motion_time = 0; @@ -383,6 +436,9 @@ class WaylandThread { struct wl_seat *wl_seat = nullptr; uint32_t wl_seat_name = 0; + // Touch + struct wl_touch *wl_touch = nullptr; + // Pointer. struct wl_pointer *wl_pointer = nullptr; @@ -418,6 +474,10 @@ class WaylandThread { PointerData pointer_data_buffer; PointerData pointer_data; + // Touch (same as with PointerData, with buffer) + std::vector touch_data_buffer; + std::vector touch_data; + // Keyboard. struct wl_keyboard *wl_keyboard = nullptr; @@ -537,6 +597,7 @@ class WaylandThread { RegistryState registry; bool initialized = false; + bool touch_avaliable = false; #ifdef LIBDECOR_ENABLED struct libdecor *libdecor_context = nullptr; @@ -546,6 +607,8 @@ class WaylandThread { static void _poll_events_thread(void *p_data); // Core Wayland event handlers. + static void _wl_display_on_error(void *data, struct wl_display *wl_display, void *object_id, uint32_t code, const char *message); + static void _wl_display_on_delete_id(void *data, struct wl_display *wl_display, uint32_t id); static void _wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version); static void _wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name); @@ -568,6 +631,14 @@ class WaylandThread { static void _cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms); + static void _wl_touch_listener_on_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void _wl_touch_listener_on_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id); + static void _wl_touch_listener_on_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void _wl_touch_listener_on_frame(void *data, struct wl_touch *wl_touch); + static void _wl_touch_listener_on_cancel(void *data, struct wl_touch *wl_touch); + static void _wl_touch_listener_on_shape(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + static void _wl_touch_listener_on_orientation(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t orientation); + static void _wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y); static void _wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface); static void _wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); @@ -605,6 +676,27 @@ class WaylandThread { static void _wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source); static void _wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action); + // wl_shell events handlers + static void _wl_shell_surface_on_ping(void *data, struct wl_shell_surface *wl_shell_surface, + uint32_t serial); + static void _wl_shell_surface_on_configure(void *data, + struct wl_shell_surface *wl_shell_surface, + uint32_t edges, + int32_t width, + int32_t height); + static void _wl_shell_surface_on_popup_done(void *data, + struct wl_shell_surface *wl_shell_surface); + + // qt_extended_surface handlers + static void _qt_extended_surface_onscreen_visibility(void *data, + struct qt_extended_surface *qt_extended_surface, + int32_t visible); + static void _qt_extended_surface_set_generic_property(void *data, + struct qt_extended_surface *qt_extended_surface, + const char *name, + struct wl_array *value); + static void _qt_extended_surface_close(void *data, struct qt_extended_surface *qt_extended_surface); + // xdg-shell event handlers. static void _xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial); @@ -673,6 +765,11 @@ class WaylandThread { static void _xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token); // Core Wayland event listeners. + static constexpr struct wl_display_listener wl_display_listener = { + .error = _wl_display_on_error, + .delete_id = _wl_display_on_delete_id + }; + static constexpr struct wl_registry_listener wl_registry_listener = { .global = _wl_registry_on_global, .global_remove = _wl_registry_on_global_remove, @@ -681,8 +778,10 @@ class WaylandThread { static constexpr struct wl_surface_listener wl_surface_listener = { .enter = _wl_surface_on_enter, .leave = _wl_surface_on_leave, +#if !defined(AURORAOS_ENABLED) .preferred_buffer_scale = _wl_surface_on_preferred_buffer_scale, .preferred_buffer_transform = _wl_surface_on_preferred_buffer_transform, +#endif }; static constexpr struct wl_callback_listener frame_wl_callback_listener = { @@ -713,12 +812,24 @@ class WaylandThread { .motion = _wl_pointer_on_motion, .button = _wl_pointer_on_button, .axis = _wl_pointer_on_axis, +#if !defined(AURORAOS_ENABLED) .frame = _wl_pointer_on_frame, .axis_source = _wl_pointer_on_axis_source, .axis_stop = _wl_pointer_on_axis_stop, .axis_discrete = _wl_pointer_on_axis_discrete, .axis_value120 = _wl_pointer_on_axis_value120, .axis_relative_direction = _wl_pointer_on_axis_relative_direction, +#endif + }; + + static constexpr struct wl_touch_listener wl_touch_listener = { + .down = _wl_touch_listener_on_down, + .up = _wl_touch_listener_on_up, + .motion = _wl_touch_listener_on_motion, + .frame = _wl_touch_listener_on_frame, + .cancel = _wl_touch_listener_on_cancel, + .shape = _wl_touch_listener_on_shape, + .orientation = _wl_touch_listener_on_orientation, }; static constexpr struct wl_keyboard_listener wl_keyboard_listener = { @@ -754,6 +865,18 @@ class WaylandThread { .action = _wl_data_source_on_action, }; + static constexpr struct wl_shell_surface_listener wl_shell_surface_listener = { + .ping = _wl_shell_surface_on_ping, + .configure = _wl_shell_surface_on_configure, + .popup_done = _wl_shell_surface_on_popup_done + }; + + static constexpr struct qt_extended_surface_listener qt_extended_surface_listener = { + .onscreen_visibility = _qt_extended_surface_onscreen_visibility, + .set_generic_property = _qt_extended_surface_set_generic_property, + .close = _qt_extended_surface_close, + }; + // xdg-shell event listeners. static constexpr struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = _xdg_wm_base_on_ping, @@ -985,6 +1108,8 @@ class WaylandThread { ScreenData screen_get_data(int p_screen) const; int get_screen_count() const; + bool get_touch_avaliable() const; + void pointer_set_constraint(PointerConstraint p_constraint); void pointer_set_hint(const Point2i &p_hint); PointerConstraint pointer_get_constraint() const; diff --git a/platform_methods.py b/platform_methods.py index c8646a402267..7f7354779957 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -22,6 +22,8 @@ "x64": "x86_64", "amd64": "x86_64", "armv7": "arm32", + "armv7l": "arm32", + "armv7hl": "arm32", "armv8": "arm64", "arm64v8": "arm64", "aarch64": "arm64", diff --git a/scu_builders.py b/scu_builders.py index 8e1f91d74063..400ee7fe907a 100644 --- a/scu_builders.py +++ b/scu_builders.py @@ -308,6 +308,7 @@ def generate_scu_files(max_includes_per_scu): process_folder(["platform/macos/export"]) process_folder(["platform/web/export"]) process_folder(["platform/windows/export"]) + process_folder(["platform/auroraos/export"]) process_folder(["modules/lightmapper_rd"]) process_folder(["modules/gltf"]) diff --git a/servers/display_server.cpp b/servers/display_server.cpp index c9399d9c65f8..3b002e5dfa4f 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -564,6 +564,16 @@ DisplayServer::ScreenOrientation DisplayServer::screen_get_orientation(int p_scr return SCREEN_LANDSCAPE; } +#ifdef AURORAOS_ENABLED +DisplayServer::ScreenOrientation DisplayServer::screen_get_sensor_orientation(int p_screen) const { + return screen_get_orientation(); +} + +Size2i DisplayServer::screen_get_native_size() const { + return {1, 1}; +} +#endif + float DisplayServer::screen_get_scale(int p_screen) const { return 1.0f; } diff --git a/servers/display_server.h b/servers/display_server.h index 80bba6897ff0..b9f1b1c45d69 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -376,6 +376,10 @@ class DisplayServer : public Object { virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW); virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const; +#ifdef AURORAOS_ENABLED + virtual ScreenOrientation screen_get_sensor_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual Size2i screen_get_native_size() const; +#endif virtual void screen_set_keep_on(bool p_enable); //disable screensaver virtual bool screen_is_kept_on() const; diff --git a/thirdparty/wayland-protocols/stable/surface-extension/surface-extension.xml b/thirdparty/wayland-protocols/stable/surface-extension/surface-extension.xml new file mode 100644 index 000000000000..4a420d9b46b1 --- /dev/null +++ b/thirdparty/wayland-protocols/stable/surface-extension/surface-extension.xml @@ -0,0 +1,65 @@ + + + + This file is part of the plugins of the Qt Toolkit. + SPDX-FileCopyrightText: 2012 Digia Plc and/or its subsidiary(-ies). + Contact: http://www.qt-project.org/legal + + SPDX-License-Identifier: BSD-3-Clause + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +