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");
}

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