diff --git a/.github/workflows/Android_Build.yml b/.github/workflows/Android_Build.yml index 7625e18d8..137577c18 100644 --- a/.github/workflows/Android_Build.yml +++ b/.github/workflows/Android_Build.yml @@ -6,15 +6,19 @@ on: - master pull_request: -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - jobs: x64: runs-on: ubuntu-latest + strategy: + matrix: + build_type: + - release + steps: + - name: Set BUILD_TYPE variable + run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV + - uses: actions/checkout@v2 - name: Fetch submodules run: git submodule update --init --recursive @@ -29,7 +33,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v3 with: - distribution: 'zulu' # See 'Supported distributions' for available options + distribution: 'zulu' java-version: '17' - name: Configure CMake @@ -37,23 +41,36 @@ jobs: - name: Build run: | + # Apply patch for GLES compatibility git apply ./.github/gles.patch - cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + # Build the project with CMake + cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }} + # Move the generated library to the appropriate location mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/x86_64/ + # Build the Android app with Gradle cd src/pandroid - ./gradlew assembleDebug + ./gradlew assemble${{ env.BUILD_TYPE }} cd ../.. - - name: Upload executable + - name: Upload artifacts uses: actions/upload-artifact@v2 with: - name: Android APK (x86-64) - path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk' + name: Android APKs (x86-64) + path: | + ./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk arm64: runs-on: ubuntu-latest + strategy: + matrix: + build_type: + - release + steps: + - name: Set BUILD_TYPE variable + run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV + - uses: actions/checkout@v2 - name: Fetch submodules run: git submodule update --init --recursive @@ -68,7 +85,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v3 with: - distribution: 'zulu' # See 'Supported distributions' for available options + distribution: 'zulu' java-version: '17' - name: Configure CMake @@ -76,16 +93,21 @@ jobs: - name: Build run: | + # Apply patch for GLES compatibility git apply ./.github/gles.patch - cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + # Build the project with CMake + cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }} + # Move the generated library to the appropriate location mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/arm64-v8a/ + # Build the Android app with Gradle cd src/pandroid - ./gradlew assembleDebug + ./gradlew assemble${{ env.BUILD_TYPE }} + ls -R app/build/outputs cd ../.. - - name: Upload executable + - name: Upload artifacts uses: actions/upload-artifact@v2 with: - name: Android APK (arm64) - path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk' - + name: Android APKs (arm64) + path: | + ./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk diff --git a/.gitmodules b/.gitmodules index 3735d0cb1..f1e8f469c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -49,3 +49,9 @@ [submodule "third_party/oaknut"] path = third_party/oaknut url = https://github.com/merryhime/oaknut +[submodule "third_party/luv"] + path = third_party/luv + url = https://github.com/luvit/luv +[submodule "third_party/libuv"] + path = third_party/libuv + url = https://github.com/libuv/libuv diff --git a/CMakeLists.txt b/CMakeLists.txt index ac6cdfb85..456d6513a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,16 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp third_party/xxhash/xxhash.c ) +if(ENABLE_LUAJIT AND NOT ANDROID) + # Build luv and libuv for Lua TCP server usage if we're not on Android + include_directories(third_party/luv/src) + include_directories(third_party/luv/deps/lua-compat-5.3/c-api) + include_directories(third_party/libuv/include) + set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/luv/src/luv.c) + set(LIBUV_BUILD_SHARED OFF) + + add_subdirectory(third_party/libuv) +endif() if(ENABLE_QT_GUI) include_directories(third_party/duckstation) @@ -433,6 +443,11 @@ endif() if(ENABLE_LUAJIT) target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_LUA=1") target_link_libraries(Alber PRIVATE libluajit) + + # If we're not on Android, link libuv too + if (NOT ANDROID) + target_link_libraries(Alber PRIVATE uv_a) + endif() endif() if(ENABLE_OPENGL) diff --git a/include/logger.hpp b/include/logger.hpp index 82d904102..e021a685d 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -2,6 +2,10 @@ #include #include +#ifdef __ANDROID__ +#include +#endif + namespace Log { // Our logger class template @@ -12,7 +16,11 @@ namespace Log { std::va_list args; va_start(args, fmt); +#ifdef __ANDROID__ + __android_log_vprint(ANDROID_LOG_DEFAULT, "Panda3DS", fmt, args); +#else std::vprintf(fmt, args); +#endif va_end(args); } }; @@ -81,4 +89,4 @@ namespace Log { #else #define MAKE_LOG_FUNCTION(functionName, logger) MAKE_LOG_FUNCTION_USER(functionName, logger) #endif -} +} \ No newline at end of file diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index c03cffbb2..c2db9ac1a 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -42,6 +42,8 @@ class MainWindow : public QMainWindow { SetCirclePadY, LoadLuaScript, EditCheat, + PressTouchscreen, + ReleaseTouchscreen, }; // Tagged union representing our message queue messages @@ -68,6 +70,11 @@ class MainWindow : public QMainWindow { struct { CheatMessage* c; } cheat; + + struct { + u16 x; + u16 y; + } touchscreen; }; }; @@ -108,6 +115,9 @@ class MainWindow : public QMainWindow { void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void loadLuaScript(const std::string& code); void editCheat(u32 handle, const std::vector& cheat, const std::function& callback); }; \ No newline at end of file diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index d6dd79dab..ab6455fc2 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -22,7 +22,6 @@ Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock* sharedMem, con // Thanks to Citra devs as always for the default mii data and other applet help output = getDefaultMii(); output.returnCode = 0; // Success - // output.selectedMiiData = miiData; output.selectedGuestMiiIndex = std::numeric_limits::max(); output.miiChecksum = boost::crc<16, 0x1021, 0, 0, false, false>(&output.selectedMiiData, sizeof(MiiData) + sizeof(output.unknown1)); @@ -84,4 +83,4 @@ MiiResult MiiSelectorApplet::getDefaultMii() { result.guestMiiName.fill(0x0); return result; -} \ No newline at end of file +} diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index 404a0e590..ddeb18de4 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -84,7 +84,7 @@ void APTService::appletUtility(u32 messagePointer) { u32 outputSize = mem.read32(messagePointer + 12); u32 inputPointer = mem.read32(messagePointer + 20); - log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X) (Stubbed)\n", utility, inputSize, outputSize, + log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X)\n", utility, inputSize, outputSize, inputPointer); std::vector out(outputSize); @@ -218,7 +218,7 @@ void APTService::initialize(u32 messagePointer) { } void APTService::inquireNotification(u32 messagePointer) { - log("APT::InquireNotification (STUBBED TO RETURN NONE)\n"); + log("APT::InquireNotification\n"); mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0)); mem.write32(messagePointer + 4, Result::Success); diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 6eeb727aa..d962f23eb 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -35,6 +35,13 @@ JNIEnv* jniEnv() { extern "C" { +#define MAKE_SETTING(functionName, type, settingName) \ +AlberFunction(void, functionName) (JNIEnv* env, jobject obj, type value) { emulator->getConfig().settingName = value; } + +MAKE_SETTING(setShaderJitEnabled, jboolean, shaderJitEnabled) + +#undef MAKE_SETTING + AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { env->GetJavaVM(&jvm); } AlberFunction(void, Pause)(JNIEnv* env, jobject obj) { emulator->pause(); } AlberFunction(void, Resume)(JNIEnv* env, jobject obj) { emulator->resume(); } diff --git a/src/lua.cpp b/src/lua.cpp index ec1287bda..09c631735 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -1,6 +1,12 @@ #ifdef PANDA3DS_ENABLE_LUA #include "lua_manager.hpp" +#ifndef __ANDROID__ +extern "C" { + #include "luv.h" +} +#endif + void LuaManager::initialize() { L = luaL_newstate(); // Open Lua @@ -9,10 +15,15 @@ void LuaManager::initialize() { initialized = false; return; } - luaL_openlibs(L); - initializeThunks(); +#ifndef __ANDROID__ + lua_pushstring(L, "luv"); + luaopen_luv(L); + lua_settable(L, LUA_GLOBALSINDEX); +#endif + + initializeThunks(); initialized = true; haveScript = false; } diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 1d7118bcc..de70cc187 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -277,6 +278,10 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; case MessageType::SetCirclePadX: emu->getServiceManager().getHID().setCirclepadX(message.circlepad.value); break; case MessageType::SetCirclePadY: emu->getServiceManager().getHID().setCirclepadY(message.circlepad.value); break; + case MessageType::PressTouchscreen: + emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y); + break; + case MessageType::ReleaseTouchscreen: emu->getServiceManager().getHID().releaseTouchScreen(); break; } } @@ -357,6 +362,40 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) { } } +void MainWindow::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::MouseButton::LeftButton) { + const QPointF clickPos = event->globalPosition(); + const QPointF widgetPos = screen.mapFromGlobal(clickPos); + + // Press is inside the screen area + if (widgetPos.x() >= 0 && widgetPos.x() < screen.width() && widgetPos.y() >= 0 && widgetPos.y() < screen.height()) { + // Go from widget positions to [0, 400) for x and [0, 480) for y + uint x = (uint)std::round(widgetPos.x() / screen.width() * 400.f); + uint y = (uint)std::round(widgetPos.y() / screen.height() * 480.f); + + // Check if touch falls in the touch screen area + if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { + // Convert to 3DS coordinates + u16 x_converted = static_cast(x) - 40; + u16 y_converted = static_cast(y) - 240; + + EmulatorMessage message{.type = MessageType::PressTouchscreen}; + message.touchscreen.x = x_converted; + message.touchscreen.y = y_converted; + sendMessage(message); + } else { + sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen}); + } + } + } +} + +void MainWindow::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::MouseButton::LeftButton) { + sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen}); + } +} + void MainWindow::loadLuaScript(const std::string& code) { EmulatorMessage message{.type = MessageType::LoadLuaScript}; diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts index f1feaf0d4..57970751b 100644 --- a/src/pandroid/app/build.gradle.kts +++ b/src/pandroid/app/build.gradle.kts @@ -21,8 +21,20 @@ android { } buildTypes { - release { + getByName("release") { isMinifyEnabled = false + isShrinkResources = false + isDebuggable = false + signingConfig = signingConfigs.getByName("debug") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + getByName("debug") { + isMinifyEnabled = false + isShrinkResources = false + isDebuggable = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -41,4 +53,5 @@ dependencies { implementation("androidx.preference:preference:1.2.1") implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("com.google.code.gson:gson:2.10.1") -} \ No newline at end of file + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") +} diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index 9f7676543..c66d37af8 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -20,6 +20,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:isGame="true" + android:hardwareAccelerated="true" android:theme="@style/Theme.Pandroid" tools:targetApi="31"> + + diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java index 5cff703c7..00b7842b0 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java @@ -22,5 +22,7 @@ public class AlberDriver { public static native void LoadLuaScript(String script); public static native byte[] GetSmdh(); + public static native void setShaderJitEnabled(boolean enable); + static { System.loadLibrary("Alber"); } } \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java index aced6faad..946ef8834 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java @@ -1,6 +1,7 @@ package com.panda3ds.pandroid.app; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; @@ -21,6 +22,7 @@ import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.PandaGlSurfaceView; import com.panda3ds.pandroid.view.PandaLayoutController; +import com.panda3ds.pandroid.view.utils.PerformanceView; public class GameActivity extends BaseActivity { private final DrawerFragment drawerFragment = new DrawerFragment(); @@ -56,6 +58,11 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { ((CheckBox) findViewById(R.id.hide_screen_controller)).setChecked(GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE)); getSupportFragmentManager().beginTransaction().replace(R.id.drawer_fragment, drawerFragment).commitNow(); + + if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) { + PerformanceView view = new PerformanceView(this); + ((FrameLayout) findViewById(R.id.panda_gl_frame)).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } } @Override @@ -66,6 +73,9 @@ protected void onResume() { InputHandler.reset(); InputHandler.setMotionDeadZone(InputMap.getDeadZone()); InputHandler.setEventListener(inputListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + getTheme().applyStyle(R.style.GameActivityNavigationBar, true); + } } @Override diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java index 02fbbbcc3..b0cdc935f 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java @@ -2,11 +2,13 @@ import android.app.Application; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.services.LoggerService; import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.input.InputMap; import com.panda3ds.pandroid.utils.GameUtils; @@ -24,6 +26,10 @@ public void onCreate() { GameUtils.initialize(); InputMap.initialize(); AlberDriver.Setup(); + + if (GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE)) { + startService(new Intent(this, LoggerService.class)); + } } public static int getThemeId() { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java index 3cd28f4bf..4f5c57614 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java @@ -1,8 +1,14 @@ package com.panda3ds.pandroid.app.base; import android.annotation.SuppressLint; + +import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.SwitchPreference; + import com.panda3ds.pandroid.lang.Function; @@ -15,4 +21,11 @@ protected void setItemClick(String key, Function listener) { return false; }); } + + protected void setActivityTitle(@StringRes int titleId) { + ActionBar header = ((AppCompatActivity) requireActivity()).getSupportActionBar(); + if (header != null) { + header.setTitle(titleId); + } + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java index ff6e4dcae..3c5ccfc5a 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java @@ -12,23 +12,42 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.data.game.GameMetadata; import com.panda3ds.pandroid.utils.FileUtils; import com.panda3ds.pandroid.utils.GameUtils; import com.panda3ds.pandroid.view.gamesgrid.GamesGridView; +import java.io.File; +import java.util.ArrayList; +import java.util.List; -public class GamesFragment extends Fragment implements ActivityResultCallback { +public class GamesFragment extends Fragment implements ActivityResultCallback, SwipeRefreshLayout.OnRefreshListener { private final ActivityResultContracts.OpenDocument openRomContract = new ActivityResultContracts.OpenDocument(); private ActivityResultLauncher pickFileRequest; private GamesGridView gameListView; + private SwipeRefreshLayout swipeRefreshLayout; @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_games, container, false); + swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh_layout); + swipeRefreshLayout.setOnRefreshListener(this); + return rootView; + } + @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_games, container, false); - } + public void onRefresh() { + refreshGameList(); + } + + private void refreshGameList() { + // Refresh the game list + gameListView.setGameList(GameUtils.getGames()); + swipeRefreshLayout.setRefreshing(false); + } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java index b3bebf8fc..4ac73661d 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java @@ -8,6 +8,7 @@ import com.panda3ds.pandroid.app.PreferenceActivity; import com.panda3ds.pandroid.app.base.BasePreferenceFragment; import com.panda3ds.pandroid.app.preferences.AppearancePreferences; +import com.panda3ds.pandroid.app.preferences.AdvancedPreferences; import com.panda3ds.pandroid.app.preferences.InputPreferences; public class SettingsFragment extends BasePreferenceFragment { @@ -16,5 +17,6 @@ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable S setPreferencesFromResource(R.xml.start_preferences, rootKey); setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class)); setItemClick("appearance", (item)-> PreferenceActivity.launch(requireContext(), AppearancePreferences.class)); + setItemClick("advanced", (item)-> PreferenceActivity.launch(requireContext(), AdvancedPreferences.class)); } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AdvancedPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AdvancedPreferences.java new file mode 100644 index 000000000..fea8aef0f --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AdvancedPreferences.java @@ -0,0 +1,49 @@ +package com.panda3ds.pandroid.app.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.preference.SwitchPreference; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.app.base.BasePreferenceFragment; +import com.panda3ds.pandroid.app.services.LoggerService; +import com.panda3ds.pandroid.data.config.GlobalConfig; + +public class AdvancedPreferences extends BasePreferenceFragment { + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.advanced_preferences, rootKey); + setActivityTitle(R.string.advanced_options); + + setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreference) pref).isChecked())); + setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreference) pref).isChecked())); + setItemClick("loggerService", pref -> { + boolean checked = ((SwitchPreference) pref).isChecked(); + Context ctx = PandroidApplication.getAppContext(); + if (checked) { + ctx.startService(new Intent(ctx, LoggerService.class)); + } else { + ctx.stopService(new Intent(ctx, LoggerService.class)); + } + GlobalConfig.set(GlobalConfig.KEY_LOGGER_SERVICE, checked); + }); + + refresh(); + } + + @Override + public void onResume() { + super.onResume(); + refresh(); + } + + private void refresh() { + ((SwitchPreference) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)); + ((SwitchPreference) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE)); + ((SwitchPreference) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT)); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AppearancePreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AppearancePreferences.java index dea4e2613..04c89d9a0 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AppearancePreferences.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/AppearancePreferences.java @@ -15,7 +15,7 @@ public class AppearancePreferences extends BasePreferenceFragment { public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.appearance_preference, rootKey); - ((BaseActivity) requireActivity()).getSupportActionBar().setTitle(R.string.appearance); + setActivityTitle(R.string.appearance); SingleSelectionPreferences themePreference = findPreference("theme"); themePreference.setSelectedItem(GlobalConfig.get(GlobalConfig.KEY_APP_THEME)); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ControllerMapperPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ControllerMapperPreferences.java index e59adfbea..f643c88f6 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ControllerMapperPreferences.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/ControllerMapperPreferences.java @@ -37,7 +37,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat currentProfile = ControllerProfileManager.get(getArguments().getString("profile")).clone(); - ((BaseActivity) requireActivity()).getSupportActionBar().hide(); + if (((BaseActivity)requireActivity()).getSupportActionBar() != null) { + ((BaseActivity) requireActivity()).getSupportActionBar().hide(); + } mapper = view.findViewById(R.id.mapper); mapper.initialize(this::onLocationChanged, currentProfile); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java index b4d148b9f..10fa10f95 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java @@ -27,7 +27,7 @@ public class InputMapPreferences extends BasePreferenceFragment implements Activ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.input_map_preferences, rootKey); - ((BaseActivity) requireActivity()).getSupportActionBar().setTitle(R.string.controller_mapping); + setActivityTitle(R.string.controller_mapping); for (KeyName key : KeyName.values()) { if (key == KeyName.NULL) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java new file mode 100644 index 000000000..e44f35039 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java @@ -0,0 +1,104 @@ +package com.panda3ds.pandroid.app.services; + +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.Build; +import android.os.IBinder; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.panda3ds.pandroid.lang.PipeStreamTask; +import com.panda3ds.pandroid.lang.Task; +import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.utils.FileUtils; + +import java.io.OutputStream; +import java.util.Arrays; + +public class LoggerService extends Service { + private static final long MAX_LOG_SIZE = 1024 * 1024 * 4; // 4MB + + private PipeStreamTask errorTask; + private PipeStreamTask outputTask; + private Process logcat; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + try { + Runtime.getRuntime().exec(new String[]{"logcat", "-c"}).waitFor(); + logcat = Runtime.getRuntime().exec(new String[]{"logcat"}); + String logPath = getExternalMediaDirs()[0].getAbsolutePath(); + FileUtils.createDir(logPath, "logs"); + logPath = logPath + "/logs"; + + if (FileUtils.exists(logPath + "/last.txt")) { + FileUtils.delete(logPath + "/last.txt"); + } + + if (FileUtils.exists(logPath + "/current.txt")) { + FileUtils.rename(logPath + "/current.txt", "last.txt"); + } + + OutputStream stream = FileUtils.getOutputStream(logPath + "/current.txt"); + errorTask = new PipeStreamTask(logcat.getErrorStream(), stream, MAX_LOG_SIZE); + outputTask = new PipeStreamTask(logcat.getInputStream(), stream, MAX_LOG_SIZE); + + errorTask.start(); + outputTask.start(); + + Log.i(Constants.LOG_TAG, "Started logger service"); + logDeviceInfo(); + } catch (Exception e) { + stopSelf(); + Log.e(Constants.LOG_TAG, "Failed to start logger service"); + } + } + + private void logDeviceInfo() { + Log.i(Constants.LOG_TAG, "----------------------"); + Log.i(Constants.LOG_TAG, "Android SDK: " + Build.VERSION.SDK_INT); + Log.i(Constants.LOG_TAG, "Device: " + Build.DEVICE); + Log.i(Constants.LOG_TAG, "Model: " + Build.MANUFACTURER + " " + Build.MODEL); + Log.i(Constants.LOG_TAG, "ABIs: " + Arrays.toString(Build.SUPPORTED_ABIS)); + try { + PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0); + Log.i(Constants.LOG_TAG, ""); + Log.i(Constants.LOG_TAG, "Package: " + info.packageName); + Log.i(Constants.LOG_TAG, "Install location: " + info.installLocation); + Log.i(Constants.LOG_TAG, "App version: " + info.versionName + " (" + info.versionCode + ")"); + } catch (Exception e) { + Log.e(Constants.LOG_TAG, "Error obtaining package info: " + e); + } + Log.i(Constants.LOG_TAG, "----------------------"); + } + + @Override + public void onTaskRemoved(Intent rootIntent) { + stopSelf(); + //This is a time for app save save log file + try { + Thread.sleep(1000); + } catch (Exception e) {} + super.onTaskRemoved(rootIntent); + } + + @Override + public void onDestroy() { + Log.i(Constants.LOG_TAG, "Logger service terminating"); + errorTask.close(); + outputTask.close(); + try { + logcat.destroy(); + } catch (Throwable t) {} + super.onDestroy(); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index d6dbe3b80..bff1f9e0e 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -5,7 +5,6 @@ import com.panda3ds.pandroid.utils.Constants; import java.io.Serializable; -import java.util.HashMap; import java.util.Map; public class GlobalConfig { @@ -19,6 +18,9 @@ public class GlobalConfig { public static DataModel data; + public static final Key KEY_SHADER_JIT = new Key<>("emu.shader_jit", false); + public static final Key KEY_SHOW_PERFORMANCE_OVERLAY = new Key<>("dev.performanceOverlay", false); + public static final Key KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false); public static final Key KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID); public static final Key KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java new file mode 100644 index 000000000..e4bbda986 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java @@ -0,0 +1,40 @@ +package com.panda3ds.pandroid.lang; + +import java.io.InputStream; +import java.io.OutputStream; + +public class PipeStreamTask extends Task { + private final InputStream input; + private final OutputStream output; + private final long limit; + private long size; + + public PipeStreamTask(InputStream input, OutputStream output, long limit) { + this.input = input; + this.output = output; + this.limit = limit; + } + + @Override + public void run() { + super.run(); + int data; + try { + while ((data = input.read()) != -1) { + output.write(data); + if (++size > limit) { + break; + } + } + } catch (Exception e) {} + close(); + } + + public void close() { + try { + output.flush(); + output.close(); + input.close(); + } catch (Exception e) {} + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java index 7745883d5..8de344b40 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java @@ -5,6 +5,8 @@ public Task(Runnable runnable) { super(runnable); } + protected Task() {} + public void runSync() { start(); waitFinish(); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java index 45faf5a47..1746f1c9e 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java @@ -70,6 +70,25 @@ public static boolean exists(String path) { return parseFile(path).exists(); } + public static void rename(String path, String newName){ + parseFile(path).renameTo(newName); + } + + public static void delete(String path) { + DocumentFile file = parseFile(path); + + if (file.exists()) { + if (file.isDirectory()) { + String[] children = listFiles(path); + for (String child : children) { + delete(path + "/" + child); + } + } + + file.delete(); + } + } + public static boolean createDir(String path, String name) { DocumentFile folder = parseFile(path); if (folder.findFile(name) != null) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java new file mode 100644 index 000000000..23adbf136 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java @@ -0,0 +1,64 @@ +package com.panda3ds.pandroid.utils; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.Debug; +import android.os.Process; + +import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.data.config.GlobalConfig; + +public class PerformanceMonitor { + private static int fps = 1; + private static String backend = ""; + private static int frames = 0; + private static long lastUpdate = 0; + private static long totalMemory = 1; + private static long availableMemory = 0; + + public static void initialize(String backendName) { + fps = 1; + backend = backendName; + } + + public static void runFrame() { + if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) { + frames++; + if (System.currentTimeMillis() - lastUpdate > 1000) { + lastUpdate = System.currentTimeMillis(); + fps = frames; + frames = 0; + try { + Context ctx = PandroidApplication.getAppContext(); + ActivityManager manager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); + manager.getMemoryInfo(info); + totalMemory = info.totalMem; + availableMemory = info.availMem; + } catch (Exception e) {} + } + } + } + + public static long getUsedMemory() { + return Math.max(1, totalMemory - availableMemory); + } + + public static long getTotalMemory() { + return totalMemory; + } + + public static long getAvailableMemory() { + return availableMemory; + } + + public static int getFps() { + return fps; + } + + public static String getBackend() { + return backend; + } + + public static void destroy() {} +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java index 52e609a33..c39b36b3b 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java @@ -7,8 +7,10 @@ import android.opengl.GLSurfaceView; import android.util.Log; import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.GameUtils; +import com.panda3ds.pandroid.utils.PerformanceMonitor; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout; @@ -38,9 +40,12 @@ protected void finalize() throws Throwable { if (screenTexture != 0) { glDeleteTextures(1, new int[] {screenTexture}, 0); } - if (screenFbo != 0) { + + if (screenFbo != 0) { glDeleteFramebuffers(1, new int[] {screenFbo}, 0); } + + PerformanceMonitor.destroy(); super.finalize(); } @@ -78,6 +83,7 @@ public void onSurfaceCreated(GL10 unused, EGLConfig config) { glBindFramebuffer(GL_FRAMEBUFFER, 0); AlberDriver.Initialize(); + AlberDriver.setShaderJitEnabled(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT)); AlberDriver.LoadRom(romPath); // Load the SMDH @@ -92,6 +98,8 @@ public void onSurfaceCreated(GL10 unused, EGLConfig config) { GameUtils.removeGame(game); GameUtils.addGame(GameMetadata.applySMDH(game, smdh)); } + + PerformanceMonitor.initialize(getBackendName()); } public void onDrawFrame(GL10 unused) { @@ -114,6 +122,8 @@ public void onDrawFrame(GL10 unused) { screenHeight - bottomScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR ); } + + PerformanceMonitor.runFrame(); } public void onSurfaceChanged(GL10 unused, int width, int height) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java new file mode 100644 index 000000000..e4d7be153 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java @@ -0,0 +1,64 @@ +package com.panda3ds.pandroid.view.utils; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.text.Html; +import android.util.AttributeSet; +import android.util.TypedValue; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; + +import com.panda3ds.pandroid.data.config.GlobalConfig; +import com.panda3ds.pandroid.utils.PerformanceMonitor; + +public class PerformanceView extends AppCompatTextView { + private boolean running = false; + + public PerformanceView(@NonNull Context context) { + this(context, null); + } + + public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs,0); + } + + public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()); + setPadding(padding,padding,padding,padding); + setTextColor(Color.WHITE); + setShadowLayer(padding,0,0,Color.BLACK); + } + + public void refresh(){ + running = isShown(); + if (!running) { + return; + } + + String debug = ""; + + // Calculate total memory in MB and the current memory usage + int memoryTotalMb = (int) Math.round(PerformanceMonitor.getTotalMemory() / (1024.0 * 1024.0)); + int memoryUsageMb = (int) Math.round(PerformanceMonitor.getUsedMemory() / (1024.0 * 1024.0)); + + debug += "FPS: " + PerformanceMonitor.getFps() + "
"; + debug += "RAM: " + Math.round(((float) memoryUsageMb / memoryTotalMb) * 100) + "% (" + memoryUsageMb + "MB/" + memoryTotalMb + "MB)
"; + debug += "BACKEND: " + PerformanceMonitor.getBackend() + (GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT) ? " + JIT" : "") + "
"; + setText(Html.fromHtml(debug, Html.FROM_HTML_MODE_COMPACT)); + postDelayed(this::refresh, 250); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (!running) { + refresh(); + } + } +} diff --git a/src/pandroid/app/src/main/res/drawable/color_surface.xml b/src/pandroid/app/src/main/res/drawable/color_surface.xml new file mode 100644 index 000000000..b8655b87c --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/color_surface.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/switch_thumb.xml b/src/pandroid/app/src/main/res/drawable/switch_thumb.xml new file mode 100644 index 000000000..02f1ab021 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/switch_thumb.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/switch_track.xml b/src/pandroid/app/src/main/res/drawable/switch_track.xml new file mode 100644 index 000000000..b665789c6 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/switch_track.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout-land/activity_main.xml b/src/pandroid/app/src/main/res/layout-land/activity_main.xml index fa4cfbca4..9741809da 100644 --- a/src/pandroid/app/src/main/res/layout-land/activity_main.xml +++ b/src/pandroid/app/src/main/res/layout-land/activity_main.xml @@ -5,7 +5,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".app.MainActivity"> + tools:context=".app.MainActivity" + android:background="?colorSurface"> + style="@style/ThemedNavigationBottom" + android:background="@drawable/color_surface"/> \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/activity_input_map.xml b/src/pandroid/app/src/main/res/layout/activity_input_map.xml index cbacc64e1..79249e62e 100644 --- a/src/pandroid/app/src/main/res/layout/activity_input_map.xml +++ b/src/pandroid/app/src/main/res/layout/activity_input_map.xml @@ -4,7 +4,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:gravity="center"> + android:gravity="center" + android:background="?colorSurface"> + tools:context=".app.MainActivity" + android:background="?colorSurface"> + style="@style/ThemedNavigationBottom" + android:background="@drawable/color_surface"/> \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/activity_preference.xml b/src/pandroid/app/src/main/res/layout/activity_preference.xml index 54b3d364b..401c3d862 100644 --- a/src/pandroid/app/src/main/res/layout/activity_preference.xml +++ b/src/pandroid/app/src/main/res/layout/activity_preference.xml @@ -3,7 +3,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" - android:orientation="vertical"> + android:orientation="vertical" + android:background="?colorSurface"> + + + + - - \ No newline at end of file + + diff --git a/src/pandroid/app/src/main/res/layout/fragment_search.xml b/src/pandroid/app/src/main/res/layout/fragment_search.xml index 5872a4043..367b0f1d2 100644 --- a/src/pandroid/app/src/main/res/layout/fragment_search.xml +++ b/src/pandroid/app/src/main/res/layout/fragment_search.xml @@ -35,14 +35,14 @@ + android:layout_height="match_parent"> + android:layout_height="match_parent" + android:paddingStart="15dp" + android:paddingEnd="15dp"/> diff --git a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml index 065b9e4f0..eeeb842bf 100644 --- a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml @@ -45,4 +45,13 @@ Abrir arquivo Criar novo Executando \"%s\" ... - \ No newline at end of file + Opções avançada. + Depuração, mostrar fps, etc. + Monitor de desempenho + Mostrar um overlay com fps, memoria, etc. + Depuração + Grave os registros para um arquivo. + Shader Jit + Usar recompilador de shaders. + Gráficos + diff --git a/src/pandroid/app/src/main/res/values-v27/themes.xml b/src/pandroid/app/src/main/res/values-v27/themes.xml new file mode 100644 index 000000000..8e960864d --- /dev/null +++ b/src/pandroid/app/src/main/res/values-v27/themes.xml @@ -0,0 +1,16 @@ + + + + + + + + - + diff --git a/src/pandroid/app/src/main/res/xml/advanced_preferences.xml b/src/pandroid/app/src/main/res/xml/advanced_preferences.xml new file mode 100644 index 000000000..ce77a6e49 --- /dev/null +++ b/src/pandroid/app/src/main/res/xml/advanced_preferences.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/xml/start_preferences.xml b/src/pandroid/app/src/main/res/xml/start_preferences.xml index 878aa9206..f41e83a25 100644 --- a/src/pandroid/app/src/main/res/xml/start_preferences.xml +++ b/src/pandroid/app/src/main/res/xml/start_preferences.xml @@ -23,4 +23,11 @@ app:summary="@string/pref_appearance_summary" app:layout="@layout/preference_start_item"/> + + \ No newline at end of file diff --git a/third_party/libuv b/third_party/libuv new file mode 160000 index 000000000..b8368a144 --- /dev/null +++ b/third_party/libuv @@ -0,0 +1 @@ +Subproject commit b8368a1441fd4ebdaaae70b67136c80b1a98be32 diff --git a/third_party/luv b/third_party/luv new file mode 160000 index 000000000..3e55ac433 --- /dev/null +++ b/third_party/luv @@ -0,0 +1 @@ +Subproject commit 3e55ac4331d06aa5f43016a142aa2aaa23264105