[Linux] Enhance GUI with icons (#90)
* [Linux] Enhance GUI with icons * Improve visibility * Smarter hiding of battery values * Add simple opacity based ear detection indication * Hide disconnected devices * Add airpods 3 icon * Support more devices * Better icons * Add documentation
@@ -10,6 +10,7 @@ Rectangle {
|
|||||||
property int batteryLevel: 50 // 0-100
|
property int batteryLevel: 50 // 0-100
|
||||||
property bool isCharging: false
|
property bool isCharging: false
|
||||||
property bool darkMode: false
|
property bool darkMode: false
|
||||||
|
property string indicator: "" // "L" or "R"
|
||||||
|
|
||||||
// Private properties
|
// Private properties
|
||||||
readonly property color darkModeBackground: "#1C1C1E"
|
readonly property color darkModeBackground: "#1C1C1E"
|
||||||
@@ -40,26 +41,16 @@ Rectangle {
|
|||||||
return batteryHighColor;
|
return batteryHighColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 5
|
spacing: 7
|
||||||
|
|
||||||
// Battery percentage text
|
|
||||||
Text {
|
|
||||||
id: percentageText
|
|
||||||
text: root.batteryLevel + "%"
|
|
||||||
color: root.textColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "SF Pro Text" // Apple system font
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Battery icon
|
// Battery icon
|
||||||
Item {
|
Item {
|
||||||
id: batteryIcon
|
id: batteryIcon
|
||||||
Layout.preferredWidth: 32
|
Layout.preferredWidth: 32
|
||||||
Layout.preferredHeight: 16
|
Layout.preferredHeight: 16
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
// Main battery body
|
// Main battery body
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -121,7 +112,7 @@ Rectangle {
|
|||||||
ctx.reset();
|
ctx.reset();
|
||||||
|
|
||||||
// Draw a lightning bolt
|
// Draw a lightning bolt
|
||||||
ctx.fillStyle = root.darkMode ? "#000000" : "#FFFFFF";
|
ctx.fillStyle = root.darkMode ? "#FFFFFF" : "#000000";
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(7, 2); // Top point
|
ctx.moveTo(7, 2); // Top point
|
||||||
ctx.lineTo(3, 8); // Middle left
|
ctx.lineTo(3, 8); // Middle left
|
||||||
@@ -135,5 +126,39 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text container
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
// Left/Right indicator
|
||||||
|
Rectangle {
|
||||||
|
id: indicatorBackground
|
||||||
|
visible: root.indicator !== ""
|
||||||
|
Layout.preferredWidth: 16
|
||||||
|
Layout.preferredHeight: 16
|
||||||
|
radius: width / 2
|
||||||
|
color: root.darkMode ? "#FFFFFF" : "#1C1C1E"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: indicatorText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.indicator
|
||||||
|
color: root.darkMode ? "#1C1C1E" : "#FFFFFF"
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.family: "SF Pro Text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery percentage
|
||||||
|
Text {
|
||||||
|
id: percentageText
|
||||||
|
text: root.batteryLevel + "%"
|
||||||
|
color: root.textColor
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.family: "SF Pro Text"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ qt_add_resources(applinux "resources"
|
|||||||
PREFIX "/icons"
|
PREFIX "/icons"
|
||||||
FILES
|
FILES
|
||||||
assets/airpods.png
|
assets/airpods.png
|
||||||
|
assets/pod.png
|
||||||
|
assets/pod_case.png
|
||||||
|
assets/pod3.png
|
||||||
|
assets/pod3_case.png
|
||||||
|
assets/pod4_case.png
|
||||||
|
assets/podpro.png
|
||||||
|
assets/podpro_case.png
|
||||||
|
assets/podmax.png
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(applinux
|
target_link_libraries(applinux
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import me.kavishdevar.Battery 1.0
|
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
visible: true
|
visible: true
|
||||||
@@ -9,6 +8,8 @@ ApplicationWindow {
|
|||||||
title: "AirPods Settings"
|
title: "AirPods Settings"
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
spacing: 20
|
spacing: 20
|
||||||
padding: 20
|
padding: 20
|
||||||
|
|
||||||
@@ -16,52 +17,77 @@ ApplicationWindow {
|
|||||||
Row {
|
Row {
|
||||||
// center the content
|
// center the content
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: 15
|
spacing: 8
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
opacity: airPodsTrayApp.isLeftPodInEar ? 1 : 0.5
|
||||||
|
visible: airPodsTrayApp.battery.leftPodAvailable
|
||||||
|
|
||||||
Text {
|
Image {
|
||||||
text: "Left"
|
source: "qrc:/icons/assets/" + airPodsTrayApp.podIcon
|
||||||
color: "#ffffff"
|
width: 72
|
||||||
font.pixelSize: 12
|
height: 72
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
antialiasing: true
|
||||||
|
mipmap: true
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
BatteryIndicator {
|
BatteryIndicator {
|
||||||
|
visible: airPodsTrayApp.leftPodAvailable
|
||||||
batteryLevel: airPodsTrayApp.battery.leftPodLevel
|
batteryLevel: airPodsTrayApp.battery.leftPodLevel
|
||||||
isCharging: airPodsTrayApp.battery.leftPodCharging
|
isCharging: airPodsTrayApp.battery.leftPodCharging
|
||||||
darkMode: true
|
darkMode: true
|
||||||
|
indicator: "L"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
opacity: airPodsTrayApp.isRightPodInEar ? 1 : 0.5
|
||||||
|
visible: airPodsTrayApp.battery.rightPodAvailable
|
||||||
|
|
||||||
Text {
|
Image {
|
||||||
text: "Right"
|
source: "qrc:/icons/assets/" + airPodsTrayApp.podIcon
|
||||||
color: "#ffffff"
|
mirror: true
|
||||||
font.pixelSize: 12
|
width: 72
|
||||||
|
height: 72
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
antialiasing: true
|
||||||
|
mipmap: true
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
BatteryIndicator {
|
BatteryIndicator {
|
||||||
|
visible: airPodsTrayApp.rightPodAvailable
|
||||||
batteryLevel: airPodsTrayApp.battery.rightPodLevel
|
batteryLevel: airPodsTrayApp.battery.rightPodLevel
|
||||||
isCharging: airPodsTrayApp.battery.rightPodCharging
|
isCharging: airPodsTrayApp.battery.rightPodCharging
|
||||||
darkMode: true
|
darkMode: true
|
||||||
|
indicator: "R"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 5
|
spacing: 5
|
||||||
// hide the case status if battery level is 0 and no pod is in case
|
// hide the case status if battery level is 0 and no pod is in case
|
||||||
visible: airPodsTrayApp.battery.caseLevel > 0 || airPodsTrayApp.oneOrMorePodsInCase
|
visible: airPodsTrayApp.battery.caseAvailable
|
||||||
|
|
||||||
Text {
|
Image {
|
||||||
text: "Case"
|
source: "qrc:/icons/assets/" + airPodsTrayApp.caseIcon
|
||||||
color: "#ffffff"
|
width: 92
|
||||||
font.pixelSize: 12
|
height: 72
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
smooth: true
|
||||||
|
antialiasing: true
|
||||||
|
mipmap: true
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
BatteryIndicator {
|
BatteryIndicator {
|
||||||
|
visible: airPodsTrayApp.caseAvailable
|
||||||
batteryLevel: airPodsTrayApp.battery.caseLevel
|
batteryLevel: airPodsTrayApp.battery.caseLevel
|
||||||
isCharging: airPodsTrayApp.battery.caseCharging
|
isCharging: airPodsTrayApp.battery.caseCharging
|
||||||
darkMode: true
|
darkMode: true
|
||||||
@@ -138,7 +164,6 @@ ApplicationWindow {
|
|||||||
text: "Rename"
|
text: "Rename"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
airPodsTrayApp.renameAirPods(newNameField.text)
|
airPodsTrayApp.renameAirPods(newNameField.text)
|
||||||
// Optional: newNameField.text = "" // Clear field after rename
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
linux/assets/pod.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
linux/assets/pod3.png
Normal file
|
After Width: | Height: | Size: 854 KiB |
BIN
linux/assets/pod3_case.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
linux/assets/pod4_case.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
linux/assets/pod_case.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
linux/assets/podmax.png
Normal file
|
After Width: | Height: | Size: 484 KiB |
BIN
linux/assets/podpro.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
linux/assets/podpro_case.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
@@ -11,10 +11,13 @@ class Battery : public QObject
|
|||||||
|
|
||||||
Q_PROPERTY(quint8 leftPodLevel READ getLeftPodLevel NOTIFY batteryStatusChanged)
|
Q_PROPERTY(quint8 leftPodLevel READ getLeftPodLevel NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(bool leftPodCharging READ isLeftPodCharging NOTIFY batteryStatusChanged)
|
Q_PROPERTY(bool leftPodCharging READ isLeftPodCharging NOTIFY batteryStatusChanged)
|
||||||
|
Q_PROPERTY(bool leftPodAvailable READ isLeftPodAvailable NOTIFY batteryStatusChanged)
|
||||||
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(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)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Battery(QObject *parent = nullptr) : QObject(parent)
|
explicit Battery(QObject *parent = nullptr) : QObject(parent)
|
||||||
@@ -36,7 +39,6 @@ public:
|
|||||||
|
|
||||||
enum class BatteryStatus
|
enum class BatteryStatus
|
||||||
{
|
{
|
||||||
Unknown = 0,
|
|
||||||
Charging = 0x01,
|
Charging = 0x01,
|
||||||
Discharging = 0x02,
|
Discharging = 0x02,
|
||||||
Disconnected = 0x04,
|
Disconnected = 0x04,
|
||||||
@@ -47,7 +49,7 @@ public:
|
|||||||
struct BatteryState
|
struct BatteryState
|
||||||
{
|
{
|
||||||
quint8 level = 0; // Battery level (0-100), 0 if unknown
|
quint8 level = 0; // Battery level (0-100), 0 if unknown
|
||||||
BatteryStatus status = BatteryStatus::Unknown;
|
BatteryStatus status = BatteryStatus::Disconnected;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse the battery status packet and detect primary/secondary pods
|
// Parse the battery status packet and detect primary/secondary pods
|
||||||
@@ -133,9 +135,6 @@ public:
|
|||||||
QString statusStr;
|
QString statusStr;
|
||||||
switch (state.status)
|
switch (state.status)
|
||||||
{
|
{
|
||||||
case BatteryStatus::Unknown:
|
|
||||||
statusStr = "Unknown";
|
|
||||||
break;
|
|
||||||
case BatteryStatus::Charging:
|
case BatteryStatus::Charging:
|
||||||
statusStr = "Charging";
|
statusStr = "Charging";
|
||||||
break;
|
break;
|
||||||
@@ -156,25 +155,24 @@ public:
|
|||||||
Component getSecondaryPod() const { return secondaryPod; }
|
Component getSecondaryPod() const { return secondaryPod; }
|
||||||
|
|
||||||
quint8 getLeftPodLevel() const { return states.value(Component::Left).level; }
|
quint8 getLeftPodLevel() const { return states.value(Component::Left).level; }
|
||||||
bool isLeftPodCharging() const
|
bool isLeftPodCharging() const { return isStatus(Component::Left, BatteryStatus::Charging); }
|
||||||
{
|
bool isLeftPodAvailable() const { return !isStatus(Component::Left, BatteryStatus::Disconnected); }
|
||||||
return states.value(Component::Left).status == BatteryStatus::Charging;
|
|
||||||
}
|
|
||||||
quint8 getRightPodLevel() const { return states.value(Component::Right).level; }
|
quint8 getRightPodLevel() const { return states.value(Component::Right).level; }
|
||||||
bool isRightPodCharging() const
|
bool isRightPodCharging() const { return isStatus(Component::Right, BatteryStatus::Charging); }
|
||||||
{
|
bool isRightPodAvailable() const { return !isStatus(Component::Right, BatteryStatus::Disconnected); }
|
||||||
return states.value(Component::Right).status == BatteryStatus::Charging;
|
|
||||||
}
|
|
||||||
quint8 getCaseLevel() const { return states.value(Component::Case).level; }
|
quint8 getCaseLevel() const { return states.value(Component::Case).level; }
|
||||||
bool isCaseCharging() const
|
bool isCaseCharging() const { return isStatus(Component::Case, BatteryStatus::Charging); }
|
||||||
{
|
bool isCaseAvailable() const { return !isStatus(Component::Case, BatteryStatus::Disconnected); }
|
||||||
return states.value(Component::Case).status == BatteryStatus::Charging;
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void batteryStatusChanged();
|
void batteryStatusChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool isStatus(Component component, BatteryStatus status) const
|
||||||
|
{
|
||||||
|
return states.value(component).status == status;
|
||||||
|
}
|
||||||
|
|
||||||
QMap<Component, BatteryState> states;
|
QMap<Component, BatteryState> states;
|
||||||
Component primaryPod;
|
Component primaryPod;
|
||||||
Component secondaryPod;
|
Component secondaryPod;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
namespace AirpodsTrayApp
|
namespace AirpodsTrayApp
|
||||||
{
|
{
|
||||||
@@ -19,5 +20,76 @@ namespace AirpodsTrayApp
|
|||||||
MaxValue = Adaptive,
|
MaxValue = Adaptive,
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(NoiseControlMode)
|
Q_ENUM_NS(NoiseControlMode)
|
||||||
|
|
||||||
|
enum class AirPodsModel
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
AirPods1,
|
||||||
|
AirPods2,
|
||||||
|
AirPods3,
|
||||||
|
AirPodsPro,
|
||||||
|
AirPodsPro2Lightning,
|
||||||
|
AirPodsPro2USBC,
|
||||||
|
AirPodsMaxLightning,
|
||||||
|
AirPodsMaxUSBC,
|
||||||
|
AirPods4,
|
||||||
|
AirPods4ANC
|
||||||
|
};
|
||||||
|
Q_ENUM_NS(AirPodsModel)
|
||||||
|
|
||||||
|
// Get model enum from model number
|
||||||
|
inline AirPodsModel parseModelNumber(const QString &modelNumber)
|
||||||
|
{
|
||||||
|
// Model numbers taken from https://support.apple.com/en-us/109525
|
||||||
|
QHash<QString, AirPodsModel> modelNumberMap = {
|
||||||
|
{"A1523", AirPodsModel::AirPods1},
|
||||||
|
{"A1722", AirPodsModel::AirPods1},
|
||||||
|
{"A2032", AirPodsModel::AirPods2},
|
||||||
|
{"A2031", AirPodsModel::AirPods2},
|
||||||
|
{"A2084", AirPodsModel::AirPodsPro},
|
||||||
|
{"A2083", AirPodsModel::AirPodsPro},
|
||||||
|
{"A2096", AirPodsModel::AirPodsMaxLightning},
|
||||||
|
{"A3184", AirPodsModel::AirPodsMaxUSBC},
|
||||||
|
{"A2565", AirPodsModel::AirPods3},
|
||||||
|
{"A2564", AirPodsModel::AirPods3},
|
||||||
|
{"A3047", AirPodsModel::AirPodsPro2USBC},
|
||||||
|
{"A3048", AirPodsModel::AirPodsPro2USBC},
|
||||||
|
{"A3049", AirPodsModel::AirPodsPro2USBC},
|
||||||
|
{"A2931", AirPodsModel::AirPodsPro2Lightning},
|
||||||
|
{"A2699", AirPodsModel::AirPodsPro2Lightning},
|
||||||
|
{"A2698", AirPodsModel::AirPodsPro2Lightning},
|
||||||
|
{"A3053", AirPodsModel::AirPods4},
|
||||||
|
{"A3050", AirPodsModel::AirPods4},
|
||||||
|
{"A3054", AirPodsModel::AirPods4},
|
||||||
|
{"A3056", AirPodsModel::AirPods4ANC},
|
||||||
|
{"A3055", AirPodsModel::AirPods4ANC},
|
||||||
|
{"A3057", AirPodsModel::AirPods4ANC}};
|
||||||
|
|
||||||
|
return modelNumberMap.value(modelNumber, AirPodsModel::Unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return icons based on model
|
||||||
|
inline QPair<QString, QString> getModelIcon(AirPodsModel model) {
|
||||||
|
switch (model) {
|
||||||
|
case AirPodsModel::AirPods1:
|
||||||
|
case AirPodsModel::AirPods2:
|
||||||
|
return {"pod.png", "pod_case.png"};
|
||||||
|
case AirPodsModel::AirPods3:
|
||||||
|
return {"pod3.png", "pod3_case.png"};
|
||||||
|
case AirPodsModel::AirPods4:
|
||||||
|
case AirPodsModel::AirPods4ANC:
|
||||||
|
return {"pod3.png", "pod4_case.png"};
|
||||||
|
case AirPodsModel::AirPodsPro:
|
||||||
|
case AirPodsModel::AirPodsPro2Lightning:
|
||||||
|
case AirPodsModel::AirPodsPro2USBC:
|
||||||
|
return {"podpro.png", "podpro_case.png"};
|
||||||
|
case AirPodsModel::AirPodsMaxLightning:
|
||||||
|
case AirPodsModel::AirPodsMaxUSBC:
|
||||||
|
return {"max.png", "max_case.png"};
|
||||||
|
default:
|
||||||
|
return {"pod.png", "pod_case.png"}; // Default icon for unknown models
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,10 @@ class AirPodsTrayApp : public QObject {
|
|||||||
Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
|
Q_PROPERTY(QString deviceName READ deviceName NOTIFY deviceNameChanged)
|
||||||
Q_PROPERTY(Battery* battery READ getBattery NOTIFY batteryStatusChanged)
|
Q_PROPERTY(Battery* battery READ getBattery NOTIFY batteryStatusChanged)
|
||||||
Q_PROPERTY(bool oneOrMorePodsInCase READ oneOrMorePodsInCase NOTIFY earDetectionStatusChanged)
|
Q_PROPERTY(bool oneOrMorePodsInCase READ oneOrMorePodsInCase NOTIFY earDetectionStatusChanged)
|
||||||
|
Q_PROPERTY(QString podIcon READ podIcon NOTIFY modelChanged)
|
||||||
|
Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged)
|
||||||
|
Q_PROPERTY(bool isLeftPodInEar READ isLeftPodInEar NOTIFY earDetectionStatusChanged)
|
||||||
|
Q_PROPERTY(bool isRightPodInEar READ isRightPodInEar NOTIFY earDetectionStatusChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AirPodsTrayApp(bool debugMode)
|
AirPodsTrayApp(bool debugMode)
|
||||||
@@ -108,6 +112,22 @@ public:
|
|||||||
QString deviceName() const { return m_deviceName; }
|
QString deviceName() const { return m_deviceName; }
|
||||||
Battery *getBattery() const { return m_battery; }
|
Battery *getBattery() const { return m_battery; }
|
||||||
bool oneOrMorePodsInCase() const { return m_earDetectionStatus.contains("In case"); }
|
bool oneOrMorePodsInCase() const { return m_earDetectionStatus.contains("In case"); }
|
||||||
|
QString podIcon() const { return getModelIcon(m_model).first; }
|
||||||
|
QString caseIcon() const { return getModelIcon(m_model).second; }
|
||||||
|
bool isLeftPodInEar() const {
|
||||||
|
if (m_battery->getPrimaryPod() == Battery::Component::Left) {
|
||||||
|
return m_primaryInEar;
|
||||||
|
} else {
|
||||||
|
return m_secoundaryInEar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool isRightPodInEar() const {
|
||||||
|
if (m_battery->getPrimaryPod() == Battery::Component::Right) {
|
||||||
|
return m_primaryInEar;
|
||||||
|
} else {
|
||||||
|
return m_secoundaryInEar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool debugMode;
|
bool debugMode;
|
||||||
@@ -454,6 +474,9 @@ private slots:
|
|||||||
QString unknownHash = extractString();
|
QString unknownHash = extractString();
|
||||||
QString trailingByte = extractString();
|
QString trailingByte = extractString();
|
||||||
|
|
||||||
|
m_model = parseModelNumber(modelNumber);
|
||||||
|
|
||||||
|
emit modelChanged();
|
||||||
emit deviceNameChanged(m_deviceName);
|
emit deviceNameChanged(m_deviceName);
|
||||||
|
|
||||||
// Log extracted metadata
|
// Log extracted metadata
|
||||||
@@ -573,6 +596,8 @@ private slots:
|
|||||||
{
|
{
|
||||||
char primary = data[6];
|
char primary = data[6];
|
||||||
char secondary = data[7];
|
char secondary = data[7];
|
||||||
|
m_primaryInEar = primary == 0x00;
|
||||||
|
m_secoundaryInEar = secondary == 0x00;
|
||||||
m_earDetectionStatus = QString("Primary: %1, Secondary: %2")
|
m_earDetectionStatus = QString("Primary: %1, Secondary: %2")
|
||||||
.arg(getEarStatus(primary), getEarStatus(secondary));
|
.arg(getEarStatus(primary), getEarStatus(secondary));
|
||||||
LOG_INFO("Ear detection status: " << m_earDetectionStatus);
|
LOG_INFO("Ear detection status: " << m_earDetectionStatus);
|
||||||
@@ -828,6 +853,7 @@ signals:
|
|||||||
void conversationalAwarenessChanged(bool enabled);
|
void conversationalAwarenessChanged(bool enabled);
|
||||||
void adaptiveNoiseLevelChanged(int level);
|
void adaptiveNoiseLevelChanged(int level);
|
||||||
void deviceNameChanged(const QString &name);
|
void deviceNameChanged(const QString &name);
|
||||||
|
void modelChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSystemTrayIcon *trayIcon;
|
QSystemTrayIcon *trayIcon;
|
||||||
@@ -849,6 +875,9 @@ private:
|
|||||||
int m_adaptiveNoiseLevel = 50;
|
int m_adaptiveNoiseLevel = 50;
|
||||||
QString m_deviceName;
|
QString m_deviceName;
|
||||||
Battery *m_battery;
|
Battery *m_battery;
|
||||||
|
AirPodsModel m_model = AirPodsModel::Unknown;
|
||||||
|
bool m_primaryInEar = false;
|
||||||
|
bool m_secoundaryInEar = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|||||||