Skip to content

Commit 57c5f9b

Browse files
jkoloclaude
andcommitted
Rename playback device fix to SourceSwitchControl, drop the wire inversion
Rebased onto the current MDRProperty API (pending()/override() replace submittedDirty()/commitOneShot(), renamed underneath this branch), and renamed the public surface as requested in review: MDR_FEATURE_PLAYBACK_DEVICE_FIX becomes MDR_FEATURE_SOURCE_SWITCH_CONTROL, mdrHeadphonesGet/SetPlaybackDeviceFixed becomes mdrHeadphonesGet/SetSourceSwitchControl, MDRPlaybackFixResult becomes MDRSourceSwitchControlResult - matching SOURCE_SWITCH_CONTROL and the packet debugger instead of translating Sound Connect's Japanese-derived label. libmdr now tracks the wire value directly (1 = switching free) instead of mangling it into a "fixed" flag; the one inversion left lives in Client.cpp, where "Fixing playback device" is a UI concept. Also dropped two MDR_LOG_DEBUG calls in the SOURCE_SWITCH_CONTROL notify/param handlers, matching the other protocol handlers, which don't log there either. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qy2qqZHg5iaikSzDSNMT9o
1 parent 4127e78 commit 57c5f9b

6 files changed

Lines changed: 73 additions & 81 deletions

File tree

client/Client.cpp

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,15 @@ const char* FormatSpeakTimeout(MDRSpeakTimeout status)
136136
}
137137
}
138138

139-
const char* FormatPlaybackFixResult(MDRPlaybackFixResult result)
139+
const char* FormatSourceSwitchControlResult(MDRSourceSwitchControlResult result)
140140
{
141141
switch (result)
142142
{
143-
case MDR_PLAYBACK_FIX_FAILED_ON_CALL:
143+
case MDR_SOURCE_SWITCH_CONTROL_FAILED_ON_CALL:
144144
return "The headphones refused: a call is in progress.";
145-
case MDR_PLAYBACK_FIX_FAILED_NOT_CONNECTED:
145+
case MDR_SOURCE_SWITCH_CONTROL_FAILED_NOT_CONNECTED:
146146
return "The headphones refused: the device has no audio connection.";
147-
case MDR_PLAYBACK_FIX_FAILED_VOICE_ASSISTANT:
147+
case MDR_SOURCE_SWITCH_CONTROL_FAILED_VOICE_ASSISTANT:
148148
return "The headphones refused: the voice assistant has priority.";
149149
default:
150150
return "The headphones refused the request.";
@@ -1343,10 +1343,12 @@ void DrawDeviceControlsDevices()
13431343
action.device_id_size = static_cast<uint32_t>(std::strlen(mac));
13441344
mdrHeadphonesSetPairedDevice(gDevice, &action);
13451345
};
1346-
const bool supportFix = FeatureAvailable(MDR_FEATURE_PLAYBACK_DEVICE_FIX);
1347-
MDRBoolean playbackFixed = MDR_FALSE;
1346+
const bool supportFix = FeatureAvailable(MDR_FEATURE_SOURCE_SWITCH_CONTROL);
1347+
MDRBoolean switchControlEnabled = MDR_TRUE;
13481348
if (supportFix)
1349-
mdrHeadphonesGetPlaybackDeviceFixed(gDevice, &playbackFixed);
1349+
mdrHeadphonesGetSourceSwitchControl(gDevice, &switchControlEnabled);
1350+
// Sound Connect's "Fixing playback device" is the negation of source switch control.
1351+
const bool playbackFixed = switchControlEnabled == MDR_FALSE;
13501352
auto DrawDeviceElement = [&](const DeviceView& device, bool selected) -> bool
13511353
{
13521354
ImGui::PushID(device.mac.c_str());
@@ -1374,7 +1376,7 @@ void DrawDeviceControlsDevices()
13741376
StageDeviceAction(MDR_PAIRED_DEVICE_SELECT_PLAYBACK, device.mac.c_str());
13751377
if (canFix &&
13761378
ImModalButton(playbackFixed ? PSI_UNLOCK " Unfix Playback" : PSI_LOCK " Fix Playback", 2, columns))
1377-
mdrHeadphonesSetPlaybackDeviceFixed(gDevice, playbackFixed ? MDR_FALSE : MDR_TRUE);
1379+
mdrHeadphonesSetSourceSwitchControl(gDevice, playbackFixed ? MDR_TRUE : MDR_FALSE);
13781380
}
13791381
else
13801382
{
@@ -1386,10 +1388,10 @@ void DrawDeviceControlsDevices()
13861388
// After the button rows: Unpair shares a row, so an inline message would land beside it.
13871389
if (supportFix && device.state.connected && device.state.playback_device)
13881390
{
1389-
MDRPlaybackFixResult fixResult = MDR_PLAYBACK_FIX_SUCCESS;
1390-
mdrHeadphonesGetPlaybackDeviceFixResult(gDevice, &fixResult);
1391-
if (fixResult != MDR_PLAYBACK_FIX_SUCCESS)
1392-
ImGui::TextWrapped(PSI_INFO_SIGN_ALT " %s", FormatPlaybackFixResult(fixResult));
1391+
MDRSourceSwitchControlResult fixResult = MDR_SOURCE_SWITCH_CONTROL_SUCCESS;
1392+
mdrHeadphonesGetSourceSwitchControlResult(gDevice, &fixResult);
1393+
if (fixResult != MDR_SOURCE_SWITCH_CONTROL_SUCCESS)
1394+
ImGui::TextWrapped(PSI_INFO_SIGN_ALT " %s", FormatSourceSwitchControlResult(fixResult));
13931395
}
13941396
}
13951397
ImGui::EndGroup();

libmdr/include/mdr-c/Headphones.h

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ typedef uint32_t MDRFeature;
4545
#define MDR_FEATURE_SHUTDOWN ((MDRFeature)26u)
4646
#define MDR_FEATURE_CONNECTION_MODE ((MDRFeature)27u)
4747
#define MDR_FEATURE_SAFE_LISTENING ((MDRFeature)28u)
48-
#define MDR_FEATURE_PLAYBACK_DEVICE_FIX ((MDRFeature)29u)
48+
#define MDR_FEATURE_SOURCE_SWITCH_CONTROL ((MDRFeature)29u)
4949

5050
typedef uint32_t MDREvent;
5151
#define MDR_EVENT_NONE ((MDREvent)0u)
@@ -128,12 +128,12 @@ typedef uint32_t MDRPlaybackAction;
128128
#define MDR_PLAYBACK_NEXT ((MDRPlaybackAction)3u)
129129
#define MDR_PLAYBACK_PREVIOUS ((MDRPlaybackAction)4u)
130130

131-
typedef uint32_t MDRPlaybackFixResult;
132-
#define MDR_PLAYBACK_FIX_SUCCESS ((MDRPlaybackFixResult)0u)
133-
#define MDR_PLAYBACK_FIX_FAILED ((MDRPlaybackFixResult)1u)
134-
#define MDR_PLAYBACK_FIX_FAILED_ON_CALL ((MDRPlaybackFixResult)2u)
135-
#define MDR_PLAYBACK_FIX_FAILED_NOT_CONNECTED ((MDRPlaybackFixResult)3u)
136-
#define MDR_PLAYBACK_FIX_FAILED_VOICE_ASSISTANT ((MDRPlaybackFixResult)4u)
131+
typedef uint32_t MDRSourceSwitchControlResult;
132+
#define MDR_SOURCE_SWITCH_CONTROL_SUCCESS ((MDRSourceSwitchControlResult)0u)
133+
#define MDR_SOURCE_SWITCH_CONTROL_FAILED ((MDRSourceSwitchControlResult)1u)
134+
#define MDR_SOURCE_SWITCH_CONTROL_FAILED_ON_CALL ((MDRSourceSwitchControlResult)2u)
135+
#define MDR_SOURCE_SWITCH_CONTROL_FAILED_NOT_CONNECTED ((MDRSourceSwitchControlResult)3u)
136+
#define MDR_SOURCE_SWITCH_CONTROL_FAILED_VOICE_ASSISTANT ((MDRSourceSwitchControlResult)4u)
137137

138138
typedef uint32_t MDRNoiseMode;
139139
#define MDR_NOISE_MODE_OFF ((MDRNoiseMode)0u)
@@ -541,30 +541,26 @@ MDR_API MDRResult mdrHeadphonesGetPairing(
541541
MDR_API MDRResult mdrHeadphonesSetPairing(MDRHeadphones* headphones, const MDRPairing* pairing);
542542

543543
/**
544-
* @brief Whether playback is pinned to the current playback device.
544+
* @brief Whether the headphones may switch playback to the other multipoint device on their own.
545545
*
546-
* Mirrors Sound Connect's "Fixing playback device": while set, the headphones keep playing to the
547-
* device that holds the playback right instead of handing audio over to the other multipoint device.
548-
* The headphones drop the fixation on their own once that device disconnects.
549-
* Requires @ref MDR_FEATURE_PLAYBACK_DEVICE_FIX.
546+
* Mirrors Sound Connect's "Fixing playback device", inverted: while enabled, switching is free and
547+
* the headphones may hand audio over to the other multipoint device. While disabled, playback stays
548+
* pinned to the device that currently holds the playback right - Sound Connect shows this as a
549+
* padlock. The headphones re-enable switching on their own once that device disconnects.
550+
* Requires @ref MDR_FEATURE_SOURCE_SWITCH_CONTROL.
550551
*/
551-
MDR_API MDRResult mdrHeadphonesGetPlaybackDeviceFixed(
552-
MDRHeadphones* headphones,
553-
MDRBoolean* out_fixed
554-
);
555-
MDR_API MDRResult mdrHeadphonesSetPlaybackDeviceFixed(MDRHeadphones* headphones, MDRBoolean fixed);
552+
MDR_API MDRResult mdrHeadphonesGetSourceSwitchControl(MDRHeadphones* headphones, MDRBoolean* out_enabled);
553+
MDR_API MDRResult mdrHeadphonesSetSourceSwitchControl(MDRHeadphones* headphones, MDRBoolean enabled);
556554

557555
/**
558-
* @brief Outcome the headphones reported for the last fix or playback switch request.
556+
* @brief Outcome the headphones reported for the last source switch control request.
559557
*
560558
* A refused request leaves the previous state in place, so a caller watching only
561-
* @ref mdrHeadphonesGetPlaybackDeviceFixed cannot tell a refusal from a no-op. Staging a new
562-
* request resets this to @ref MDR_PLAYBACK_FIX_SUCCESS.
559+
* @ref mdrHeadphonesGetSourceSwitchControl cannot tell a refusal from a no-op. Staging a new
560+
* request resets this to @ref MDR_SOURCE_SWITCH_CONTROL_SUCCESS.
563561
*/
564-
MDR_API MDRResult mdrHeadphonesGetPlaybackDeviceFixResult(
565-
MDRHeadphones* headphones,
566-
MDRPlaybackFixResult* out_result
567-
);
562+
MDR_API MDRResult mdrHeadphonesGetSourceSwitchControlResult(MDRHeadphones* headphones,
563+
MDRSourceSwitchControlResult* out_result);
568564

569565
/* General settings and assignable controls. */
570566
MDR_API MDRResult mdrHeadphonesGetGeneralSettingInfo(

libmdr/src/Details.hpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -450,14 +450,12 @@ namespace mdr
450450
MDRProperty<String> mMultipointDeviceMac;
451451
MDRProperty<String> mPairedDeviceDisconnectMac, mPairedDeviceConnectMac, mPairedDeviceUnpairMac;
452452

453-
// Sound Connect's "Fixing playback device": pins playback to the current playback device so the
454-
// headphones stop handing audio over to the other multipoint device. Carried by
455-
// SOURCE_SWITCH_CONTROL's param byte, which is inverted against this flag - the wire value is 1
456-
// while switching is free and 0 once the device is fixed (verified on WF-1000XM5).
457-
MDRProperty<bool> mPlaybackDeviceFixed;
458-
// Outcome of the last fix/switch attempt the headphones reported.
459-
v2::t2::SourceSwitchControlResult mSourceSwitchControlResult{
460-
v2::t2::SourceSwitchControlResult::SUCCESS};
453+
// SOURCE_SWITCH_CONTROL's param byte: 1 while playback may switch to the other multipoint
454+
// device on its own, 0 once it's pinned to the current device (verified on WF-1000XM5). This
455+
// is Sound Connect's "Fixing playback device", inverted.
456+
MDRProperty<bool> mSourceSwitchControlEnabled;
457+
// Outcome of the last source switch control request the headphones reported.
458+
v2::t2::SourceSwitchControlResult mSourceSwitchControlResult{v2::t2::SourceSwitchControlResult::SUCCESS};
461459

462460
MDRProperty<bool> mSafeListeningPreviewMode;
463461
#pragma endregion

libmdr/src/Headphones.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ namespace mdr
8080
mSupport.contains(T2::PAIRING_DEVICE_MANAGEMENT_WITH_BLUETOOTH_CLASS_OF_DEVICE_CLASSIC_LE);
8181
features[MDR_FEATURE_PAIRED_DEVICE_MANAGEMENT] = pairing;
8282
features[MDR_FEATURE_PAIRING_MODE] = pairing;
83-
features[MDR_FEATURE_PLAYBACK_DEVICE_FIX] = mSupport.contains(T2::SOURCE_SWITCH_CONTROL);
83+
features[MDR_FEATURE_SOURCE_SWITCH_CONTROL] = mSupport.contains(T2::SOURCE_SWITCH_CONTROL);
8484
features[MDR_FEATURE_GENERAL_SETTINGS] =
8585
mSupport.contains(T1::GENERAL_SETTING_1) || mSupport.contains(T1::GENERAL_SETTING_2) ||
8686
mSupport.contains(T1::GENERAL_SETTING_3) || mSupport.contains(T1::GENERAL_SETTING_4);
@@ -235,7 +235,7 @@ namespace mdr
235235
dirty |= mEqPresetId.dirty() || mEqClearBass.dirty() || mEqConfig.dirty();
236236
dirty |= mVoiceGuidanceEnabled.dirty() || mVoiceGuidanceVolume.dirty() || mPairingMode.dirty();
237237
dirty |= mMultipointDeviceMac.dirty() || mSafeListeningPreviewMode.dirty();
238-
dirty |= mPlaybackDeviceFixed.dirty();
238+
dirty |= mSourceSwitchControlEnabled.dirty();
239239
dirty |= mPairedDeviceConnectMac.dirty() || mPairedDeviceDisconnectMac.dirty() || mPairedDeviceUnpairMac.
240240
dirty();
241241
return dirty;
@@ -1084,7 +1084,7 @@ MDRResult mdrHeadphonesGetFeature(
10841084
if (!headphones || !outAvailability)
10851085
return MDR_RESULT_ERROR_INVALID_ARGUMENT;
10861086
// Keep the upper bound on the last MDR_FEATURE_* id, or newly added features read as invalid.
1087-
if (feature < MDR_FEATURE_IDENTITY || feature > MDR_FEATURE_PLAYBACK_DEVICE_FIX)
1087+
if (feature < MDR_FEATURE_IDENTITY || feature > MDR_FEATURE_SOURCE_SWITCH_CONTROL)
10881088
return MDR_RESULT_ERROR_INVALID_ARGUMENT;
10891089
const auto& h = *Impl(headphones);
10901090
if (!h.mNeutralInitialized)
@@ -1549,47 +1549,47 @@ MDRResult mdrHeadphonesSetPairing(MDRHeadphones* headphones, const MDRPairing* p
15491549
return MDR_RESULT_OK;
15501550
}
15511551

1552-
MDRResult mdrHeadphonesGetPlaybackDeviceFixed(MDRHeadphones* headphones, MDRBoolean* outFixed)
1552+
MDRResult mdrHeadphonesGetSourceSwitchControl(MDRHeadphones* headphones, MDRBoolean* outEnabled)
15531553
{
1554-
if (!headphones || !outFixed)
1554+
if (!headphones || !outEnabled)
15551555
return MDR_RESULT_ERROR_INVALID_ARGUMENT;
1556-
*outFixed = static_cast<MDRBoolean>(Impl(headphones)->mPlaybackDeviceFixed.current);
1556+
*outEnabled = static_cast<MDRBoolean>(Impl(headphones)->mSourceSwitchControlEnabled.current);
15571557
return MDR_RESULT_OK;
15581558
}
15591559

1560-
MDRResult mdrHeadphonesSetPlaybackDeviceFixed(MDRHeadphones* headphones, MDRBoolean fixed)
1560+
MDRResult mdrHeadphonesSetSourceSwitchControl(MDRHeadphones* headphones, MDRBoolean enabled)
15611561
{
1562-
if (!headphones || !ValidBoolean(fixed))
1562+
if (!headphones || !ValidBoolean(enabled))
15631563
return MDR_RESULT_ERROR_INVALID_ARGUMENT;
15641564
auto* h = Impl(headphones);
1565-
if (!h->mSupport.contains(MDR_FEATURE_PLAYBACK_DEVICE_FIX))
1565+
if (!h->mSupport.contains(MDR_FEATURE_SOURCE_SWITCH_CONTROL))
15661566
return MDR_RESULT_ERROR_NOT_SUPPORTED;
15671567
h->mSourceSwitchControlResult = mdr::v2::t2::SourceSwitchControlResult::SUCCESS;
1568-
h->mPlaybackDeviceFixed.stage(fixed != MDR_FALSE);
1568+
h->mSourceSwitchControlEnabled.stage(enabled != MDR_FALSE);
15691569
return MDR_RESULT_OK;
15701570
}
15711571

1572-
MDRResult mdrHeadphonesGetPlaybackDeviceFixResult(MDRHeadphones* headphones, MDRPlaybackFixResult* outResult)
1572+
MDRResult mdrHeadphonesGetSourceSwitchControlResult(MDRHeadphones* headphones, MDRSourceSwitchControlResult* outResult)
15731573
{
15741574
if (!headphones || !outResult)
15751575
return MDR_RESULT_ERROR_INVALID_ARGUMENT;
15761576
using enum mdr::v2::t2::SourceSwitchControlResult;
15771577
switch (Impl(headphones)->mSourceSwitchControlResult)
15781578
{
15791579
case SUCCESS:
1580-
*outResult = MDR_PLAYBACK_FIX_SUCCESS;
1580+
*outResult = MDR_SOURCE_SWITCH_CONTROL_SUCCESS;
15811581
break;
15821582
case FAIL_CALLING:
1583-
*outResult = MDR_PLAYBACK_FIX_FAILED_ON_CALL;
1583+
*outResult = MDR_SOURCE_SWITCH_CONTROL_FAILED_ON_CALL;
15841584
break;
15851585
case FAIL_A2DP_NOT_CONNECT:
1586-
*outResult = MDR_PLAYBACK_FIX_FAILED_NOT_CONNECTED;
1586+
*outResult = MDR_SOURCE_SWITCH_CONTROL_FAILED_NOT_CONNECTED;
15871587
break;
15881588
case FAIL_GIVE_PRIORITY_TO_VOICE_ASSISTANT:
1589-
*outResult = MDR_PLAYBACK_FIX_FAILED_VOICE_ASSISTANT;
1589+
*outResult = MDR_SOURCE_SWITCH_CONTROL_FAILED_VOICE_ASSISTANT;
15901590
break;
15911591
default:
1592-
*outResult = MDR_PLAYBACK_FIX_FAILED;
1592+
*outResult = MDR_SOURCE_SWITCH_CONTROL_FAILED;
15931593
break;
15941594
}
15951595
return MDR_RESULT_OK;

libmdr/src/HeadphonesV2.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,7 @@ namespace mdr
160160
/* Source Switch Control */
161161
if (mSupport.contains(t2::FunctionType::SOURCE_SWITCH_CONTROL))
162162
{
163-
SendCommandACK(t2::PeripheralGetParam,
164-
{.inquiredType = t2::PeripheralInquiredType::SOURCE_SWITCH_CONTROL});
163+
SendCommandACK(t2::PeripheralGetParam, {.inquiredType = t2::PeripheralInquiredType::SOURCE_SWITCH_CONTROL});
165164
}
166165

167166
/* Speak To Chat */
@@ -339,7 +338,7 @@ namespace mdr
339338
mVoiceGuidanceVolume.submit();
340339
mPairingMode.submit();
341340
mMultipointDeviceMac.submit();
342-
mPlaybackDeviceFixed.submit();
341+
mSourceSwitchControlEnabled.submit();
343342
mPairedDeviceDisconnectMac.submit();
344343
mPairedDeviceConnectMac.submit();
345344
mPairedDeviceUnpairMac.submit();
@@ -467,20 +466,19 @@ namespace mdr
467466
}
468467
}
469468

470-
/* Playback Device Fixation */
471-
if (mPlaybackDeviceFixed.submittedDirty())
469+
/* Source Switch Control */
470+
if (mSourceSwitchControlEnabled.pending())
472471
{
473472
using namespace t2;
474473
if (mSupport.contains(t2::FunctionType::SOURCE_SWITCH_CONTROL))
475474
{
476475
PeripheralSetParamSourceSwitchControl res;
477-
// Inverted: the wire flag says "switching is free", we track "device is fixed".
478-
res.value = mPlaybackDeviceFixed.submitted ? 0 : 1;
476+
res.value = mSourceSwitchControlEnabled.submitted ? 1 : 0;
479477
SendCommandACK(PeripheralSetParamSourceSwitchControl, res);
480-
mPlaybackDeviceFixed.commit();
478+
mSourceSwitchControlEnabled.commit();
481479
}
482480
else
483-
mPlaybackDeviceFixed.commitOneShot(false);
481+
mSourceSwitchControlEnabled.override(true);
484482
}
485483

486484
/* Connection Ops */

libmdr/src/HeadphonesV2T2.cpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,21 +150,19 @@ namespace mdr
150150
return MDR_EVENT_PAIRED_DEVICES_CHANGED;
151151
}
152152
case SOURCE_SWITCH_CONTROL:
153-
{
154-
const auto command = static_cast<Command>(cmd[0]);
155-
if (command == Command::PERI_NTFY_PARAM)
156153
{
157-
Deserialize(PeripheralNotifyParamSourceSwitchControl, res, cmd);
158-
self->mPlaybackDeviceFixed.overwrite(res.value1 == 0);
159-
self->mSourceSwitchControlResult = res.result;
160-
MDR_LOG_DEBUG("SourceSwitchControl notify: value1={} result={}", res.value1, res.result);
154+
const auto command = static_cast<Command>(cmd[0]);
155+
if (command == Command::PERI_NTFY_PARAM)
156+
{
157+
Deserialize(PeripheralNotifyParamSourceSwitchControl, res, cmd);
158+
self->mSourceSwitchControlEnabled.overwrite(res.value1 != 0);
159+
self->mSourceSwitchControlResult = res.result;
160+
return MDR_EVENT_PAIRED_DEVICES_CHANGED;
161+
}
162+
Deserialize(PeripheralRetParamSourceSwitchControl, res, cmd);
163+
self->mSourceSwitchControlEnabled.overwrite(res.value != 0);
161164
return MDR_EVENT_PAIRED_DEVICES_CHANGED;
162165
}
163-
Deserialize(PeripheralRetParamSourceSwitchControl, res, cmd);
164-
self->mPlaybackDeviceFixed.overwrite(res.value == 0);
165-
MDR_LOG_DEBUG("SourceSwitchControl param: value={}", res.value);
166-
return MDR_EVENT_PAIRED_DEVICES_CHANGED;
167-
}
168166
case PAIRING_DEVICE_MANAGEMENT_WITH_BLUETOOTH_CLASS_OF_DEVICE:
169167
{
170168
const auto command = static_cast<Command>(cmd[0]);

0 commit comments

Comments
 (0)