Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ computer. Those lines say "update Satellite too".
- The Xbox/Guide button on XInput-style wired USB controllers (Xbox 360
and its many licensed clones, plus the Amazon Luna Controller) now
works. Before this fix, pressing it did nothing.
- Plugging in a controller that is already connected over Bluetooth no
longer leaves the cable doing nothing. The controller card now shows
"USB available" with a "Use wired" button that walks you through
switching to the cable; Dish never switches on its own, so charging
while you keep playing over Bluetooth works exactly as before.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ interface SlotActionListener {

fun onSwitchToDirect(slotId: String)

fun onSetupWired(slotId: String)

fun onManageDestinations()

fun onReconnect(slotId: String)
Expand Down Expand Up @@ -264,6 +266,9 @@ class ControllerAdapter(
val specs = mutableListOf(PillSpec(ctx.getString(label), icon, PillTone.FACT))
// The Direct/Standard mode chip only applies once a USB controller is on a known path.
if (isUsb && kind != null) specs.add(usbModeSpec(card))
if (isBt && card?.wiredSwitchAvailable == true) {
specs.add(PillSpec(ctx.getString(R.string.binding_usb_available), R.drawable.ic_usb, PillTone.WARN))
}
return specs
}

Expand Down Expand Up @@ -511,7 +516,9 @@ class ControllerAdapter(
val slot = row.slot
val bound = slot.boundStatus
if (bound == null || slot.boundConnectionId == null) {
return listOf(
val unboundActions = mutableListOf<CardAction>()
if (row.pathCard?.wiredSwitchAvailable == true) unboundActions += setupWiredAction()
unboundActions +=
if (row.connections.isEmpty()) {
CardAction(
R.drawable.ic_satellite,
Expand All @@ -526,8 +533,8 @@ class ControllerAdapter(
outlined = true,
kind = ActionKind.CONFIGURE,
)
},
)
}
return unboundActions
}
val actions = mutableListOf<CardAction>()
val connected = bound.live == LinkState.Connected
Expand Down Expand Up @@ -561,6 +568,7 @@ class ControllerAdapter(
kind = ActionKind.SWITCH_DIRECT,
)
}
if (row.pathCard?.wiredSwitchAvailable == true) actions += setupWiredAction()
actions +=
CardAction(
R.drawable.ic_tune,
Expand All @@ -571,6 +579,14 @@ class ControllerAdapter(
return actions
}

private fun setupWiredAction(): CardAction =
CardAction(
R.drawable.ic_usb,
ctx.getString(R.string.binding_action_use_wired),
outlined = false,
kind = ActionKind.SETUP_WIRED,
)

private fun dispatch(
kind: ActionKind,
slotId: String,
Expand All @@ -579,6 +595,7 @@ class ControllerAdapter(
ActionKind.GAMEPAD -> listener.onOpenGamepad(slotId)
ActionKind.TOUCHPAD -> listener.onOpenTouchpad(slotId)
ActionKind.SWITCH_DIRECT -> listener.onSwitchToDirect(slotId)
ActionKind.SETUP_WIRED -> listener.onSetupWired(slotId)
ActionKind.CONFIGURE -> listener.onConfigure(slotId)
ActionKind.FIND_HOSTS -> listener.onManageDestinations()
}
Expand Down Expand Up @@ -685,7 +702,7 @@ class ControllerAdapter(
val kind: ActionKind,
)

private enum class ActionKind { GAMEPAD, TOUCHPAD, SWITCH_DIRECT, CONFIGURE, FIND_HOSTS }
private enum class ActionKind { GAMEPAD, TOUCHPAD, SWITCH_DIRECT, SETUP_WIRED, CONFIGURE, FIND_HOSTS }

private enum class EdgeState { NONE, HOST_LOST, INPUT_LOST, UNSTEADY }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ class MainActivity :
viewModel.setInputPath(slotId, PathChoice.Direct)
}

override fun onSetupWired(slotId: String) {
nav.toSetupUsb()
}

override fun onOpenGamepad(slotId: String) {
val state = viewModel.uiState.value
val slot = state.slots.firstOrNull { it.id == slotId } ?: return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.tinkernorth.dish.source.store.MotionEnabledStore
import com.tinkernorth.dish.source.store.TouchpadModeStore
import com.tinkernorth.dish.source.store.UsbPathPreferenceStore
import com.tinkernorth.dish.source.usb.PathChoice
import com.tinkernorth.dish.source.usb.UsbController
import com.tinkernorth.dish.source.usb.UsbGamepadManager
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
Expand Down Expand Up @@ -115,11 +116,12 @@ class MainViewModel
slotsBase,
pathPrefs.state,
inputRateStore.state,
) { base, _, rates ->
usbGamepadManager.controllers,
) { base, _, rates, usbControllers ->
val pathCards =
base.slots
.mapNotNull { slot ->
pathCardFor(slot, base.devices)?.let { slot.id to it }
pathCardFor(slot, base.devices, usbControllers)?.let { slot.id to it }
}.toMap()
val inputRates =
base.slots
Expand Down Expand Up @@ -210,6 +212,7 @@ class MainViewModel
private fun pathCardFor(
slot: ControllerSlot,
devices: Map<Int, PhysicalGamepadRegistry.Device>,
usbControllers: Map<Int, UsbController>,
): PathCard? {
if (slot.inputType != SlotInputType.PHYSICAL) return null
val device = devices[slot.physicalDeviceId] ?: return null
Expand Down Expand Up @@ -243,6 +246,7 @@ class MainViewModel
restoreStuck = device.restoreStuck,
directFailure = device.directFailure,
padHasTouchpad = native.modelHasTouchpad(vid, pid),
wiredUsbPresent = wiredUsbPresentFor(device, usbControllers.values),
)
}

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/tinkernorth/dish/ui/main/PathCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ data class PathCard(
// Why the last Direct claim failed, when not already covered by needsReplug/restoreStuck.
val failure: DirectClaimFailure? = null,
val suggestDirectForTouch: Boolean = false,
val wiredSwitchAvailable: Boolean = false,
)

object PathCardMapper {
Expand All @@ -55,6 +56,7 @@ object PathCardMapper {
restoreStuck: Boolean = false,
directFailure: DirectClaimFailure? = null,
padHasTouchpad: Boolean = false,
wiredUsbPresent: Boolean = false,
): PathCard {
// The card reflects the mode the controller is ACTUALLY in: Direct only when a synthetic is live
// (claimed, not mid-release, not stuck). Badge and toggle both derive from this so they can never
Expand Down Expand Up @@ -91,6 +93,7 @@ object PathCardMapper {
restoreStuck = restoreStuck,
failure = directFailure,
suggestDirectForTouch = suggestDirectForTouch,
wiredSwitchAvailable = wiredUsbPresent && transport == Transport.Bluetooth,
)
}
}
25 changes: 25 additions & 0 deletions app/src/main/java/com/tinkernorth/dish/ui/main/WiredUsbPresence.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

package com.tinkernorth.dish.ui.main

import com.tinkernorth.dish.hotpath.input.PhysicalGamepadRegistry
import com.tinkernorth.dish.hotpath.input.Transport
import com.tinkernorth.dish.source.usb.UsbController
import com.tinkernorth.dish.source.usb.UsbPhase

internal fun wiredUsbPresentFor(
device: PhysicalGamepadRegistry.Device,
usbControllers: Collection<UsbController>,
): Boolean {
if (device.transport != Transport.Bluetooth || device.isUsbSynthetic) return false
return usbControllers.any { unrepresentedUsbTwinOf(device, it) }
}

private fun unrepresentedUsbTwinOf(
device: PhysicalGamepadRegistry.Device,
controller: UsbController,
): Boolean {
if (controller.phase != UsbPhase.Routed || !controller.usbPresent || controller.syntheticId != null) return false
if (controller.vendorId != device.vendorId) return false
return controller.frameworkId == null || controller.frameworkId == device.id
}
2 changes: 2 additions & 0 deletions app/src/main/res/values-bs/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@
<string name="binding_link_onscreen">Na ekranu</string>
<string name="binding_mode_direct">Brzi</string>
<string name="binding_mode_standard">Standardni</string>
<string name="binding_usb_available">USB dostupan</string>
<string name="binding_func_rumble">Vibracija</string>
<string name="binding_func_motion">Pokret</string>
<string name="binding_func_touchpad">Dodirna ploča</string>
Expand All @@ -360,6 +361,7 @@
<string name="binding_card_no_functions">Nijedna</string>
<string name="binding_action_configure">Podesi povezivanje</string>
<string name="binding_action_find_hosts">Pronađi hostove</string>
<string name="binding_action_use_wired">Koristi kabl</string>
<string name="binding_activity_title">Podesi povezivanje</string>
<string name="binding_activity_title_bind">Poveži kontroler</string>
<string name="binding_activity_apply">Primijeni</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@
<string name="binding_link_onscreen">Bildschirm</string>
<string name="binding_mode_direct">Direkt</string>
<string name="binding_mode_standard">Standard</string>
<string name="binding_usb_available">USB verfügbar</string>
<string name="binding_func_rumble">Vibration</string>
<string name="binding_func_motion">Bewegung</string>
<string name="binding_func_touchpad">Touchpad</string>
Expand All @@ -357,6 +358,7 @@
<string name="binding_card_no_functions">Keine</string>
<string name="binding_action_configure">Zuordnung konfigurieren</string>
<string name="binding_action_find_hosts">Hosts finden</string>
<string name="binding_action_use_wired">Kabel verwenden</string>
<string name="binding_activity_title">Zuordnung konfigurieren</string>
<string name="binding_activity_title_bind">Controller zuordnen</string>
<string name="binding_activity_apply">Übernehmen</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@
<string name="binding_link_onscreen">En pantalla</string>
<string name="binding_mode_direct">Directo</string>
<string name="binding_mode_standard">Estándar</string>
<string name="binding_usb_available">USB disponible</string>
<string name="binding_func_rumble">Vibración</string>
<string name="binding_func_motion">Movimiento</string>
<string name="binding_func_touchpad">Panel táctil</string>
Expand All @@ -361,6 +362,7 @@
<string name="binding_card_no_functions">Ninguna</string>
<string name="binding_action_configure">Configurar vínculos</string>
<string name="binding_action_find_hosts">Buscar hosts</string>
<string name="binding_action_use_wired">Usar cable</string>
<string name="binding_activity_title">Configurar vínculos</string>
<string name="binding_activity_title_bind">Vincular el mando</string>
<string name="binding_activity_apply">Aplicar</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@
<string name="binding_link_onscreen">À l\'écran</string>
<string name="binding_mode_direct">Direct</string>
<string name="binding_mode_standard">Standard</string>
<string name="binding_usb_available">USB disponible</string>
<string name="binding_func_rumble">Vibration</string>
<string name="binding_func_motion">Mouvement</string>
<string name="binding_func_touchpad">Pavé tactile</string>
Expand All @@ -360,6 +361,7 @@
<string name="binding_card_no_functions">Aucune</string>
<string name="binding_action_configure">Configurer la liaison</string>
<string name="binding_action_find_hosts">Trouver des hôtes</string>
<string name="binding_action_use_wired">Utiliser le câble</string>
<string name="binding_activity_title">Configurer la liaison</string>
<string name="binding_activity_title_bind">Lier la manette</string>
<string name="binding_activity_apply">Appliquer</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-pt-rBR/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@
<string name="binding_link_onscreen">Na tela</string>
<string name="binding_mode_direct">Direto</string>
<string name="binding_mode_standard">Padrão</string>
<string name="binding_usb_available">USB disponível</string>
<string name="binding_func_rumble">Vibração</string>
<string name="binding_func_motion">Movimento</string>
<string name="binding_func_touchpad">Touchpad</string>
Expand All @@ -361,6 +362,7 @@
<string name="binding_card_no_functions">Nenhuma</string>
<string name="binding_action_configure">Configurar vínculos</string>
<string name="binding_action_find_hosts">Encontrar hosts</string>
<string name="binding_action_use_wired">Usar cabo</string>
<string name="binding_activity_title">Configurar vínculos</string>
<string name="binding_activity_title_bind">Vincular o controle</string>
<string name="binding_activity_apply">Aplicar</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@
<string name="binding_link_onscreen">On-screen</string>
<string name="binding_mode_direct">Direct</string>
<string name="binding_mode_standard">Standard</string>
<string name="binding_usb_available">USB available</string>

<string name="binding_func_rumble">Rumble</string>
<string name="binding_func_motion">Motion</string>
Expand All @@ -508,6 +509,7 @@

<string name="binding_action_configure">Configure bindings</string>
<string name="binding_action_find_hosts">Find hosts</string>
<string name="binding_action_use_wired">Use wired</string>

<string name="binding_activity_title">Configure bindings</string>
<string name="binding_activity_title_bind">Bind controller</string>
Expand Down
Loading
Loading