mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-21 05:31:42 +00:00
[Linux] Use segment control for setting noise control mode (#92)
* [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 * Add segmented control * Support keyboard navigation * Fix ear detection when primary pod changes * Support up to 9 modes with the keyboard * Redisign * Use id * Use correct images * Remove duplicates * Use correct image * Remove more merge conflicts * Remove unused code * Remove unused code * Make all text readbale
This commit is contained in:
@@ -27,6 +27,7 @@ qt_add_qml_module(applinux
|
|||||||
QML_FILES
|
QML_FILES
|
||||||
Main.qml
|
Main.qml
|
||||||
BatteryIndicator.qml
|
BatteryIndicator.qml
|
||||||
|
SegmentedControl.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add the resource file
|
# Add the resource file
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 5
|
spacing: 5
|
||||||
opacity: airPodsTrayApp.isLeftPodInEar ? 1 : 0.5
|
opacity: airPodsTrayApp.leftPodInEar ? 1 : 0.5
|
||||||
visible: airPodsTrayApp.battery.leftPodAvailable
|
visible: airPodsTrayApp.battery.leftPodAvailable
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
@@ -36,7 +36,6 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -46,7 +45,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 5
|
spacing: 5
|
||||||
opacity: airPodsTrayApp.isRightPodInEar ? 1 : 0.5
|
opacity: airPodsTrayApp.rightPodInEar ? 1 : 0.5
|
||||||
visible: airPodsTrayApp.battery.rightPodAvailable
|
visible: airPodsTrayApp.battery.rightPodAvailable
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
@@ -62,7 +61,6 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -87,7 +85,6 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -95,19 +92,21 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SegmentedControl {
|
||||||
|
id: noiseControlMode
|
||||||
|
// width: parent.width
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"]
|
||||||
|
currentIndex: airPodsTrayApp.noiseControlMode
|
||||||
|
onCurrentIndexChanged: airPodsTrayApp.noiseControlMode = currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: earDetectionStatus
|
id: earDetectionStatus
|
||||||
text: "Ear Detection Status: " + airPodsTrayApp.earDetectionStatus
|
text: "Ear Detection Status: " + airPodsTrayApp.earDetectionStatus
|
||||||
color: "#ffffff"
|
color: "#ffffff"
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: noiseControlMode
|
|
||||||
model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"]
|
|
||||||
currentIndex: airPodsTrayApp.noiseControlMode
|
|
||||||
onCurrentIndexChanged: airPodsTrayApp.noiseControlMode = currentIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
id: caToggle
|
id: caToggle
|
||||||
text: "Conversational Awareness"
|
text: "Conversational Awareness"
|
||||||
@@ -116,6 +115,7 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Slider {
|
Slider {
|
||||||
|
id: noiseLevelSlider
|
||||||
visible: airPodsTrayApp.adaptiveModeActive
|
visible: airPodsTrayApp.adaptiveModeActive
|
||||||
from: 0
|
from: 0
|
||||||
to: 100
|
to: 100
|
||||||
@@ -125,8 +125,8 @@ ApplicationWindow {
|
|||||||
property Timer debounceTimer: Timer {
|
property Timer debounceTimer: Timer {
|
||||||
interval: 500 // 500ms delay after last change
|
interval: 500 // 500ms delay after last change
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!parent.pressed) {
|
if (!noiseLevelSlider.pressed) {
|
||||||
airPodsTrayApp.setAdaptiveNoiseLevel(parent.value)
|
airPodsTrayApp.setAdaptiveNoiseLevel(noiseLevelSlider.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
104
linux/SegmentedControl.qml
Normal file
104
linux/SegmentedControl.qml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
property var model: ["Option 1", "Option 2"] // Default model
|
||||||
|
property int currentIndex: 0
|
||||||
|
|
||||||
|
// Colors using system palette
|
||||||
|
readonly property color backgroundColor: palette.light
|
||||||
|
readonly property color selectedColor: palette.highlight
|
||||||
|
readonly property color textColor: palette.buttonText
|
||||||
|
readonly property color selectedTextColor: palette.highlightedText
|
||||||
|
|
||||||
|
// System palette
|
||||||
|
SystemPalette {
|
||||||
|
id: palette
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal properties
|
||||||
|
padding: 6
|
||||||
|
implicitHeight: 32
|
||||||
|
// Removed: implicitWidth: Math.max(200, model.length * 100)
|
||||||
|
|
||||||
|
// Set focus policy to enable keyboard navigation
|
||||||
|
focusPolicy: Qt.StrongFocus
|
||||||
|
activeFocusOnTab: true
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
background: Rectangle {
|
||||||
|
radius: height / 2
|
||||||
|
color: root.backgroundColor
|
||||||
|
border.width: root.activeFocus ? 1 : 0
|
||||||
|
border.color: root.selectedColor
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Row {
|
||||||
|
spacing: root.padding
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.model
|
||||||
|
|
||||||
|
delegate: Button {
|
||||||
|
id: segmentButton
|
||||||
|
required property int index
|
||||||
|
required property string modelData
|
||||||
|
text: modelData
|
||||||
|
// Removed: width: (root.availableWidth - (root.model.length - 1) * root.padding) / root.model.length
|
||||||
|
height: root.availableHeight
|
||||||
|
focusPolicy: Qt.NoFocus // Let the root control handle focus
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: height / 2
|
||||||
|
color: root.currentIndex === segmentButton.index ? root.selectedColor : "transparent"
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 600
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (root.currentIndex !== index) {
|
||||||
|
root.currentIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle key events for navigation
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (event.key === Qt.Key_Left) {
|
||||||
|
if (root.currentIndex > 0) {
|
||||||
|
root.currentIndex--;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
} else if (event.key === Qt.Key_Right) {
|
||||||
|
if (root.currentIndex < root.model.length - 1) {
|
||||||
|
root.currentIndex++;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
} else if (event.key === Qt.Key_Home) {
|
||||||
|
root.currentIndex = 0;
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_End) {
|
||||||
|
root.currentIndex = root.model.length - 1;
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
|
||||||
|
const index = event.key - Qt.Key_1;
|
||||||
|
if (index <= root.model.length) {
|
||||||
|
root.currentIndex = index;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -104,7 +104,12 @@ public:
|
|||||||
// Set primary and secondary pods based on order
|
// Set primary and secondary pods based on order
|
||||||
if (!podsInPacket.isEmpty())
|
if (!podsInPacket.isEmpty())
|
||||||
{
|
{
|
||||||
primaryPod = podsInPacket[0]; // First pod is primary
|
Component newPrimaryPod = podsInPacket[0]; // First pod is primary
|
||||||
|
if (newPrimaryPod != primaryPod)
|
||||||
|
{
|
||||||
|
primaryPod = newPrimaryPod;
|
||||||
|
emit primaryChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (podsInPacket.size() >= 2)
|
if (podsInPacket.size() >= 2)
|
||||||
{
|
{
|
||||||
@@ -166,6 +171,7 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void batteryStatusChanged();
|
void batteryStatusChanged();
|
||||||
|
void primaryChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isStatus(Component component, BatteryStatus status) const
|
bool isStatus(Component component, BatteryStatus status) const
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class AirPodsTrayApp : public QObject {
|
|||||||
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 podIcon READ podIcon NOTIFY modelChanged)
|
||||||
Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged)
|
Q_PROPERTY(QString caseIcon READ caseIcon NOTIFY modelChanged)
|
||||||
Q_PROPERTY(bool isLeftPodInEar READ isLeftPodInEar NOTIFY earDetectionStatusChanged)
|
Q_PROPERTY(bool leftPodInEar READ isLeftPodInEar NOTIFY primaryChanged)
|
||||||
Q_PROPERTY(bool isRightPodInEar READ isRightPodInEar NOTIFY earDetectionStatusChanged)
|
Q_PROPERTY(bool rightPodInEar READ isRightPodInEar NOTIFY primaryChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AirPodsTrayApp(bool debugMode)
|
AirPodsTrayApp(bool debugMode)
|
||||||
@@ -55,6 +55,8 @@ public:
|
|||||||
mediaController->initializeMprisInterface();
|
mediaController->initializeMprisInterface();
|
||||||
mediaController->followMediaChanges();
|
mediaController->followMediaChanges();
|
||||||
|
|
||||||
|
connect(m_battery, &Battery::primaryChanged, this, &AirPodsTrayApp::primaryChanged);
|
||||||
|
|
||||||
// load conversational awareness state
|
// load conversational awareness state
|
||||||
setConversationalAwareness(loadConversationalAwarenessState());
|
setConversationalAwareness(loadConversationalAwarenessState());
|
||||||
|
|
||||||
@@ -476,6 +478,9 @@ private slots:
|
|||||||
|
|
||||||
m_model = parseModelNumber(modelNumber);
|
m_model = parseModelNumber(modelNumber);
|
||||||
|
|
||||||
|
emit modelChanged();
|
||||||
|
m_model = parseModelNumber(modelNumber);
|
||||||
|
|
||||||
emit modelChanged();
|
emit modelChanged();
|
||||||
emit deviceNameChanged(m_deviceName);
|
emit deviceNameChanged(m_deviceName);
|
||||||
|
|
||||||
@@ -598,10 +603,13 @@ private slots:
|
|||||||
char secondary = data[7];
|
char secondary = data[7];
|
||||||
m_primaryInEar = primary == 0x00;
|
m_primaryInEar = primary == 0x00;
|
||||||
m_secoundaryInEar = secondary == 0x00;
|
m_secoundaryInEar = secondary == 0x00;
|
||||||
|
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);
|
||||||
emit earDetectionStatusChanged(m_earDetectionStatus);
|
emit earDetectionStatusChanged(m_earDetectionStatus);
|
||||||
|
emit primaryChanged();
|
||||||
}
|
}
|
||||||
// Battery Status
|
// Battery Status
|
||||||
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
|
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
|
||||||
@@ -854,6 +862,7 @@ signals:
|
|||||||
void adaptiveNoiseLevelChanged(int level);
|
void adaptiveNoiseLevelChanged(int level);
|
||||||
void deviceNameChanged(const QString &name);
|
void deviceNameChanged(const QString &name);
|
||||||
void modelChanged();
|
void modelChanged();
|
||||||
|
void primaryChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSystemTrayIcon *trayIcon;
|
QSystemTrayIcon *trayIcon;
|
||||||
|
|||||||
Reference in New Issue
Block a user