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.
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.
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.
Fresh clone of
mainat b5a3eae, clean build, nothing patched. Every startup: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 inConnectingState.writePacketToSocketguards on exactly that: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 withOperationError, 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:Fix
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
retryCountis a function-localstatic(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
requireReadyparam, leavesif (socket && socket->isOpen())as-is. It also drops theConnectedStatecheck fromareAirpodsConnected(), 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 addslinux-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.