Merge pull request #111 from jazzysoggy/main

[Linux] Add seperators to tray, add tray option to reopen app and settings, prevent duplicate instances of app from being opened
This commit is contained in:
Kavish Devar
2025-04-30 07:27:13 +05:30
committed by GitHub
4 changed files with 160 additions and 3 deletions

View File

@@ -9,9 +9,33 @@ ApplicationWindow {
width: 400
height: 300
title: "AirPods Settings"
objectName: "mainWindowObject"
onClosing: mainWindow.visible = false
function reopen(pageToLoad) {
if (pageToLoad == "settings")
{
if (stackView.depth == 1)
{
stackView.push(settingsPage)
}
}
else
{
if (stackView.depth > 1)
{
stackView.pop()
}
}
if (!mainWindow.visible) {
mainWindow.visible = true
}
raise()
requestActivate()
}
// Mouse area for handling back/forward navigation
MouseArea {
anchors.fill: parent

View File

@@ -1,5 +1,6 @@
#include <QSettings>
#include <QLocalServer>
#include <QLocalSocket>
#include "main.h"
#include "airpods_packets.h"
#include "logger.h"
@@ -38,7 +39,7 @@ class AirPodsTrayApp : public QObject {
Q_PROPERTY(bool hideOnStart READ hideOnStart CONSTANT)
public:
AirPodsTrayApp(bool debugMode, bool hideOnStart, QObject *parent = nullptr)
AirPodsTrayApp(bool debugMode, bool hideOnStart, QQmlApplicationEngine *parent = nullptr)
: QObject(parent)
, debugMode(debugMode)
, m_battery(new Battery(this))
@@ -46,6 +47,7 @@ public:
, m_settings(new QSettings("AirPodsTrayApp", "AirPodsTrayApp"))
, m_autoStartManager(new AutoStartManager(this))
, m_hideOnStart(hideOnStart)
, parent(parent)
{
if (debugMode) {
QLoggingCategory::setFilterRules("airpodsApp.debug=true");
@@ -58,6 +60,8 @@ public:
trayManager = new TrayIconManager(this);
trayManager->setNotificationsEnabled(loadNotificationsEnabled());
connect(trayManager, &TrayIconManager::trayClicked, this, &AirPodsTrayApp::onTrayIconActivated);
connect(trayManager, &TrayIconManager::openApp, this, &AirPodsTrayApp::onOpenApp);
connect(trayManager, &TrayIconManager::openSettings, this, &AirPodsTrayApp::onOpenSettings);
connect(trayManager, &TrayIconManager::noiseControlChanged, this, qOverload<NoiseControlMode>(&AirPodsTrayApp::setNoiseControlMode));
connect(trayManager, &TrayIconManager::conversationalAwarenessToggled, this, &AirPodsTrayApp::setConversationalAwareness);
connect(this, &AirPodsTrayApp::batteryStatusChanged, trayManager, &TrayIconManager::updateBatteryStatus);
@@ -146,6 +150,9 @@ public:
private:
bool debugMode;
bool isConnectedLocally = false;
QQmlApplicationEngine *parent = nullptr;
struct {
bool isAvailable = true;
bool isEnabled = true; // Ability to disable the feature
@@ -352,6 +359,30 @@ private slots:
}
}
void onOpenApp()
{
QObject *rootObject = parent->rootObjects().first();
if (rootObject) {
QMetaObject::invokeMethod(rootObject, "reopen", Q_ARG(QVariant, "app"));
}
else
{
parent->loadFromModule("linux", "Main");
}
}
void onOpenSettings()
{
QObject *rootObject = parent->rootObjects().first();
if (rootObject) {
QMetaObject::invokeMethod(rootObject, "reopen", Q_ARG(QVariant, "settings"));
}
else
{
parent->loadFromModule("linux", "Main");
}
}
void sendHandshake() {
LOG_INFO("Connected to device, sending initial packets");
writePacketToSocket(AirPodsPackets::Connection::HANDSHAKE, "Handshake packet written: ");
@@ -681,8 +712,12 @@ private slots:
LOG_INFO("Already connected to the phone");
return;
}
QBluetoothAddress phoneAddress(PHONE_MAC_ADDRESS);
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
if (!env.value("PHONE_MAC_ADDRESS").isEmpty())
{
QBluetoothAddress phoneAddress = QBluetoothAddress(env.value("PHONE_MAC_ADDRESS"));
}
phoneSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol);
connect(phoneSocket, &QBluetoothSocket::connected, this, [this]() {
LOG_INFO("Connected to phone");
@@ -918,6 +953,32 @@ private:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QSharedMemory sharedMemory;
sharedMemory.setKey("TcpServer-Key");
// Check if app is already open
if(sharedMemory.create(1) == false)
{
LOG_INFO("Another instance already running! Opening App Window Instead");
QLocalSocket socket;
// Connect to the original app, then trigger the reopen signal
socket.connectToServer("app_server");
if (socket.waitForConnected(500)) {
socket.write("reopen");
socket.flush();
socket.waitForBytesWritten(500);
socket.disconnectFromServer();
app.exit(); // exit; process already running
return 0;
}
else
{
// Failed connection, log and open the app (assume it's not running)
LOG_ERROR("Failed to connect to the original app instance. Assuming it is not running.");
LOG_DEBUG("Socket error: " << socket.errorString());
}
}
app.setQuitOnLastWindowClosed(false);
bool debugMode = false;
@@ -935,6 +996,59 @@ int main(int argc, char *argv[]) {
AirPodsTrayApp *trayApp = new AirPodsTrayApp(debugMode, hideOnStart, &engine);
engine.rootContext()->setContextProperty("airPodsTrayApp", trayApp);
engine.loadFromModule("linux", "Main");
QLocalServer server;
QLocalServer::removeServer("app_server");
if (!server.listen("app_server"))
{
LOG_ERROR("Unable to start the listening server");
LOG_DEBUG("Server error: " << server.errorString());
}
else
{
LOG_DEBUG("Server started, waiting for connections...");
}
QObject::connect(&server, &QLocalServer::newConnection, [&]() {
QLocalSocket* socket = server.nextPendingConnection();
// Handles Proper Connection
QObject::connect(socket, &QLocalSocket::readyRead, [socket, &engine]() {
QString msg = socket->readAll();
// Check if the message is "reopen", if so, trigger onOpenApp function
if (msg == "reopen") {
LOG_INFO("Reopening app window");
QObject *rootObject = engine.rootObjects().first();
if (rootObject) {
QMetaObject::invokeMethod(rootObject, "reopen", Q_ARG(QVariant, "app"));
}
else
{
engine.loadFromModule("linux", "Main");
}
}
else
{
LOG_ERROR("Unknown message received: " << msg);
}
socket->disconnectFromServer();
});
// Handles connection errors
QObject::connect(socket, &QLocalSocket::errorOccurred, [socket]() {
LOG_ERROR("Failed to connect to the duplicate app instance");
LOG_DEBUG("Connection error: " << socket->errorString());
});
// Handle server-level errors
QObject::connect(&server, &QLocalServer::serverError, [&]() {
LOG_ERROR("Server failed to accept a new connection");
LOG_DEBUG("Server error: " << server.errorString());
});
});
QObject::connect(&app, &QCoreApplication::aboutToQuit, [&]() {
LOG_DEBUG("Application is about to quit. Cleaning up...");
sharedMemory.detach();
});
return app.exec();
}

View File

@@ -56,6 +56,19 @@ void TrayIconManager::updateConversationalAwareness(bool enabled)
void TrayIconManager::setupMenuActions()
{
// Open action
QAction *openAction = new QAction("Open", trayMenu);
trayMenu->addAction(openAction);
connect(openAction, &QAction::triggered, qApp, [this](){emit openApp();});
// Settings Menu
QAction *settingsMenu = new QAction("Settings", trayMenu);
trayMenu->addAction(settingsMenu);
connect(settingsMenu, &QAction::triggered, qApp, [this](){emit openSettings();});
trayMenu->addSeparator();
// Conversational Awareness Toggle
caToggleAction = new QAction("Toggle Conversational Awareness", trayMenu);
caToggleAction->setCheckable(true);
@@ -63,6 +76,8 @@ void TrayIconManager::setupMenuActions()
connect(caToggleAction, &QAction::triggered, this, [this](bool checked)
{ emit conversationalAwarenessToggled(checked); });
trayMenu->addSeparator();
// Noise Control Options
noiseControlGroup = new QActionGroup(trayMenu);
const QPair<QString, NoiseControlMode> noiseOptions[] = {
@@ -82,6 +97,8 @@ void TrayIconManager::setupMenuActions()
{ emit noiseControlChanged(mode); });
}
trayMenu->addSeparator();
// Quit action
QAction *quitAction = new QAction("Quit", trayMenu);
trayMenu->addAction(quitAction);

View File

@@ -54,4 +54,6 @@ signals:
void trayClicked();
void noiseControlChanged(AirpodsTrayApp::Enums::NoiseControlMode);
void conversationalAwarenessToggled(bool enabled);
void openApp();
void openSettings();
};