diff --git a/src/.clang-format b/src/.clang-format index 1c6b71b2e..bf8872643 100644 --- a/src/.clang-format +++ b/src/.clang-format @@ -85,4 +85,88 @@ SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never +--- +Language: Java +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +IncludeCategories: + - Regex: '^\<[^Q][^/.>]*\>' + Priority: -2 + - Regex: '^\<' + Priority: -1 + - Regex: '^\"' + Priority: 0 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 150 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never ... diff --git a/src/android/app/src/main/cpp/CMakeLists.txt b/src/android/app/src/main/cpp/CMakeLists.txt index 674b9287c..f3a7e0131 100644 --- a/src/android/app/src/main/cpp/CMakeLists.txt +++ b/src/android/app/src/main/cpp/CMakeLists.txt @@ -1,10 +1,16 @@ cmake_minimum_required(VERSION 3.8) add_library(citra-android SHARED - dummy.cpp + logging/log.cpp + logging/logcat_backend.cpp + logging/logcat_backend.h + native_interface.cpp + native_interface.h + ui/main/main_activity.cpp ) # find Android's log library find_library(log-lib log) target_link_libraries(citra-android ${log-lib} core common inih) +target_include_directories(citra-android PRIVATE "../../../../../" "./") diff --git a/src/android/app/src/main/cpp/dummy.cpp b/src/android/app/src/main/cpp/dummy.cpp deleted file mode 100644 index d0ef94a09..000000000 --- a/src/android/app/src/main/cpp/dummy.cpp +++ /dev/null @@ -1,3 +0,0 @@ -int dummy(int a, int b) { - return a + b; -} diff --git a/src/android/app/src/main/cpp/logging/log.cpp b/src/android/app/src/main/cpp/logging/log.cpp new file mode 100644 index 000000000..044f4eb4c --- /dev/null +++ b/src/android/app/src/main/cpp/logging/log.cpp @@ -0,0 +1,15 @@ +#include "common/logging/log.h" +#include "native_interface.h" + +namespace Log { +extern "C" { +JNICALL void Java_org_citra_1emu_citra_LOG_logEntry(JNIEnv* env, jclass type, jint level, + jstring file_name, jint line_number, + jstring function, jstring msg) { + using CitraJNI::GetJString; + FmtLogMessage(Class::Frontend, static_cast(level), GetJString(env, file_name).data(), + static_cast(line_number), GetJString(env, function).data(), + GetJString(env, msg).data()); +} +} +} // namespace Log diff --git a/src/android/app/src/main/cpp/logging/logcat_backend.cpp b/src/android/app/src/main/cpp/logging/logcat_backend.cpp new file mode 100644 index 000000000..17b6ae1a0 --- /dev/null +++ b/src/android/app/src/main/cpp/logging/logcat_backend.cpp @@ -0,0 +1,38 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/assert.h" +#include "common/logging/text_formatter.h" +#include "logcat_backend.h" + +namespace Log { +void LogcatBackend::Write(const Entry& entry) { + android_LogPriority priority; + switch (entry.log_level) { + case Level::Trace: + priority = ANDROID_LOG_VERBOSE; + break; + case Level::Debug: + priority = ANDROID_LOG_DEBUG; + break; + case Level::Info: + priority = ANDROID_LOG_INFO; + break; + case Level::Warning: + priority = ANDROID_LOG_WARN; + break; + case Level::Error: + priority = ANDROID_LOG_ERROR; + break; + case Level::Critical: + priority = ANDROID_LOG_FATAL; + break; + case Level::Count: + UNREACHABLE(); + } + + __android_log_print(priority, "citra", "%s\n", FormatLogMessage(entry).c_str()); +} +} // namespace Log \ No newline at end of file diff --git a/src/android/app/src/main/cpp/logging/logcat_backend.h b/src/android/app/src/main/cpp/logging/logcat_backend.h new file mode 100644 index 000000000..f3bac4762 --- /dev/null +++ b/src/android/app/src/main/cpp/logging/logcat_backend.h @@ -0,0 +1,22 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/logging/backend.h" + +namespace Log { +class LogcatBackend : public Backend { +public: + static const char* Name() { + return "Logcat"; + } + + const char* GetName() const override { + return Name(); + } + + void Write(const Entry& entry) override; +}; +} // namespace Log diff --git a/src/android/app/src/main/cpp/native_interface.cpp b/src/android/app/src/main/cpp/native_interface.cpp new file mode 100644 index 000000000..fc4d73b77 --- /dev/null +++ b/src/android/app/src/main/cpp/native_interface.cpp @@ -0,0 +1,22 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "native_interface.h" + +namespace CitraJNI { +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return JNI_VERSION_1_6; +} + +std::string GetJString(JNIEnv* env, jstring jstr) { + std::string result = ""; + if (!jstr) + return result; + + const char* s = env->GetStringUTFChars(jstr, nullptr); + result = s; + env->ReleaseStringUTFChars(jstr, s); + return result; +} +} // namespace CitraJNI diff --git a/src/android/app/src/main/cpp/native_interface.h b/src/android/app/src/main/cpp/native_interface.h new file mode 100644 index 000000000..a7b99cb51 --- /dev/null +++ b/src/android/app/src/main/cpp/native_interface.h @@ -0,0 +1,16 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace CitraJNI { +extern "C" { +jint JNI_OnLoad(JavaVM* vm, void* reserved); +} + +std::string GetJString(JNIEnv* env, jstring jstr); +} // namespace CitraJNI diff --git a/src/android/app/src/main/cpp/ui/main/main_activity.cpp b/src/android/app/src/main/cpp/ui/main/main_activity.cpp new file mode 100644 index 000000000..b99ba1890 --- /dev/null +++ b/src/android/app/src/main/cpp/ui/main/main_activity.cpp @@ -0,0 +1,31 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/logging/filter.h" +#include "common/logging/log.h" +#include "core/settings.h" +#include "logging/logcat_backend.h" +#include "native_interface.h" + +namespace MainActivity { +extern "C" { +JNICALL void Java_org_citra_1emu_citra_ui_main_MainActivity_initUserPath(JNIEnv* env, jclass type, + jstring path) { + FileUtil::SetUserPath(CitraJNI::GetJString(env, path) + '/'); +} + +JNICALL void Java_org_citra_1emu_citra_ui_main_MainActivity_initLogging(JNIEnv* env, jclass type) { + Log::Filter log_filter(Log::Level::Debug); + log_filter.ParseFilterString(Settings::values.log_filter); + Log::SetGlobalFilter(log_filter); + + const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + FileUtil::CreateFullPath(log_dir); + Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); + Log::AddBackend(std::make_unique()); +} +}; +}; // namespace MainActivity diff --git a/src/android/app/src/main/java/org/citra_emu/citra/CitraApplication.java b/src/android/app/src/main/java/org/citra_emu/citra/CitraApplication.java index 07a782853..10cb52783 100644 --- a/src/android/app/src/main/java/org/citra_emu/citra/CitraApplication.java +++ b/src/android/app/src/main/java/org/citra_emu/citra/CitraApplication.java @@ -6,4 +6,8 @@ package org.citra_emu.citra; import android.app.Application; -public class CitraApplication extends Application {} +public class CitraApplication extends Application { + static { + System.loadLibrary("citra-android"); + } +} diff --git a/src/android/app/src/main/java/org/citra_emu/citra/LOG.java b/src/android/app/src/main/java/org/citra_emu/citra/LOG.java new file mode 100644 index 000000000..c52f30b68 --- /dev/null +++ b/src/android/app/src/main/java/org/citra_emu/citra/LOG.java @@ -0,0 +1,41 @@ +package org.citra_emu.citra; + +public class LOG { + + private interface LOG_LEVEL { + int TRACE = 0, DEBUG = 1, INFO = 2, WARNING = 3, ERROR = 4, CRITICAL = 5; + } + + public static void TRACE(String msg, Object... args) { + LOG(LOG_LEVEL.TRACE, msg, args); + } + + public static void DEBUG(String msg, Object... args) { + LOG(LOG_LEVEL.DEBUG, msg, args); + } + + public static void INFO(String msg, Object... args) { + LOG(LOG_LEVEL.INFO, msg, args); + } + + public static void WARNING(String msg, Object... args) { + LOG(LOG_LEVEL.WARNING, msg, args); + } + + public static void ERROR(String msg, Object... args) { + LOG(LOG_LEVEL.ERROR, msg, args); + } + + public static void CRITICAL(String msg, Object... args) { + LOG(LOG_LEVEL.CRITICAL, msg, args); + } + + private static void LOG(int level, String msg, Object... args) { + StackTraceElement trace = Thread.currentThread().getStackTrace()[4]; + logEntry(level, trace.getFileName(), trace.getLineNumber(), trace.getMethodName(), + String.format(msg, args)); + } + + private static native void logEntry(int level, String file_name, int line_number, + String function, String message); +} diff --git a/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java b/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java index 97b36f0c1..5b4f3d3bc 100644 --- a/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java +++ b/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java @@ -4,15 +4,55 @@ package org.citra_emu.citra.ui.main; +import android.Manifest; +import android.content.pm.PackageManager; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import org.citra_emu.citra.R; +import org.citra_emu.citra.utils.FileUtil; +import org.citra_emu.citra.utils.PermissionUtil; public final class MainActivity extends AppCompatActivity { + + // Java enums suck + private interface PermissionCodes { int INITIALIZE = 0; } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + + PermissionUtil.verifyPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, + PermissionCodes.INITIALIZE); } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + switch (requestCode) { + case PermissionCodes.INITIALIZE: + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + initUserPath(FileUtil.getUserPath().toString()); + initLogging(); + } else { + AlertDialog.Builder dialog = + new AlertDialog.Builder(this) + .setTitle("Permission Error") + .setMessage("Citra requires storage permissions to function.") + .setCancelable(false) + .setPositiveButton("OK", (dialogInterface, which) -> { + PermissionUtil.verifyPermission( + MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE, + PermissionCodes.INITIALIZE); + }); + dialog.show(); + } + } + } + + private static native void initUserPath(String path); + private static native void initLogging(); } diff --git a/src/android/app/src/main/java/org/citra_emu/citra/utils/FileUtil.java b/src/android/app/src/main/java/org/citra_emu/citra/utils/FileUtil.java new file mode 100644 index 000000000..5346c5352 --- /dev/null +++ b/src/android/app/src/main/java/org/citra_emu/citra/utils/FileUtil.java @@ -0,0 +1,19 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra_emu.citra.utils; + +import android.os.Environment; + +import java.io.File; + +public class FileUtil { + public static File getUserPath() { + File storage = Environment.getExternalStorageDirectory(); + File userPath = new File(storage, "citra"); + if (!userPath.isDirectory()) + userPath.mkdir(); + return userPath; + } +} diff --git a/src/android/app/src/main/java/org/citra_emu/citra/utils/PermissionUtil.java b/src/android/app/src/main/java/org/citra_emu/citra/utils/PermissionUtil.java new file mode 100644 index 000000000..33c8129e5 --- /dev/null +++ b/src/android/app/src/main/java/org/citra_emu/citra/utils/PermissionUtil.java @@ -0,0 +1,32 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra_emu.citra.utils; + +import android.app.Activity; +import android.content.pm.PackageManager; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; + +public class PermissionUtil { + + /** + * Checks a permission, if needed shows a dialog to request it + * + * @param activity the activity requiring the permission + * @param permission the permission needed + * @param requestCode supplied to the callback to determine the next action + */ + public static void verifyPermission(Activity activity, String permission, int requestCode) { + if (ContextCompat.checkSelfPermission(activity, permission) == + PackageManager.PERMISSION_GRANTED) { + // call the callback called by requestPermissions + activity.onRequestPermissionsResult(requestCode, new String[] {permission}, + new int[] {PackageManager.PERMISSION_GRANTED}); + return; + } + + ActivityCompat.requestPermissions(activity, new String[] {permission}, requestCode); + } +} diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 52288847a..62a9a5b95 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -543,11 +543,12 @@ std::string GetCurrentDir() { // Get the current working directory (getcwd uses malloc) #ifdef _WIN32 wchar_t* dir; - if (!(dir = _wgetcwd(nullptr, 0))) { + if (!(dir = _wgetcwd(nullptr, 0))) #else char* dir; - if (!(dir = getcwd(nullptr, 0))) { + if (!(dir = getcwd(nullptr, 0))) #endif + { LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); return nullptr; } @@ -695,6 +696,8 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); +#elif ANDROID + ASSERT_MSG(false, "Specified path {} is not valid", path); #else if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d93c5bbd7..d440d910a 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #ifdef _WIN32 @@ -253,13 +254,15 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign using std::chrono::duration_cast; using std::chrono::steady_clock; + // matches from the beginning up to the last '../' or 'src/' + static const std::regex trim_source_path(R"(.*([\/\\]|^)((\.\.)|(src))[\/\\])"); static steady_clock::time_point time_origin = steady_clock::now(); Entry entry; entry.timestamp = duration_cast(steady_clock::now() - time_origin); entry.log_class = log_class; entry.log_level = log_level; - entry.filename = Common::TrimSourcePath(filename); + entry.filename = std::regex_replace(filename, trim_source_path, ""); entry.line_num = line_nr; entry.function = function; entry.message = std::move(message); diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 541a2bf4e..f3c64884a 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -209,26 +209,4 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t return std::string(buffer, len); } - -const char* TrimSourcePath(const char* path, const char* root) { - const char* p = path; - - while (*p != '\0') { - const char* next_slash = p; - while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') { - ++next_slash; - } - - bool is_src = Common::ComparePartialString(p, next_slash, root); - p = next_slash; - - if (*p != '\0') { - ++p; - } - if (is_src) { - path = p; - } - } - return path; -} } // namespace Common diff --git a/src/common/string_util.h b/src/common/string_util.h index d8fa4053e..110715dce 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -64,16 +64,4 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { */ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len); -/** - * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's - * intended to be used to strip a system-specific build directory from the `__FILE__` macro, - * leaving only the path relative to the sources root. - * - * @param path The input file path as a null-terminated string - * @param root The name of the root source directory as a null-terminated string. Path up to and - * including the last occurrence of this name will be stripped - * @return A pointer to the same string passed as `path`, but starting at the trimmed portion - */ -const char* TrimSourcePath(const char* path, const char* root = "src"); - } // namespace Common