From 5a5f7ec1b7554927bde8be94bc9f40951fbba225 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Tue, 12 May 2026 19:16:58 -0500 Subject: [PATCH] Clarify timing controls --- controller_gui.py | 168 ++++++++++++++++++++++++++++++----------- controller_profiles.py | 4 +- wheel_bindings.json | 2 +- 3 files changed, 128 insertions(+), 46 deletions(-) diff --git a/controller_gui.py b/controller_gui.py index 4aec7be..a01cf0d 100644 --- a/controller_gui.py +++ b/controller_gui.py @@ -12,6 +12,7 @@ from PySide6.QtWidgets import ( QDialog, QDialogButtonBox, QFormLayout, + QGroupBox, QHeaderView, QHBoxLayout, QLabel, @@ -73,7 +74,6 @@ class ControllerWindow(QMainWindow): self.lastButtonPress = {} self.lastLockedPressedButtons = set() self.remapButtonsAlreadyDown = set() - self.buttonCooldown = self.globalDebounceSeconds() self.remapAction = None self.updatingControls = False @@ -124,36 +124,16 @@ class ControllerWindow(QMainWindow): self.buttonPressesLogged.setChecked(bool(self.config.get("button_presses_logged", True))) 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.addWidget(self.buttonPressesLogged) 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.bindingTable.setHorizontalHeaderLabels(["Button", "Spotify API Call", "Debounce", "Vol Step", "Repeat MS"]) + self.timingButton = QPushButton("Timing Settings") + 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.setSelectionMode(QAbstractItemView.SingleSelection) self.bindingTable.verticalHeader().setVisible(False) @@ -165,6 +145,7 @@ class ControllerWindow(QMainWindow): self.bindingTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents) self.bindingTable.horizontalHeader().setSectionResizeMode(3, 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.clicked.connect(self.startRemap) @@ -325,12 +306,17 @@ class ControllerWindow(QMainWindow): self.config["button_presses_logged"] = self.buttonPressesLogged.isChecked() saveConfig(self.config) - def globalSettingsChanged(self): - self.config.setdefault("global_settings", {}) - self.config["global_settings"]["debounce_seconds"] = self.globalDebounceInput.value() - self.config["global_settings"]["volume_increment"] = self.globalVolumeInput.value() - self.config["global_settings"]["polling_interval_ms"] = self.globalPollingInput.value() - saveConfig(self.config) + def openTimingSettings(self): + dialog = TimingSettingsDialog(self.config, self.currentButtonSettings, self) + + if dialog.exec() == QDialog.Accepted: + self.currentButtonSettings = copy.deepcopy(dialog.buttonSettings) + + 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): controller = self.selectedController() @@ -394,11 +380,18 @@ class ControllerWindow(QMainWindow): actionBox.setCurrentIndex(actionBox.findData(self.currentBindings.get(binding, ""))) 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.setRange(0.00, 5.00) debounceInput.setSingleStep(0.05) debounceInput.setDecimals(2) - debounceInput.setSpecialValueText("Global") + debounceInput.setSpecialValueText("Off") debounceInput.setValue(self.buttonSetting(binding, "debounce_seconds", 0)) 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.setCellWidget(row, 1, actionBox) - self.bindingTable.setCellWidget(row, 2, debounceInput) - self.bindingTable.setCellWidget(row, 3, volumeInput) - self.bindingTable.setCellWidget(row, 4, pollingInput) + self.bindingTable.setCellWidget(row, 2, debounceModeBox) + self.bindingTable.setCellWidget(row, 3, debounceInput) + self.bindingTable.setCellWidget(row, 4, volumeInput) + self.bindingTable.setCellWidget(row, 5, pollingInput) self.bindingTable.setRowHeight(row, 34) self.bindingTable.resizeColumnsToContents() @@ -449,9 +443,10 @@ class ControllerWindow(QMainWindow): binding = buttonItem.text() actionBox = self.bindingTable.cellWidget(row, 1) - debounceInput = self.bindingTable.cellWidget(row, 2) - volumeInput = self.bindingTable.cellWidget(row, 3) - pollingInput = self.bindingTable.cellWidget(row, 4) + debounceModeBox = self.bindingTable.cellWidget(row, 2) + debounceInput = self.bindingTable.cellWidget(row, 3) + volumeInput = self.bindingTable.cellWidget(row, 4) + pollingInput = self.bindingTable.cellWidget(row, 5) action = actionBox.currentData() if action: @@ -461,7 +456,12 @@ class ControllerWindow(QMainWindow): settings = {} - if debounceInput.value() > 0: + debounceMode = debounceModeBox.currentData() + + if debounceMode != "global": + settings["debounce_mode"] = debounceMode + + if debounceMode == "custom": settings["debounce_seconds"] = debounceInput.value() if volumeInput.value() > 0: @@ -726,7 +726,7 @@ class ControllerWindow(QMainWindow): self.lockedButtonSettings = copy.deepcopy(self.currentButtonSettings) 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): 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) 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): return self.lockedButtonSettings.get(binding, {}).get("volume_increment", self.globalVolumeIncrement()) @@ -788,6 +797,79 @@ class SpotifySetupDialog(QDialog): 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(): app = QApplication(sys.argv) diff --git a/controller_profiles.py b/controller_profiles.py index cbefd4a..fb15d33 100644 --- a/controller_profiles.py +++ b/controller_profiles.py @@ -52,7 +52,7 @@ def makeDefaultConfig(): "default_profile_id": DEFAULT_WHEEL_PROFILE_ID, "button_presses_logged": True, "global_settings": { - "debounce_seconds": 0.25, + "debounce_seconds": 0.08, "volume_increment": 5, "polling_interval_ms": 120, }, @@ -121,7 +121,7 @@ def normalizeConfig(config): config.setdefault("default_profile_id", DEFAULT_WHEEL_PROFILE_ID) config.setdefault("button_presses_logged", True) 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("polling_interval_ms", 120) config.setdefault("profiles", []) diff --git a/wheel_bindings.json b/wheel_bindings.json index 5731b8b..d15634e 100644 --- a/wheel_bindings.json +++ b/wheel_bindings.json @@ -3,7 +3,7 @@ "default_profile_id": "friend_directinput_wheel", "button_presses_logged": true, "global_settings": { - "debounce_seconds": 0.25, + "debounce_seconds": 0.08, "volume_increment": 5, "polling_interval_ms": 120 },