Merge 71a0fc21ab
into 9a04793ae8
This commit is contained in:
commit
b36511d583
21 changed files with 536 additions and 5 deletions
10
src/android/app/src/ea/res/drawable/ic_multiplayer.xml
Normal file
10
src/android/app/src/ea/res/drawable/ic_multiplayer.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M0,720L0,667Q0,628.43 41.5,604.22Q83,580 150.38,580Q162.54,580 173.77,580.5Q185,581 196,582.65Q188,600 184,617.82Q180,635.63 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,635.14 776.5,617.57Q773,600 765,582.73Q776,581 787.17,580.5Q798.34,580 810,580Q877.5,580 918.75,603.77Q960,627.54 960,667L960,720L780,720ZM300,660L660,660L660,654Q660,617 609.5,593.5Q559,570 480,570Q401,570 350.5,593.5Q300,617 300,655L300,660ZM149.57,550Q121,550 100.5,529.44Q80,508.88 80,480Q80,451 100.56,430.5Q121.13,410 150,410Q179,410 199.5,430.5Q220,451 220,480.43Q220,509 199.5,529.5Q179,550 149.57,550ZM809.57,550Q781,550 760.5,529.44Q740,508.88 740,480Q740,451 760.56,430.5Q781.13,410 810,410Q839,410 859.5,430.5Q880,451 880,480.43Q880,509 859.5,529.5Q839,550 809.57,550ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480.35,420Q506,420 523,402.65Q540,385.3 540,359.65Q540,334 522.85,317Q505.7,300 480.35,300Q455,300 437.5,317.15Q420,334.3 420,359.65Q420,385 437.35,402.5Q454.7,420 480.35,420ZM480,660L480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660L480,660L480,660ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z"/>
|
||||
</vector>
|
|
@ -11,6 +11,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
|
|
@ -491,6 +491,17 @@ object NativeLibrary {
|
|||
*/
|
||||
external fun submitInlineKeyboardInput(key_code: Int)
|
||||
|
||||
/**
|
||||
* Connects to a room (similar with desktop version's "direct connect")
|
||||
*/
|
||||
external fun connectToRoom(nickname: String, server_addr: String, server_port: Int, password: String)
|
||||
|
||||
/**
|
||||
* Returns the state of the room member (client)
|
||||
* @return The state as a string
|
||||
*/
|
||||
external fun getRoomMemberState(): String
|
||||
|
||||
/**
|
||||
* Button type for use in onTouchEvent
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,8 @@ import android.content.Context
|
|||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.MultiplayerHelper
|
||||
import org.yuzu.yuzu_emu.utils.NetworkHelper
|
||||
import java.io.File
|
||||
|
||||
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
|
||||
|
@ -46,6 +48,8 @@ class YuzuApplication : Application() {
|
|||
documentsTree = DocumentsTree()
|
||||
DirectoryInitialization.start(applicationContext)
|
||||
GpuDriverHelper.initializeDriverParameters(applicationContext)
|
||||
NetworkHelper.getRoute(applicationContext)
|
||||
MultiplayerHelper.initRoom(applicationContext)
|
||||
NativeLibrary.logDeviceInfo()
|
||||
|
||||
createNotificationChannels();
|
||||
|
|
|
@ -107,7 +107,9 @@ class Settings {
|
|||
const val SECTION_RENDERER = "Renderer"
|
||||
const val SECTION_AUDIO = "Audio"
|
||||
const val SECTION_CPU = "Cpu"
|
||||
const val SECTION_NETWORK = "Network"
|
||||
const val SECTION_THEME = "Theme"
|
||||
const val SECTION_MULTIPLAYER = "Multiplayer"
|
||||
const val SECTION_DEBUG = "Debug"
|
||||
|
||||
const val PREF_OVERLAY_INIT = "OverlayInit"
|
||||
|
@ -130,6 +132,14 @@ class Settings {
|
|||
const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13"
|
||||
const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14"
|
||||
|
||||
const val PREF_FORCE_WIFI = "Network_ForceWifi"
|
||||
|
||||
const val PREF_ROOM_ADDRESS = "MultiplayerRoom_ServerAddress"
|
||||
const val PREF_ROOM_PORT = "MultiplayerRoom_ServerPort"
|
||||
const val PREF_ROOM_NICKNAME = "MultiplayerRoom_Nickname"
|
||||
const val PREF_ROOM_PASSWORD = "MultiplayerRoom_Password"
|
||||
const val PREF_ROOM_CONNECT_ON_START = "MultiplayerRoom_ConnectOnStart"
|
||||
|
||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
||||
|
@ -154,6 +164,7 @@ class Settings {
|
|||
SECTION_SYSTEM,
|
||||
SECTION_RENDERER,
|
||||
SECTION_AUDIO,
|
||||
SECTION_NETWORK,
|
||||
SECTION_CPU
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ enum class StringSetting(
|
|||
override val section: String,
|
||||
override val defaultValue: String
|
||||
) : AbstractStringSetting {
|
||||
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
|
||||
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"),
|
||||
NETWORK_ROUTE("network_route", Settings.SECTION_NETWORK, ";;;;");
|
||||
|
||||
override var string: String = defaultValue
|
||||
|
||||
|
@ -27,7 +28,8 @@ enum class StringSetting(
|
|||
|
||||
companion object {
|
||||
private val NOT_RUNTIME_EDITABLE = listOf(
|
||||
CUSTOM_RTC
|
||||
CUSTOM_RTC,
|
||||
NETWORK_ROUTE
|
||||
)
|
||||
|
||||
fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
|
||||
|
|
|
@ -35,5 +35,6 @@ abstract class SettingsItem(
|
|||
const val TYPE_STRING_SINGLE_CHOICE = 5
|
||||
const val TYPE_DATETIME_SETTING = 6
|
||||
const val TYPE_RUNNABLE = 7
|
||||
const val TYPE_TEXT_SETTING = 8
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||
|
||||
class TextSetting(
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_TEXT_SETTING
|
||||
|
||||
val value: String
|
||||
get() = if (setting != null) {
|
||||
val setting = setting as AbstractStringSetting
|
||||
setting.string
|
||||
} else {
|
||||
defaultValue!!
|
||||
}
|
||||
|
||||
fun setSelectedValue(string: String): AbstractStringSetting {
|
||||
val stringSetting = setting as AbstractStringSetting
|
||||
stringSetting.string = string
|
||||
return stringSetting
|
||||
}
|
||||
}
|
|
@ -7,13 +7,14 @@ import android.content.Context
|
|||
import android.content.DialogInterface
|
||||
import android.icu.util.Calendar
|
||||
import android.icu.util.TimeZone
|
||||
import android.text.TextWatcher
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -21,6 +22,7 @@ import com.google.android.material.slider.Slider
|
|||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import com.google.android.material.timepicker.TimeFormat
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
|
||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||
|
@ -83,6 +85,10 @@ class SettingsAdapter(
|
|||
RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_TEXT_SETTING -> {
|
||||
TextSettingViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// TODO: Create an error view since we can't return null now
|
||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||
|
@ -167,6 +173,7 @@ class SettingsAdapter(
|
|||
.setSelection(storedTime)
|
||||
.setTitleText(R.string.select_rtc_date)
|
||||
.build()
|
||||
|
||||
val timePicker: MaterialTimePicker = MaterialTimePicker.Builder()
|
||||
.setTimeFormat(timeFormat)
|
||||
.setHour(calendar.get(Calendar.HOUR_OF_DAY))
|
||||
|
@ -199,6 +206,38 @@ class SettingsAdapter(
|
|||
)
|
||||
}
|
||||
|
||||
fun onTextSettingClick(item: TextSetting, position: Int) {
|
||||
clickedItem = item
|
||||
clickedPosition = position
|
||||
var value = item.value
|
||||
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val editTextBinding = DialogEditTextBinding.inflate(inflater)
|
||||
|
||||
editTextBinding.editText.setText(value)
|
||||
|
||||
editTextBinding.editText.doAfterTextChanged {
|
||||
value = it.toString()
|
||||
}
|
||||
|
||||
dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(item.nameId)
|
||||
.setView(editTextBinding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (item.value != value) {
|
||||
fragmentView.onSettingChanged()
|
||||
}
|
||||
notifyItemChanged(clickedPosition)
|
||||
|
||||
val setting = item.setSelectedValue(value)
|
||||
fragmentView.putSetting(setting)
|
||||
clickedItem = null
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
||||
.show()
|
||||
|
||||
}
|
||||
|
||||
fun onSliderClick(item: SliderSetting, position: Int) {
|
||||
clickedItem = item
|
||||
clickedPosition = position
|
||||
|
|
|
@ -7,12 +7,12 @@ import android.content.SharedPreferences
|
|||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
@ -67,6 +67,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
Settings.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
Settings.SECTION_AUDIO -> addAudioSettings(sl)
|
||||
Settings.SECTION_NETWORK -> addNetworkSettings(sl)
|
||||
Settings.SECTION_MULTIPLAYER -> addMultiplayerSettings(sl)
|
||||
Settings.SECTION_THEME -> addThemeSettings(sl)
|
||||
Settings.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
else -> {
|
||||
|
@ -102,6 +104,13 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
Settings.SECTION_RENDERER
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
R.string.preferences_network,
|
||||
0,
|
||||
Settings.SECTION_NETWORK
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
R.string.preferences_audio,
|
||||
|
@ -466,6 +475,179 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
}
|
||||
}
|
||||
|
||||
private fun addNetworkSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_network))
|
||||
|
||||
sl.apply {
|
||||
val forceWifi: AbstractBooleanSetting = object : AbstractBooleanSetting {
|
||||
override var boolean: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_FORCE_WIFI, false)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_FORCE_WIFI, value)
|
||||
.apply()
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getBoolean(Settings.PREF_FORCE_WIFI, false).toString()
|
||||
override val defaultValue: Any = false
|
||||
}
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
forceWifi,
|
||||
R.string.set_force_wifi,
|
||||
R.string.force_wifi_desc,
|
||||
"",
|
||||
false
|
||||
)
|
||||
)
|
||||
add(
|
||||
TextSetting(
|
||||
StringSetting.NETWORK_ROUTE,
|
||||
R.string.set_network_route,
|
||||
R.string.network_route_desc,
|
||||
StringSetting.NETWORK_ROUTE.key,
|
||||
StringSetting.NETWORK_ROUTE.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMultiplayerSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_multiplayer))
|
||||
|
||||
sl.apply {
|
||||
val serverAddress: AbstractStringSetting = object : AbstractStringSetting {
|
||||
override var string: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: ""
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putString(Settings.PREF_ROOM_ADDRESS, value)
|
||||
.apply()
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: ""
|
||||
override val defaultValue: Any = -1
|
||||
}
|
||||
|
||||
val serverPort: AbstractStringSetting = object : AbstractStringSetting {
|
||||
override var string: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872"
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putString(Settings.PREF_ROOM_PORT, value)
|
||||
.apply()
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872"
|
||||
override val defaultValue: Any = "24872"
|
||||
}
|
||||
|
||||
val nickname: AbstractStringSetting = object : AbstractStringSetting {
|
||||
override var string: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: ""
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putString(Settings.PREF_ROOM_NICKNAME, value)
|
||||
.apply()
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: ""
|
||||
override val defaultValue: Any = -1
|
||||
}
|
||||
|
||||
val password: AbstractStringSetting = object : AbstractStringSetting {
|
||||
override var string: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: ""
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putString(Settings.PREF_ROOM_PASSWORD, value)
|
||||
.apply()
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: ""
|
||||
override val defaultValue: Any = -1
|
||||
}
|
||||
|
||||
val connectOnStart: AbstractBooleanSetting = object : AbstractBooleanSetting {
|
||||
override var boolean: Boolean
|
||||
get() = preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false)
|
||||
set(value) {
|
||||
preferences.edit()
|
||||
.putBoolean(Settings.PREF_ROOM_CONNECT_ON_START, value)
|
||||
.apply()
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String? = null
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String
|
||||
get() = preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false).toString()
|
||||
override val defaultValue: Any = -1
|
||||
}
|
||||
|
||||
add(
|
||||
TextSetting(
|
||||
serverAddress,
|
||||
R.string.multiplayer_room_server_address,
|
||||
0,
|
||||
"",
|
||||
""
|
||||
)
|
||||
)
|
||||
add(
|
||||
TextSetting(
|
||||
serverPort,
|
||||
R.string.multiplayer_room_server_port,
|
||||
0,
|
||||
"",
|
||||
""
|
||||
)
|
||||
)
|
||||
add(
|
||||
TextSetting(
|
||||
nickname,
|
||||
R.string.multiplayer_room_nickname,
|
||||
0,
|
||||
"",
|
||||
""
|
||||
)
|
||||
)
|
||||
add(
|
||||
TextSetting(
|
||||
password,
|
||||
R.string.multiplayer_room_password,
|
||||
0,
|
||||
"",
|
||||
""
|
||||
)
|
||||
)
|
||||
add(
|
||||
SwitchSetting(
|
||||
connectOnStart,
|
||||
R.string.multiplayer_room_connect_on_start,
|
||||
0,
|
||||
"",
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
|
||||
sl.apply {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.TextSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
|
||||
class TextSettingViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: TextSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as TextSetting
|
||||
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
if (setting.isEditable) {
|
||||
adapter.onTextSettingClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
if (setting.isEditable) {
|
||||
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -84,6 +84,11 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
|
||||
HomeSetting(
|
||||
R.string.preferences_multiplayer,
|
||||
R.string.multiplayer_description,
|
||||
R.drawable.ic_multiplayer
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_MULTIPLAYER, "") },
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
object MultiplayerHelper {
|
||||
fun initRoom(context: Context) {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
if(preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false)) {
|
||||
val addr = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: ""
|
||||
val port = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872"
|
||||
val nickname = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: ""
|
||||
val password = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: ""
|
||||
|
||||
NativeLibrary.connectToRoom(nickname, addr, port.toIntOrNull() ?: 0, password)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
object NetworkHelper {
|
||||
/**
|
||||
* Gets available network interface info/route info - currently the active network info.
|
||||
* @return The route info separated by semicolons (interface, address, netmask, gateway), or null if no networks are available.
|
||||
*/
|
||||
fun getRoute(context: Context): String? {
|
||||
val connectivity =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
if(connectivity.isActiveNetworkMetered && preferences.getBoolean(Settings.PREF_FORCE_WIFI, false))
|
||||
return null
|
||||
|
||||
val lp = connectivity.getLinkProperties(connectivity.activeNetwork) ?: return null
|
||||
|
||||
val ifName = lp.interfaceName
|
||||
val addr = lp.linkAddresses[0]
|
||||
val cidr = addr.prefixLength
|
||||
|
||||
val bits = 0xffffffff xor ((1 shl 32 - cidr)).toLong() - 1
|
||||
val mask = String.format(
|
||||
"%d.%d.%d.%d",
|
||||
bits and 0x0000000000ff000000L shr 24,
|
||||
bits and 0x000000000000ff0000L shr 16,
|
||||
bits and 0x00000000000000ff00L shr 8,
|
||||
bits and 0x0000000000000000ffL shr 0
|
||||
)
|
||||
|
||||
val gw = lp.routes.last { it.isDefaultRoute }.gateway?.hostAddress
|
||||
|
||||
return "$ifName;$addr;$mask;$gw"
|
||||
}
|
||||
}
|
|
@ -248,6 +248,10 @@ void Config::ReadValues() {
|
|||
ReadSetting("Audio", Settings::values.audio_output_device_id);
|
||||
ReadSetting("Audio", Settings::values.volume);
|
||||
|
||||
// Network
|
||||
|
||||
Settings::values.network_route = config->GetString("Network", "network_route", "");
|
||||
|
||||
// Miscellaneous
|
||||
// log_filter has a different default here than from common
|
||||
Settings::values.log_filter = "*:Info";
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include <android/api-level.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <core/loader/nro.h>
|
||||
#include <network/network.h>
|
||||
#include <network/room_member.h>
|
||||
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/dynamic_library.h"
|
||||
|
@ -420,6 +422,30 @@ public:
|
|||
return m_software_keyboard;
|
||||
}
|
||||
|
||||
void DirectConnectToRoom(const std::string& nickname, const char* server_addr = "127.0.0.1",
|
||||
u16 server_port = Network::DefaultRoomPort,
|
||||
const std::string& password = "") {
|
||||
auto room_network = m_system.GetRoomNetwork();
|
||||
|
||||
if (const auto member = room_network.GetRoomMember().lock()) {
|
||||
// Prevent the user from trying to join a room while they are already joining.
|
||||
if (member->GetState() == Network::RoomMember::State::Joining ||
|
||||
member->IsConnected()) {
|
||||
return;
|
||||
} else {
|
||||
member->Join(nickname, server_addr, server_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Network::RoomMember::State GetRoomMemberState() {
|
||||
if (const auto member = m_system.GetRoomNetwork().GetRoomMember().lock()) {
|
||||
return member->GetState();
|
||||
} else {
|
||||
return Network::RoomMember::State::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct RomMetadata {
|
||||
std::string title;
|
||||
|
@ -860,4 +886,40 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env
|
|||
EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_connectToRoom(JNIEnv* env, jclass clazz,
|
||||
jstring nickname, jstring server_addr,
|
||||
jint server_port, jstring password) {
|
||||
EmulationSession::GetInstance().DirectConnectToRoom(GetJString(env, nickname),
|
||||
GetJString(env, server_addr).c_str(),
|
||||
server_port, GetJString(env, password));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRoomMemberState(JNIEnv* env, jclass clazz) {
|
||||
auto state = EmulationSession::GetInstance().GetRoomMemberState();
|
||||
|
||||
std::string state_str{};
|
||||
|
||||
switch (state) {
|
||||
using State = Network::RoomMember::State;
|
||||
|
||||
case State::Uninitialized:
|
||||
state_str = "Uninitialized";
|
||||
break;
|
||||
case State::Idle:
|
||||
state_str = "Idle";
|
||||
break;
|
||||
case State::Joining:
|
||||
state_str = "Joining";
|
||||
break;
|
||||
case State::Joined:
|
||||
state_str = "Joined";
|
||||
break;
|
||||
case State::Moderator:
|
||||
state_str = "Moderator";
|
||||
break;
|
||||
}
|
||||
|
||||
return env->NewStringUTF(state_str.c_str());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
10
src/android/app/src/main/res/drawable/ic_multiplayer.xml
Normal file
10
src/android/app/src/main/res/drawable/ic_multiplayer.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M0,720L0,667Q0,628.43 41.5,604.22Q83,580 150.38,580Q162.54,580 173.77,580.5Q185,581 196,582.65Q188,600 184,617.82Q180,635.63 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,635.14 776.5,617.57Q773,600 765,582.73Q776,581 787.17,580.5Q798.34,580 810,580Q877.5,580 918.75,603.77Q960,627.54 960,667L960,720L780,720ZM300,660L660,660L660,654Q660,617 609.5,593.5Q559,570 480,570Q401,570 350.5,593.5Q300,617 300,655L300,660ZM149.57,550Q121,550 100.5,529.44Q80,508.88 80,480Q80,451 100.56,430.5Q121.13,410 150,410Q179,410 199.5,430.5Q220,451 220,480.43Q220,509 199.5,529.5Q179,550 149.57,550ZM809.57,550Q781,550 760.5,529.44Q740,508.88 740,480Q740,451 760.56,430.5Q781.13,410 810,410Q839,410 859.5,430.5Q880,451 880,480.43Q880,509 859.5,529.5Q839,550 809.57,550ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480.35,420Q506,420 523,402.65Q540,385.3 540,359.65Q540,334 522.85,317Q505.7,300 480.35,300Q455,300 437.5,317.15Q420,334.3 420,359.65Q420,385 437.35,402.5Q454.7,420 480.35,420ZM480,660L480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660L480,660L480,660ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z"/>
|
||||
</vector>
|
|
@ -81,6 +81,7 @@
|
|||
<string name="open_user_folder">Open yuzu folder</string>
|
||||
<string name="open_user_folder_description">Manage yuzu\'s internal files</string>
|
||||
<string name="theme_and_color_description">Modify the look of the app</string>
|
||||
<string name="multiplayer_description">Play with your friends!</string>
|
||||
<string name="no_file_manager">No file manager found</string>
|
||||
<string name="notification_no_directory_link">Could not open yuzu directory</string>
|
||||
<string name="notification_no_directory_link_description">Please locate the user folder with the file manager\'s side panel manually.</string>
|
||||
|
@ -157,6 +158,19 @@
|
|||
<string name="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string>
|
||||
<string name="set_custom_rtc">Set custom RTC</string>
|
||||
|
||||
<!-- Network settings strings -->
|
||||
<string name="network_route_desc">Sets the default network route</string>
|
||||
<string name="set_network_route">Set network route</string>
|
||||
<string name="set_force_wifi">Force Wi-Fi</string>
|
||||
<string name="force_wifi_desc">Forces yuzu to use only Wi-Fi (non-metered connections in general)</string>
|
||||
|
||||
<!-- Multiplayer settings strings -->
|
||||
<string name="multiplayer_room_server_address">Server address</string>
|
||||
<string name="multiplayer_room_server_port">Port</string>
|
||||
<string name="multiplayer_room_nickname">Nickname</string>
|
||||
<string name="multiplayer_room_password">Password</string>
|
||||
<string name="multiplayer_room_connect_on_start">Connect on start</string>
|
||||
|
||||
<!-- Graphics settings strings -->
|
||||
<string name="renderer_api">API</string>
|
||||
<string name="renderer_accuracy">Accuracy level</string>
|
||||
|
@ -212,9 +226,11 @@
|
|||
<string name="preferences_settings">Settings</string>
|
||||
<string name="preferences_general">General</string>
|
||||
<string name="preferences_system">System</string>
|
||||
<string name="preferences_network">Network</string>
|
||||
<string name="preferences_graphics">Graphics</string>
|
||||
<string name="preferences_audio">Audio</string>
|
||||
<string name="preferences_theme">Theme and color</string>
|
||||
<string name="preferences_multiplayer">Multiplayer</string>
|
||||
<string name="preferences_debug">Debug</string>
|
||||
|
||||
<!-- ROM loading errors -->
|
||||
|
|
|
@ -581,6 +581,7 @@ struct Values {
|
|||
|
||||
// Network
|
||||
Setting<std::string> network_interface{std::string(), "network_interface"};
|
||||
Setting<std::string> network_route{std::string(), "network_route"};
|
||||
|
||||
// WebService
|
||||
Setting<bool> enable_telemetry{true, "enable_telemetry"};
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "common/bit_cast.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
@ -18,8 +20,10 @@
|
|||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#endif
|
||||
|
||||
namespace Network {
|
||||
|
@ -91,8 +95,28 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
|
|||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
#elif defined(__ANDROID__)
|
||||
std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
|
||||
std::vector<NetworkInterface> result;
|
||||
|
||||
std::vector<std::string> route_parts;
|
||||
|
||||
boost::split(route_parts, Settings::values.network_route.GetValue(), boost::is_any_of(";"));
|
||||
|
||||
struct in_addr ip {
|
||||
}, sm{}, gw{};
|
||||
|
||||
inet_pton(AF_INET, route_parts[1].c_str(), &ip);
|
||||
inet_pton(AF_INET, route_parts[2].c_str(), &sm);
|
||||
inet_pton(AF_INET, route_parts[3].c_str(), &gw);
|
||||
|
||||
result.emplace_back(
|
||||
NetworkInterface{.name{route_parts[0]}, .ip_address{ip}, .subnet_mask{sm}, .gateway{gw}});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
|
||||
struct ifaddrs* ifaddr = nullptr;
|
||||
|
||||
|
@ -187,6 +211,10 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
|
|||
#endif
|
||||
|
||||
std::optional<NetworkInterface> GetSelectedNetworkInterface() {
|
||||
#ifdef __ANDROID__
|
||||
Network::SelectFirstNetworkInterface(); // TODO ANDROID
|
||||
#endif
|
||||
|
||||
const auto& selected_network_interface = Settings::values.network_interface.GetValue();
|
||||
const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
|
||||
if (network_interfaces.empty()) {
|
||||
|
|
|
@ -81,6 +81,7 @@ void DirectConnectWindow::Connect() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ui->ip->hasAcceptableInput()) {
|
||||
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID);
|
||||
return;
|
||||
|
|
Loading…
Add table
Reference in a new issue