Skip to content

[Linux] writePacketToSocket writes before the L2CAP socket is connected, causing a spurious reconnect on every startup #698

Description

@NitroxHead

Fresh clone of main at b5a3eae, clean build, nothing patched. Every startup:

librepods:  Connecting to device:  "AirPods Pro"
librepods:  Setting hearing aid to:  enabled
librepods:  Socket error:  QBluetoothSocket::SocketError::OperationError ,  "Cannot write while not connected"
librepods:  Retrying connection (attempt  1 )
librepods:  Connected to device, sending initial packets

It connects in the end, so it's easy to miss, but the connection that succeeds is the retry. The first one gets torn down and redialed 1500 ms later for nothing.

Cause

connectToService() (main.cpp:657) is async. QBluetoothSocket::isOpen() is QIODevice's open mode, set the moment the connect starts, so it's already true while the socket is still in ConnectingState.

writePacketToSocket guards on exactly that:

// main.cpp:386
if (socket && socket->isOpen())

At startup the QML property restore calls setHearingAidEnabled() right after the connect kicks off. Guard passes, write() hits a socket that isn't connected, fails with OperationError, and that lands in the retry handler — which kills the in-flight connection.

main.cpp:129 already does it right, and it's the only one of ~15 isOpen() call sites that checks state:

bool areAirpodsConnected() const { return socket && socket->isOpen() && socket->state() == QBluetoothSocket::SocketState::ConnectedState; }

Fix

-        if (socket && socket->isOpen())
+        if (socket && socket->state() == QBluetoothSocket::SocketState::ConnectedState)

Tested on the same build: error and retry both gone, connects first try. Dropping that packet costs nothing, the real hearing aid state comes back after the handshake anyway (Hearing aid state received: true).

Same code path

retryCount is a function-local static (main.cpp:638), only reset once attempts are exhausted, never on success. It accumulates across sockets for the life of the process, so three transient errors over a long session (the bug above supplies one per startup) and reconnects stop being retried until restart. Making it a member and zeroing it on a successful connect covers it.

#518

Touches this function but doesn't fix the guard — adds a requireReady param, leaves if (socket && socket->isOpen()) as-is. It also drops the ConnectedState check from areAirpodsConnected(), which removes the one correct usage in the file.

#193

Same error on the phone socket (phoneSocket->isOpen(), main.cpp:165 and a few others). Closed as fixed by #241, but #241 adds linux-rust/ without touching the Qt client.

Both bugs are on every branch carrying linux/main.cpp.

Environment

Ubuntu 26.04, Qt 6.10.2, GNOME/Wayland, AirPods Pro 2 (A2698). main @ b5a3eae.

Happy to PR both.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions