mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-01-28 22:01:50 +00:00
linux: AirPods Max battery status support (#272)
This commit is contained in:
@@ -16,5 +16,5 @@ indent_size = 4
|
|||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
max_line_length = off
|
max_line_length = off
|
||||||
|
|
||||||
[*.{py,java,r,R,kt,xml,kts}]
|
[*.{py,java,r,R,kt,xml,kts,h,hpp,cpp,qml}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|||||||
@@ -118,6 +118,14 @@ ApplicationWindow {
|
|||||||
batteryLevel: airPodsTrayApp.deviceInfo.battery.caseLevel
|
batteryLevel: airPodsTrayApp.deviceInfo.battery.caseLevel
|
||||||
isCharging: airPodsTrayApp.deviceInfo.battery.caseCharging
|
isCharging: airPodsTrayApp.deviceInfo.battery.caseCharging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PodColumn {
|
||||||
|
visible: airPodsTrayApp.deviceInfo.battery.headsetAvailable
|
||||||
|
inEar: true
|
||||||
|
iconSource: "qrc:/icons/assets/" + airPodsTrayApp.deviceInfo.podIcon
|
||||||
|
batteryLevel: airPodsTrayApp.deviceInfo.battery.headsetLevel
|
||||||
|
isCharging: airPodsTrayApp.deviceInfo.battery.headsetCharging
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SegmentedControl {
|
SegmentedControl {
|
||||||
@@ -318,4 +326,4 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,24 +113,24 @@ namespace AirPodsPackets
|
|||||||
static const QByteArray HEADER = ControlCommand::HEADER + static_cast<char>(0x2C);
|
static const QByteArray HEADER = ControlCommand::HEADER + static_cast<char>(0x2C);
|
||||||
static const QByteArray ENABLED = ControlCommand::createCommand(0x2C, 0x01, 0x01);
|
static const QByteArray ENABLED = ControlCommand::createCommand(0x2C, 0x01, 0x01);
|
||||||
static const QByteArray DISABLED = ControlCommand::createCommand(0x2C, 0x02, 0x02);
|
static const QByteArray DISABLED = ControlCommand::createCommand(0x2C, 0x02, 0x02);
|
||||||
|
|
||||||
inline std::optional<bool> parseState(const QByteArray &data)
|
inline std::optional<bool> parseState(const QByteArray &data)
|
||||||
{
|
{
|
||||||
if (!data.startsWith(HEADER) || data.size() < HEADER.size() + 2)
|
if (!data.startsWith(HEADER) || data.size() < HEADER.size() + 2)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
QByteArray value = data.mid(HEADER.size(), 2);
|
QByteArray value = data.mid(HEADER.size(), 2);
|
||||||
if (value.size() != 2)
|
if (value.size() != 2)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
char b1 = value.at(0);
|
char b1 = value.at(0);
|
||||||
char b2 = value.at(1);
|
char b2 = value.at(1);
|
||||||
|
|
||||||
if (b1 == 0x01 && b2 == 0x01)
|
if (b1 == 0x01 && b2 == 0x01)
|
||||||
return true;
|
return true;
|
||||||
if (b1 == 0x02 || b2 == 0x02)
|
if (b1 == 0x02 || b2 == 0x02)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class Battery : public QObject
|
|||||||
Q_PROPERTY(quint8 rightPodLevel READ getRightPodLevel NOTIFY batteryStatusChanged)
|
Q_PROPERTY(quint8 rightPodLevel READ getRightPodLevel NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(bool rightPodCharging READ isRightPodCharging NOTIFY batteryStatusChanged)
|
Q_PROPERTY(bool rightPodCharging READ isRightPodCharging NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(bool rightPodAvailable READ isRightPodAvailable NOTIFY batteryStatusChanged)
|
Q_PROPERTY(bool rightPodAvailable READ isRightPodAvailable NOTIFY batteryStatusChanged)
|
||||||
|
Q_PROPERTY(quint8 headsetLevel READ getHeadsetLevel NOTIFY batteryStatusChanged)
|
||||||
|
Q_PROPERTY(bool headsetCharging READ isHeadsetCharging NOTIFY batteryStatusChanged)
|
||||||
|
Q_PROPERTY(bool headsetAvailable READ isHeadsetAvailable NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(quint8 caseLevel READ getCaseLevel NOTIFY batteryStatusChanged)
|
Q_PROPERTY(quint8 caseLevel READ getCaseLevel NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(bool caseCharging READ isCaseCharging NOTIFY batteryStatusChanged)
|
Q_PROPERTY(bool caseCharging READ isCaseCharging NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(bool caseAvailable READ isCaseAvailable NOTIFY batteryStatusChanged)
|
Q_PROPERTY(bool caseAvailable READ isCaseAvailable NOTIFY batteryStatusChanged)
|
||||||
@@ -32,6 +35,7 @@ public:
|
|||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
// Initialize all components to unknown state
|
// Initialize all components to unknown state
|
||||||
|
states[Component::Headset] = {};
|
||||||
states[Component::Left] = {};
|
states[Component::Left] = {};
|
||||||
states[Component::Right] = {};
|
states[Component::Right] = {};
|
||||||
states[Component::Case] = {};
|
states[Component::Case] = {};
|
||||||
@@ -41,6 +45,7 @@ public:
|
|||||||
// Enum for AirPods components
|
// Enum for AirPods components
|
||||||
enum class Component
|
enum class Component
|
||||||
{
|
{
|
||||||
|
Headset = 0x01, // AirPods Max
|
||||||
Right = 0x02,
|
Right = 0x02,
|
||||||
Left = 0x04,
|
Left = 0x04,
|
||||||
Case = 0x08,
|
Case = 0x08,
|
||||||
@@ -105,7 +110,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If this is a pod (Left or Right), add it to the list
|
// If this is a pod (Left or Right), add it to the list
|
||||||
if (comp == Component::Left || comp == Component::Right)
|
if (comp == Component::Left || comp == Component::Right || comp == Component::Headset)
|
||||||
{
|
{
|
||||||
podsInPacket.append(comp);
|
podsInPacket.append(comp);
|
||||||
}
|
}
|
||||||
@@ -117,11 +122,17 @@ public:
|
|||||||
// Set primary and secondary pods based on order
|
// Set primary and secondary pods based on order
|
||||||
if (!podsInPacket.isEmpty())
|
if (!podsInPacket.isEmpty())
|
||||||
{
|
{
|
||||||
Component newPrimaryPod = podsInPacket[0]; // First pod is primary
|
if (podsInPacket.count() == 1 && podsInPacket[0] == Component::Headset) {
|
||||||
if (newPrimaryPod != primaryPod)
|
// AirPods Max
|
||||||
{
|
primaryPod = podsInPacket[0];
|
||||||
primaryPod = newPrimaryPod;
|
|
||||||
emit primaryChanged();
|
emit primaryChanged();
|
||||||
|
} else {
|
||||||
|
Component newPrimaryPod = podsInPacket[0]; // First pod is primary
|
||||||
|
if (newPrimaryPod != primaryPod)
|
||||||
|
{
|
||||||
|
primaryPod = newPrimaryPod;
|
||||||
|
emit primaryChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (podsInPacket.size() >= 2)
|
if (podsInPacket.size() >= 2)
|
||||||
@@ -132,14 +143,18 @@ public:
|
|||||||
// Emit signal to notify about battery status change
|
// Emit signal to notify about battery status change
|
||||||
emit batteryStatusChanged();
|
emit batteryStatusChanged();
|
||||||
|
|
||||||
// Log which is left and right pod
|
if (primaryPod == Component::Headset) {
|
||||||
LOG_INFO("Primary Pod:" << primaryPod);
|
LOG_INFO("Primary Pod:" << primaryPod);
|
||||||
LOG_INFO("Secondary Pod:" << secondaryPod);
|
} else {
|
||||||
|
// Log which is left and right pod
|
||||||
|
LOG_INFO("Primary Pod:" << primaryPod);
|
||||||
|
LOG_INFO("Secondary Pod:" << secondaryPod);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parseEncryptedPacket(const QByteArray &packet, bool isLeftPodPrimary, bool podInCase)
|
bool parseEncryptedPacket(const QByteArray &packet, bool isLeftPodPrimary, bool podInCase, bool isHeadset)
|
||||||
{
|
{
|
||||||
// Validate packet size (expect 16 bytes based on provided payloads)
|
// Validate packet size (expect 16 bytes based on provided payloads)
|
||||||
if (packet.size() != 16)
|
if (packet.size() != 16)
|
||||||
@@ -160,30 +175,42 @@ public:
|
|||||||
auto [isLeftCharging, rawLeftBattery] = formatBattery(rawLeftBatteryByte);
|
auto [isLeftCharging, rawLeftBattery] = formatBattery(rawLeftBatteryByte);
|
||||||
auto [isRightCharging, rawRightBattery] = formatBattery(rawRightBatteryByte);
|
auto [isRightCharging, rawRightBattery] = formatBattery(rawRightBatteryByte);
|
||||||
auto [isCaseCharging, rawCaseBattery] = formatBattery(rawCaseBatteryByte);
|
auto [isCaseCharging, rawCaseBattery] = formatBattery(rawCaseBatteryByte);
|
||||||
|
if (isHeadset) {
|
||||||
|
int batteries[] = {rawLeftBattery, rawRightBattery, rawCaseBattery};
|
||||||
|
bool statuses[] = {isLeftCharging, isRightCharging, isCaseCharging};
|
||||||
|
// Find the first battery that isn't CHAR_MAX
|
||||||
|
auto it = std::find_if(std::begin(batteries), std::end(batteries), [](int i) { return i != CHAR_MAX; });
|
||||||
|
if (it != std::end(batteries)) {
|
||||||
|
std::size_t idx = it - std::begin(batteries);
|
||||||
|
int battery = *it;
|
||||||
|
primaryPod = Component::Headset;
|
||||||
|
states[Component::Headset] = {static_cast<quint8>(battery), statuses[idx] ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rawLeftBattery == CHAR_MAX) {
|
||||||
|
rawLeftBattery = states.value(Component::Left).level; // Use last valid level
|
||||||
|
isLeftCharging = states.value(Component::Left).status == BatteryStatus::Charging;
|
||||||
|
}
|
||||||
|
|
||||||
if (rawLeftBattery == CHAR_MAX) {
|
if (rawRightBattery == CHAR_MAX) {
|
||||||
rawLeftBattery = states.value(Component::Left).level; // Use last valid level
|
rawRightBattery = states.value(Component::Right).level; // Use last valid level
|
||||||
isLeftCharging = states.value(Component::Left).status == BatteryStatus::Charging;
|
isRightCharging = states.value(Component::Right).status == BatteryStatus::Charging;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawRightBattery == CHAR_MAX) {
|
if (rawCaseBattery == CHAR_MAX) {
|
||||||
rawRightBattery = states.value(Component::Right).level; // Use last valid level
|
rawCaseBattery = states.value(Component::Case).level; // Use last valid level
|
||||||
isRightCharging = states.value(Component::Right).status == BatteryStatus::Charging;
|
isCaseCharging = states.value(Component::Case).status == BatteryStatus::Charging;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawCaseBattery == CHAR_MAX) {
|
// Update states
|
||||||
rawCaseBattery = states.value(Component::Case).level; // Use last valid level
|
states[Component::Left] = {static_cast<quint8>(rawLeftBattery), isLeftCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
||||||
isCaseCharging = states.value(Component::Case).status == BatteryStatus::Charging;
|
states[Component::Right] = {static_cast<quint8>(rawRightBattery), isRightCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
||||||
|
if (podInCase) {
|
||||||
|
states[Component::Case] = {static_cast<quint8>(rawCaseBattery), isCaseCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
||||||
|
}
|
||||||
|
primaryPod = isLeftPodPrimary ? Component::Left : Component::Right;
|
||||||
|
secondaryPod = isLeftPodPrimary ? Component::Right : Component::Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update states
|
|
||||||
states[Component::Left] = {static_cast<quint8>(rawLeftBattery), isLeftCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
|
||||||
states[Component::Right] = {static_cast<quint8>(rawRightBattery), isRightCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
|
||||||
if (podInCase) {
|
|
||||||
states[Component::Case] = {static_cast<quint8>(rawCaseBattery), isCaseCharging ? BatteryStatus::Charging : BatteryStatus::Discharging};
|
|
||||||
}
|
|
||||||
primaryPod = isLeftPodPrimary ? Component::Left : Component::Right;
|
|
||||||
secondaryPod = isLeftPodPrimary ? Component::Right : Component::Left;
|
|
||||||
emit batteryStatusChanged();
|
emit batteryStatusChanged();
|
||||||
emit primaryChanged();
|
emit primaryChanged();
|
||||||
|
|
||||||
@@ -236,6 +263,9 @@ public:
|
|||||||
quint8 getCaseLevel() const { return states.value(Component::Case).level; }
|
quint8 getCaseLevel() const { return states.value(Component::Case).level; }
|
||||||
bool isCaseCharging() const { return isStatus(Component::Case, BatteryStatus::Charging); }
|
bool isCaseCharging() const { return isStatus(Component::Case, BatteryStatus::Charging); }
|
||||||
bool isCaseAvailable() const { return !isStatus(Component::Case, BatteryStatus::Disconnected); }
|
bool isCaseAvailable() const { return !isStatus(Component::Case, BatteryStatus::Disconnected); }
|
||||||
|
quint8 getHeadsetLevel() const { return states.value(Component::Headset).level; }
|
||||||
|
bool isHeadsetCharging() const { return isStatus(Component::Headset, BatteryStatus::Charging); }
|
||||||
|
bool isHeadsetAvailable() const { return !isStatus(Component::Headset, BatteryStatus::Disconnected); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void batteryStatusChanged();
|
void batteryStatusChanged();
|
||||||
@@ -257,4 +287,4 @@ private:
|
|||||||
QMap<Component, BatteryState> states;
|
QMap<Component, BatteryState> states;
|
||||||
Component primaryPod;
|
Component primaryPod;
|
||||||
Component secondaryPod;
|
Component secondaryPod;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,7 +197,12 @@ public:
|
|||||||
int leftLevel = getBattery()->getState(Battery::Component::Left).level;
|
int leftLevel = getBattery()->getState(Battery::Component::Left).level;
|
||||||
int rightLevel = getBattery()->getState(Battery::Component::Right).level;
|
int rightLevel = getBattery()->getState(Battery::Component::Right).level;
|
||||||
int caseLevel = getBattery()->getState(Battery::Component::Case).level;
|
int caseLevel = getBattery()->getState(Battery::Component::Case).level;
|
||||||
setBatteryStatus(QString("Left: %1%, Right: %2%, Case: %3%").arg(leftLevel).arg(rightLevel).arg(caseLevel));
|
if (getBattery()->getPrimaryPod() == Battery::Component::Headset) {
|
||||||
|
int headsetLevel = getBattery()->getState(Battery::Component::Headset).level;
|
||||||
|
setBatteryStatus(QString("Headset: %1%").arg(headsetLevel));
|
||||||
|
} else {
|
||||||
|
setBatteryStatus(QString("Left: %1%, Right: %2%, Case: %3%").arg(leftLevel).arg(rightLevel).arg(caseLevel));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -229,4 +234,4 @@ private:
|
|||||||
QString m_manufacturer;
|
QString m_manufacturer;
|
||||||
QString m_bluetoothAddress;
|
QString m_bluetoothAddress;
|
||||||
EarDetection *m_earDetection;
|
EarDetection *m_earDetection;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,11 +85,23 @@ namespace AirpodsTrayApp
|
|||||||
return {"podpro.png", "podpro_case.png"};
|
return {"podpro.png", "podpro_case.png"};
|
||||||
case AirPodsModel::AirPodsMaxLightning:
|
case AirPodsModel::AirPodsMaxLightning:
|
||||||
case AirPodsModel::AirPodsMaxUSBC:
|
case AirPodsModel::AirPodsMaxUSBC:
|
||||||
return {"max.png", "max_case.png"};
|
return {"podmax.png", "max_case.png"};
|
||||||
default:
|
default:
|
||||||
return {"pod.png", "pod_case.png"}; // Default icon for unknown models
|
return {"pod.png", "pod_case.png"}; // Default icon for unknown models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Only used for parseEncryptedPacket for battery status. Is it possible to determine this
|
||||||
|
// from the data in the packet rather than by model? i.e number of batteries
|
||||||
|
inline bool isModelHeadset(AirPodsModel model) {
|
||||||
|
switch (model) {
|
||||||
|
case AirPodsModel::AirPodsMaxLightning:
|
||||||
|
case AirPodsModel::AirPodsMaxUSBC:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -666,7 +666,7 @@ private slots:
|
|||||||
else if (data.startsWith(AirPodsPackets::Parse::FEATURES_ACK))
|
else if (data.startsWith(AirPodsPackets::Parse::FEATURES_ACK))
|
||||||
{
|
{
|
||||||
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
||||||
|
|
||||||
QTimer::singleShot(2000, this, [this]() {
|
QTimer::singleShot(2000, this, [this]() {
|
||||||
if (m_deviceInfo->batteryStatus().isEmpty()) {
|
if (m_deviceInfo->batteryStatus().isEmpty()) {
|
||||||
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
writePacketToSocket(AirPodsPackets::Connection::REQUEST_NOTIFICATIONS, "Request notifications packet written: ");
|
||||||
@@ -718,7 +718,7 @@ private slots:
|
|||||||
mediaController->handleEarDetection(m_deviceInfo->getEarDetection());
|
mediaController->handleEarDetection(m_deviceInfo->getEarDetection());
|
||||||
}
|
}
|
||||||
// Battery Status
|
// Battery Status
|
||||||
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
|
else if ((data.size() == 22 || data.size() == 12) && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
|
||||||
{
|
{
|
||||||
m_deviceInfo->getBattery()->parsePacket(data);
|
m_deviceInfo->getBattery()->parsePacket(data);
|
||||||
m_deviceInfo->updateBatteryStatus();
|
m_deviceInfo->updateBatteryStatus();
|
||||||
@@ -766,7 +766,7 @@ private slots:
|
|||||||
}
|
}
|
||||||
QBluetoothAddress phoneAddress("00:00:00:00:00:00"); // Default address, will be overwritten if PHONE_MAC_ADDRESS is set
|
QBluetoothAddress phoneAddress("00:00:00:00:00:00"); // Default address, will be overwritten if PHONE_MAC_ADDRESS is set
|
||||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||||
|
|
||||||
if (!env.value("PHONE_MAC_ADDRESS").isEmpty())
|
if (!env.value("PHONE_MAC_ADDRESS").isEmpty())
|
||||||
{
|
{
|
||||||
phoneAddress = QBluetoothAddress(env.value("PHONE_MAC_ADDRESS"));
|
phoneAddress = QBluetoothAddress(env.value("PHONE_MAC_ADDRESS"));
|
||||||
@@ -875,7 +875,7 @@ private slots:
|
|||||||
if (BLEUtils::isValidIrkRpa(m_deviceInfo->magicAccIRK(), device.address)) {
|
if (BLEUtils::isValidIrkRpa(m_deviceInfo->magicAccIRK(), device.address)) {
|
||||||
m_deviceInfo->setModel(device.modelName);
|
m_deviceInfo->setModel(device.modelName);
|
||||||
auto decryptet = BLEUtils::decryptLastBytes(device.encryptedPayload, m_deviceInfo->magicAccEncKey());
|
auto decryptet = BLEUtils::decryptLastBytes(device.encryptedPayload, m_deviceInfo->magicAccEncKey());
|
||||||
m_deviceInfo->getBattery()->parseEncryptedPacket(decryptet, device.primaryLeft, device.isThisPodInTheCase);
|
m_deviceInfo->getBattery()->parseEncryptedPacket(decryptet, device.primaryLeft, device.isThisPodInTheCase, isModelHeadset(m_deviceInfo->model()));
|
||||||
m_deviceInfo->getEarDetection()->overrideEarDetectionStatus(device.isPrimaryInEar, device.isSecondaryInEar);
|
m_deviceInfo->getEarDetection()->overrideEarDetectionStatus(device.isPrimaryInEar, device.isSecondaryInEar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -991,7 +991,7 @@ int main(int argc, char *argv[]) {
|
|||||||
sharedMemory.setKey("TcpServer-Key2");
|
sharedMemory.setKey("TcpServer-Key2");
|
||||||
|
|
||||||
// Check if app is already open
|
// Check if app is already open
|
||||||
if(sharedMemory.create(1) == false)
|
if(sharedMemory.create(1) == false)
|
||||||
{
|
{
|
||||||
LOG_INFO("Another instance already running! Opening App Window Instead");
|
LOG_INFO("Another instance already running! Opening App Window Instead");
|
||||||
QLocalSocket socket;
|
QLocalSocket socket;
|
||||||
@@ -1083,7 +1083,7 @@ int main(int argc, char *argv[]) {
|
|||||||
LOG_ERROR("Failed to connect to the duplicate app instance");
|
LOG_ERROR("Failed to connect to the duplicate app instance");
|
||||||
LOG_DEBUG("Connection error: " << socket->errorString());
|
LOG_DEBUG("Connection error: " << socket->errorString());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle server-level errors
|
// Handle server-level errors
|
||||||
QObject::connect(&server, &QLocalServer::serverError, [&]() {
|
QObject::connect(&server, &QLocalServer::serverError, [&]() {
|
||||||
LOG_ERROR("Server failed to accept a new connection");
|
LOG_ERROR("Server failed to accept a new connection");
|
||||||
|
|||||||
@@ -109,20 +109,21 @@ void TrayIconManager::updateIconFromBattery(const QString &status)
|
|||||||
{
|
{
|
||||||
int leftLevel = 0;
|
int leftLevel = 0;
|
||||||
int rightLevel = 0;
|
int rightLevel = 0;
|
||||||
|
int minLevel = 0;
|
||||||
|
|
||||||
if (!status.isEmpty())
|
if (!status.isEmpty())
|
||||||
{
|
{
|
||||||
// Parse the battery status string
|
// Parse the battery status string
|
||||||
QStringList parts = status.split(", ");
|
QStringList parts = status.split(", ");
|
||||||
if (parts.size() >= 2)
|
if (parts.size() >= 2) {
|
||||||
{
|
|
||||||
leftLevel = parts[0].split(": ")[1].replace("%", "").toInt();
|
leftLevel = parts[0].split(": ")[1].replace("%", "").toInt();
|
||||||
rightLevel = parts[1].split(": ")[1].replace("%", "").toInt();
|
rightLevel = parts[1].split(": ")[1].replace("%", "").toInt();
|
||||||
|
minLevel = (leftLevel == 0) ? rightLevel : (rightLevel == 0) ? leftLevel
|
||||||
|
: qMin(leftLevel, rightLevel);
|
||||||
|
} else if (parts.size() == 1) {
|
||||||
|
minLevel = parts[0].split(": ")[1].replace("%", "").toInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int minLevel = (leftLevel == 0) ? rightLevel : (rightLevel == 0) ? leftLevel
|
|
||||||
: qMin(leftLevel, rightLevel);
|
|
||||||
|
|
||||||
QPixmap pixmap(32, 32);
|
QPixmap pixmap(32, 32);
|
||||||
pixmap.fill(Qt::transparent);
|
pixmap.fill(Qt::transparent);
|
||||||
|
|||||||
Reference in New Issue
Block a user