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:
parent
d165984c89
commit
f9fc9fa5ed
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
105
app/src/main/java/emu/skyline/utils/DriversFetcher.kt
Normal file
105
app/src/main/java/emu/skyline/utils/DriversFetcher.kt
Normal 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()
|
||||
}
|
||||
}
|
24
app/src/main/res/layout/dialog_keyboard.xml
Normal file
24
app/src/main/res/layout/dialog_keyboard.xml
Normal 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>
|
@ -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>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.9.22'
|
||||
kotlin_version = '2.0.10'
|
||||
hilt_version = '2.50'
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user