0
0
mirror of https://github.com/Ishan09811/pine.git synced 2025-04-24 08:55:10 +00:00

Implement GpuDriver import option (#66)

This commit is contained in:
Ishan09811 2025-02-01 21:11:27 +05:30 committed by GitHub
parent d165984c89
commit f9fc9fa5ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 249 additions and 12 deletions

View File

@ -193,6 +193,14 @@ dependencies {
implementation 'info.debatty:java-string-similarity:2.0.0'
implementation 'com.github.KikiManjaro:colorpicker:v1.1.12'
implementation 'com.github.android:renderscript-intrinsics-replacement-toolkit:344be3f'
/* Network */
implementation 'io.ktor:ktor-client-core:3.0.3'
implementation 'io.ktor:ktor-client-cio:3.0.3'
implementation 'io.ktor:ktor-client-json:3.0.3'
implementation 'io.ktor:ktor-serialization-kotlinx-json:3.0.3'
implementation 'io.ktor:ktor-client-content-negotiation:3.0.3'
implementation 'io.ktor:ktor-client-logging:3.0.3'
}
kapt {

View File

@ -25,6 +25,7 @@ import emu.skyline.input.StickId.Right
import emu.skyline.utils.SwitchColors
import emu.skyline.utils.add
import emu.skyline.utils.multiply
import emu.skyline.SkylineApplication
import kotlin.math.roundToInt
open class CircularButton(
@ -33,7 +34,7 @@ open class CircularButton(
defaultRelativeX : Float,
defaultRelativeY : Float,
defaultRelativeRadiusToX : Float,
drawableId : Int = R.drawable.ic_button,
drawableId : Int = SkylineApplication.context.resources.getIdentifier("ic_button", "drawable", SkylineApplication.context.packageName),
defaultEnabled : Boolean = true
) : OnScreenButton(
onScreenControllerView,
@ -65,9 +66,9 @@ open class JoystickButton(
defaultRelativeX,
defaultRelativeY,
defaultRelativeRadiusToX,
R.drawable.ic_button
SkylineApplication.context.resources.getIdentifier("ic_button", "drawable", SkylineApplication.context.packageName)
) {
private val innerButton = CircularButton(onScreenControllerView, buttonId, config.relativeX, config.relativeY, defaultRelativeRadiusToX * 0.75f, R.drawable.ic_stick)
private val innerButton = CircularButton(onScreenControllerView, buttonId, config.relativeX, config.relativeY, defaultRelativeRadiusToX * 0.75f, SkylineApplication.context.resources.getIdentifier("ic_stick", "drawable", SkylineApplication.context.packageName))
open var recenterSticks = false
set(value) {
@ -275,7 +276,7 @@ open class RectangularButton(
defaultRelativeY : Float,
defaultRelativeWidth : Float,
defaultRelativeHeight : Float,
drawableId : Int = R.drawable.ic_rectangular_button,
drawableId : Int = SkylineApplication.context.resources.getIdentifier("ic_rectangular_button", "drawable", SkylineApplication.context.packageName),
defaultEnabled : Boolean = true
) : OnScreenButton(
onScreenControllerView,
@ -305,9 +306,9 @@ class TriggerButton(
defaultRelativeWidth,
defaultRelativeHeight,
when (buttonId) {
ZL -> R.drawable.ic_trigger_button_left
ZL -> SkylineApplication.context.resources.getIdentifier("ic_trigger_button_left", "drawable", SkylineApplication.context.packageName)
ZR -> R.drawable.ic_trigger_button_right
ZR -> SkylineApplication.context.resources.getIdentifier("ic_trigger_button_right", "drawable", SkylineApplication.context.packageName)
else -> error("Unsupported trigger button")
}

View File

@ -9,16 +9,19 @@ import android.content.Intent
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.os.Bundle
import android.view.ViewTreeObserver
import android.view.LayoutInflater
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.WindowCompat
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewbinding.ViewBinding
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.R
import emu.skyline.adapter.GenericListItem
@ -28,17 +31,24 @@ import emu.skyline.adapter.SpacingItemDecoration
import emu.skyline.data.BaseAppItem
import emu.skyline.data.AppItemTag
import emu.skyline.databinding.GpuDriverActivityBinding
import emu.skyline.databinding.DialogKeyboardBinding
import emu.skyline.settings.EmulationSettings
import emu.skyline.utils.GpuDriverHelper
import emu.skyline.utils.GpuDriverInstallResult
import emu.skyline.utils.WindowInsetsHelper
import emu.skyline.utils.serializable
import emu.skyline.utils.DriversFetcher
import emu.skyline.utils.DriversFetcher.DownloadResult
import emu.skyline.di.getSettings
import emu.skyline.SkylineApplication
import emu.skyline.getPublicFilesDir
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.distinctUntilChanged
import java.io.File
import java.io.FileInputStream
/**
* This activity is used to manage the installed gpu drivers and select one to use.
@ -189,11 +199,28 @@ class GpuDriverActivity : AppCompatActivity() {
binding.driverList.addItemDecoration(SpacingItemDecoration(resources.getDimensionPixelSize(R.dimen.grid_padding)))
binding.addDriverButton.setOnClickListener {
val items = arrayOf(getString(R.string.driver_import), getString(R.string.install))
var checkedItem = 0
var selectedItem: String? = items[0]
MaterialAlertDialogBuilder(this)
.setTitle(R.string.choose)
.setSingleChoiceItems(items, checkedItem) { dialog, which ->
selectedItem = items[which]
}
.setPositiveButton(android.R.string.ok) { dialog, _ ->
if (selectedItem == getString(R.string.install)) {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addFlags(FLAG_GRANT_READ_URI_PERMISSION)
type = "application/zip"
}
installCallback.launch(intent)
} else {
handleGpuDriverImport()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
populateAdapter()
@ -207,6 +234,72 @@ class GpuDriverActivity : AppCompatActivity() {
}
}
private fun handleGpuDriverImport() {
val inflater = LayoutInflater.from(this)
val inputBinding = DialogKeyboardBinding.inflate(inflater)
var textInputValue: String = getString(R.string.default_driver_repo_url)
inputBinding.editTextInput.setText(textInputValue)
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
textInputValue = text.toString()
}
MaterialAlertDialogBuilder(this)
.setView(inputBinding.root)
.setTitle(R.string.enter_repo_url)
.setPositiveButton(R.string.fetch) { _, _ ->
if (textInputValue.isNotEmpty()) {
fetchAndShowDrivers(textInputValue)
}
}
.setNegativeButton(android.R.string.cancel) {_, _ -> }
.show()
}
private fun fetchAndShowDrivers(repoUrl: String) {
lifecycleScope.launch(Dispatchers.Main) {
val releases = DriversFetcher.fetchReleases(repoUrl)
if (releases.isEmpty()) {
Snackbar.make(binding.root, "Failed to fetch ${repoUrl}: validation failed or check your internet connection", Snackbar.LENGTH_SHORT).show()
return@launch
}
val releaseNames = releases.map { it.first }
val releaseUrls = releases.map { it.second }
var chosenUrl: String? = releaseUrls[0]
var chosenName: String? = releaseNames[0]
MaterialAlertDialogBuilder(this@GpuDriverActivity)
.setTitle(R.string.drivers)
.setSingleChoiceItems(releaseNames.toTypedArray(), 0) { _, which ->
chosenUrl = releaseUrls[which]
chosenName = releaseNames[which]
}
.setPositiveButton(R.string.driver_import) { _, _ ->
downloadDriver(chosenUrl!!, chosenName!!)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}
private fun downloadDriver(chosenUrl: String, chosenName: String) {
GlobalScope.launch(Dispatchers.Main) {
var driverFile = File("${SkylineApplication.instance.getPublicFilesDir().canonicalPath}/${chosenName}.zip")
if (!driverFile.exists()) driverFile.createNewFile()
val result = DriversFetcher.downloadAsset(chosenUrl!!, driverFile)
when (result) {
is DownloadResult.Success -> {
val result = GpuDriverHelper.installDriver(this@GpuDriverActivity, FileInputStream(driverFile))
Snackbar.make(binding.root, resolveInstallResultString(result), Snackbar.LENGTH_LONG).show()
if (result == GpuDriverInstallResult.Success) populateAdapter()
driverFile.delete()
}
is DownloadResult.Error -> Snackbar.make(binding.root, "Failed to import ${chosenName}: ${result.message}", Snackbar.LENGTH_SHORT).show()
}
}
}
private fun resolveInstallResultString(result : GpuDriverInstallResult) = when (result) {
GpuDriverInstallResult.Success -> getString(R.string.gpu_driver_install_success)
GpuDriverInstallResult.InvalidArchive -> getString(R.string.gpu_driver_install_invalid_archive)

View File

@ -0,0 +1,105 @@
package emu.skyline.utils
import android.content.Context
import android.net.Uri
import android.util.Log
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.utils.io.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.io.OutputStream
import java.io.FileOutputStream
import java.io.File
object DriversFetcher {
private val httpClient = HttpClient {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(Logging) {
level = LogLevel.BODY
}
}
@Serializable
data class GitHubRelease(
val name: String,
val assets: List<Asset> = emptyList()
)
@Serializable
data class Asset(val browser_download_url: String)
suspend fun fetchReleases(repoUrl: String): List<Pair<String, String?>> {
val repoPath = repoUrl.removePrefix("https://github.com/")
val validationUrl = "https://api.github.com/repos/$repoPath/contents/.adrenoDrivers"
val apiUrl = "https://api.github.com/repos/$repoPath/releases"
return try {
val isValid = withContext(Dispatchers.IO) {
try {
httpClient.get(validationUrl).status.value == 200
} catch (e: Exception) {
false
}
}
if (!isValid) {
Log.d("DriversFetcher", "Provided driver repo url is not valid.")
return emptyList()
}
val releases: List<GitHubRelease> = withContext(Dispatchers.IO) {
httpClient.get(apiUrl).body()
}
releases.map { release ->
val assetUrl = release.assets.firstOrNull()?.browser_download_url
release.name to assetUrl
}
} catch (e: Exception) {
Log.e("DriversFetcher", "Error fetching releases: ${e.message}", e)
emptyList()
}
}
suspend fun downloadAsset(assetUrl: String, destinationFile: File): DownloadResult {
return try {
withContext(Dispatchers.IO) {
val response: HttpResponse = httpClient.get(assetUrl)
FileOutputStream(destinationFile)?.use { outputStream ->
writeResponseToStream(response, outputStream)
} ?: return@withContext DownloadResult.Error("Failed to open ${destinationFile.absolutePath.toString()}")
}
DownloadResult.Success
} catch (e: Exception) {
Log.e("DriversFetcher", "Error downloading file: ${e.message}", e)
DownloadResult.Error(e.message)
}
}
private suspend fun writeResponseToStream(response: HttpResponse, outputStream: OutputStream) {
val channel = response.bodyAsChannel()
val buffer = ByteArray(8192) // 8KB buffer size
while (!channel.isClosedForRead) {
val bytesRead = channel.readAvailable(buffer)
if (bytesRead > 0) {
outputStream.write(buffer, 0, bytesRead)
}
}
outputStream.flush()
}
sealed class DownloadResult {
object Success : DownloadResult()
data class Error(val message: String?) : DownloadResult()
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingHorizontal="24dp"
app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_text_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="text"
android:minHeight="48dp"
android:textAlignment="viewStart" />
</com.google.android.material.textfield.TextInputLayout>

View File

@ -201,6 +201,12 @@
<string name="system_driver">System Driver</string>
<string name="system_driver_desc">The GPU driver provided by your system</string>
<string name="install">Install</string>
<string name="driver_import">Import</string>
<string name="choose">Choose</string>
<string name="drivers">Drivers</string>
<string name="enter_repo_url">Enter repo url</string>
<string name="default_driver_repo_url">https://github.com/K11MCH1/AdrenoToolsDrivers</string>
<string name="fetch">Fetch</string>
<string name="gpu_driver_install_inprogress">Installing the GPU driver…</string>
<string name="gpu_driver_install_success">GPU driver installed successfully</string>
<string name="gpu_driver_install_invalid_archive">Failed to unzip the provided driver package</string>

View File

@ -2,7 +2,7 @@
buildscript {
ext {
kotlin_version = '1.9.22'
kotlin_version = '2.0.10'
hilt_version = '2.50'
}