From b83628ab64341b3473106c291bbbb95e3af796b5 Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Mon, 23 Feb 2026 14:13:23 +0100 Subject: [PATCH 1/6] Add thrust interlock and move gamepad status to Flight tab When connecting to a Crazyflie or selecting/changing an input device or mapping, a thrust interlock is now engaged that forces thrust to zero until the physical throttle is brought to zero by the user. This prevents unexpected take-off due to a misconfigured mapping or a non-zero throttle position at connection time. The gamepad device info (device name, mapping, mux) previously shown in the status bar is now displayed in a dedicated Gamepad section in the Flight tab, alongside an interlock status indicator. Fixes #775 --- src/cfclient/ui/main.py | 88 ++++++++++++++-------------- src/cfclient/ui/tabs/FlightTab.py | 23 ++++++++ src/cfclient/ui/tabs/flightTab.ui | 67 ++++++++++++++++++++- src/cfclient/utils/input/__init__.py | 25 ++++++++ 4 files changed, 157 insertions(+), 46 deletions(-) diff --git a/src/cfclient/ui/main.py b/src/cfclient/ui/main.py index 0990cb097..a574b6e0c 100644 --- a/src/cfclient/ui/main.py +++ b/src/cfclient/ui/main.py @@ -61,7 +61,6 @@ from PyQt6.QtGui import QShortcut from PyQt6.QtGui import QDesktopServices from PyQt6.QtGui import QPalette -from PyQt6.QtWidgets import QLabel from PyQt6.QtWidgets import QMenu from PyQt6.QtWidgets import QMessageBox @@ -100,6 +99,8 @@ class MainUI(QtWidgets.QMainWindow, main_window_class): disconnectedSignal = pyqtSignal(str) linkQualitySignal = pyqtSignal(float) + gamepad_device_updated = pyqtSignal(str, str, str) + _input_device_error_signal = pyqtSignal(str) _input_discovery_signal = pyqtSignal(object) _log_error_signal = pyqtSignal(object, str) @@ -130,18 +131,8 @@ def __init__(self, *args): self.scanner.interfaceFoundSignal.connect(self.foundInterfaces) self.scanner.start() - # Create and start the Input Reader - self._statusbar_label = QLabel("No input-device found, insert one to" - " fly.") - self.statusBar().addWidget(self._statusbar_label) - - # - # We use this hacky-trick to find out if we are in dark-mode and - # figure out what bgcolor to set from that. We always use the current - # palette forgreound. - # - self.textColor = self._statusbar_label.palette().color(QPalette.ColorRole.WindowText) - self.bgColor = self._statusbar_label.palette().color(QPalette.ColorRole.Window) + self.textColor = self.palette().color(QPalette.ColorRole.WindowText) + self.bgColor = self.palette().color(QPalette.ColorRole.Window) self.isDark = self.textColor.value() > self.bgColor.value() self.joystickReader = JoystickReader() @@ -552,6 +543,19 @@ def set_preferred_dock_area(self, area): tab_toolbox = dock_widget.tab_toolbox tab_toolbox.set_preferred_dock_area(area) + def _rescan_devices(self): + self.gamepad_device_updated.emit("No input device connected", "—", "—") + self._menu_devices.clear() + self._active_device = "" + self.joystickReader.stop_input() + + # for c in self._menu_mappings.actions(): + # c.setEnabled(False) + # devs = self.joystickReader.available_devices() + # if (len(devs) > 0): + # self.device_discovery(devs) + + def _show_input_device_config_dialog(self): self.inputConfig = InputConfigDialogue(self.joystickReader) self.inputConfig.show() @@ -583,6 +587,7 @@ def _update_battery(self, timestamp, data, logconf): def _connected(self): self.uiState = UIState.CONNECTED self._update_ui_state() + self.joystickReader.require_thrust_zero() Config().set("link_uri", str(self._connectivity_manager.get_interface())) @@ -709,41 +714,34 @@ def _mux_selected(self, checked): if type(dev_node) is QAction and dev_node.isChecked(): dev_node.toggled.emit(True) - self._update_input_device_footer() - - def _get_dev_status(self, device): - msg = "{}".format(device.name) - if device.supports_mapping: - map_name = "No input mapping" - if device.input_map: - # Display the friendly name instead of the config file name - map_name = ConfigManager().get_display_name(device.input_map_name) - msg += " ({})".format(map_name) - return msg + self._update_input_device_status() - def _update_input_device_footer(self): - """Update the footer in the bottom of the UI with status for the - input device and its mapping""" - - msg = "" + def _update_input_device_status(self): + """Update the gamepad device info in the Flight tab.""" if len(self.joystickReader.available_devices()) > 0: mux = self.joystickReader._selected_mux - msg = "Using {} mux with ".format(mux.name) - for key in list(mux._devs.keys())[:-1]: - if mux._devs[key]: - msg += "{}, ".format(self._get_dev_status(mux._devs[key])) + mux_name = mux.name + device_names = [] + mapping_names = [] + for dev in mux._devs.values(): + if dev: + device_names.append(dev.name) + if dev.supports_mapping: + mapping_names.append( + dev.input_map_name if dev.input_map else "No mapping") + else: + mapping_names.append("N/A") else: - msg += "N/A, " - # Last item - key = list(mux._devs.keys())[-1] - if mux._devs[key]: - msg += "{}".format(self._get_dev_status(mux._devs[key])) - else: - msg += "N/A" + device_names.append("N/A") + mapping_names.append("N/A") + device_str = ", ".join(device_names) + mapping_str = ", ".join(mapping_names) else: - msg = "No input device found" - self._statusbar_label.setText(msg) + device_str = "No input device found" + mapping_str = "—" + mux_name = "—" + self.gamepad_device_updated.emit(device_str, mapping_str, mux_name) def _inputdevice_selected(self, checked): """Called when a new input device has been selected from the menu. The @@ -777,7 +775,7 @@ def _inputdevice_selected(self, checked): self._mapping_support = self.joystickReader.start_input( device.name, role_in_mux) - self._update_input_device_footer() + self._update_input_device_status() def _inputconfig_selected(self, checked): """Called when a new configuration has been selected from the menu. The @@ -790,7 +788,7 @@ def _inputconfig_selected(self, checked): selected_mapping = self.sender().config_name device = self.sender().data().data()[1] self.joystickReader.set_input_map(device.name, selected_mapping) - self._update_input_device_footer() + self._update_input_device_status() def device_discovery(self, devs): """Called when new devices have been added""" @@ -870,7 +868,7 @@ def device_discovery(self, devs): self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True) logger.info("Select first device") - self._update_input_device_footer() + self._update_input_device_status() def _open_config_folder(self): QDesktopServices.openUrl( diff --git a/src/cfclient/ui/tabs/FlightTab.py b/src/cfclient/ui/tabs/FlightTab.py index 3706d104b..f4aec0637 100644 --- a/src/cfclient/ui/tabs/FlightTab.py +++ b/src/cfclient/ui/tabs/FlightTab.py @@ -93,6 +93,8 @@ class FlightTab(TabToolbox, flight_tab_class): _log_data_signal = pyqtSignal(int, object, object) _pose_data_signal = pyqtSignal(object, object) + _thrust_lock_signal = pyqtSignal(bool) + _gamepad_device_signal = pyqtSignal(str, str, str) _input_updated_signal = pyqtSignal(float, float, float, float) _rp_trim_updated_signal = pyqtSignal(float, float) _emergency_stop_updated_signal = pyqtSignal(bool) @@ -122,6 +124,14 @@ def __init__(self, helper): super(FlightTab, self).__init__(helper, 'Flight Control') self.setupUi(self) + self._thrust_lock_signal.connect(self._thrust_lock_updated) + self._helper.inputDeviceReader.thrust_lock_active.add_callback( + self._thrust_lock_signal.emit) + + self._gamepad_device_signal.connect(self._gamepad_device_updated) + self._helper.mainUI.gamepad_device_updated.connect( + self._gamepad_device_signal.emit) + self.disconnectedSignal.connect(self.disconnected) self.connectionFinishedSignal.connect(self.connected) # Incomming signals @@ -216,6 +226,19 @@ def __init__(self, helper): self._helper.pose_logger.data_received_cb.add_callback(self._pose_data_signal.emit) + def _thrust_lock_updated(self, active): + if active: + self.gamepadStatusLabel.setText("Lower throttle to arm") + self.gamepadStatusLabel.setStyleSheet("color: red;") + else: + self.gamepadStatusLabel.setText("Ready") + self.gamepadStatusLabel.setStyleSheet("") + + def _gamepad_device_updated(self, device, mapping, mux): + self.gamepadNameLabel.setText(device) + self.gamepadMappingLabel.setText(mapping) + self.gamepadMuxLabel.setText(mux) + def _set_limiting_enabled(self, rp_limiting_enabled, yaw_limiting_enabled, thrust_limiting_enabled): self.targetCalRoll.setEnabled(rp_limiting_enabled) diff --git a/src/cfclient/ui/tabs/flightTab.ui b/src/cfclient/ui/tabs/flightTab.ui index 8634d0e22..8e4aba464 100644 --- a/src/cfclient/ui/tabs/flightTab.ui +++ b/src/cfclient/ui/tabs/flightTab.ui @@ -360,6 +360,71 @@ + + + + Gamepad + + + + + + Status + + + + + + + Ready + + + + + + + Gamepad + + + + + + + No input device found + + + + + + + Mapping + + + + + + + + + + + + + + Mux + + + + + + + + + + + + + @@ -649,7 +714,7 @@ - Gamepad Input + Gamepad Setpoint Qt::AlignmentFlag::AlignCenter diff --git a/src/cfclient/utils/input/__init__.py b/src/cfclient/utils/input/__init__.py index 09628b9d3..58cb007dd 100644 --- a/src/cfclient/utils/input/__init__.py +++ b/src/cfclient/utils/input/__init__.py @@ -166,6 +166,8 @@ def __init__(self, do_device_discovery=True): ConfigManager().get_list_of_configs() + self._thrust_interlock = False + self.input_updated = Caller() self.assisted_input_updated = Caller() self.heighthold_input_updated = Caller() @@ -182,6 +184,9 @@ def __init__(self, do_device_discovery=True): # Call with 3 bools (rp_limiting, yaw_limiting, thrust_limiting) self.limiting_updated = Caller() + # Called with True when interlock is engaged, False when released + self.thrust_lock_active = Caller() + def _get_device_from_name(self, device_name): """Get the raw device from a name""" for d in readers.devices(): @@ -189,6 +194,18 @@ def _get_device_from_name(self, device_name): return d return None + def require_thrust_zero(self): + """Engage the thrust interlock. + + While active, thrust output is forced to zero until the physical + throttle is brought to zero by the user. This prevents unexpected + take-off when connecting with a bad input mapping or after switching + mappings. + """ + if not self._thrust_interlock: + self._thrust_interlock = True + self.thrust_lock_active.call(True) + def set_hover_max_height(self, height): self._hover_max_height = height @@ -315,6 +332,7 @@ def set_input_map(self, device_name, input_map_name): dev.input_map_name = input_map_name Config().get("device_config_mapping")[device_name] = input_map_name dev.set_dead_band(self._rp_dead_band) + self.require_thrust_zero() def start_input(self, device_name, role="Device", config_name=None): """ @@ -330,6 +348,7 @@ def start_input(self, device_name, role="Device", config_name=None): self.limiting_updated.call(device.limit_rp, device.limit_yaw, device.limit_thrust) + self.require_thrust_zero() self._read_timer.start() return device.supports_mapping except Exception: @@ -515,6 +534,12 @@ def read_input(self): else: # Using alt hold the data is not in a percentage if not data.assistedControl: + if self._thrust_interlock: + if data.thrust < 1: + self._thrust_interlock = False + self.thrust_lock_active.call(False) + else: + data.thrust = 0 data.thrust = JoystickReader.p2t(data.thrust) # Thrust might be <0 here, make sure it's not otherwise From e1e75988a640760490a7d22c3f7232301b04d557 Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Tue, 17 Mar 2026 18:45:40 +0100 Subject: [PATCH 2/6] Fix gamepad status display issues in Flight tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show "—" instead of "Ready" when no gamepad is connected - Update mapping name in Flight tab and input menu after saving a custom mapping in the config dialog - Restore original mapping when cancelling the config dialog - Skip thrust interlock when throttle is already at zero to avoid a brief red flash when selecting a mapping --- .../ui/dialogs/inputconfigdialogue.py | 30 +++++++++- src/cfclient/ui/main.py | 57 +++++++++++++++++++ src/cfclient/ui/tabs/FlightTab.py | 12 +++- src/cfclient/ui/tabs/flightTab.ui | 2 +- src/cfclient/utils/input/__init__.py | 18 +++++- 5 files changed, 112 insertions(+), 7 deletions(-) diff --git a/src/cfclient/ui/dialogs/inputconfigdialogue.py b/src/cfclient/ui/dialogs/inputconfigdialogue.py index 829144029..42eccc9a6 100644 --- a/src/cfclient/ui/dialogs/inputconfigdialogue.py +++ b/src/cfclient/ui/dialogs/inputconfigdialogue.py @@ -35,6 +35,7 @@ from PyQt6.QtCore import QTimer from PyQt6.QtCore import pyqtSignal from PyQt6.QtWidgets import QMessageBox +from cfclient.utils.config import Config from cfclient.utils.config_manager import ConfigManager from PyQt6 import QtWidgets from PyQt6 import uic @@ -51,6 +52,8 @@ class InputConfigDialogue(QtWidgets.QWidget, inputconfig_widget_class): + closed = pyqtSignal() + def __init__(self, joystickReader, *args): super(InputConfigDialogue, self).__init__(*args) self.setupUi(self) @@ -171,6 +174,9 @@ def __init__(self, joystickReader, *args): self._map = {} self._saved_open_device = None + self._original_input_map = None + self._original_input_map_name = None + self._config_was_saved = False @staticmethod def _scale(max_value, value): @@ -224,8 +230,13 @@ def _show_config_popup(self, caption, message, directions=[]): self._popup.show() def _start_configuration(self): - self._input.enableRawReading( - str(self.inputDeviceSelector.currentText())) + device_name = str(self.inputDeviceSelector.currentText()) + dev = self._input._get_device_from_name(device_name) + if dev: + self._original_input_map = dev.input_map + self._original_input_map_name = getattr( + dev, 'input_map_name', None) + self._input.enableRawReading(device_name) self._input_device_reader.start_reading() self._populate_config_dropdown() self.profileCombo.setEnabled(True) @@ -391,6 +402,14 @@ def _save_config(self): if config_name is None: config_name = str(self.profileCombo.currentText()) ConfigManager().save_config(self._map, config_name) + # Update the name on the raw device so the Flight tab shows it. + # The actual mapping data is already applied via set_raw_input_map. + if self._input._input_device: + self._input._input_device.input_map_name = config_name + device_name = str(self.inputDeviceSelector.currentText()) + Config().get("device_config_mapping")[ + device_name] = config_name + self._config_was_saved = True self.close() def showEvent(self, event): @@ -403,8 +422,15 @@ def closeEvent(self, event): """Called when dialog is closed""" self._input.stop_raw_reading() self._input_device_reader.stop_reading() + if not self._config_was_saved and self._original_input_map is not None: + device_name = str(self.inputDeviceSelector.currentText()) + dev = self._input._get_device_from_name(device_name) + if dev: + dev.input_map = self._original_input_map + dev.input_map_name = self._original_input_map_name # self._input.start_input(self._saved_open_device) self._input.resume_input() + self.closed.emit() class DeviceReader(QThread): diff --git a/src/cfclient/ui/main.py b/src/cfclient/ui/main.py index a574b6e0c..c8b3b6c1b 100644 --- a/src/cfclient/ui/main.py +++ b/src/cfclient/ui/main.py @@ -558,8 +558,13 @@ def _rescan_devices(self): def _show_input_device_config_dialog(self): self.inputConfig = InputConfigDialogue(self.joystickReader) + self.inputConfig.closed.connect(self._on_input_config_closed) self.inputConfig.show() + def _on_input_config_closed(self): + self._sync_input_map_menus() + self._update_input_device_status() + def _show_connect_dialog(self): self.logConfigDialogue.show() @@ -716,6 +721,58 @@ def _mux_selected(self, checked): self._update_input_device_status() + def _sync_input_map_menus(self): + """Sync the Input device menu to reflect the current mapping. + + After the config dialog saves a new mapping, the menu may be + missing the new entry and still have the old one checked. Walk + every role menu, find the checked device, and make sure its map + sub-menu contains and selects the active mapping name. + """ + for menu in self._all_role_menus: + role_menu = menu["rolemenu"] + for dev_node in role_menu.actions(): + if not dev_node.isChecked(): + continue + data = dev_node.data() + if data is None or not isinstance(data, tuple): + continue + if len(data) < 3: + continue + (map_menu, device, _mux_menu) = data + if map_menu is None or not device.supports_mapping: + continue + + active_name = getattr(device, 'input_map_name', None) + if not active_name: + continue + + # Get the QActionGroup from an existing map action + map_actions = map_menu.actions() + map_group = None + if map_actions: + map_group = map_actions[0].actionGroup() + + # Add a menu entry if the config is new + existing = {a.text() for a in map_actions} + if active_name not in existing and map_group: + node = QAction(active_name, map_menu, + checkable=True, enabled=True) + node.toggled.connect(self._inputconfig_selected) + node.setData(dev_node) + map_menu.addAction(node) + map_group.addAction(node) + + # Check the active mapping without triggering + # _inputconfig_selected (which would reload the config + # from disk and overwrite the live mapping). + # Uncheck all first since blockSignals prevents the + # exclusive QActionGroup from doing it automatically. + for action in map_menu.actions(): + action.blockSignals(True) + action.setChecked(action.text() == active_name) + action.blockSignals(False) + def _update_input_device_status(self): """Update the gamepad device info in the Flight tab.""" diff --git a/src/cfclient/ui/tabs/FlightTab.py b/src/cfclient/ui/tabs/FlightTab.py index f4aec0637..d374832f4 100644 --- a/src/cfclient/ui/tabs/FlightTab.py +++ b/src/cfclient/ui/tabs/FlightTab.py @@ -172,6 +172,7 @@ def __init__(self, helper): self._log_error_signal.connect(self._logging_error) self._isConnected = False + self._has_device = False # Connect UI signals that are in this tab self.flightModeCombo.currentIndexChanged.connect(self.flightmodeChange) @@ -231,13 +232,22 @@ def _thrust_lock_updated(self, active): self.gamepadStatusLabel.setText("Lower throttle to arm") self.gamepadStatusLabel.setStyleSheet("color: red;") else: - self.gamepadStatusLabel.setText("Ready") + if self._has_device: + self.gamepadStatusLabel.setText("Ready") + else: + self.gamepadStatusLabel.setText("\u2014") self.gamepadStatusLabel.setStyleSheet("") def _gamepad_device_updated(self, device, mapping, mux): self.gamepadNameLabel.setText(device) self.gamepadMappingLabel.setText(mapping) self.gamepadMuxLabel.setText(mux) + no_device_strings = ("No input device connected", + "No input device found") + self._has_device = device not in no_device_strings + if not self._has_device: + self.gamepadStatusLabel.setText("\u2014") + self.gamepadStatusLabel.setStyleSheet("") def _set_limiting_enabled(self, rp_limiting_enabled, yaw_limiting_enabled, thrust_limiting_enabled): diff --git a/src/cfclient/ui/tabs/flightTab.ui b/src/cfclient/ui/tabs/flightTab.ui index 8e4aba464..afb4e04be 100644 --- a/src/cfclient/ui/tabs/flightTab.ui +++ b/src/cfclient/ui/tabs/flightTab.ui @@ -376,7 +376,7 @@ - Ready + diff --git a/src/cfclient/utils/input/__init__.py b/src/cfclient/utils/input/__init__.py index 58cb007dd..c217b7837 100644 --- a/src/cfclient/utils/input/__init__.py +++ b/src/cfclient/utils/input/__init__.py @@ -201,10 +201,22 @@ def require_thrust_zero(self): throttle is brought to zero by the user. This prevents unexpected take-off when connecting with a bad input mapping or after switching mappings. + + If thrust is already at zero, the interlock is not engaged to avoid + a brief red flash in the UI. """ - if not self._thrust_interlock: - self._thrust_interlock = True - self.thrust_lock_active.call(True) + if self._thrust_interlock: + return + + try: + data = self._selected_mux.read() + if data and data.thrust < 1: + return + except Exception: + pass + + self._thrust_interlock = True + self.thrust_lock_active.call(True) def set_hover_max_height(self, height): self._hover_max_height = height From b6e205873949fc3a602499f3c94e24b3fb1b9ed5 Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Wed, 18 Mar 2026 13:19:56 +0100 Subject: [PATCH 3/6] Show "No mapping selected" when gamepad has no input mapping When a gamepad is connected but no mapping is selected, the status now shows "No mapping selected" in orange instead of "Ready", which was misleading since the gamepad won't work without a mapping. --- src/cfclient/ui/tabs/FlightTab.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/cfclient/ui/tabs/FlightTab.py b/src/cfclient/ui/tabs/FlightTab.py index d374832f4..789036cd4 100644 --- a/src/cfclient/ui/tabs/FlightTab.py +++ b/src/cfclient/ui/tabs/FlightTab.py @@ -173,6 +173,7 @@ def __init__(self, helper): self._isConnected = False self._has_device = False + self._has_mapping = False # Connect UI signals that are in this tab self.flightModeCombo.currentIndexChanged.connect(self.flightmodeChange) @@ -232,11 +233,17 @@ def _thrust_lock_updated(self, active): self.gamepadStatusLabel.setText("Lower throttle to arm") self.gamepadStatusLabel.setStyleSheet("color: red;") else: - if self._has_device: + if self._has_device and self._has_mapping: self.gamepadStatusLabel.setText("Ready") + self.gamepadStatusLabel.setStyleSheet("") + elif self._has_device: + self.gamepadStatusLabel.setText( + "No mapping selected") + self.gamepadStatusLabel.setStyleSheet( + "color: orange;") else: self.gamepadStatusLabel.setText("\u2014") - self.gamepadStatusLabel.setStyleSheet("") + self.gamepadStatusLabel.setStyleSheet("") def _gamepad_device_updated(self, device, mapping, mux): self.gamepadNameLabel.setText(device) @@ -245,7 +252,15 @@ def _gamepad_device_updated(self, device, mapping, mux): no_device_strings = ("No input device connected", "No input device found") self._has_device = device not in no_device_strings - if not self._has_device: + if self._has_device: + self._has_mapping = "No mapping" not in mapping + if not self._has_mapping: + self.gamepadStatusLabel.setText( + "No mapping selected") + self.gamepadStatusLabel.setStyleSheet( + "color: orange;") + else: + self._has_mapping = False self.gamepadStatusLabel.setText("\u2014") self.gamepadStatusLabel.setStyleSheet("") From 2d27142e7d65a24cdc1567bcabccab84043f0fd5 Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Wed, 18 Mar 2026 13:29:42 +0100 Subject: [PATCH 4/6] Fix extra blank line from rebase conflict resolution --- src/cfclient/ui/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cfclient/ui/main.py b/src/cfclient/ui/main.py index c8b3b6c1b..77d936477 100644 --- a/src/cfclient/ui/main.py +++ b/src/cfclient/ui/main.py @@ -555,7 +555,6 @@ def _rescan_devices(self): # if (len(devs) > 0): # self.device_discovery(devs) - def _show_input_device_config_dialog(self): self.inputConfig = InputConfigDialogue(self.joystickReader) self.inputConfig.closed.connect(self._on_input_config_closed) From 4b10995f2ca15075ca1e3d03299b2f53df72a7cf Mon Sep 17 00:00:00 2001 From: Arnaud Taffanel Date: Wed, 18 Mar 2026 17:00:55 +0100 Subject: [PATCH 5/6] Fix PR review comments: remove extra blank lines and rename signal - Remove unnecessary blank lines between signal declarations in FlightTab - Rename gamepad_device_updated to _gamepad_device_updated for private naming convention --- src/cfclient/ui/main.py | 7 +++---- src/cfclient/ui/tabs/FlightTab.py | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/cfclient/ui/main.py b/src/cfclient/ui/main.py index 77d936477..5e58251e7 100644 --- a/src/cfclient/ui/main.py +++ b/src/cfclient/ui/main.py @@ -99,8 +99,7 @@ class MainUI(QtWidgets.QMainWindow, main_window_class): disconnectedSignal = pyqtSignal(str) linkQualitySignal = pyqtSignal(float) - gamepad_device_updated = pyqtSignal(str, str, str) - + _gamepad_device_updated = pyqtSignal(str, str, str) _input_device_error_signal = pyqtSignal(str) _input_discovery_signal = pyqtSignal(object) _log_error_signal = pyqtSignal(object, str) @@ -544,7 +543,7 @@ def set_preferred_dock_area(self, area): tab_toolbox.set_preferred_dock_area(area) def _rescan_devices(self): - self.gamepad_device_updated.emit("No input device connected", "—", "—") + self._gamepad_device_updated.emit("No input device connected", "—", "—") self._menu_devices.clear() self._active_device = "" self.joystickReader.stop_input() @@ -797,7 +796,7 @@ def _update_input_device_status(self): device_str = "No input device found" mapping_str = "—" mux_name = "—" - self.gamepad_device_updated.emit(device_str, mapping_str, mux_name) + self._gamepad_device_updated.emit(device_str, mapping_str, mux_name) def _inputdevice_selected(self, checked): """Called when a new input device has been selected from the menu. The diff --git a/src/cfclient/ui/tabs/FlightTab.py b/src/cfclient/ui/tabs/FlightTab.py index 789036cd4..4fec3269a 100644 --- a/src/cfclient/ui/tabs/FlightTab.py +++ b/src/cfclient/ui/tabs/FlightTab.py @@ -92,7 +92,6 @@ class FlightTab(TabToolbox, flight_tab_class): _log_data_signal = pyqtSignal(int, object, object) _pose_data_signal = pyqtSignal(object, object) - _thrust_lock_signal = pyqtSignal(bool) _gamepad_device_signal = pyqtSignal(str, str, str) _input_updated_signal = pyqtSignal(float, float, float, float) @@ -102,7 +101,6 @@ class FlightTab(TabToolbox, flight_tab_class): _assisted_control_updated_signal = pyqtSignal(bool) _heighthold_input_updated_signal = pyqtSignal(float, float, float, float) _hover_input_updated_signal = pyqtSignal(float, float, float, float) - _log_error_signal = pyqtSignal(object, str) # UI_DATA_UPDATE_FPS = 10 @@ -129,7 +127,7 @@ def __init__(self, helper): self._thrust_lock_signal.emit) self._gamepad_device_signal.connect(self._gamepad_device_updated) - self._helper.mainUI.gamepad_device_updated.connect( + self._helper.mainUI._gamepad_device_updated.connect( self._gamepad_device_signal.emit) self.disconnectedSignal.connect(self.disconnected) From 7fa1c43e240f7e061e53877d56db1b80cc39695a Mon Sep 17 00:00:00 2001 From: Enya Date: Tue, 7 Apr 2026 13:28:26 +0200 Subject: [PATCH 6/6] Cleaned up displayed mapping name --- src/cfclient/ui/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cfclient/ui/main.py b/src/cfclient/ui/main.py index 5e58251e7..491026ec6 100644 --- a/src/cfclient/ui/main.py +++ b/src/cfclient/ui/main.py @@ -784,7 +784,8 @@ def _update_input_device_status(self): device_names.append(dev.name) if dev.supports_mapping: mapping_names.append( - dev.input_map_name if dev.input_map else "No mapping") + ConfigManager().get_display_name(dev.input_map_name) + if dev.input_map else "No mapping") else: mapping_names.append("N/A") else: