Compare commits

..

1 Commits

Author SHA1 Message Date
Allison Cunha
adcab5767b recursive file writing via SAF with permission request intent example 2025-11-22 18:24:54 -03:00
5 changed files with 127 additions and 36 deletions

View File

@@ -125,16 +125,14 @@ if (YUZU_STATIC_BUILD)
set(Boost_USE_STATIC_LIBS ON)
set(BUILD_SHARED_LIBS OFF)
if (NOT PLATFORM_LINUX)
## find .a libs first (static, usually)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
## find .a libs first (static, usually)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
## some libraries define a Library::Name_static alternative ##
set(YUZU_STATIC_SUFFIX _static)
## some libraries define a Library::Name_static alternative ##
set(YUZU_STATIC_SUFFIX _static)
## some libraries use CMAKE_IMPORT_LIBRARY_SUFFIX e.g. Harfbuzz ##
set(CMAKE_IMPORT_LIBRARY_SUFFIX ".a")
endif()
## some libraries use CMAKE_IMPORT_LIBRARY_SUFFIX e.g. Harfbuzz ##
set(CMAKE_IMPORT_LIBRARY_SUFFIX ".a")
if (MINGW)
# simple hook to reject dynamic libs
@@ -177,15 +175,6 @@ if (YUZU_STATIC_BUILD)
set(SPIRV-Tools_FORCE_BUNDLED ON)
set(SPIRV-Headers_FORCE_BUNDLED ON)
set(zstd_FORCE_BUNDLED ON)
elseif(PLATFORM_LINUX)
# Most distros don't package static libs :(
set(YUZU_USE_CPM ON)
set(CPMUTIL_FORCE_BUNDLED ON)
set(YUZU_USE_BUNDLED_FFMPEG ON)
set(YUZU_USE_BUNDLED_SDL2 ON)
set(YUZU_USE_BUNDLED_OPENSSL ON)
set(YUZU_USE_BUNDLED_SIRIT ON)
endif()
endif()
@@ -813,16 +802,6 @@ if (YUZU_TESTS OR DYNARMIC_TESTS)
find_package(Catch2)
endif()
# Qt expects this target
if (YUZU_STATIC_BUILD AND PLATFORM_LINUX)
get_target_property(RDOC_TARGET RenderDoc::API ALIASED_TARGET)
if (RDOC_TARGET)
add_library(RenderDoc::RenderDoc ALIAS ${RDOC_TARGET})
else()
add_library(RenderDoc::RenderDoc ALIAS RenderDoc::API)
endif()
endif()
if (ENABLE_QT)
if (YUZU_USE_BUNDLED_QT)
download_qt(6.8.3)

View File

@@ -16,15 +16,13 @@ function(static_qt_link target)
# NB: yes, we have to put them here twice. I have no idea why
# libtiff.a
if (MINGW)
extra_libs(tiff jbig bz2 lzma deflate jpeg tiff)
extra_libs(tiff jbig bz2 lzma deflate jpeg tiff)
# libfreetype.a
extra_libs(freetype bz2 Lerc brotlidec brotlicommon freetype)
# libfreetype.a
extra_libs(freetype bz2 Lerc brotlidec brotlicommon freetype)
# libharfbuzz.a
extra_libs(harfbuzz graphite2)
endif()
# libharfbuzz.a
extra_libs(harfbuzz graphite2)
# sijfjkfnjkdfjsbjsbsdfhvbdf
if (ENABLE_OPENSSL)

View File

@@ -174,7 +174,7 @@ else()
add_compile_definitions(QT_STATICPLUGIN)
# macos doesn't even let you make static executables... wtf?
if (NOT APPLE AND NOT PLATFORM_LINUX)
if (NOT APPLE)
add_compile_options(-static)
if (YUZU_STATIC_BUILD)
# yuzu-cmd requires us to explicitly link libpthread, libgcc, and libstdc++ as static

View File

@@ -44,6 +44,17 @@ import androidx.core.content.edit
import androidx.core.view.doOnNextLayout
class GamesFragment : Fragment() {
private lateinit var safRecursiveTimestampWriter: org.yuzu.yuzu_emu.utils.SAFWriter
private val REQUEST_CODE_OPEN_DOCUMENT_TREE = 1001
override fun onActivityResult(requestCode: Int, resultCode: Int, data: android.content.Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE && resultCode == android.app.Activity.RESULT_OK) {
val uri = data?.data ?: return
safRecursiveTimestampWriter.handlePermissionResult(uri)
}
}
private var _binding: FragmentGamesBinding? = null
private val binding get() = _binding!!
@@ -112,9 +123,25 @@ class GamesFragment : Fragment() {
applyGridGamesBinding()
safRecursiveTimestampWriter = org.yuzu.yuzu_emu.utils.SAFWriter(requireContext())
binding.swipeRefresh.apply {
(binding.swipeRefresh as? SwipeRefreshLayout)?.setOnRefreshListener {
gamesViewModel.reloadGames(false)
safRecursiveTimestampWriter.refreshGamesFolder(
folders = gamesViewModel.folders.value,
requestPermission = { uri ->
val intent = android.content.Intent(android.content.Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.putExtra(android.provider.DocumentsContract.EXTRA_INITIAL_URI, uri)
intent.addFlags(
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION or
android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
)
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE)
},
reloadGames = { gamesViewModel.reloadGames(false) }
)
}
(binding.swipeRefresh as? SwipeRefreshLayout)?.setProgressBackgroundColorSchemeColor(
com.google.android.material.color.MaterialColors.getColor(

View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
class SAFWriter(private val context: Context) {
fun refreshGamesFolder(
folders: List<Any>,
requestPermission: (Uri) -> Unit,
reloadGames: () -> Unit
) {
if (folders.isNotEmpty()) {
val gamesDirUriString = try {
val uriField = folders[0]::class.java.getDeclaredField("uriString")
uriField.isAccessible = true
uriField.get(folders[0]) as? String
} catch (e: Exception) {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Error accessing uriString: $e")
null
}
if (gamesDirUriString != null) {
val gamesRootDocFile = DocumentFile.fromTreeUri(context, Uri.parse(gamesDirUriString))
Log.i("YuzuDebug", "[SAFWriter] [SAF] Refresh triggered. gamesDirUri: $gamesDirUriString, gamesRootDocFile: $gamesRootDocFile")
if (gamesRootDocFile != null && gamesRootDocFile.isDirectory) {
val dirtreeDocFile = gamesRootDocFile.findFile("dirtree")
if (dirtreeDocFile != null && dirtreeDocFile.isDirectory) {
createRecursiveTimestampFilesInSAF(dirtreeDocFile, requestPermission)
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] 'dirtree' subfolder not found or not a directory.")
}
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Invalid games folder DocumentFile.")
}
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Could not get gamesDirUriString from folders.")
}
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] No games folder found in gamesViewModel.folders.")
}
reloadGames()
}
private var pendingSAFAction: (() -> Unit)? = null
private val REQUEST_CODE_OPEN_DOCUMENT_TREE = 1001
// Recursively create a timestamp file in each subfolder using SAF
fun createRecursiveTimestampFilesInSAF(root: DocumentFile, requestPermission: (Uri) -> Unit, onPermissionGranted: (() -> Unit)? = null) {
Log.i("YuzuDebug", "[SAFWriter] [SAF] Processing folder: ${root.uri}, name: ${root.name}, canWrite: ${root.canWrite()}, isDirectory: ${root.isDirectory}, mimeType: ${root.type}")
if (!root.isDirectory) return
if (!root.canWrite()) {
Log.w("YuzuDebug", "[SAFWriter] [SAF] Cannot write to folder: ${root.uri}, requesting permission...")
// Save the action to retry after permission
pendingSAFAction = { createRecursiveTimestampFilesInSAF(root, requestPermission, onPermissionGranted) }
requestPermission(root.uri)
return
}
val timestamp = java.text.SimpleDateFormat("yyyyMMddHHmmss", java.util.Locale.US).format(java.util.Date())
try {
val exists = root.findFile(timestamp)
if (exists == null) {
val file = root.createFile("text/plain", timestamp)
Log.i("YuzuDebug", "[SAFWriter] [SAF] Attempted to create file: $timestamp, result: ${file?.uri}")
} else {
Log.i("YuzuDebug", "[SAFWriter] [SAF] File already exists: ${exists.uri}")
}
} catch (e: Exception) {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Error creating file in ${root.uri}: $e")
}
root.listFiles()?.filter { it.isDirectory }?.forEach { createRecursiveTimestampFilesInSAF(it, requestPermission, onPermissionGranted) }
}
fun handlePermissionResult(uri: Uri) {
context.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
Log.i("YuzuDebug", "[SAFWriter] [SAF] Permission granted for uri: $uri")
// Retry the pending action if any
pendingSAFAction?.invoke()
pendingSAFAction = null
}
}