Add Nothing Track mod (nothing-track) - #5374
Conversation
|
Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review. To get started, comment See the pull request review process for the full details. |
|
/ai-review |
|
/ai-review |
|
@lenorio |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Nice, self-contained feature and the taskbar-injection scaffolding follows the established patterns. The main problems are around teardown — several things can outlive 1. The static BluetoothManager& Instance() {
[[clang::no_destroy]] static BluetoothManager s_instance;
return s_instance;
}Reference: vlc-discord-rpc.wh.cpp#L256. 2. The strong XAML globals need the same treatment. [[clang::no_destroy]] static Grid g_injectedGrid{nullptr};
[[clang::no_destroy]] static FrameworkElement g_injectionParent{nullptr};
[[clang::no_destroy]] static FrameworkElement g_trackedElement{nullptr};
[[clang::no_destroy]] static DispatcherTimer g_dispatcherTimer{nullptr};
3. Ten The cleanest fix here is to not spawn threads at all: give 4. 5.
6.
You already record the right thing at injection time — auto targetGrid = g_injectionParent.try_as<Grid>();
int widgetCol = g_injectedColumn;
RemoveWidgetGridChildren(targetGrid);
if (widgetCol >= 0 && targetGrid && widgetCol < (int)targetGrid.ColumnDefinitions().Size()) {
...
}7. The paired-device match is far too loose, and the mod sends proprietary command packets to whatever it picks. 8. g_settings.hideDisconnectedBuds = Wh_GetIntSetting(L"hideDisconnectedBuds");The current string read plus 9. Settings UI strings default to Russian. Mod names, descriptions and settings - marginSide: "4 4"
$name: "Отступы виджета (слева справа)"
$description: "Отступ в пикселях: левый и правый через пробел (по умолчанию 4 4)"Please flip them so the base strings are English and add 10. Disabling the mod can hang for a long time. 11. The README has no screenshot. This mod's whole value is a visible taskbar widget and a fairly elaborate flyout, so please add at least one image of each ( Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Nice work overall — the taskbar/XAML integration follows the established pattern ( 1. The Refresh button runs blocking Bluetooth I/O on the Explorer UI thread refreshBtn.Click([](auto const&, auto const&) {
BluetoothManager::Instance().QueryAll();
});
refreshBtn.Click([](auto const&, auto const&) {
BluetoothManager::Instance().PostAction([]() {
BluetoothManager::Instance().QueryAll();
});
});2. DataReader reader(socket.InputStream());
reader.InputStreamOptions(InputStreamOptions::Partial);
...
if (reader.LoadAsync(7).get() < 7) break;
...
uint32_t toRead = pLen + 2;
if (reader.LoadAsync(toRead).get() < toRead) break;With 3. The Bluetooth stack starts in every
void Wh_ModAfterInit() {
g_taskbarWnd = FindCurrentProcessTaskbarWnd();
if (!g_taskbarWnd) {
return; // not the shell process, nothing to do here
}
BluetoothManager::Instance().Start();
RunFromWindowThread(g_taskbarWnd, ...);
}(You'd also want to start it from 4. Unload can block for a long time — the WinRT async operations are never cancelled
So disabling/updating the mod can wedge Windhawk's unload for a long time. Keep the in-flight 5. Teardown can leave callbacks alive after the mod image is unloaded Two paths where a callback that lives in the mod image can survive
6. Mention how this relates to the existing Bluetooth battery mod BT Battery Monitor already shows Bluetooth device battery levels in the tray. Your mod is clearly different in substance (vendor RFCOMM protocol, ANC/EQ/bass/latency controls, a taskbar XAML widget rather than a tray icon), but the battery-display half overlaps, and users will hit both when searching. A sentence in the README saying what this does that the generic mod can't would help. Optional improvements
Minor polish — none of this affects users in normal operation, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer |
|
Thanks for the submission.
|
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Good iteration — most of the previous round is genuinely fixed: 1. static BluetoothManager& Instance() {
auto& instance = InstanceStorage();
if (!instance) instance.emplace(); // check-then-act, unsynchronized
return *instance;
}
void Start() {
if (m_running.load()) return; // check-then-act, unsynchronized
...
m_worker = std::thread([this]() { WorkerLoop(); });
Both are cheap to close: void Start() {
if (m_running.exchange(true)) return; // atomic claim
m_stopRequested.store(false);
m_worker = std::thread([this]() { WorkerLoop(); });
m_actionWorker = std::thread([this]() { ActionLoop(); });
}and a mutex (or 2. The idle path enumerates every paired Bluetooth device every 3 seconds, forever
There's an event-driven alternative here, so this isn't a "no better option" situation: resolve the 3. Credit the taskbar-integration code that this mod reuses, and add an The whole taskbar/tray injection layer — the 25 Optional improvements
Minor polish — none of this affects users in normal operation, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Another good round — the previous list is genuinely addressed: 1. BOOL sent = SendMessageTimeoutW(hWnd, kMsg, 0, (LPARAM)&pay,
SMTO_ABORTIFHUNG | SMTO_BLOCK, timeoutMs, &result);
...
for (int attempt = 0; attempt < 3 && !removed; ++attempt) {
removed = RunFromWindowThread(hWnd, [](void*) { RemoveWidgetGrid(); }, nullptr);
if (!removed) Sleep(50);
}
if (!removed) Wh_Log(L"Wh_ModUninit: Failed to dispatch taskbar teardown");
The mod you based the injection layer on uses a plain blocking While you're in 2. BluetoothManager::Instance().StopRinging(); // m_stopRequested is still false here
BluetoothManager::Instance().Stop();
if (operation.wait_for(std::chrono::seconds(3)) != winrt::Windows::Foundation::AsyncStatus::Completed) {
try { asyncInfo.Cancel(); } catch (...) {}
untrack();
throw winrt::hresult_canceled();
}Alternatively, run the stop-ring on the action worker and wait on it with a bounded timeout before calling Optional improvements
Minor polish — none of this affects users in normal operation, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Both items from the last round are addressed: 1. The stop signal is published without the mutex the waiters use, so it can be missed — and then
m_stopRequested.store(true);
m_actionCv.notify_all(); // m_actionMutex not held
m_workerWakeState->cv.notify_all(); // m_workerWaitMutex not heldBoth waiters evaluate their predicate while holding their mutex and then atomically release-and-block. Because the notifier never takes that mutex, a
struct WorkerWakeState {
std::mutex mutex;
std::condition_variable cv;
std::atomic<uint64_t> connectionStatusVersion{0};
};
void Stop() {
std::lock_guard<std::mutex> lock(m_lifecycleMutex);
if (!m_running.exchange(false)) return;
{
std::lock_guard<std::mutex> a(m_actionMutex);
std::lock_guard<std::mutex> w(m_workerWakeState->mutex);
m_stopRequested.store(true);
}
m_actionCv.notify_all();
m_workerWakeState->cv.notify_all();
...The same applies to the two other notifiers, where the consequence is functional rather than a stall: 2. The dispatcher fallback in dispatcher.RunAsync(
winrt::Windows::UI::Core::CoreDispatcherPriority::Normal,
[]() { RemoveWidgetGrid(); }).get();This path runs only when Bound the wait so unload can't hang: auto op = dispatcher.RunAsync(
winrt::Windows::UI::Core::CoreDispatcherPriority::Normal,
[]() { RemoveWidgetGrid(); });
if (op.wait_for(std::chrono::seconds(5)) ==
winrt::Windows::Foundation::AsyncStatus::Completed) {
removed = true;
}Note the tradeoff: a timed-out wait leaves the delegate queued, so if the thread later drains its queue it would call Optional improvements
Minor polish — none of this affects users in normal operation, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Both items from the last round are fixed: 1. The handler registered at line 3243 starts with: auto layoutGrid = sender.try_as<Grid>();
if (!layoutGrid) return;
Worth noting that every mod in the repo that uses this event ignores g_layoutUpdateToken = targetGrid.LayoutUpdated(
[targetGrid](winrt::Windows::Foundation::IInspectable const&,
winrt::Windows::Foundation::IInspectable const&) {
try {
if (!g_injectedGrid || !g_trackedElement || g_unloading) return;
...
auto transform = g_trackedElement.TransformToVisual(targetGrid);2. Battery readings expire on fixed 35 s / 15 s timers while
The thresholds need to be derived from the interval, e.g.: void ExpireStaleBatteryReadings() {
auto ttl = std::chrono::seconds(g_pollIntervalSeconds.load() * 2 + 5);
auto now = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> lock(g_stateMutex);
if (g_earbudsState.left.present && now - g_earbudsState.left.lastSeen > ttl)
g_earbudsState.left.present = false;
...
}If the case is meant to expire faster than the buds on purpose, it still needs a floor of at least one poll interval, otherwise it can never survive a cycle. Optional improvements
Minor polish — none of this affects users in normal operation, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
Adds Nothing Track, a native Windows 11 taskbar widget and flyout control for Nothing and CMF earbuds.
It communicates with the earbuds directly over Bluetooth RFCOMM (SPP) profile to display battery status and control ANC, Ultra Bass, EQ presets, Low Latency game mode, and Find My Earbuds without requiring a mobile app or Android emulator.
The PR adds a single file:
mods/nothing-track.wh.cpp.Changelog
If this pull request updates an existing mod, describe the changes below:
Mod authorship
If this pull request introduces a new mod, please complete the section below.
This mod was created by:
Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.