Clarify timing controls

This commit is contained in:
2026-05-12 19:16:58 -05:00
parent 80239d27a6
commit 5a5f7ec1b7
3 changed files with 128 additions and 46 deletions

View File

@@ -12,6 +12,7 @@ from PySide6.QtWidgets import (
QDialog, QDialog,
QDialogButtonBox, QDialogButtonBox,
QFormLayout, QFormLayout,
QGroupBox,
QHeaderView, QHeaderView,
QHBoxLayout, QHBoxLayout,
QLabel, QLabel,
@@ -73,7 +74,6 @@ class ControllerWindow(QMainWindow):
self.lastButtonPress = {} self.lastButtonPress = {}
self.lastLockedPressedButtons = set() self.lastLockedPressedButtons = set()
self.remapButtonsAlreadyDown = set() self.remapButtonsAlreadyDown = set()
self.buttonCooldown = self.globalDebounceSeconds()
self.remapAction = None self.remapAction = None
self.updatingControls = False self.updatingControls = False
@@ -124,36 +124,16 @@ class ControllerWindow(QMainWindow):
self.buttonPressesLogged.setChecked(bool(self.config.get("button_presses_logged", True))) self.buttonPressesLogged.setChecked(bool(self.config.get("button_presses_logged", True)))
self.buttonPressesLogged.stateChanged.connect(self.buttonLoggingChanged) self.buttonPressesLogged.stateChanged.connect(self.buttonLoggingChanged)
self.globalDebounceInput = QDoubleSpinBox()
self.globalDebounceInput.setRange(0.01, 5.00)
self.globalDebounceInput.setSingleStep(0.05)
self.globalDebounceInput.setDecimals(2)
self.globalDebounceInput.setValue(self.globalDebounceSeconds())
self.globalDebounceInput.valueChanged.connect(self.globalSettingsChanged)
self.globalVolumeInput = QSpinBox()
self.globalVolumeInput.setRange(1, 100)
self.globalVolumeInput.setValue(self.globalVolumeIncrement())
self.globalVolumeInput.valueChanged.connect(self.globalSettingsChanged)
self.globalPollingInput = QSpinBox()
self.globalPollingInput.setRange(25, 5000)
self.globalPollingInput.setSingleStep(25)
self.globalPollingInput.setValue(self.globalPollingIntervalMs())
self.globalPollingInput.valueChanged.connect(self.globalSettingsChanged)
settingsRow = QHBoxLayout() settingsRow = QHBoxLayout()
settingsRow.addWidget(self.buttonPressesLogged) settingsRow.addWidget(self.buttonPressesLogged)
settingsRow.addStretch(1) settingsRow.addStretch(1)
settingsRow.addWidget(QLabel("Global debounce"))
settingsRow.addWidget(self.globalDebounceInput)
settingsRow.addWidget(QLabel("Global volume step"))
settingsRow.addWidget(self.globalVolumeInput)
settingsRow.addWidget(QLabel("Volume repeat ms"))
settingsRow.addWidget(self.globalPollingInput)
self.bindingTable = QTableWidget(0, 5) self.timingButton = QPushButton("Timing Settings")
self.bindingTable.setHorizontalHeaderLabels(["Button", "Spotify API Call", "Debounce", "Vol Step", "Repeat MS"]) self.timingButton.clicked.connect(self.openTimingSettings)
settingsRow.addWidget(self.timingButton)
self.bindingTable = QTableWidget(0, 6)
self.bindingTable.setHorizontalHeaderLabels(["Button", "Spotify API Call", "Debounce Mode", "Debounce", "Volume Step %", "Held Repeat MS"])
self.bindingTable.setSelectionBehavior(QAbstractItemView.SelectRows) self.bindingTable.setSelectionBehavior(QAbstractItemView.SelectRows)
self.bindingTable.setSelectionMode(QAbstractItemView.SingleSelection) self.bindingTable.setSelectionMode(QAbstractItemView.SingleSelection)
self.bindingTable.verticalHeader().setVisible(False) self.bindingTable.verticalHeader().setVisible(False)
@@ -165,6 +145,7 @@ class ControllerWindow(QMainWindow):
self.bindingTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents) self.bindingTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
self.bindingTable.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents) self.bindingTable.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents)
self.bindingTable.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeToContents) self.bindingTable.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeToContents)
self.bindingTable.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeToContents)
self.mapButton = QPushButton("Capture Button For Selected Action") self.mapButton = QPushButton("Capture Button For Selected Action")
self.mapButton.clicked.connect(self.startRemap) self.mapButton.clicked.connect(self.startRemap)
@@ -325,12 +306,17 @@ class ControllerWindow(QMainWindow):
self.config["button_presses_logged"] = self.buttonPressesLogged.isChecked() self.config["button_presses_logged"] = self.buttonPressesLogged.isChecked()
saveConfig(self.config) saveConfig(self.config)
def globalSettingsChanged(self): def openTimingSettings(self):
self.config.setdefault("global_settings", {}) dialog = TimingSettingsDialog(self.config, self.currentButtonSettings, self)
self.config["global_settings"]["debounce_seconds"] = self.globalDebounceInput.value()
self.config["global_settings"]["volume_increment"] = self.globalVolumeInput.value() if dialog.exec() == QDialog.Accepted:
self.config["global_settings"]["polling_interval_ms"] = self.globalPollingInput.value() self.currentButtonSettings = copy.deepcopy(dialog.buttonSettings)
saveConfig(self.config)
if self.selectedController() is not None and self.selectedController().get("instance_id") == self.lockedInstanceId:
self.lockedButtonSettings = copy.deepcopy(self.currentButtonSettings)
self.updateBindingTable()
self.statusLabel.setText("Timing settings updated.")
def lockSelectedController(self): def lockSelectedController(self):
controller = self.selectedController() controller = self.selectedController()
@@ -394,11 +380,18 @@ class ControllerWindow(QMainWindow):
actionBox.setCurrentIndex(actionBox.findData(self.currentBindings.get(binding, ""))) actionBox.setCurrentIndex(actionBox.findData(self.currentBindings.get(binding, "")))
actionBox.currentIndexChanged.connect(lambda ignored=None, row=row: self.tableRowChanged(row)) actionBox.currentIndexChanged.connect(lambda ignored=None, row=row: self.tableRowChanged(row))
debounceModeBox = QComboBox()
debounceModeBox.addItem("Global", "global")
debounceModeBox.addItem("Off", "off")
debounceModeBox.addItem("Custom", "custom")
debounceModeBox.setCurrentIndex(debounceModeBox.findData(self.buttonSetting(binding, "debounce_mode", "global")))
debounceModeBox.currentIndexChanged.connect(lambda ignored=None, row=row: self.tableRowChanged(row))
debounceInput = QDoubleSpinBox() debounceInput = QDoubleSpinBox()
debounceInput.setRange(0.00, 5.00) debounceInput.setRange(0.00, 5.00)
debounceInput.setSingleStep(0.05) debounceInput.setSingleStep(0.05)
debounceInput.setDecimals(2) debounceInput.setDecimals(2)
debounceInput.setSpecialValueText("Global") debounceInput.setSpecialValueText("Off")
debounceInput.setValue(self.buttonSetting(binding, "debounce_seconds", 0)) debounceInput.setValue(self.buttonSetting(binding, "debounce_seconds", 0))
debounceInput.valueChanged.connect(lambda ignored=None, row=row: self.tableRowChanged(row)) debounceInput.valueChanged.connect(lambda ignored=None, row=row: self.tableRowChanged(row))
@@ -417,9 +410,10 @@ class ControllerWindow(QMainWindow):
self.bindingTable.setItem(row, 0, buttonItem) self.bindingTable.setItem(row, 0, buttonItem)
self.bindingTable.setCellWidget(row, 1, actionBox) self.bindingTable.setCellWidget(row, 1, actionBox)
self.bindingTable.setCellWidget(row, 2, debounceInput) self.bindingTable.setCellWidget(row, 2, debounceModeBox)
self.bindingTable.setCellWidget(row, 3, volumeInput) self.bindingTable.setCellWidget(row, 3, debounceInput)
self.bindingTable.setCellWidget(row, 4, pollingInput) self.bindingTable.setCellWidget(row, 4, volumeInput)
self.bindingTable.setCellWidget(row, 5, pollingInput)
self.bindingTable.setRowHeight(row, 34) self.bindingTable.setRowHeight(row, 34)
self.bindingTable.resizeColumnsToContents() self.bindingTable.resizeColumnsToContents()
@@ -449,9 +443,10 @@ class ControllerWindow(QMainWindow):
binding = buttonItem.text() binding = buttonItem.text()
actionBox = self.bindingTable.cellWidget(row, 1) actionBox = self.bindingTable.cellWidget(row, 1)
debounceInput = self.bindingTable.cellWidget(row, 2) debounceModeBox = self.bindingTable.cellWidget(row, 2)
volumeInput = self.bindingTable.cellWidget(row, 3) debounceInput = self.bindingTable.cellWidget(row, 3)
pollingInput = self.bindingTable.cellWidget(row, 4) volumeInput = self.bindingTable.cellWidget(row, 4)
pollingInput = self.bindingTable.cellWidget(row, 5)
action = actionBox.currentData() action = actionBox.currentData()
if action: if action:
@@ -461,7 +456,12 @@ class ControllerWindow(QMainWindow):
settings = {} settings = {}
if debounceInput.value() > 0: debounceMode = debounceModeBox.currentData()
if debounceMode != "global":
settings["debounce_mode"] = debounceMode
if debounceMode == "custom":
settings["debounce_seconds"] = debounceInput.value() settings["debounce_seconds"] = debounceInput.value()
if volumeInput.value() > 0: if volumeInput.value() > 0:
@@ -726,7 +726,7 @@ class ControllerWindow(QMainWindow):
self.lockedButtonSettings = copy.deepcopy(self.currentButtonSettings) self.lockedButtonSettings = copy.deepcopy(self.currentButtonSettings)
def globalDebounceSeconds(self): def globalDebounceSeconds(self):
return self.config.get("global_settings", {}).get("debounce_seconds", 0.25) return self.config.get("global_settings", {}).get("debounce_seconds", 0.08)
def globalVolumeIncrement(self): def globalVolumeIncrement(self):
return self.config.get("global_settings", {}).get("volume_increment", 5) return self.config.get("global_settings", {}).get("volume_increment", 5)
@@ -735,7 +735,16 @@ class ControllerWindow(QMainWindow):
return self.config.get("global_settings", {}).get("polling_interval_ms", 120) return self.config.get("global_settings", {}).get("polling_interval_ms", 120)
def debounceSecondsForButton(self, binding): def debounceSecondsForButton(self, binding):
return self.lockedButtonSettings.get(binding, {}).get("debounce_seconds", self.globalDebounceSeconds()) debounceSettings = self.lockedButtonSettings.get(binding, {})
debounceMode = debounceSettings.get("debounce_mode", "global")
if debounceMode == "off":
return 0
if debounceMode == "custom":
return debounceSettings.get("debounce_seconds", self.globalDebounceSeconds())
return self.globalDebounceSeconds()
def volumeIncrementForButton(self, binding): def volumeIncrementForButton(self, binding):
return self.lockedButtonSettings.get(binding, {}).get("volume_increment", self.globalVolumeIncrement()) return self.lockedButtonSettings.get(binding, {}).get("volume_increment", self.globalVolumeIncrement())
@@ -788,6 +797,79 @@ class SpotifySetupDialog(QDialog):
self.accept() self.accept()
class TimingSettingsDialog(QDialog):
def __init__(self, config, buttonSettings, parent=None):
super().__init__(parent)
self.config = config
self.buttonSettings = copy.deepcopy(buttonSettings)
self.setWindowTitle("Timing Settings")
self.debounceInput = QDoubleSpinBox()
self.debounceInput.setRange(0.01, 5.00)
self.debounceInput.setSingleStep(0.01)
self.debounceInput.setDecimals(2)
self.debounceInput.setValue(self.globalDebounceSeconds())
self.volumeInput = QSpinBox()
self.volumeInput.setRange(1, 100)
self.volumeInput.setValue(self.globalVolumeIncrement())
self.repeatInput = QSpinBox()
self.repeatInput.setRange(25, 5000)
self.repeatInput.setSingleStep(25)
self.repeatInput.setValue(self.globalPollingIntervalMs())
buttonGroup = QGroupBox("Button Press Timing")
buttonLayout = QFormLayout()
buttonLayout.addRow("Debounce", self.debounceInput)
buttonLayout.addRow(QLabel("Used for play/pause, next, previous, mute, and other click actions. Per-button overrides can set Debounce Mode to Off for rotary encoders."))
buttonGroup.setLayout(buttonLayout)
volumeGroup = QGroupBox("Spotify Volume Timing")
volumeLayout = QFormLayout()
volumeLayout.addRow("Spotify volume change per step (%)", self.volumeInput)
volumeLayout.addRow("Held volume repeat speed (milliseconds)", self.repeatInput)
volumeLayout.addRow(QLabel("Used only for Spotify volume up/down. Holding a volume button repeats at this speed."))
volumeGroup.setLayout(volumeLayout)
overrideGroup = QGroupBox("Per-Button Overrides")
overrideLayout = QVBoxLayout()
overrideLayout.addWidget(QLabel("The mapping table can override timing for a specific button. Debounce Mode can be Global, Off, or Custom. Leave volume/repeat overrides at Global to use these defaults."))
overrideGroup.setLayout(overrideLayout)
self.buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
self.buttons.accepted.connect(self.saveAndAccept)
self.buttons.rejected.connect(self.reject)
layout = QVBoxLayout()
layout.addWidget(buttonGroup)
layout.addWidget(volumeGroup)
layout.addWidget(overrideGroup)
layout.addWidget(self.buttons)
self.setLayout(layout)
self.resize(620, 300)
def saveAndAccept(self):
self.config.setdefault("global_settings", {})
self.config["global_settings"]["debounce_seconds"] = self.debounceInput.value()
self.config["global_settings"]["volume_increment"] = self.volumeInput.value()
self.config["global_settings"]["polling_interval_ms"] = self.repeatInput.value()
saveConfig(self.config)
self.accept()
def globalDebounceSeconds(self):
return self.config.get("global_settings", {}).get("debounce_seconds", 0.08)
def globalVolumeIncrement(self):
return self.config.get("global_settings", {}).get("volume_increment", 5)
def globalPollingIntervalMs(self):
return self.config.get("global_settings", {}).get("polling_interval_ms", 120)
def runGui(): def runGui():
app = QApplication(sys.argv) app = QApplication(sys.argv)

View File

@@ -52,7 +52,7 @@ def makeDefaultConfig():
"default_profile_id": DEFAULT_WHEEL_PROFILE_ID, "default_profile_id": DEFAULT_WHEEL_PROFILE_ID,
"button_presses_logged": True, "button_presses_logged": True,
"global_settings": { "global_settings": {
"debounce_seconds": 0.25, "debounce_seconds": 0.08,
"volume_increment": 5, "volume_increment": 5,
"polling_interval_ms": 120, "polling_interval_ms": 120,
}, },
@@ -121,7 +121,7 @@ def normalizeConfig(config):
config.setdefault("default_profile_id", DEFAULT_WHEEL_PROFILE_ID) config.setdefault("default_profile_id", DEFAULT_WHEEL_PROFILE_ID)
config.setdefault("button_presses_logged", True) config.setdefault("button_presses_logged", True)
config.setdefault("global_settings", {}) config.setdefault("global_settings", {})
config["global_settings"].setdefault("debounce_seconds", 0.25) config["global_settings"].setdefault("debounce_seconds", 0.08)
config["global_settings"].setdefault("volume_increment", 5) config["global_settings"].setdefault("volume_increment", 5)
config["global_settings"].setdefault("polling_interval_ms", 120) config["global_settings"].setdefault("polling_interval_ms", 120)
config.setdefault("profiles", []) config.setdefault("profiles", [])

View File

@@ -3,7 +3,7 @@
"default_profile_id": "friend_directinput_wheel", "default_profile_id": "friend_directinput_wheel",
"button_presses_logged": true, "button_presses_logged": true,
"global_settings": { "global_settings": {
"debounce_seconds": 0.25, "debounce_seconds": 0.08,
"volume_increment": 5, "volume_increment": 5,
"polling_interval_ms": 120 "polling_interval_ms": 120
}, },