diff --git a/components/micropython/usermodule/mp_audio.c b/components/micropython/usermodule/mp_audio.c
index 4762e87b33e51794d950dea9d1b7654b21d05649..557c4973bd7b8078d6bbd88773cdf37ea215b802 100644
--- a/components/micropython/usermodule/mp_audio.c
+++ b/components/micropython/usermodule/mp_audio.c
@@ -28,12 +28,20 @@ STATIC mp_obj_t mp_headphones_are_connected() {
 STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headphones_are_connected_obj,
                                  mp_headphones_are_connected);
 
-STATIC mp_obj_t mp_headphones_detection_override(mp_obj_t enable) {
-    st3m_audio_headphones_detection_override(mp_obj_get_int(enable));
+STATIC mp_obj_t mp_headphones_detection_override(size_t n_args,
+                                                 const mp_obj_t *args) {
+    bool enable = mp_obj_get_int(args[0]);
+    bool override_state = true;
+    if (n_args > 1) {
+        override_state = mp_obj_get_int(args[1]);
+    }
+
+    st3m_audio_headphones_detection_override(enable, override_state);
     return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_detection_override_obj,
-                                 mp_headphones_detection_override);
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_headphones_detection_override_obj,
+                                           1, 2,
+                                           mp_headphones_detection_override);
 
 STATIC mp_obj_t mp_headphones_set_volume_dB(mp_obj_t vol_dB) {
     return mp_obj_new_float(
diff --git a/components/st3m/st3m_audio.c b/components/st3m/st3m_audio.c
index 19eb0422438d0de7a749679ffcb73fe016b1d787..214bd6726e699c5081d623f83abcfe19ff06a807 100644
--- a/components/st3m/st3m_audio.c
+++ b/components/st3m/st3m_audio.c
@@ -216,9 +216,14 @@ static void _audio_speaker_apply(st3m_audio_output_t *out) {
 typedef struct {
     flow3r_bsp_audio_jacksense_state_t jacksense;
 
-    // True if system should pretend headphones are plugged in.
+    // True if system should ignore jacksense and use
+    // headphones_detection_override_state to determine
+    // whether headphones are connected (true) or not (false)
     bool headphones_detection_override;
 
+    // Defaults to true
+    bool headphones_detection_override_state;
+
     // The two output channels.
     st3m_audio_output_t headphones;
     st3m_audio_output_t speaker;
@@ -262,6 +267,7 @@ static st3m_audio_state_t state = {
             .line_in = false,
         },
     .headphones_detection_override = false,
+    .headphones_detection_override_state = true,
     .headphones =
         {
             .volume = 0,
@@ -310,7 +316,10 @@ static st3m_audio_state_t state = {
 //
 // Lock must be taken.
 static bool _headphones_connected(void) {
-    return state.jacksense.headphones || state.headphones_detection_override;
+    if (state.headphones_detection_override) {
+        return state.headphones_detection_override_state;
+    }
+    return state.jacksense.headphones;
 }
 
 static void _audio_input_set_source(st3m_audio_input_source_t source) {
@@ -915,9 +924,11 @@ float st3m_audio_headphones_set_volume_dB(float vol_dB) {
     LOCKED(float, _output_set_volume(&state.headphones, vol_dB));
 }
 
-void st3m_audio_headphones_detection_override(bool enable) {
+void st3m_audio_headphones_detection_override(bool enable,
+                                              bool override_state) {
     LOCK;
     state.headphones_detection_override = enable;
+    state.headphones_detection_override_state = override_state;
     _output_apply(&state.headphones);
     _output_apply(&state.speaker);
     UNLOCK;
diff --git a/components/st3m/st3m_audio.h b/components/st3m/st3m_audio.h
index 6830be463c9c6dc13f558a88ca27e225d669ee13..51ea9339f27a33d33e28671f71561e15a47b114b 100644
--- a/components/st3m/st3m_audio.h
+++ b/components/st3m/st3m_audio.h
@@ -76,12 +76,19 @@ bool st3m_audio_headset_is_connected(void);
 /* Returns true if the line-in jack is connected to a cable. */
 bool st3m_audio_line_in_is_connected(void);
 
-/* If a sleeve contact mic doesn't pull the detection pin low enough the
- * codec's built in headphone detection might fail. Calling this function
- * with 'enable = 1' overrides the detection and assumes there's headphones
- * plugged in. Call with 'enable = 0' to revert to automatic detection.
+/* Set to 'enable = 1' if the system should ignore jacksense and use
+ * headphones_detection_override_state to determine whether headphones are
+ * connected (true) or not (false).
+ *
+ * Use cases:
+ * - If a sleeve contact mic doesn't pull the detection pin low enough the
+ *   codec's built in headphone detection might fail.
+ * - If the headset only has a mic connected but we wish to use the internal
+ *   speaker anyway
+ *
+ * Call with 'enable = 0' to revert to automatic detection.
  */
-void st3m_audio_headphones_detection_override(bool enable);
+void st3m_audio_headphones_detection_override(bool enable, bool override_state);
 
 /* Attempts to set target volume for the headphone output/onboard speakers
  * respectively, clamps/rounds if necessary and returns the actual volume.
diff --git a/docs/api/audio.rst b/docs/api/audio.rst
index bb3a8cb7690d194e1c0bea02928db8f873231f24..9b980ecb723e095ff056ca37999831b3ff296c07 100644
--- a/docs/api/audio.rst
+++ b/docs/api/audio.rst
@@ -152,16 +152,28 @@ in the user config.
 OS development
 --------------
 
+.. warning::
+
+   These functions are not to be used in applications, but only by OS settings.
+
 Many of these functions are available in three variants: headphone volume,
 speaker volume, and volume. If :code:`headphones_are_connected()` returns 1
 the "headphone" variant is chosen, else the "speaker" variant is chosen.
 
-.. py:function:: headphones_detection_override(enable : bool)
+.. py:function:: headphones_detection_override(enable : bool, override_state : bool)
+
+   Set to 'enable = True' if the system should ignore jacksense and use
+   override_state to determine whether headphones are connected (True) or not
+   (False).
+
+   Use cases:
+
+   - If a sleeve contact mic doesn't pull the detection pin low enough the
+     codec's built in headphone detection might fail.
+   - If the headset only has a mic connected but we wish to use the internal
+     speaker anyway
 
-   If a sleeve contact mic doesn't pull the detection pin low enough the
-   codec's built in headphone detection might fail. Calling this function
-   with 'enable = 1' overrides the detection and assumes there's headphones
-   plugged in. Call with 'enable = 0' to revert to automatic detection.
+   Call with 'enable = 0' to revert to automatic detection.
 
 .. py:function:: headphones_set_volume_dB(vol_dB : float) -> float
 .. py:function:: speaker_set_volume_dB(vol_dB : float) -> float
diff --git a/python_payload/apps/audio_config/__init__.py b/python_payload/apps/audio_config/__init__.py
index db87a1cfe5f59a6e0c61ba93f12a88b8ad78a864..c0177976e9c9e08ddb56043c41159a0fde37c381 100644
--- a/python_payload/apps/audio_config/__init__.py
+++ b/python_payload/apps/audio_config/__init__.py
@@ -334,12 +334,12 @@ class SpeakerMenu(Submenu):
 class HeadphonesMenu(Submenu):
     def __init__(self, press):
         super().__init__(press)
-        self.num_widgets = 5
-        self.overhang = -40
+        self.num_widgets = 6
+        self.overhang = -80
         self.mid_x = 50
-        self.focus_pos_limit_min = -100
+        self.focus_pos_limit_min = -20
         self.focus_pos_limit_max = 100
-        self.focus_pos_limit_first = -100
+        self.focus_pos_limit_first = -40
         self.focus_pos_limit_last = 100
 
     def _draw(self, ctx):
@@ -377,6 +377,43 @@ class HeadphonesMenu(Submenu):
                 audio.headphones_get_maximum_volume_dB()
             )
 
+        self.y += 8
+
+        # note: jack detection is the inverse of headphones detection override
+        # jack detection off means headphones detection override on
+        tmp = self.draw_boolean(
+            "jack detection",
+            settings.onoff_headphones_detection_override.value,
+            off_str="on",  # sic
+            on_str="off",  # sic
+        )
+        if settings.onoff_headphones_detection_override.value != tmp:
+            settings.onoff_headphones_detection_override.set_value(tmp)
+            audio.headphones_detection_override(
+                settings.onoff_headphones_detection_override.value,
+                settings.onoff_headphones_detection_override_state.value,
+            )
+
+        if tmp:
+            self.num_widgets = 7
+            tmp = self.draw_boolean(
+                "headphone state",
+                settings.onoff_headphones_detection_override_state.value,
+                on_str="on",
+                off_str="off",
+                on_hint="sound will always play\nthrough headphones",
+                off_hint="sound will always play\nthrough speaker",
+            )
+            if settings.onoff_headphones_detection_override_state.value != tmp:
+                settings.onoff_headphones_detection_override_state.set_value(tmp)
+                audio.headphones_detection_override(
+                    settings.onoff_headphones_detection_override.value,
+                    settings.onoff_headphones_detection_override_state.value,
+                )
+        else:
+            self.num_widgets = 6
+            self.overhang = -80
+
 
 class VolumeControlMenu(Submenu):
     def __init__(self, press):
diff --git a/python_payload/st3m/run.py b/python_payload/st3m/run.py
index 644fa5c60732310ba089eb7379c2e308c5fdf2cc..357cf372fe4d9379fc429eb0750e8ad129b996c7 100644
--- a/python_payload/st3m/run.py
+++ b/python_payload/st3m/run.py
@@ -159,6 +159,11 @@ def run_main() -> None:
     audio.onboard_mic_to_speaker_set_allowed(
         settings.onoff_onboard_mic_to_speaker_allowed.value
     )
+    if settings.onoff_headphones_detection_override.value:
+        audio.headphones_detection_override(
+            settings.onoff_headphones_detection_override.value,
+            settings.onoff_headphones_detection_override_state.value,
+        )
 
     leds.set_brightness(settings.num_leds_brightness.value)
     sys_display.set_backlight(settings.num_display_brightness.value)
diff --git a/python_payload/st3m/settings.py b/python_payload/st3m/settings.py
index 611ea14afeefd2d976323bf8b7e21ccf26ea76a6..979a95b320996f2ad1f6c7bbfaad6c8f7e5a95cd 100644
--- a/python_payload/st3m/settings.py
+++ b/python_payload/st3m/settings.py
@@ -255,6 +255,16 @@ onoff_onboard_mic_to_speaker_allowed = OnOffTunable(
     "system.audio.onboard_mic_to_speaker_allowed",
     False,
 )
+onoff_headphones_detection_override = OnOffTunable(
+    "Headphones detection override enabled",
+    "system.audio.headphones_detection_override",
+    False,
+)
+onoff_headphones_detection_override_state = OnOffTunable(
+    "Headphones detection override state",
+    "system.audio.headphones_detection_override_state",
+    True,
+)
 
 num_headset_mic_gain_db = NumberTunable(
     "Headset Mic Gain dB", "system.audio.headset_mic_gain_dB", 0
@@ -306,6 +316,8 @@ load_save_settings: List[UnaryTunable] = [
     onoff_onboard_mic_allowed,
     onoff_line_in_allowed,
     onoff_onboard_mic_to_speaker_allowed,
+    onoff_headphones_detection_override,
+    onoff_headphones_detection_override_state,
     num_headset_mic_gain_db,
     num_onboard_mic_gain_db,
     num_line_in_gain_db,