Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ qt_add_executable(librepods
thirdparty/QR-Code-generator/qrcodegen.hpp
QRCodeImageProvider.hpp
eardetection.hpp
dbusadaptor.hpp
media/playerstatuswatcher.cpp
media/playerstatuswatcher.h
systemsleepmonitor.hpp
Expand Down
4 changes: 3 additions & 1 deletion linux/ble/blemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ AirpodsTrayApp::Enums::AirPodsModel getModelName(quint16 modelId)
{0x1F20, AirPodsModel::AirPodsMaxUSBC},
{0x0E20, AirPodsModel::AirPodsPro},
{0x1420, AirPodsModel::AirPodsPro2Lightning},
{0x2420, AirPodsModel::AirPodsPro2USBC}
{0x2420, AirPodsModel::AirPodsPro2USBC},
{0x2720, AirPodsModel::AirPodsPro3},
{0x3F20, AirPodsModel::AirPodsPro3}
};

return modelMap.value(modelId, AirPodsModel::Unknown);
Expand Down
111 changes: 111 additions & 0 deletions linux/dbusadaptor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#pragma once

#include <QObject>
#include <QDBusAbstractAdaptor>
#include <QDBusConnection>
#include <QDBusMessage>
#include "battery.hpp"
#include "deviceinfo.hpp"

class BatteryDBusAdaptor : public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "me.kavishdevar.librepods.Battery")

// Battery levels (0-100)
Q_PROPERTY(int LeftLevel READ leftLevel NOTIFY BatteryChanged)
Q_PROPERTY(int RightLevel READ rightLevel NOTIFY BatteryChanged)
Q_PROPERTY(int CaseLevel READ caseLevel NOTIFY BatteryChanged)
Q_PROPERTY(int HeadsetLevel READ headsetLevel NOTIFY BatteryChanged)

// Charging status
Q_PROPERTY(bool LeftCharging READ leftCharging NOTIFY BatteryChanged)
Q_PROPERTY(bool RightCharging READ rightCharging NOTIFY BatteryChanged)
Q_PROPERTY(bool CaseCharging READ caseCharging NOTIFY BatteryChanged)
Q_PROPERTY(bool HeadsetCharging READ headsetCharging NOTIFY BatteryChanged)

// Availability (connected/detected)
Q_PROPERTY(bool LeftAvailable READ leftAvailable NOTIFY BatteryChanged)
Q_PROPERTY(bool RightAvailable READ rightAvailable NOTIFY BatteryChanged)
Q_PROPERTY(bool CaseAvailable READ caseAvailable NOTIFY BatteryChanged)
Q_PROPERTY(bool HeadsetAvailable READ headsetAvailable NOTIFY BatteryChanged)

// Device info
Q_PROPERTY(QString DeviceName READ deviceName NOTIFY DeviceChanged)
Q_PROPERTY(bool Connected READ connected NOTIFY DeviceChanged)

public:
BatteryDBusAdaptor(Battery *battery, DeviceInfo *deviceInfo, QObject *parent)
: QDBusAbstractAdaptor(parent), m_battery(battery), m_deviceInfo(deviceInfo)
{
setAutoRelaySignals(true);

// Connect battery signals to our relay
connect(m_battery, &Battery::batteryStatusChanged, this, [this]() {
emit BatteryChanged();
});

connect(m_deviceInfo, &DeviceInfo::batteryStatusChanged, this, [this]() {
emit BatteryChanged();
});

connect(m_deviceInfo, &DeviceInfo::deviceNameChanged, this, [this]() {
emit DeviceChanged();
});
}

// Battery levels
int leftLevel() const { return m_battery->getLeftPodLevel(); }
int rightLevel() const { return m_battery->getRightPodLevel(); }
int caseLevel() const { return m_battery->getCaseLevel(); }
int headsetLevel() const { return m_battery->getHeadsetLevel(); }

// Charging status
bool leftCharging() const { return m_battery->isLeftPodCharging(); }
bool rightCharging() const { return m_battery->isRightPodCharging(); }
bool caseCharging() const { return m_battery->isCaseCharging(); }
bool headsetCharging() const { return m_battery->isHeadsetCharging(); }

// Availability
bool leftAvailable() const { return m_battery->isLeftPodAvailable(); }
bool rightAvailable() const { return m_battery->isRightPodAvailable(); }
bool caseAvailable() const { return m_battery->isCaseAvailable(); }
bool headsetAvailable() const { return m_battery->isHeadsetAvailable(); }

// Device info - connected if device name is set and any battery is available
QString deviceName() const { return m_deviceInfo->deviceName(); }
bool connected() const {
return !m_deviceInfo->deviceName().isEmpty() &&
(leftAvailable() || rightAvailable() || headsetAvailable());
}

public slots:
// Method to get all battery info at once (useful for waybar)
QVariantMap GetBatteryInfo()
{
QVariantMap info;
info["left_level"] = leftLevel();
info["left_charging"] = leftCharging();
info["left_available"] = leftAvailable();
info["right_level"] = rightLevel();
info["right_charging"] = rightCharging();
info["right_available"] = rightAvailable();
info["case_level"] = caseLevel();
info["case_charging"] = caseCharging();
info["case_available"] = caseAvailable();
info["headset_level"] = headsetLevel();
info["headset_charging"] = headsetCharging();
info["headset_available"] = headsetAvailable();
info["device_name"] = deviceName();
info["connected"] = connected();
return info;
}

signals:
void BatteryChanged();
void DeviceChanged();

private:
Battery *m_battery;
DeviceInfo *m_deviceInfo;
};
7 changes: 6 additions & 1 deletion linux/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ namespace AirpodsTrayApp
AirPodsPro,
AirPodsPro2Lightning,
AirPodsPro2USBC,
AirPodsPro3,
AirPodsMaxLightning,
AirPodsMaxUSBC,
AirPods4,
Expand Down Expand Up @@ -63,7 +64,10 @@ namespace AirpodsTrayApp
{"A3054", AirPodsModel::AirPods4},
{"A3056", AirPodsModel::AirPods4ANC},
{"A3055", AirPodsModel::AirPods4ANC},
{"A3057", AirPodsModel::AirPods4ANC}};
{"A3057", AirPodsModel::AirPods4ANC},
{"A3063", AirPodsModel::AirPodsPro3},
{"A3064", AirPodsModel::AirPodsPro3},
{"A3065", AirPodsModel::AirPodsPro3}};

return modelNumberMap.value(modelNumber, AirPodsModel::Unknown);
}
Expand All @@ -82,6 +86,7 @@ namespace AirpodsTrayApp
case AirPodsModel::AirPodsPro:
case AirPodsModel::AirPodsPro2Lightning:
case AirPodsModel::AirPodsPro2USBC:
case AirPodsModel::AirPodsPro3:
return {"podpro.png", "podpro_case.png"};
case AirPodsModel::AirPodsMaxLightning:
case AirPodsModel::AirPodsMaxUSBC:
Expand Down
28 changes: 27 additions & 1 deletion linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include "ble/bleutils.h"
#include "QRCodeImageProvider.hpp"
#include "systemsleepmonitor.hpp"
#include "dbusadaptor.hpp"
#include <QDBusConnection>

using namespace AirpodsTrayApp::Enums;

Expand Down Expand Up @@ -149,7 +151,31 @@ class AirPodsTrayApp : public QObject {
bool isEnabled = true; // Ability to disable the feature
} CrossDevice;

void initializeDBus() { }
void initializeDBus() {
// Create D-Bus adaptor for battery info
new BatteryDBusAdaptor(m_deviceInfo->getBattery(), m_deviceInfo, this);

// Register on session bus
QDBusConnection sessionBus = QDBusConnection::sessionBus();
if (!sessionBus.isConnected()) {
LOG_ERROR("Cannot connect to D-Bus session bus");
return;
}

// Register service
if (!sessionBus.registerService("me.kavishdevar.librepods")) {
LOG_ERROR("Cannot register D-Bus service: " << sessionBus.lastError().message());
return;
}

// Register object
if (!sessionBus.registerObject("/battery", this)) {
LOG_ERROR("Cannot register D-Bus object: " << sessionBus.lastError().message());
return;
}

LOG_INFO("D-Bus service registered: me.kavishdevar.librepods at /battery");
}
Comment on lines +154 to +178
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for D-Bus service name conflicts and registration patterns

# Check if BatteryDBusAdaptor follows Qt's parent-managed pattern
echo "=== Checking BatteryDBusAdaptor implementation ==="
rg -n "class BatteryDBusAdaptor" -A 20

# Check for any cleanup or unregister logic
echo -e "\n=== Checking for D-Bus cleanup/unregister logic ==="
rg -n "unregisterService|unregisterObject" --type cpp

# Check how other parts of the code handle single-instance scenarios
echo -e "\n=== Checking single-instance handling ==="
rg -n "QLocalServer|instance.*running" -C 3 --type cpp

Repository: kavishdevar/librepods

Length of output: 4236


🏁 Script executed:

#!/bin/bash
# Check BatteryDBusAdaptor constructor to confirm parent pattern
echo "=== BatteryDBusAdaptor constructor ==="
rg -n "BatteryDBusAdaptor::" -A 5

# Check for destructors or cleanup code
echo -e "\n=== Checking destructors/cleanup ==="
rg -n "~.*Application|~.*MainWindow|~.*TrayApplication" -A 10 --type cpp

# Check if there's any shutdown handling for D-Bus
echo -e "\n=== Checking application shutdown/exit code ==="
rg -n "aboutToQuit|destroyed|closeEvent" -B 2 -A 5 --type cpp

# Check the full initializeDBus function context
echo -e "\n=== Full D-Bus initialization context ==="
sed -n '150,180p' linux/main.cpp

Repository: kavishdevar/librepods

Length of output: 1716


🏁 Script executed:

#!/bin/bash
# Check full BatteryDBusAdaptor header definition
echo "=== Full BatteryDBusAdaptor header ==="
cat -n linux/dbusadaptor.hpp

Repository: kavishdevar/librepods

Length of output: 5212


Add D-Bus cleanup on registration failures and application shutdown.

The D-Bus initialization code is mostly correct. The BatteryDBusAdaptor memory management is proper—it correctly uses Qt's parent-child pattern by passing this to QDBusAbstractAdaptor as a parent (line 38-39).

However, there are two issues to address:

  1. Partial initialization cleanup missing: If service registration succeeds (line 166) but object registration fails (line 172), the service remains registered without an object. Consider calling sessionBus.unregisterService() before returning on object registration failure.

  2. No D-Bus cleanup on shutdown: The aboutToQuit handler (line 1142-1147) only closes the local server but doesn't unregister the D-Bus service and object. Add calls to sessionBus.unregisterObject() and sessionBus.unregisterService() to properly clean up on shutdown.

Service name conflicts are mitigated by the existing single-instance mechanism (via QLocalServer), which prevents concurrent instances from running.

🤖 Prompt for AI Agents
In linux/main.cpp around lines 154-178, the D-Bus init routine currently
registers the service and object but fails to clean up if object registration
fails; update the failure path to call
sessionBus.unregisterService("me.kavishdevar.librepods") (or unregister the
service name you registered) before returning when registerObject() fails so the
service isn't left registered alone. Also update the aboutToQuit handler around
lines 1142-1147 to explicitly call
QDBusConnection::sessionBus().unregisterObject("/battery") and
QDBusConnection::sessionBus().unregisterService("me.kavishdevar.librepods")
(guarded if necessary) so the D-Bus object and service are unregistered on
shutdown.


bool isAirPodsDevice(const QBluetoothDeviceInfo &device)
{
Expand Down