diff --git a/.github/workflows/build-and-release.yaml b/.github/workflows/build-and-release.yaml deleted file mode 100644 index 1d99e8e..0000000 --- a/.github/workflows/build-and-release.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: Build and Release - -on: - workflow_dispatch: - -jobs: - build: - uses: odizinne/shared-workflows/.github/workflows/build-and-release.yml@main \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 280a125..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: Build - -on: - push: - branches: - - main - -jobs: - build: - uses: odizinne/shared-workflows/.github/workflows/build.yml@main \ No newline at end of file diff --git a/.github/workflows/qt-msvc-build-and-release.yml b/.github/workflows/qt-msvc-build-and-release.yml new file mode 100644 index 0000000..8b214e4 --- /dev/null +++ b/.github/workflows/qt-msvc-build-and-release.yml @@ -0,0 +1,181 @@ +name: Build and Release + +on: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Visual Studio shell + uses: egor-tensin/vs-shell@v2 + with: + arch: x64 + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: '6.7.2' + add-tools-to-path: true + + - name: Install jom + id: jom-setup + shell: pwsh + run: | + $url = "https://download.qt.io/official_releases/jom/jom_1_1_4.zip" + $outputPath = "jom_1_1_4.zip" + Invoke-WebRequest -Uri $url -OutFile $outputPath + $extractPath = "jom" + if (-not (Test-Path $extractPath)) { + New-Item -ItemType Directory -Path $extractPath | Out-Null + } + Expand-Archive -Path $outputPath -DestinationPath $extractPath + $jomDir = "$(pwd)\jom" + $jomExe = "$jomDir\jom.exe" + if (Test-Path $jomExe) { + Write-Output "JOM Path: $jomDir" + Write-Output "::set-output name=jom_path::$jomDir" + } else { + Write-Error "jom.exe not found in $jomDir" + exit 1 + } + + - name: Build with qmake and jom + shell: pwsh + run: | + mkdir build + cd build + qmake ..\HeadsetControl-Qt.pro CONFIG+=release + # Use the JOM path variable + $jomPath = "${{ steps.jom-setup.outputs.jom_path }}" + & "$jomPath\jom.exe" + + - name: Remove source and object files + shell: pwsh + run: | + # Define the directory + $buildDir = "build/release" + + # Check if the directory exists + if (Test-Path $buildDir) { + # Remove .cpp, .h, .obj, and .res files + Get-ChildItem -Path $buildDir -Include *.cpp, *.h, *.obj, *.res -Recurse | Remove-Item -Force + } else { + Write-Host "Directory not found: $buildDir" + } + + - name: Deploy Qt + shell: pwsh + run: | + # Navigate to the directory containing the executable + cd build + + # Use the found path to windeployqt + $windeployqtPath = "D:\a\HeadsetControl-Qt\Qt\6.7.2\msvc2019_64\bin\windeployqt6.exe" + + # Check if the executable exists + if (Test-Path $windeployqtPath) { + # Run windeployqt with the updated options + & $windeployqtPath ` + --exclude-plugins qmodernwindowsstyle,qsvgicon,qsvg,qico,qjpeg,qgif,qnetworklistmanager,qtuiotouchplugin ` + --no-opengl-sw ` + --no-system-dxc-compiler ` + --no-compiler-runtime ` + --no-translations ` + --no-system-d3d-compiler ` + D:\a\HeadsetControl-Qt\HeadsetControl-Qt\build\release\HeadsetControl-Qt.exe + } else { + Write-Error "windeployqt not found at the expected path!" + exit 1 + } + + - name: Rename release folder + shell: pwsh + run: | + $releaseDir = "build/release" + $newDir = "HeadsetControl-Qt" + if (Test-Path $releaseDir) { + Rename-Item -Path $releaseDir -NewName $newDir + } else { + Write-Error "Release folder not found!" + exit 1 + } + + - name: Zip binaries folder + run: | + $zipFile = "build/HeadsetControl-Qt_msvc_64.zip" + $folder = "build/HeadsetControl-Qt" + Compress-Archive -Path $folder -DestinationPath $zipFile + shell: pwsh + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: HeadsetControl-Qt_msvc_64 + path: build/HeadsetControl-Qt_msvc_64.zip + + release: + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download artifact zip + uses: actions/download-artifact@v4 + with: + name: HeadsetControl-Qt_msvc_64 + + - name: List files in current directory + run: ls -la + + - name: Bump version and create release + id: bump_release + run: | + git fetch --tags + + # Determine the latest major version tag + LAST_MAJOR_TAG=$(git tag --list 'v*.*.*' | sed -E 's/^v?([0-9]+)\..*/\1/' | sort -nr | head -n 1) + + # Increment the major version number + if [ -z "$LAST_MAJOR_TAG" ]; then + NEW_TAG="v1" + else + NEW_TAG="v$(($LAST_MAJOR_TAG + 1))" + fi + + # Check if the tag already exists + if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then + echo "Tag '$NEW_TAG' already exists. Incrementing to next major version." + LAST_MAJOR_TAG=$(git tag --list 'v*' | sed -E 's/^v?([0-9]+).*/\1/' | sort -nr | head -n 1) + NEW_TAG="v$(($LAST_MAJOR_TAG + 1))" + fi + + echo "New tag is $NEW_TAG" + git tag $NEW_TAG + git push origin $NEW_TAG + echo "new_tag=$NEW_TAG" >> $GITHUB_ENV + + - name: Create GitHub release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: ${{ env.new_tag }} + release_name: ${{ env.new_tag }} + body: "" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload release assets + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: HeadsetControl-Qt_msvc_64.zip + asset_name: HeadsetControl-Qt_msvc_64.zip + asset_content_type: application/zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/qt-msvc-build.yml b/.github/workflows/qt-msvc-build.yml new file mode 100644 index 0000000..aab44c8 --- /dev/null +++ b/.github/workflows/qt-msvc-build.yml @@ -0,0 +1,121 @@ +name: Build + +on: + push: + branches: + - main + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Visual Studio shell + uses: egor-tensin/vs-shell@v2 + with: + arch: x64 + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: '6.7.2' + add-tools-to-path: true + + - name: Install jom + id: jom-setup + shell: pwsh + run: | + $url = "https://download.qt.io/official_releases/jom/jom_1_1_4.zip" + $outputPath = "jom_1_1_4.zip" + Invoke-WebRequest -Uri $url -OutFile $outputPath + $extractPath = "jom" + if (-not (Test-Path $extractPath)) { + New-Item -ItemType Directory -Path $extractPath | Out-Null + } + Expand-Archive -Path $outputPath -DestinationPath $extractPath + $jomDir = "$(pwd)\jom" + $jomExe = "$jomDir\jom.exe" + if (Test-Path $jomExe) { + Write-Output "JOM Path: $jomDir" + Write-Output "::set-output name=jom_path::$jomDir" + } else { + Write-Error "jom.exe not found in $jomDir" + exit 1 + } + + - name: Build with qmake and jom + shell: pwsh + run: | + mkdir build + cd build + qmake ..\HeadsetControl-Qt.pro CONFIG+=release + # Use the JOM path variable + $jomPath = "${{ steps.jom-setup.outputs.jom_path }}" + & "$jomPath\jom.exe" + + - name: Remove source and object files + shell: pwsh + run: | + # Define the directory + $buildDir = "build/release" + + # Check if the directory exists + if (Test-Path $buildDir) { + # Remove .cpp, .h, .obj, and .res files + Get-ChildItem -Path $buildDir -Include *.cpp, *.h, *.obj, *.res -Recurse | Remove-Item -Force + } else { + Write-Host "Directory not found: $buildDir" + } + + - name: Deploy Qt + shell: pwsh + run: | + # Navigate to the directory containing the executable + cd build + + # Use the found path to windeployqt + $windeployqtPath = "D:\a\HeadsetControl-Qt\Qt\6.7.2\msvc2019_64\bin\windeployqt6.exe" + + # Check if the executable exists + if (Test-Path $windeployqtPath) { + # Run windeployqt with the updated options + & $windeployqtPath ` + --exclude-plugins qmodernwindowsstyle,qsvgicon,qsvg,qico,qjpeg,qgif,qnetworklistmanager,qtuiotouchplugin ` + --no-opengl-sw ` + --no-system-dxc-compiler ` + --no-compiler-runtime ` + --no-translations ` + --no-system-d3d-compiler ` + D:\a\HeadsetControl-Qt\HeadsetControl-Qt\build\release\HeadsetControl-Qt.exe + } else { + Write-Error "windeployqt not found at the expected path!" + exit 1 + } + + - name: Rename release folder + shell: pwsh + run: | + $releaseDir = "build/release" + $newDir = "HeadsetControl-Qt" + if (Test-Path $releaseDir) { + Rename-Item -Path $releaseDir -NewName $newDir + } else { + Write-Error "Release folder not found!" + exit 1 + } + + - name: Zip binaries folder + run: | + $zipFile = "build/HeadsetControl-Qt_msvc_64.zip" + $folder = "build/HeadsetControl-Qt" + Compress-Archive -Path $folder -DestinationPath $zipFile + shell: pwsh + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: HeadsetControl-Qt_msvc_64 + path: build/HeadsetControl-Qt_msvc_64.zip diff --git a/.gitignore b/.gitignore index 99155ae..ae30fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ build/ -__pycache__/ -.venv/ -.idea/ \ No newline at end of file +HeadsetControl-Qt.pro.user \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 54231dc..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Build with cx_Freeze", - "type": "shell", - "command": "${command:python.interpreterPath}", - "args": [ - "src/setup.py", - "build" - ], - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, - "problemMatcher": [] - } - ] -} diff --git a/HeadsetControl-Qt.pro b/HeadsetControl-Qt.pro new file mode 100644 index 0000000..d20f727 --- /dev/null +++ b/HeadsetControl-Qt.pro @@ -0,0 +1,48 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++17 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +INCLUDEPATH += \ + src/ShortcutManager \ + src/HeadsetControlQt \ + src/Utils + +SOURCES += \ + src/ShortcutManager/shortcutmanager.cpp \ + src/main.cpp \ + src/HeadsetControlQt/headsetcontrolqt.cpp \ + src/Utils/utils.cpp + +HEADERS += \ + src/ShortcutManager/shortcutmanager.h \ + src/HeadsetControlQt/headsetcontrolqt.h \ + src/Utils/utils.h + +FORMS += \ + src/HeadsetControlQt/headsetcontrolqt.ui + +RESOURCES += \ + src/Resources/resources.qrc + +RC_FILE = src/Resources/appicon.rc + +LIBS += -lole32 + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +# Define the source directory and the target directory in the build folder +DEPENDENCIES_DIR = $$PWD/src/dependencies +DEST_DIR = $$OUT_PWD/release/dependencies + +QMAKE_POST_LINK += powershell -Command "New-Item -ItemType Directory -Path '$$DEST_DIR' -Force; Copy-Item -Path '$$DEPENDENCIES_DIR\*' -Destination '$$DEST_DIR' -Recurse -Force" +#mkdir $$DEST_DIR + diff --git a/README.md b/README.md index 29b29b8..e1944c6 100644 --- a/README.md +++ b/README.md @@ -2,52 +2,20 @@ [![Github All Releases](https://img.shields.io/github/downloads/odizinne/headsetcontrol-qt/total.svg)]() [![license](https://img.shields.io/github/license/odizinne/headsetcontrol-qt)]() -PyQt6 Gui for headsetcontrol.
-Windows / linux compatible. +Qt Gui for headsetcontrol. +Features a tray icon with battery status in tooltip. ![image](assets/screenshot.png) -Features a tray icon with battery status in tooltip.
-Lights state will be restored on next start. - -Linux compatibility was designed with plasma system icons in mind. -Gnome users should have a look at [headsetcontrol-indicator](https://github.com/Odizinne/headsetcontrol-indicator) instead. -For any other environment, light custom icons will be used by default. -You can change it in application settings. - ## Supported devices -Supported devices list can be found [here](https://github.com/Sapd/HeadsetControl?tab=readme-ov-file#supported-headsets). +Supported devices list can be found [here](https://github.com/Sapd/HeadsetControl?tab=readme-ov-file#supported-headsets). +If a particular setting is greyed out on the settings page, it indicates that your device does not support it. ## Download -Precompiled windows binaries can be found in [release](https://odizinne.net/Odizinne/HeadsetControl-Qt/releases/latest) section - -## Build and run - -Clone this repository: `git clone https://github.com/Odizinne/HeadsetControl-Qt.git` -CD to the cloned repo: `cd HeadsetControl-Qt` - -**Windows** -Install dependencies: -`pip install -r requirements.txt` - -Build exe: -`python .\src\setup.py build` - -Install directory and create startup shortcut: -`python .\src\setup.py install` - -**Linux** -Dependencies: -- PyQt6 (from package manager is recommended, else use pip) -- [HeadsetControl](https://github.com/Sapd/HeadsetControl) - -There is nothing to build. - -Make sure `headsetcontrol-qt.py` is executable: `chmod +x ./headsetcontrol-qt.py` -Then run it: `headsetcontrol-qt.py` +Precompiled windows binaries can be found in [release](https://odizinne.net/Odizinne/HeadsetControl-Qt/releases/latest) section ## To-do - Add other headsetcontrol supported settings (My headset does not support them so i cannot test) -- Build linux appimage +- Bring back linux support diff --git a/assets/screenshot.png b/assets/screenshot.png index a177c4b..6d4373c 100644 Binary files a/assets/screenshot.png and b/assets/screenshot.png differ diff --git a/install.sh b/install.sh deleted file mode 100755 index 4a3b7a2..0000000 --- a/install.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -SRC_DIR="$(dirname "$(realpath "$0")")" -DEST_DIR="$HOME/.local/bin/headsetcontrol-qt" -DESKTOP_ENTRY_FILE="$HOME/.local/share/applications/headsetcontrol-qt.desktop" - -mkdir -p "$DEST_DIR" -cp -r "$SRC_DIR/src/"* "$DEST_DIR/" - -EXEC_CMD="$DEST_DIR/headsetcontrol-qt.py" - -cat > "$DESKTOP_ENTRY_FILE" << EOL -[Desktop Entry] -Name=HeadsetControl-Qt -Exec=$EXEC_CMD -Icon=$DEST_DIR/icons/icon.png -Path=$DEST_DIR/ -Terminal=false -Type=Application -EOL - -chmod +x "$DESKTOP_ENTRY_FILE" -echo "" -echo "Setup complete. HeadsetControl-Qt should now be available in your applications menu." diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2441df6..0000000 Binary files a/requirements.txt and /dev/null differ diff --git a/src/HeadsetControlQt/headsetcontrolqt.cpp b/src/HeadsetControlQt/headsetcontrolqt.cpp new file mode 100644 index 0000000..865dcee --- /dev/null +++ b/src/HeadsetControlQt/headsetcontrolqt.cpp @@ -0,0 +1,433 @@ +#include "HeadsetControlQt.h" +#include "ui_HeadsetControlQt.h" +#include "utils.h" +#include "shortcutmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +const QString HeadsetControlQt::settingsFile = QStandardPaths::writableLocation( + QStandardPaths::AppDataLocation) + + "/HeadsetControl-Qt/settings.json"; + +HeadsetControlQt::HeadsetControlQt(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::HeadsetControlQt) + , trayIcon(new QSystemTrayIcon(this)) + , timer(new QTimer(this)) + , firstRun(false) +{ + ui->setupUi(this); + setWindowTitle("HeadsetControl-Qt"); + setWindowIcon(QIcon(":/icons/icon.png")); + setFixedSize(size()); + setFont(); + loadSettings(); + initUI(); + populateComboBoxes(); + createTrayIcon(); + updateHeadsetInfo(); + timer->start(10000); + checkStartupCheckbox(); + connect(timer, &QTimer::timeout, this, &HeadsetControlQt::updateHeadsetInfo); + if (firstRun) { + this->show(); + } +} + +HeadsetControlQt::~HeadsetControlQt() +{ + delete ui; +} + +void HeadsetControlQt::initUI() +{ + connect(ui->ledBox, &QCheckBox::stateChanged, this, &HeadsetControlQt::onLedBoxStateChanged); + connect(ui->lightBatterySpinbox, QOverload::of(&QSpinBox::valueChanged), this, &HeadsetControlQt::onLightBatterySpinboxValueChanged); + connect(ui->notificationBatterySpinbox, QOverload::of(&QSpinBox::valueChanged), this, &HeadsetControlQt::onNotificationBatterySpinboxValueChanged); + connect(ui->startupCheckbox, &QCheckBox::stateChanged, this, &HeadsetControlQt::onStartupCheckBoxStateChanged); + connect(ui->sidetoneSlider, &QSlider::sliderReleased, this, &HeadsetControlQt::onSidetoneSliderSliderReleased); + connect(ui->themeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &HeadsetControlQt::onThemeComboBoxCurrentIndexChanged); +} + +void HeadsetControlQt::populateComboBoxes() +{ + ui->themeComboBox->addItem(tr("System")); + ui->themeComboBox->addItem(tr("Dark")); + ui->themeComboBox->addItem(tr("Light")); +} + +void HeadsetControlQt::createTrayIcon() +{ + trayIcon->setIcon(QIcon(":/icons/icon.png")); + QMenu *trayMenu = new QMenu(this); + QAction *showAction = new QAction("Show", this); + connect(showAction, &QAction::triggered, this, &HeadsetControlQt::toggleWindow); + trayMenu->addAction(showAction); + QAction *exitAction = new QAction("Exit", this); + connect(exitAction, &QAction::triggered, this, &HeadsetControlQt::exitApp); + trayMenu->addAction(exitAction); + trayIcon->setContextMenu(trayMenu); + trayIcon->show(); + connect(trayIcon, &QSystemTrayIcon::activated, this, &HeadsetControlQt::trayIconActivated); +} + +void HeadsetControlQt::createDefaultSettings() +{ + ui->ledBox->setChecked(true); + ui->lightBatterySpinbox->setValue(20); + ui->notificationBatterySpinbox->setValue(20); + ui->sidetoneSlider->setValue(0); + ui->themeComboBox->setCurrentIndex(0); + saveSettings(); + + firstRun = true; +} + +void HeadsetControlQt::loadSettings() +{ + QDir settingsDir(QFileInfo(settingsFile).absolutePath()); + if (!settingsDir.exists()) { + settingsDir.mkpath(settingsDir.absolutePath()); + } + + QFile file(settingsFile); + if (!file.exists()) { + createDefaultSettings(); + + } else { + if (file.open(QIODevice::ReadOnly)) { + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &parseError); + if (parseError.error == QJsonParseError::NoError) { + settings = doc.object(); + } + file.close(); + } + } + applySettings(); +} + +void HeadsetControlQt::applySettings() +{ + ui->ledBox->setChecked(settings.value("led_state").toBool()); + ui->lightBatterySpinbox->setEnabled(settings.value("led_state").toBool()); + ui->lightBatteryLabel->setEnabled(settings.value("led_state").toBool()); + ui->lightBatterySpinbox->setValue(settings.value("light_battery_threshold").toInt()); + ui->notificationBatterySpinbox->setValue(settings.value("notification_battery_threshold").toInt()); + ui->sidetoneSlider->setValue(settings.value("sidetone").toInt()); + ui->themeComboBox->setCurrentIndex(settings.value("theme").toInt()); + setSidetone(); + toggleLED(); +} + +void HeadsetControlQt::saveSettings() +{ + settings["led_state"] = ui->ledBox->isChecked(); + settings["light_battery_threshold"] = ui->lightBatterySpinbox->value(); + settings["notification_battery_threshold"] = ui->notificationBatterySpinbox->value(); + settings["sidetone"] = ui->sidetoneSlider->value(); + settings["theme"] = ui->themeComboBox->currentIndex(); + + QFile file(settingsFile); + if (file.open(QIODevice::WriteOnly)) { + QJsonDocument doc(settings); + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); + } +} + +void HeadsetControlQt::updateHeadsetInfo() +{ + QProcess process; + process.start("dependencies/headsetcontrol.exe", QStringList() << "-o" << "json"); + + if (!process.waitForStarted()) { + qDebug() << "Failed to start process:" << process.errorString(); + return; + } + + if (!process.waitForFinished()) { + qDebug() << "Process did not finish successfully:" << process.errorString(); + return; + } + + QByteArray output = process.readAllStandardOutput(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(output); + if (jsonDoc.isNull()) { + return; + } + + QJsonObject jsonObj = jsonDoc.object(); + + if (jsonObj.contains("devices")) { + QJsonArray devicesArray = jsonObj["devices"].toArray(); + if (devicesArray.size() > 0) { + QJsonObject headsetInfo = devicesArray.first().toObject(); + + updateUIWithHeadsetInfo(headsetInfo); + manageLEDBasedOnBattery(headsetInfo); + sendNotificationBasedOnBattery(headsetInfo); + } else { + qDebug() << "No devices found."; + noDeviceFound(); + } + } else { + qDebug() << "No 'devices' key found in JSON."; + noDeviceFound(); + } +} + +void HeadsetControlQt::manageLEDBasedOnBattery(const QJsonObject &headsetInfo) +{ + if (!ui->ledBox->isChecked()) { + return; + } + + ui->lightBatterySpinbox->setEnabled(true); + ui->lightBatteryLabel->setEnabled(true); + + QJsonObject batteryInfo = headsetInfo["battery"].toObject(); + int batteryLevel = batteryInfo["level"].toInt(); + QString batteryStatus = batteryInfo["status"].toString(); + bool available = (batteryStatus == "BATTERY_AVAILABLE"); + + if (batteryLevel < ui->lightBatterySpinbox->value() && ledState && available) { + toggleLED(); + ledState = false; + saveSettings(); + } else if (batteryLevel >= ui->lightBatterySpinbox->value() + 5 && !ledState && available) { + toggleLED(); + ledState = true; + saveSettings(); + } +} + +void HeadsetControlQt::sendNotificationBasedOnBattery(const QJsonObject &headsetInfo) +{ + QJsonObject batteryInfo = headsetInfo["battery"].toObject(); + QString headsetName = headsetInfo["device"].toString(); + int batteryLevel = batteryInfo["level"].toInt(); + QString batteryStatus = batteryInfo["status"].toString(); + bool available = (batteryStatus == "BATTERY_AVAILABLE"); + + if (batteryLevel < ui->notificationBatterySpinbox->value() && !notificationSent && available) { + sendNotification("Low battery", QString("%1 has %2% battery left.").arg(headsetName).arg(batteryLevel), QIcon(":/icons/icon.png"), 3000); + notificationSent = true; + } else if (batteryLevel >= ui->notificationBatterySpinbox->value() + 5 && notificationSent && available) { + notificationSent = false; + } +} + +void HeadsetControlQt::sendNotification(const QString &title, const QString &message, const QIcon &icon, int duration) +{ + trayIcon->showMessage(title, message, icon, duration); +} + +void HeadsetControlQt::toggleLED() +{ + QProcess process; + process.start("dependencies/headsetcontrol.exe", QStringList() << "-l" << (ui->ledBox->isChecked() ? "1" : "0")); + process.waitForFinished(); +} + +void HeadsetControlQt::updateUIWithHeadsetInfo(const QJsonObject &headsetInfo) +{ + QString deviceName = headsetInfo["device"].toString(); + QStringList capabilities = headsetInfo["capabilities_str"].toVariant().toStringList(); + QJsonObject batteryInfo = headsetInfo["battery"].toObject(); + + ui->deviceGroupBox->setTitle(deviceName); + + QString batteryStatus = batteryInfo["status"].toString(); + int batteryLevel = batteryInfo["level"].toInt(); + + if (batteryStatus == "BATTERY_AVAILABLE") { + ui->batteryBar->setValue(batteryLevel); + ui->batteryBar->setFormat(QString::number(batteryLevel) + "%"); + trayIcon->setToolTip(QString("Battery Level: %1%").arg(batteryLevel)); + + QString iconPath = getBatteryIcon(batteryLevel, false, false); + trayIcon->setIcon(QIcon(iconPath)); + } else if (batteryStatus == "BATTERY_CHARGING") { + ui->batteryBar->setValue(0); + ui->batteryBar->setFormat("Charging"); + trayIcon->setToolTip("Battery Charging"); + + QString iconPath = getBatteryIcon(batteryLevel, true, false); + trayIcon->setIcon(QIcon(iconPath)); + } else { + ui->batteryBar->setValue(0); + ui->batteryBar->setFormat("Off"); + trayIcon->setToolTip("Battery Unavailable"); + + QString iconPath = getBatteryIcon(batteryLevel, false, true); + trayIcon->setIcon(QIcon(iconPath)); + } + + ui->ledBox->setEnabled(capabilities.contains("lights")); + ui->ledLabel->setEnabled(capabilities.contains("lights")); + + ui->sidetoneSlider->setEnabled(capabilities.contains("sidetone")); + ui->sidetoneLabel->setEnabled(capabilities.contains("sidetone")); + + toggleUIElements(true); +} + +QString HeadsetControlQt::getBatteryIcon(int batteryLevel, bool charging, bool missing) +{ + QString theme; + int themeIndex = ui->themeComboBox->currentIndex(); + if (themeIndex == 0) { + theme = getTheme(); + } else if (themeIndex == 1) { + theme = "light"; + } else if (themeIndex == 2) { + theme = "dark"; + } + + QString iconName; + if (missing) { + iconName = QString("battery-missing-%1").arg(theme); + } else if (charging) { + iconName = "battery-100-charging-" + theme; + } else { + if (batteryLevel >= 90) iconName = "battery-100-" + theme; + else if (batteryLevel >= 80) iconName = "battery-090-" + theme; + else if (batteryLevel >= 70) iconName = "battery-080-" + theme; + else if (batteryLevel >= 60) iconName = "battery-070-" + theme; + else if (batteryLevel >= 50) iconName = "battery-060-" + theme; + else if (batteryLevel >= 40) iconName = "battery-050-" + theme; + else if (batteryLevel >= 30) iconName = "battery-040-" + theme; + else if (batteryLevel >= 20) iconName = "battery-030-" + theme; + else if (batteryLevel >= 10) iconName = "battery-020-" + theme; + else iconName = "battery-010-" + theme; + } + + return QString(":/icons/%1.png").arg(iconName); +} + +void HeadsetControlQt::noDeviceFound() +{ + toggleUIElements(false); + trayIcon->setToolTip("No Device Found"); +} + +void HeadsetControlQt::toggleUIElements(bool show) +{ + ui->deviceGroupBox->setVisible(show); + ui->generalGroupBox->setVisible(show); + ui->notFoundLabel->setVisible(!show); + this->setMinimumSize(0, 0); + this->adjustSize(); +} + +void HeadsetControlQt::onLedBoxStateChanged() +{ + toggleLED(); + ui->lightBatterySpinbox->setEnabled(ui->ledBox->isChecked()); + ui->lightBatteryLabel->setEnabled(ui->ledBox->isChecked()); + saveSettings(); +} + +void HeadsetControlQt::onLightBatterySpinboxValueChanged(int value) +{ + saveSettings(); +} + +void HeadsetControlQt::onNotificationBatterySpinboxValueChanged(int value) +{ + saveSettings(); +} + +void HeadsetControlQt::onStartupCheckBoxStateChanged() +{ + if (ui->startupCheckbox->isChecked()) { + manageShortcut(true); + } else { + manageShortcut(false); + } +} + +void HeadsetControlQt::onSidetoneSliderSliderReleased() +{ + setSidetone(); + saveSettings(); +} + +void HeadsetControlQt::onThemeComboBoxCurrentIndexChanged(int index) +{ + updateHeadsetInfo(); + saveSettings(); +} + +void HeadsetControlQt::setSidetone() +{ + QProcess process; + process.start("dependencies/headsetcontrol.exe", QStringList() << "-s" << QString::number(ui->sidetoneSlider->value())); + process.waitForFinished(); +} + +void HeadsetControlQt::toggleWindow() +{ + if (isVisible()) { + hide(); + trayIcon->contextMenu()->actions().first()->setText("Show"); + } else { + show(); + trayIcon->contextMenu()->actions().first()->setText("Hide"); + } +} + +void HeadsetControlQt::trayIconActivated(QSystemTrayIcon::ActivationReason reason) +{ + if (reason == QSystemTrayIcon::ActivationReason::Trigger) { + toggleWindow(); + } +} + +void HeadsetControlQt::exitApp() +{ + trayIcon->hide(); + QApplication::quit(); +} + +void HeadsetControlQt::checkStartupCheckbox() +{ + if (isShortcutPresent()) { + ui->startupCheckbox->setChecked(true); + } +} + +void HeadsetControlQt::closeEvent(QCloseEvent *event) +{ + event->ignore(); + hide(); +} + + +void HeadsetControlQt::setFont() +{ + QList groupBoxes = { + ui->deviceGroupBox, + ui->generalGroupBox + }; + + for (QGroupBox* groupBox : groupBoxes) { + groupBox->setStyleSheet("font-weight: bold;"); + + const QList children = groupBox->findChildren(); + for (QWidget* child : children) { + child->setStyleSheet("font-weight: normal;"); + } + } +} diff --git a/src/HeadsetControlQt/headsetcontrolqt.h b/src/HeadsetControlQt/headsetcontrolqt.h new file mode 100644 index 0000000..d35e132 --- /dev/null +++ b/src/HeadsetControlQt/headsetcontrolqt.h @@ -0,0 +1,75 @@ +#ifndef HEADSETCONTROLQT_H +#define HEADSETCONTROLQT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class HeadsetControlQt; +} +QT_END_NAMESPACE + +class HeadsetControlQt : public QMainWindow +{ + Q_OBJECT + +public: + explicit HeadsetControlQt(QWidget *parent = nullptr); + ~HeadsetControlQt(); + +private slots: + void onLedBoxStateChanged(); + void onLightBatterySpinboxValueChanged(int value); + void onNotificationBatterySpinboxValueChanged(int value); + void onSidetoneSliderSliderReleased(); + void onThemeComboBoxCurrentIndexChanged(int index); + void updateHeadsetInfo(); + void toggleWindow(); + void trayIconActivated(QSystemTrayIcon::ActivationReason reason); + void exitApp(); + void checkStartupCheckbox(); + void onStartupCheckBoxStateChanged(); + +private: + void initUI(); + void populateComboBoxes(); + void createTrayIcon(); + void loadSettings(); + void applySettings(); + void saveSettings(); + void createDefaultSettings(); + void manageLEDBasedOnBattery(const QJsonObject &headsetInfo); + void sendNotificationBasedOnBattery(const QJsonObject &headsetInfo); + void sendNotification(const QString &title, const QString &message, const QIcon &icon, int duration); + void toggleLED(); + void setSidetone(); + void setFont(); + void updateUIWithHeadsetInfo(const QJsonObject &headsetInfo); + QString getBatteryIcon(int batteryLevel, bool charging = false, bool missing = false); + void noDeviceFound(); + void toggleUIElements(bool show); + static const QString settingsFile; + + Ui::HeadsetControlQt *ui; + QSystemTrayIcon *trayIcon; + QTimer *timer; + QJsonObject settings; + bool ledState; + bool notificationSent; + bool firstRun; + +protected: + void closeEvent(QCloseEvent *event) override; +}; + +#endif // HEADSETCONTROLQT_H diff --git a/src/designer/ui_mainwindow.ui b/src/HeadsetControlQt/headsetcontrolqt.ui similarity index 100% rename from src/designer/ui_mainwindow.ui rename to src/HeadsetControlQt/headsetcontrolqt.ui diff --git a/src/Resources/appicon.rc b/src/Resources/appicon.rc new file mode 100644 index 0000000..dfccaf7 --- /dev/null +++ b/src/Resources/appicon.rc @@ -0,0 +1,3 @@ +#include + +IDI_ICON1 ICON "icons/icon.ico" diff --git a/src/icons/battery-010-dark.png b/src/Resources/icons/battery-010-dark.png similarity index 100% rename from src/icons/battery-010-dark.png rename to src/Resources/icons/battery-010-dark.png diff --git a/src/icons/battery-010-light.png b/src/Resources/icons/battery-010-light.png similarity index 100% rename from src/icons/battery-010-light.png rename to src/Resources/icons/battery-010-light.png diff --git a/src/icons/battery-020-dark.png b/src/Resources/icons/battery-020-dark.png similarity index 100% rename from src/icons/battery-020-dark.png rename to src/Resources/icons/battery-020-dark.png diff --git a/src/icons/battery-020-light.png b/src/Resources/icons/battery-020-light.png similarity index 100% rename from src/icons/battery-020-light.png rename to src/Resources/icons/battery-020-light.png diff --git a/src/icons/battery-030-dark.png b/src/Resources/icons/battery-030-dark.png similarity index 100% rename from src/icons/battery-030-dark.png rename to src/Resources/icons/battery-030-dark.png diff --git a/src/icons/battery-030-light.png b/src/Resources/icons/battery-030-light.png similarity index 100% rename from src/icons/battery-030-light.png rename to src/Resources/icons/battery-030-light.png diff --git a/src/icons/battery-040-dark.png b/src/Resources/icons/battery-040-dark.png similarity index 100% rename from src/icons/battery-040-dark.png rename to src/Resources/icons/battery-040-dark.png diff --git a/src/icons/battery-040-light.png b/src/Resources/icons/battery-040-light.png similarity index 100% rename from src/icons/battery-040-light.png rename to src/Resources/icons/battery-040-light.png diff --git a/src/icons/battery-050-dark.png b/src/Resources/icons/battery-050-dark.png similarity index 100% rename from src/icons/battery-050-dark.png rename to src/Resources/icons/battery-050-dark.png diff --git a/src/icons/battery-050-light.png b/src/Resources/icons/battery-050-light.png similarity index 100% rename from src/icons/battery-050-light.png rename to src/Resources/icons/battery-050-light.png diff --git a/src/icons/battery-060-dark.png b/src/Resources/icons/battery-060-dark.png similarity index 100% rename from src/icons/battery-060-dark.png rename to src/Resources/icons/battery-060-dark.png diff --git a/src/icons/battery-060-light.png b/src/Resources/icons/battery-060-light.png similarity index 100% rename from src/icons/battery-060-light.png rename to src/Resources/icons/battery-060-light.png diff --git a/src/icons/battery-070-dark.png b/src/Resources/icons/battery-070-dark.png similarity index 100% rename from src/icons/battery-070-dark.png rename to src/Resources/icons/battery-070-dark.png diff --git a/src/icons/battery-070-light.png b/src/Resources/icons/battery-070-light.png similarity index 100% rename from src/icons/battery-070-light.png rename to src/Resources/icons/battery-070-light.png diff --git a/src/icons/battery-080-dark.png b/src/Resources/icons/battery-080-dark.png similarity index 100% rename from src/icons/battery-080-dark.png rename to src/Resources/icons/battery-080-dark.png diff --git a/src/icons/battery-080-light.png b/src/Resources/icons/battery-080-light.png similarity index 100% rename from src/icons/battery-080-light.png rename to src/Resources/icons/battery-080-light.png diff --git a/src/icons/battery-090-dark.png b/src/Resources/icons/battery-090-dark.png similarity index 100% rename from src/icons/battery-090-dark.png rename to src/Resources/icons/battery-090-dark.png diff --git a/src/icons/battery-090-light.png b/src/Resources/icons/battery-090-light.png similarity index 100% rename from src/icons/battery-090-light.png rename to src/Resources/icons/battery-090-light.png diff --git a/src/icons/battery-100-charging-dark.png b/src/Resources/icons/battery-100-charging-dark.png similarity index 100% rename from src/icons/battery-100-charging-dark.png rename to src/Resources/icons/battery-100-charging-dark.png diff --git a/src/icons/battery-100-charging-light.png b/src/Resources/icons/battery-100-charging-light.png similarity index 100% rename from src/icons/battery-100-charging-light.png rename to src/Resources/icons/battery-100-charging-light.png diff --git a/src/icons/battery-100-dark.png b/src/Resources/icons/battery-100-dark.png similarity index 100% rename from src/icons/battery-100-dark.png rename to src/Resources/icons/battery-100-dark.png diff --git a/src/icons/battery-100-light.png b/src/Resources/icons/battery-100-light.png similarity index 100% rename from src/icons/battery-100-light.png rename to src/Resources/icons/battery-100-light.png diff --git a/src/icons/battery-missing-dark.png b/src/Resources/icons/battery-missing-dark.png similarity index 100% rename from src/icons/battery-missing-dark.png rename to src/Resources/icons/battery-missing-dark.png diff --git a/src/icons/battery-missing-light.png b/src/Resources/icons/battery-missing-light.png similarity index 100% rename from src/icons/battery-missing-light.png rename to src/Resources/icons/battery-missing-light.png diff --git a/src/icons/icon.ico b/src/Resources/icons/icon.ico similarity index 100% rename from src/icons/icon.ico rename to src/Resources/icons/icon.ico diff --git a/src/icons/icon.png b/src/Resources/icons/icon.png similarity index 100% rename from src/icons/icon.png rename to src/Resources/icons/icon.png diff --git a/src/Resources/resources.qrc b/src/Resources/resources.qrc new file mode 100644 index 0000000..5808dd8 --- /dev/null +++ b/src/Resources/resources.qrc @@ -0,0 +1,30 @@ + + + icons/battery-010-dark.png + icons/battery-020-dark.png + icons/battery-030-dark.png + icons/battery-040-dark.png + icons/battery-050-dark.png + icons/battery-060-dark.png + icons/battery-070-dark.png + icons/battery-080-dark.png + icons/battery-090-dark.png + icons/battery-100-dark.png + icons/battery-100-charging-dark.png + icons/battery-missing-dark.png + icons/battery-010-light.png + icons/battery-020-light.png + icons/battery-030-light.png + icons/battery-040-light.png + icons/battery-050-light.png + icons/battery-060-light.png + icons/battery-070-light.png + icons/battery-080-light.png + icons/battery-090-light.png + icons/battery-100-light.png + icons/battery-100-charging-light.png + icons/battery-missing-light.png + icons/icon.ico + icons/icon.png + + diff --git a/src/ShortcutManager/shortcutmanager.cpp b/src/ShortcutManager/shortcutmanager.cpp new file mode 100644 index 0000000..e943fb4 --- /dev/null +++ b/src/ShortcutManager/shortcutmanager.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QString getStartupFolder() +{ + QString path; + WCHAR szPath[MAX_PATH]; + if (SHGetFolderPath(NULL, CSIDL_STARTUP, NULL, 0, szPath) == S_OK) { + path = QString::fromWCharArray(szPath); + } + return path; +} + +void setPaths(QString &targetPath, QString &startupFolder) +{ + TCHAR executablePath[MAX_PATH]; + GetModuleFileName(NULL, executablePath, MAX_PATH); + targetPath = QString::fromWCharArray(executablePath); + + startupFolder = getStartupFolder(); +} + +QString getShortcutPath() +{ + static QString shortcutName = "HeadsetControl-Qt.lnk"; + return getStartupFolder() + "\\" + shortcutName; +} + +void createShortcut(const QString &targetPath) +{ + QString shortcutPath = getShortcutPath(); + QString workingDirectory = QFileInfo(targetPath).path(); + + IShellLink *pShellLink = nullptr; + IPersistFile *pPersistFile = nullptr; + + if (FAILED(CoInitialize(nullptr))) { + qDebug() << "Failed to initialize COM library."; + return; + } + + if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IShellLink, + (void **) &pShellLink))) { + pShellLink->SetPath(targetPath.toStdWString().c_str()); + pShellLink->SetWorkingDirectory(workingDirectory.toStdWString().c_str()); + pShellLink->SetDescription(L"Launch HeadsetControl-Qt"); + + if (SUCCEEDED(pShellLink->QueryInterface(IID_IPersistFile, (void **) &pPersistFile))) { + pPersistFile->Save(shortcutPath.toStdWString().c_str(), TRUE); + pPersistFile->Release(); + } + + pShellLink->Release(); + } else { + qDebug() << "Failed to create ShellLink instance."; + } + + CoUninitialize(); +} + +bool isShortcutPresent() +{ + QString shortcutPath = getShortcutPath(); + return QFile::exists(shortcutPath); +} + +void removeShortcut() +{ + QString shortcutPath = getShortcutPath(); + if (isShortcutPresent()) { + QFile::remove(shortcutPath); + } +} + +void manageShortcut(bool state) +{ + QString targetPath; + QString startupFolder; + + setPaths(targetPath, startupFolder); + + if (state) { + if (!isShortcutPresent()) { + createShortcut(targetPath); + } + } else { + if (isShortcutPresent()) { + removeShortcut(); + } + } +} diff --git a/src/ShortcutManager/shortcutmanager.h b/src/ShortcutManager/shortcutmanager.h new file mode 100644 index 0000000..03dc509 --- /dev/null +++ b/src/ShortcutManager/shortcutmanager.h @@ -0,0 +1,9 @@ +#ifndef SHORTCUTMANAGER_H +#define SHORTCUTMANAGER_H + +#include + +bool isShortcutPresent(); +void manageShortcut(bool state); + +#endif // SHORTCUTMANAGER_H diff --git a/src/Utils/utils.cpp b/src/Utils/utils.cpp new file mode 100644 index 0000000..81aae56 --- /dev/null +++ b/src/Utils/utils.cpp @@ -0,0 +1,32 @@ +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +QString getTheme() +{ + // Determine the theme based on registry value + QSettings settings( + "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + QSettings::NativeFormat); + int value = settings.value("AppsUseLightTheme", 1).toInt(); + + // Return the opposite to match icon (dark icon on light theme) + return (value == 0) ? "light" : "dark"; +} + +QIcon getIconForTheme() +{ + QString theme = getTheme(); + QString iconPath = QString(":/icons/icon_%1.png").arg(theme); + return QIcon(iconPath); +} + diff --git a/src/Utils/utils.h b/src/Utils/utils.h new file mode 100644 index 0000000..d517977 --- /dev/null +++ b/src/Utils/utils.h @@ -0,0 +1,9 @@ +#ifndef UTILS_H +#define UTILS_H +#include +#include + +QString getTheme(); +QIcon getIconForTheme(); + +#endif // UTILS_H diff --git a/src/headsetcontrol-qt.py b/src/headsetcontrol-qt.py deleted file mode 100644 index 4eee2c0..0000000 --- a/src/headsetcontrol-qt.py +++ /dev/null @@ -1,410 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import subprocess -import json -import os -import winreg -from PyQt6.QtWidgets import QApplication, QMainWindow, QSystemTrayIcon, QMenu -from PyQt6.QtGui import QIcon, QAction -from PyQt6.QtCore import QTimer, QTranslator, QLocale -from ui_mainwindow import Ui_HeadsetControlQt - -if sys.platform == "win32": - import winshell - - SETTINGS_DIR = os.path.join(os.getenv("APPDATA"), "HeadsetControl-Qt") - HEADSETCONTROL_EXECUTABLE = os.path.join("dependencies", "headsetcontrol.exe") - STARTUP_FOLDER = winshell.startup() -else: - SETTINGS_DIR = os.path.join(os.path.expanduser("~"), ".config", "HeadsetControl-Qt") - HEADSETCONTROL_EXECUTABLE = "headsetcontrol" - DESKTOP_FILE_PATH = os.path.join(os.path.expanduser("~"), ".config", "autostart", "headsetcontrol-qt.desktop") - -ICONS_DIR = os.path.join("icons") -APP_ICON = os.path.join(ICONS_DIR, "icon.png") -SETTINGS_FILE = os.path.join(SETTINGS_DIR, "settings.json") - - -def is_dark_mode_enabled(): - registry_key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" - ) - value, regtype = winreg.QueryValueEx(registry_key, "AppsUseLightTheme") - winreg.CloseKey(registry_key) - return value == 0 - - -class HeadsetControlApp(QMainWindow): - def __init__(self): - super().__init__() - self.timer = None - self.tray_icon = None - self.ui = Ui_HeadsetControlQt() - self.ui.setupUi(self) - self.setWindowTitle("HeadsetControl-Qt") - self.setWindowIcon(QIcon(APP_ICON)) - self.setFixedSize(self.size()) - self.led_state = None - self.notification_sent = False - self.init_ui() - self.create_tray_icon() - self.load_settings() - self.update_headset_info() - self.init_timer() - self.check_startup_checkbox() - self.set_sidetone() - self.on_ledBox_state_changed() - - def init_ui(self): - self.ui.ledBox.stateChanged.connect(self.on_ledBox_state_changed) - self.ui.lightBatterySpinbox.valueChanged.connect(self.save_settings) - self.ui.notificationBatterySpinbox.valueChanged.connect(self.save_settings) - self.ui.startupCheckbox.stateChanged.connect(self.on_startup_checkbox_state_changed) - self.ui.sidetoneSlider.sliderReleased.connect(self.set_sidetone) - self.ui.themeComboBox.addItem(self.tr("System")) - self.ui.themeComboBox.addItem(self.tr("Light")) - self.ui.themeComboBox.addItem(self.tr("Dark")) - self.ui.themeComboBox.currentIndexChanged.connect(self.on_themeComboBox_index_changed) - - def create_tray_icon(self): - self.tray_icon = QSystemTrayIcon(self) - self.tray_icon.setIcon(QIcon(APP_ICON)) - tray_menu = QMenu(self) - show_action = QAction("Show", self) - show_action.triggered.connect(self.toggle_window) - tray_menu.addAction(show_action) - exit_action = QAction("Exit", self) - exit_action.triggered.connect(self.exit_app) - tray_menu.addAction(exit_action) - self.tray_icon.setContextMenu(tray_menu) - self.tray_icon.show() - self.tray_icon.activated.connect(self.tray_icon_activated) - - def tray_icon_activated(self, reason): - if reason == QSystemTrayIcon.ActivationReason.Trigger: - self.toggle_window() - - def toggle_window(self): - if self.isVisible(): - self.hide() - self.tray_icon.contextMenu().actions()[0].setText("Show") - else: - self.show() - self.tray_icon.contextMenu().actions()[0].setText("Hide") - - def init_timer(self): - self.timer = QTimer(self) - self.timer.timeout.connect(self.update_headset_info) - self.timer.start(10000) - - def load_settings(self): - if not os.path.exists(SETTINGS_FILE): - os.makedirs(SETTINGS_DIR, exist_ok=True) - self.create_default_settings() - with open(SETTINGS_FILE, "r") as f: - settings = json.load(f) - self.led_state = settings.get("led_state", True) - self.ui.ledBox.setChecked(self.led_state) - self.ui.lightBatterySpinbox.setEnabled(self.led_state) - self.ui.lightBatteryLabel.setEnabled(self.led_state) - self.ui.lightBatterySpinbox.setValue(settings.get("light_battery_threshold", 20)) - self.ui.notificationBatterySpinbox.setValue(settings.get("notification_battery_threshold", 20)) - self.ui.sidetoneSlider.setValue(settings.get("sidetone", 0)) - self.ui.themeComboBox.setCurrentIndex(settings.get("theme", 0)) - - def save_settings(self): - settings = { - "led_state": self.ui.ledBox.isChecked(), - "light_battery_threshold": self.ui.lightBatterySpinbox.value(), - "notification_battery_threshold": self.ui.notificationBatterySpinbox.value(), - "sidetone": self.ui.sidetoneSlider.value(), - "theme": self.ui.themeComboBox.currentIndex(), - } - with open(SETTINGS_FILE, "w") as f: - json.dump(settings, f, indent=4) - - def create_default_settings(self): - settings = { - "led_state": True, - "light_battery_threshold": 20, - "notification_battery_threshold": 20, - "sidetone": 0, - "theme": 0, - } - with open(SETTINGS_FILE, "w") as f: - json.dump(settings, f, indent=4) - - def update_headset_info(self): - command = [HEADSETCONTROL_EXECUTABLE, "-o", "json"] - creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 - result = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, creationflags=creation_flags - ) - stdout, stderr = result.communicate(timeout=10) - - if result.returncode == 0: - data = json.loads(stdout) - if "devices" in data and len(data["devices"]) > 0: - headset_info = data["devices"][0] - self.update_ui_with_headset_info(headset_info) - self.manage_led_based_on_battery(headset_info) - self.send_notification_based_on_battery(headset_info) - else: - self.no_device_found() - else: - print("Error running headsetcontrol:", stderr) - self.no_device_found() - - def manage_led_based_on_battery(self, headset_info): - if not self.ui.ledBox.isChecked(): - return - - self.ui.lightBatterySpinbox.setEnabled(True) - self.ui.lightBatteryLabel.setEnabled(True) - battery_info = headset_info.get("battery", {}) - battery_level = battery_info.get("level", 0) - battery_status = battery_info.get("status", "UNKNOWN") - available = battery_status == "BATTERY_AVAILABLE" - - if battery_level < self.ui.lightBatterySpinbox.value() and self.led_state and available: - self.toggle_led(False) - self.led_state = False - self.save_settings() - elif battery_level >= self.ui.lightBatterySpinbox.value() + 5 and not self.led_state and available: - self.toggle_led(True) - self.led_state = True - self.save_settings() - - def send_notification_based_on_battery(self, headset_info): - battery_info = headset_info.get("battery", {}) - headset_name = headset_info.get("device", "Unknown Device") - battery_level = battery_info.get("level", 0) - battery_status = battery_info.get("status", "UNKNOWN") - available = battery_status == "BATTERY_AVAILABLE" - - if battery_level < self.ui.notificationBatterySpinbox.value() and not self.notification_sent and available: - self.send_notification( - "Low battery", f"{headset_name} has {battery_level}% battery left.", QIcon("icons/icon.png"), 3000 - ) - self.notification_sent = True - elif battery_level >= self.ui.notificationBatterySpinbox.value() + 5 and self.notification_sent and available: - self.notification_sent = False - - def send_notification(self, title, message, icon, duration): - self.tray_icon.showMessage(title, message, icon, duration) - - def toggle_led(self, state): - command = [HEADSETCONTROL_EXECUTABLE, "-l", "1" if state else "0"] - creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 - subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, creationflags=creation_flags - ) - - def update_ui_with_headset_info(self, headset_info): - device_name = headset_info.get("device", "Unknown Device") - capabilities = headset_info.get("capabilities_str", []) - battery_info = headset_info.get("battery", {}) - - self.ui.deviceGroupBox.setTitle(f"{device_name}") - - battery_status = battery_info.get("status", "UNKNOWN") - if battery_status == "BATTERY_AVAILABLE": - battery_level = battery_info.get("level", 0) - self.ui.batteryBar.setValue(battery_level) - self.ui.batteryBar.setFormat(f"{battery_level}%") - self.tray_icon.setToolTip(f"Battery Level: {battery_level}%") - - icon_path = self.get_battery_icon(battery_level, charging=False) - elif battery_status == "BATTERY_CHARGING": - self.ui.batteryBar.setValue(0) - self.ui.batteryBar.setFormat("Charging") - self.tray_icon.setToolTip("Battery Charging") - - icon_path = self.get_battery_icon(battery_level=None, charging=True) - else: - self.ui.batteryBar.setValue(0) - self.ui.batteryBar.setFormat("Off") - self.tray_icon.setToolTip("Battery Unavailable") - - icon_path = self.get_battery_icon(battery_level=None, missing=True) - - if sys.platform == "win32": - self.tray_icon.setIcon(QIcon(icon_path)) - elif sys.platform == "linux": - self.tray_icon.setIcon(QIcon.fromTheme(icon_path)) - - if "lights" in capabilities: - self.ui.ledBox.setEnabled(True) - self.ui.ledLabel.setEnabled(True) - else: - self.ui.ledBox.setEnabled(False) - self.ui.ledLabel.setEnabled(False) - - if "sidetone" in capabilities: - self.ui.sidetoneSlider.setEnabled(True) - self.ui.sidetoneLabel.setEnabled(True) - else: - self.ui.sidetoneSlider.setEnabled(False) - self.ui.sidetoneLabel.setEnabled(False) - - self.toggle_ui_elements(True) - - def get_battery_icon(self, battery_level, charging=False, missing=False): - theme = None - if self.ui.themeComboBox.currentIndex() == 0: - if sys.platform == "win32": - dark_mode = is_dark_mode_enabled() - theme = "light" if dark_mode else "dark" - elif sys.platform == "linux": - if os.getenv("XDG_CURRENT_DESKTOP") == "KDE": - theme = "symbolic" - else: - # I cannot detect every desktop and settings, so assume user is using dark theme and use light icons - theme = "light" - elif self.ui.themeComboBox.currentIndex() == 1: - theme = "light" - elif self.ui.themeComboBox.currentIndex() == 2: - theme = "dark" - - if missing: - icon_name = f"battery-missing-{theme}" - elif charging: - icon_name = f"battery-100-charging-{theme}" - else: - if battery_level is not None: - battery_levels = { - 90: "100", - 80: "090", - 70: "080", - 60: "070", - 50: "060", - 40: "050", - 30: "040", - 20: "030", - 10: "020", - 0: "010", - } - icon_name = None - for level, percentage in battery_levels.items(): - if battery_level >= level: - icon_name = f"battery-{percentage}-{theme}" - break - else: - icon_name = f"battery-missing-{theme}" - - if sys.platform == "win32": - icon_name += ".png" - icon_path = os.path.join(ICONS_DIR, icon_name) - return icon_path - elif sys.platform == "linux": - icon_path = icon_name - return icon_path - - def no_device_found(self): - self.toggle_ui_elements(False) - self.tray_icon.setToolTip("No Device Found") - - def on_ledBox_state_changed(self): - lights = True if self.ui.ledBox.isChecked() else False - self.toggle_led(lights) - - self.ui.lightBatterySpinbox.setEnabled(True if self.ui.ledBox.isChecked() else False) - self.ui.lightBatteryLabel.setEnabled(True if self.ui.ledBox.isChecked() else False) - self.save_settings() - - def on_themeComboBox_index_changed(self): - self.update_headset_info() - self.save_settings() - - def set_sidetone(self): - sidetone_value = self.ui.sidetoneSlider.value() - command = [HEADSETCONTROL_EXECUTABLE, "-s", str(sidetone_value)] - creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 - subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, creationflags=creation_flags - ) - self.save_settings() - - def toggle_ui_elements(self, show): - self.ui.deviceGroupBox.setVisible(show) - self.ui.generalGroupBox.setVisible(show) - self.ui.notFoundLabel.setVisible(not show) - self.setMinimumSize(0, 0) - self.adjustSize() - self.setFixedSize(self.size()) - - def show_window(self): - self.show() - - def exit_app(self): - self.tray_icon.hide() - QApplication.quit() - - def closeEvent(self, event): - event.ignore() - self.hide() - - def on_startup_checkbox_state_changed(self): - checked = self.ui.startupCheckbox.isChecked() - - if sys.platform == "win32": - shortcut_path = os.path.join(winshell.startup(), "HeadsetControl-Qt.lnk") - target_path = sys.executable - working_directory = os.path.dirname(target_path) - - if checked: - winshell.CreateShortcut( - Path=shortcut_path, - Target=target_path, - Icon=(target_path, 0), - Description="Launch HeadsetControl-Qt", - StartIn=working_directory, - ) - else: - if os.path.exists(shortcut_path): - os.remove(shortcut_path) - - elif sys.platform == "linux": - if checked: - if not os.path.exists(os.path.dirname(DESKTOP_FILE_PATH)): - os.makedirs(os.path.dirname(DESKTOP_FILE_PATH)) - - script_folder = os.path.dirname(__file__) - desktop_entry_content = ( - "[Desktop Entry]\n" - f"Path={script_folder}\n" - "Type=Application\n" - f"Exec={sys.executable} {__file__}\n" - "Name=HeadsetControl-Qt\n" - ) - with open(DESKTOP_FILE_PATH, "w") as f: - f.write(desktop_entry_content) - else: - if os.path.exists(DESKTOP_FILE_PATH): - os.remove(DESKTOP_FILE_PATH) - - def check_startup_checkbox(self): - if sys.platform == "win32": - shortcut_path = os.path.join(winshell.startup(), "HeadsetControl-Qt.lnk") - self.ui.startupCheckbox.setChecked(os.path.exists(shortcut_path)) - elif sys.platform == "linux": - self.ui.startupCheckbox.setChecked(os.path.exists(DESKTOP_FILE_PATH)) - - -if __name__ == "__main__": - app = QApplication(sys.argv) - translator = QTranslator() - locale_name = QLocale.system().name() - locale = locale_name[:2] - if locale: - file_name = f"tr/headsetcontrol-qt_{locale}.qm" - else: - file_name = None - - if file_name and translator.load(file_name): - app.installTranslator(translator) - - app.setStyle("fusion") - window = HeadsetControlApp() - sys.exit(app.exec()) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cc10573 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,11 @@ +#include "headsetcontrolqt.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + a.setStyle("fusion"); + HeadsetControlQt w; + return a.exec(); +} diff --git a/src/setup.py b/src/setup.py deleted file mode 100644 index 92bf030..0000000 --- a/src/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -from cx_Freeze import setup, Executable - -src_dir = os.path.dirname(os.path.abspath(__file__)) -build_dir = "build/HeadsetControl-Qt" -install_dir = os.path.join(os.getenv("LOCALAPPDATA"), "programs", "HeadsetControl-Qt") - -include_files = [os.path.join(src_dir, "dependencies/"), os.path.join(src_dir, "icons"), os.path.join(src_dir, "tr/")] - -zip_include_packages = ["PyQt6"] - -build_exe_options = { - "build_exe": build_dir, - "include_files": include_files, - "zip_include_packages": zip_include_packages, - "excludes": ["tkinter"], - "silent": True, -} - -executables = [ - Executable( - script=os.path.join(src_dir, "headsetcontrol-qt.py"), - base="Win32GUI", - icon=os.path.join(src_dir, "icons/icon.ico"), - target_name="HeadsetControl-Qt", - ) -] - -setup( - name="HeadsetControl-Qt", - version="1.0", - options={"build_exe": build_exe_options}, - executables=executables, -) diff --git a/src/tr/headsetcontrol-qt_de.qm b/src/tr/headsetcontrol-qt_de.qm deleted file mode 100644 index be651ee..0000000 --- a/src/tr/headsetcontrol-qt_de.qm +++ /dev/null @@ -1 +0,0 @@ -<¸dÊÍ!¿`¡½Ý \ No newline at end of file diff --git a/src/tr/headsetcontrol-qt_de.ts b/src/tr/headsetcontrol-qt_de.ts deleted file mode 100644 index eb0cccf..0000000 --- a/src/tr/headsetcontrol-qt_de.ts +++ /dev/null @@ -1,80 +0,0 @@ - - - - - HeadsetControlApp - - - System - - - - - Light - - - - - Dark - - - - - HeadsetControlQt - - - HeadsetControl-Qt - - - - - GroupBox - - - - - Battery - - - - - Sidetone - - - - - Lights - - - - - Disable lights when battery goes below: - - - - - Send notification when battery goes below - - - - - Settings - - - - - Icon theme - - - - - Run at startup - - - - - No supported headset found. - - - - diff --git a/src/tr/headsetcontrol-qt_en.qm b/src/tr/headsetcontrol-qt_en.qm deleted file mode 100644 index fb6e67e..0000000 Binary files a/src/tr/headsetcontrol-qt_en.qm and /dev/null differ diff --git a/src/tr/headsetcontrol-qt_en.ts b/src/tr/headsetcontrol-qt_en.ts deleted file mode 100644 index a4742c4..0000000 --- a/src/tr/headsetcontrol-qt_en.ts +++ /dev/null @@ -1,80 +0,0 @@ - - - - - HeadsetControlApp - - - System - - - - - Light - - - - - Dark - - - - - HeadsetControlQt - - - HeadsetControl-Qt - - - - - GroupBox - - - - - Battery - - - - - Sidetone - - - - - Lights - - - - - Disable lights when battery goes below: - - - - - Send notification when battery goes below - - - - - Settings - - - - - Icon theme - - - - - Run at startup - - - - - No supported headset found. - - - - diff --git a/src/tr/headsetcontrol-qt_es.qm b/src/tr/headsetcontrol-qt_es.qm deleted file mode 100644 index be651ee..0000000 --- a/src/tr/headsetcontrol-qt_es.qm +++ /dev/null @@ -1 +0,0 @@ -<¸dÊÍ!¿`¡½Ý \ No newline at end of file diff --git a/src/tr/headsetcontrol-qt_es.ts b/src/tr/headsetcontrol-qt_es.ts deleted file mode 100644 index eb0cccf..0000000 --- a/src/tr/headsetcontrol-qt_es.ts +++ /dev/null @@ -1,80 +0,0 @@ - - - - - HeadsetControlApp - - - System - - - - - Light - - - - - Dark - - - - - HeadsetControlQt - - - HeadsetControl-Qt - - - - - GroupBox - - - - - Battery - - - - - Sidetone - - - - - Lights - - - - - Disable lights when battery goes below: - - - - - Send notification when battery goes below - - - - - Settings - - - - - Icon theme - - - - - Run at startup - - - - - No supported headset found. - - - - diff --git a/src/tr/headsetcontrol-qt_fr.qm b/src/tr/headsetcontrol-qt_fr.qm deleted file mode 100644 index 387c50d..0000000 Binary files a/src/tr/headsetcontrol-qt_fr.qm and /dev/null differ diff --git a/src/tr/headsetcontrol-qt_fr.ts b/src/tr/headsetcontrol-qt_fr.ts deleted file mode 100644 index c171fd5..0000000 --- a/src/tr/headsetcontrol-qt_fr.ts +++ /dev/null @@ -1,84 +0,0 @@ - - - - - HeadsetControlApp - - - System - Système - - - - Light - Clair - - - - Dark - Sombre - - - - HeadsetControlQt - - - HeadsetControl-Qt - - - - - GroupBox - - - - - Battery - Batterie - - - - Sidetone - Retour voix - - - - Lights - LEDs - - - - Disable lights when battery goes below: - Désactiver les LEDs quand la batterie tombe sous: - - - - Send notification when battery goes below - Notifier quand la batterie tombe sous - - - - Settings - Paramètres - - - - Icon theme - Thème des icones - - - - Run at startup - Lancer au démarrage - - - - No supported headset found. - Aucun casque détécté. - - - General settings - Paramètres - - - diff --git a/src/ui_mainwindow.py b/src/ui_mainwindow.py deleted file mode 100644 index f26ef2f..0000000 --- a/src/ui_mainwindow.py +++ /dev/null @@ -1,154 +0,0 @@ -# Form implementation generated from reading ui file '.\designer\ui_mainwindow.ui' -# -# Created by: PyQt6 UI code generator 6.7.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_HeadsetControlQt(object): - def setupUi(self, HeadsetControlQt): - HeadsetControlQt.setObjectName("HeadsetControlQt") - HeadsetControlQt.resize(433, 351) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(HeadsetControlQt.sizePolicy().hasHeightForWidth()) - HeadsetControlQt.setSizePolicy(sizePolicy) - self.centralwidget = QtWidgets.QWidget(parent=HeadsetControlQt) - self.centralwidget.setObjectName("centralwidget") - self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) - self.gridLayout_2.setContentsMargins(9, 9, 9, 9) - self.gridLayout_2.setObjectName("gridLayout_2") - self.deviceGroupBox = QtWidgets.QGroupBox(parent=self.centralwidget) - font = QtGui.QFont() - font.setBold(True) - self.deviceGroupBox.setFont(font) - self.deviceGroupBox.setObjectName("deviceGroupBox") - self.gridLayout_4 = QtWidgets.QGridLayout(self.deviceGroupBox) - self.gridLayout_4.setObjectName("gridLayout_4") - self.gridLayout_7 = QtWidgets.QGridLayout() - self.gridLayout_7.setSpacing(9) - self.gridLayout_7.setObjectName("gridLayout_7") - self.batteryLabel = QtWidgets.QLabel(parent=self.deviceGroupBox) - self.batteryLabel.setMinimumSize(QtCore.QSize(0, 25)) - self.batteryLabel.setObjectName("batteryLabel") - self.gridLayout_7.addWidget(self.batteryLabel, 0, 0, 1, 1) - self.batteryBar = QtWidgets.QProgressBar(parent=self.deviceGroupBox) - self.batteryBar.setMinimumSize(QtCore.QSize(0, 25)) - self.batteryBar.setProperty("value", 50) - self.batteryBar.setTextVisible(True) - self.batteryBar.setObjectName("batteryBar") - self.gridLayout_7.addWidget(self.batteryBar, 0, 1, 1, 1) - self.sidetoneLabel = QtWidgets.QLabel(parent=self.deviceGroupBox) - self.sidetoneLabel.setMinimumSize(QtCore.QSize(0, 25)) - self.sidetoneLabel.setObjectName("sidetoneLabel") - self.gridLayout_7.addWidget(self.sidetoneLabel, 1, 0, 1, 1) - self.sidetoneSlider = QtWidgets.QSlider(parent=self.deviceGroupBox) - self.sidetoneSlider.setMinimumSize(QtCore.QSize(0, 25)) - self.sidetoneSlider.setMaximum(128) - self.sidetoneSlider.setOrientation(QtCore.Qt.Orientation.Horizontal) - self.sidetoneSlider.setObjectName("sidetoneSlider") - self.gridLayout_7.addWidget(self.sidetoneSlider, 1, 1, 1, 1) - self.gridLayout_4.addLayout(self.gridLayout_7, 0, 0, 1, 3) - self.ledLabel = QtWidgets.QLabel(parent=self.deviceGroupBox) - self.ledLabel.setMinimumSize(QtCore.QSize(0, 25)) - self.ledLabel.setObjectName("ledLabel") - self.gridLayout_4.addWidget(self.ledLabel, 1, 0, 1, 2) - self.ledBox = QtWidgets.QCheckBox(parent=self.deviceGroupBox) - self.ledBox.setMinimumSize(QtCore.QSize(0, 25)) - self.ledBox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) - self.ledBox.setText("") - self.ledBox.setChecked(True) - self.ledBox.setTristate(False) - self.ledBox.setObjectName("ledBox") - self.gridLayout_4.addWidget(self.ledBox, 1, 2, 1, 1) - self.lightBatteryLabel = QtWidgets.QLabel(parent=self.deviceGroupBox) - self.lightBatteryLabel.setMinimumSize(QtCore.QSize(0, 25)) - self.lightBatteryLabel.setObjectName("lightBatteryLabel") - self.gridLayout_4.addWidget(self.lightBatteryLabel, 2, 0, 1, 1) - spacerItem = QtWidgets.QSpacerItem(0, 25, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_4.addItem(spacerItem, 2, 1, 1, 1) - self.lightBatterySpinbox = QtWidgets.QSpinBox(parent=self.deviceGroupBox) - self.lightBatterySpinbox.setMinimumSize(QtCore.QSize(100, 25)) - self.lightBatterySpinbox.setFrame(True) - self.lightBatterySpinbox.setMinimum(0) - self.lightBatterySpinbox.setMaximum(100) - self.lightBatterySpinbox.setProperty("value", 20) - self.lightBatterySpinbox.setObjectName("lightBatterySpinbox") - self.gridLayout_4.addWidget(self.lightBatterySpinbox, 2, 2, 1, 1) - self.notification_label = QtWidgets.QLabel(parent=self.deviceGroupBox) - self.notification_label.setMinimumSize(QtCore.QSize(0, 25)) - self.notification_label.setObjectName("notification_label") - self.gridLayout_4.addWidget(self.notification_label, 3, 0, 1, 1) - self.notificationBatterySpinbox = QtWidgets.QSpinBox(parent=self.deviceGroupBox) - self.notificationBatterySpinbox.setMinimumSize(QtCore.QSize(100, 25)) - self.notificationBatterySpinbox.setFrame(True) - self.notificationBatterySpinbox.setMinimum(0) - self.notificationBatterySpinbox.setProperty("value", 20) - self.notificationBatterySpinbox.setObjectName("notificationBatterySpinbox") - self.gridLayout_4.addWidget(self.notificationBatterySpinbox, 3, 2, 1, 1) - self.gridLayout_2.addWidget(self.deviceGroupBox, 0, 0, 1, 1) - self.generalGroupBox = QtWidgets.QGroupBox(parent=self.centralwidget) - self.generalGroupBox.setMinimumSize(QtCore.QSize(0, 0)) - font = QtGui.QFont() - font.setBold(True) - self.generalGroupBox.setFont(font) - self.generalGroupBox.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight) - self.generalGroupBox.setAutoFillBackground(False) - self.generalGroupBox.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) - self.generalGroupBox.setObjectName("generalGroupBox") - self.gridLayout_3 = QtWidgets.QGridLayout(self.generalGroupBox) - self.gridLayout_3.setObjectName("gridLayout_3") - self.label = QtWidgets.QLabel(parent=self.generalGroupBox) - self.label.setMinimumSize(QtCore.QSize(0, 25)) - self.label.setObjectName("label") - self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1) - spacerItem1 = QtWidgets.QSpacerItem(0, 25, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_3.addItem(spacerItem1, 0, 1, 1, 1) - self.themeComboBox = QtWidgets.QComboBox(parent=self.generalGroupBox) - self.themeComboBox.setMinimumSize(QtCore.QSize(100, 25)) - self.themeComboBox.setObjectName("themeComboBox") - self.gridLayout_3.addWidget(self.themeComboBox, 0, 2, 1, 1) - self.startupLabel = QtWidgets.QLabel(parent=self.generalGroupBox) - self.startupLabel.setMinimumSize(QtCore.QSize(0, 25)) - self.startupLabel.setObjectName("startupLabel") - self.gridLayout_3.addWidget(self.startupLabel, 1, 0, 1, 1) - self.startupCheckbox = QtWidgets.QCheckBox(parent=self.generalGroupBox) - self.startupCheckbox.setMinimumSize(QtCore.QSize(0, 25)) - self.startupCheckbox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) - self.startupCheckbox.setText("") - self.startupCheckbox.setObjectName("startupCheckbox") - self.gridLayout_3.addWidget(self.startupCheckbox, 1, 2, 1, 1) - self.gridLayout_2.addWidget(self.generalGroupBox, 1, 0, 1, 1) - self.notFoundLabel = QtWidgets.QLabel(parent=self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.notFoundLabel.sizePolicy().hasHeightForWidth()) - self.notFoundLabel.setSizePolicy(sizePolicy) - self.notFoundLabel.setMinimumSize(QtCore.QSize(0, 25)) - self.notFoundLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) - self.notFoundLabel.setObjectName("notFoundLabel") - self.gridLayout_2.addWidget(self.notFoundLabel, 2, 0, 1, 1) - HeadsetControlQt.setCentralWidget(self.centralwidget) - - self.retranslateUi(HeadsetControlQt) - QtCore.QMetaObject.connectSlotsByName(HeadsetControlQt) - - def retranslateUi(self, HeadsetControlQt): - _translate = QtCore.QCoreApplication.translate - HeadsetControlQt.setWindowTitle(_translate("HeadsetControlQt", "HeadsetControl-Qt")) - self.deviceGroupBox.setTitle(_translate("HeadsetControlQt", "GroupBox")) - self.batteryLabel.setText(_translate("HeadsetControlQt", "Battery")) - self.sidetoneLabel.setText(_translate("HeadsetControlQt", "Sidetone")) - self.ledLabel.setText(_translate("HeadsetControlQt", "Lights")) - self.lightBatteryLabel.setText(_translate("HeadsetControlQt", "Disable lights when battery goes below:")) - self.notification_label.setText(_translate("HeadsetControlQt", "Send notification when battery goes below")) - self.generalGroupBox.setTitle(_translate("HeadsetControlQt", "Settings")) - self.label.setText(_translate("HeadsetControlQt", "Icon theme")) - self.startupLabel.setText(_translate("HeadsetControlQt", "Run at startup")) - self.notFoundLabel.setText(_translate("HeadsetControlQt", "No supported headset found.")) diff --git a/tr_script.py b/tr_script.py deleted file mode 100644 index c600392..0000000 --- a/tr_script.py +++ /dev/null @@ -1,63 +0,0 @@ -import argparse -import subprocess - - -PROJECT = "headsetcontrol-qt" - - -def run_pylupdate(): - try: - subprocess.run( - [ - "pylupdate6.exe", - f"./src/{PROJECT}.py", - "./src/designer/ui_mainwindow.ui", - "-ts", - f"./src/tr/{PROJECT}_fr.ts", - "-ts", - f"./src/tr/{PROJECT}_de.ts", - "-ts", - f"./src/tr/{PROJECT}_es.ts", - "-ts", - f"./src/tr/{PROJECT}_en.ts", - ], - check=True, - ) - print("pylupdate6 executed successfully.") - except subprocess.CalledProcessError as e: - print(f"Error running pylupdate6: {e}") - - -def run_lrelease(): - try: - subprocess.run( - [ - "lrelease.exe", - f"./src/tr/{PROJECT}_de.ts", - f"./src/tr/{PROJECT}_en.ts", - f"./src/tr/{PROJECT}_es.ts", - f"./src/tr/{PROJECT}_fr.ts", - ], - check=True, - ) - print("lrelease executed successfully.") - except subprocess.CalledProcessError as e: - print(f"Error running lrelease: {e}") - - -def main(): - parser = argparse.ArgumentParser(description="Run pylupdate6 or lrelease.") - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--generate", "-g", action="store_true", help="Run pylupdate6") - group.add_argument("--compile", "-c", action="store_true", help="Run lrelease") - - args = parser.parse_args() - - if args.generate: - run_pylupdate() - elif args.compile: - run_lrelease() - - -if __name__ == "__main__": - main()