Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
43 changes: 37 additions & 6 deletions 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 All @@ -49,17 +51,17 @@ class AirPodsTrayApp : public QObject {
Q_PROPERTY(bool hearingAidEnabled READ hearingAidEnabled WRITE setHearingAidEnabled NOTIFY hearingAidEnabledChanged)

public:
AirPodsTrayApp(bool debugMode, bool hideOnStart, QQmlApplicationEngine *parent = nullptr)
AirPodsTrayApp(bool debugMode, bool hideOnStart, bool noTray = false, QQmlApplicationEngine *parent = nullptr)
: QObject(parent), debugMode(debugMode), m_settings(new QSettings("AirPodsTrayApp", "AirPodsTrayApp"))
, m_autoStartManager(new AutoStartManager(this)), m_hideOnStart(hideOnStart), parent(parent)
, m_autoStartManager(new AutoStartManager(this)), m_hideOnStart(hideOnStart), m_noTray(noTray), parent(parent)
, m_deviceInfo(new DeviceInfo(this)), m_bleManager(new BleManager(this))
, m_systemSleepMonitor(new SystemSleepMonitor(this))
{
QLoggingCategory::setFilterRules(QString("librepods.debug=%1").arg(debugMode ? "true" : "false"));
LOG_INFO("Initializing LibrePods");

// Initialize tray icon and connect signals
trayManager = new TrayIconManager(this);
// Initialize tray icon and connect signals (skip if --no-tray)
trayManager = new TrayIconManager(this, noTray);
trayManager->setNotificationsEnabled(loadNotificationsEnabled());
connect(trayManager, &TrayIconManager::trayClicked, this, &AirPodsTrayApp::onTrayIconActivated);
connect(trayManager, &TrayIconManager::openApp, this, &AirPodsTrayApp::onOpenApp);
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 Expand Up @@ -982,6 +1008,7 @@ private slots:
AutoStartManager *m_autoStartManager;
int m_retryAttempts = 3;
bool m_hideOnStart = false;
bool m_noTray = false;
DeviceInfo *m_deviceInfo;
BleManager *m_bleManager;
SystemSleepMonitor *m_systemSleepMonitor = nullptr;
Expand Down Expand Up @@ -1034,18 +1061,22 @@ int main(int argc, char *argv[]) {

bool debugMode = false;
bool hideOnStart = false;
bool noTray = false;
for (int i = 1; i < argc; ++i) {
if (QString(argv[i]) == "--debug")
debugMode = true;

if (QString(argv[i]) == "--hide")
hideOnStart = true;

if (QString(argv[i]) == "--no-tray")
noTray = true;
}

QQmlApplicationEngine engine;
qmlRegisterType<Battery>("me.kavishdevar.Battery", 1, 0, "Battery");
qmlRegisterType<DeviceInfo>("me.kavishdevar.DeviceInfo", 1, 0, "DeviceInfo");
AirPodsTrayApp *trayApp = new AirPodsTrayApp(debugMode, hideOnStart, &engine);
AirPodsTrayApp *trayApp = new AirPodsTrayApp(debugMode, hideOnStart, noTray, &engine);
engine.rootContext()->setContextProperty("airPodsTrayApp", trayApp);

// Expose PHONE_MAC_ADDRESS environment variable to QML for placeholder in settings
Expand Down
7 changes: 5 additions & 2 deletions linux/trayiconmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

using namespace AirpodsTrayApp::Enums;

TrayIconManager::TrayIconManager(QObject *parent) : QObject(parent)
TrayIconManager::TrayIconManager(QObject *parent, bool noTray) : QObject(parent), m_noTray(noTray)
{
// Initialize tray icon
trayIcon = new QSystemTrayIcon(QIcon(":/icons/assets/airpods.png"), this);
Expand All @@ -24,7 +24,10 @@ TrayIconManager::TrayIconManager(QObject *parent) : QObject(parent)
trayIcon->setContextMenu(trayMenu);
connect(trayIcon, &QSystemTrayIcon::activated, this, &TrayIconManager::onTrayIconActivated);

trayIcon->show();
// Only show tray icon if not disabled
if (!noTray) {
trayIcon->show();
}
}

void TrayIconManager::showNotification(const QString &title, const QString &message)
Expand Down
3 changes: 2 additions & 1 deletion linux/trayiconmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class TrayIconManager : public QObject
Q_PROPERTY(bool notificationsEnabled READ notificationsEnabled WRITE setNotificationsEnabled NOTIFY notificationsEnabledChanged)

public:
explicit TrayIconManager(QObject *parent = nullptr);
explicit TrayIconManager(QObject *parent = nullptr, bool noTray = false);

void updateBatteryStatus(const QString &status);

Expand Down Expand Up @@ -51,6 +51,7 @@ private slots:
QAction *caToggleAction;
QActionGroup *noiseControlGroup;
bool m_notificationsEnabled = true;
bool m_noTray = false;

void setupMenuActions();

Expand Down