├── src ├── Resources │ ├── appicon.rc │ ├── headphones-exe.ico │ ├── icons │ │ ├── dark │ │ │ ├── png │ │ │ │ ├── battery-low.png │ │ │ │ ├── headphones.png │ │ │ │ ├── battery-error.png │ │ │ │ ├── battery-medium.png │ │ │ │ ├── battery-charging.png │ │ │ │ ├── battery-level-full.png │ │ │ │ └── battery-fully-charged.png │ │ │ └── index.theme │ │ └── light │ │ │ ├── png │ │ │ ├── battery-low.png │ │ │ ├── headphones.png │ │ │ ├── battery-error.png │ │ │ ├── battery-charging.png │ │ │ ├── battery-medium.png │ │ │ ├── battery-level-full.png │ │ │ └── battery-fully-charged.png │ │ │ └── index.theme │ ├── icons.qrc │ └── tr │ │ ├── HeadsetControl_GUI_en.ts │ │ └── HeadsetControl_GUI_it.ts ├── Utils │ ├── utils.h │ ├── headsetcontrolapi.h │ ├── utils.cpp │ └── headsetcontrolapi.cpp ├── UI │ ├── dialoginfo.h │ ├── dialoginfo.cpp │ ├── loaddevicewindow.h │ ├── loaddevicewindow.cpp │ ├── settingswindow.h │ ├── loaddevicewindow.ui │ ├── mainwindow.h │ ├── dialoginfo.ui │ ├── settingswindow.cpp │ ├── settingswindow.ui │ └── mainwindow.cpp ├── main.cpp └── DataTypes │ ├── settings.h │ ├── device.h │ ├── settings.cpp │ └── device.cpp ├── data └── linux │ ├── icons │ └── hicolor │ │ └── 256x256 │ │ └── apps │ │ └── HeadsetControl-GUI.png │ └── applications │ └── HeadsetControl-GUI.desktop ├── .gitignore ├── .github └── workflows │ ├── build.yaml │ └── build-and-release.yml ├── HeadsetControl-GUI.pro ├── update-winget-version.ps1 ├── README.md └── LICENSE /src/Resources/appicon.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "headphones-exe.ico" 2 | -------------------------------------------------------------------------------- /src/Resources/headphones-exe.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/headphones-exe.ico -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/dark/png/battery-low.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/headphones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/dark/png/headphones.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/light/png/battery-low.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/headphones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/light/png/headphones.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/dark/png/battery-error.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/dark/png/battery-medium.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/light/png/battery-error.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-charging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/dark/png/battery-charging.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-charging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/light/png/battery-charging.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/light/png/battery-medium.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-level-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/dark/png/battery-level-full.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-level-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/light/png/battery-level-full.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-fully-charged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/dark/png/battery-fully-charged.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-fully-charged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/src/Resources/icons/light/png/battery-fully-charged.png -------------------------------------------------------------------------------- /data/linux/icons/hicolor/256x256/apps/HeadsetControl-GUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/HEAD/data/linux/icons/hicolor/256x256/apps/HeadsetControl-GUI.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/index.theme: -------------------------------------------------------------------------------- 1 | [Icon Theme] 2 | Name=dark 3 | Comment=dark theme icons 4 | PanelDefault=22 5 | PanelSizes=22 6 | Directories=png 7 | 8 | [png] 9 | Size=512 10 | Context=Applications 11 | MinSize=16 12 | MaxSize=512 13 | Type=Fixed -------------------------------------------------------------------------------- /data/linux/applications/HeadsetControl-GUI.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=HeadsetControl-GUI 3 | Exec=HeadsetControl-GUI 4 | Icon=HeadsetControl-GUI 5 | Type=Application 6 | Categories=Application;System;Settings;XFCE;X-XFCE-SettingsDialog;X-XFCE-SystemSettings; 7 | -------------------------------------------------------------------------------- /src/Resources/icons/light/index.theme: -------------------------------------------------------------------------------- 1 | [Icon Theme] 2 | Name=light 3 | Comment=light theme icons 4 | PanelDefault=22 5 | PanelSizes=22 6 | Directories=png 7 | 8 | [png] 9 | Size=512 10 | Context=Applications 11 | MinSize=16 12 | MaxSize=512 13 | Type=Fixed -------------------------------------------------------------------------------- /src/Utils/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | 6 | QString getLatestGitHubReleaseVersion(const QString &owner, const QString &repo); 7 | 8 | bool fileExists(const QString &filepath); 9 | 10 | bool openFileExplorer(const QString &path); 11 | 12 | bool setOSRunOnStartup(bool enable); 13 | 14 | #endif // UTILS_H 15 | -------------------------------------------------------------------------------- /src/UI/dialoginfo.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGINFO_H 2 | #define DIALOGINFO_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class dialogInfo; 8 | } 9 | 10 | class DialogInfo : public QDialog 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit DialogInfo(QWidget *parent = nullptr); 16 | ~DialogInfo(); 17 | 18 | void setTitle(const QString &title); 19 | void setLabel(const QString &text); 20 | 21 | private: 22 | Ui::dialogInfo *ui; 23 | }; 24 | 25 | #endif // DIALOGINFO_H 26 | -------------------------------------------------------------------------------- /src/UI/dialoginfo.cpp: -------------------------------------------------------------------------------- 1 | #include "dialoginfo.h" 2 | #include "ui_dialoginfo.h" 3 | 4 | DialogInfo::DialogInfo(QWidget *parent) 5 | : QDialog(parent) 6 | , ui(new Ui::dialogInfo) 7 | { 8 | setModal(true); 9 | ui->setupUi(this); 10 | } 11 | 12 | DialogInfo::~DialogInfo() 13 | { 14 | delete ui; 15 | } 16 | 17 | void DialogInfo::setTitle(const QString &title) 18 | { 19 | setWindowTitle(title); 20 | } 21 | 22 | void DialogInfo::setLabel(const QString &text) 23 | { 24 | ui->label->setText(text); 25 | } 26 | -------------------------------------------------------------------------------- /src/UI/loaddevicewindow.h: -------------------------------------------------------------------------------- 1 | #ifndef LOADDEVICEWINDOW_H 2 | #define LOADDEVICEWINDOW_H 3 | 4 | #include "device.h" 5 | 6 | #include 7 | 8 | namespace Ui { 9 | class loaddevicewindow; 10 | } 11 | 12 | class LoaddeviceWindow : public QDialog 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit LoaddeviceWindow(const QList &devices, QWidget *parent = nullptr); 18 | ~LoaddeviceWindow(); 19 | 20 | int getDeviceIndex(); 21 | 22 | private: 23 | Ui::loaddevicewindow *ui; 24 | }; 25 | 26 | #endif // LOADDEVICEWINDOW_H 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | #Translations 35 | *.qm 36 | 37 | # Folders 38 | build/* 39 | manifests/* 40 | 41 | # User files 42 | HeadsetControl-GUI.pro.user 43 | 44 | .vscode -------------------------------------------------------------------------------- /src/UI/loaddevicewindow.cpp: -------------------------------------------------------------------------------- 1 | #include "loaddevicewindow.h" 2 | #include "ui_loaddevicewindow.h" 3 | 4 | LoaddeviceWindow::LoaddeviceWindow(const QList &devices, QWidget *parent) 5 | : QDialog(parent) 6 | , ui(new Ui::loaddevicewindow) 7 | { 8 | setModal(true); 9 | ui->setupUi(this); 10 | 11 | for (Device *device : devices) { 12 | ui->devicelistComboBox->addItem(device->device); 13 | } 14 | } 15 | 16 | int LoaddeviceWindow::getDeviceIndex() 17 | { 18 | return ui->devicelistComboBox->currentIndex(); 19 | } 20 | 21 | LoaddeviceWindow::~LoaddeviceWindow() 22 | { 23 | delete ui; 24 | } 25 | -------------------------------------------------------------------------------- /src/UI/settingswindow.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGSWINDOW_H 2 | #define SETTINGSWINDOW_H 3 | 4 | #include "settings.h" 5 | 6 | #include 7 | 8 | namespace Ui { 9 | class settingswindow; 10 | } 11 | 12 | class SettingsWindow : public QDialog 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit SettingsWindow(const Settings &programSettings, QWidget *parent = nullptr); 18 | ~SettingsWindow(); 19 | 20 | Settings getSettings(); 21 | 22 | private: 23 | Ui::settingswindow *ui; 24 | 25 | void setRunOnStartup(); 26 | void loadStyles(); 27 | void saveStyle(); 28 | void removeStyle(); 29 | void setCommandExe(); 30 | void clearCommandExe(); 31 | }; 32 | 33 | #endif // SETTINGSWINDOW_H 34 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | #include 5 | 6 | const QString APP_NAME = "HeadsetControl-GUI"; 7 | const QString GUI_VERSION = "0.20.1"; 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | QApplication app(argc, argv); 12 | app.setApplicationName(APP_NAME); 13 | app.setApplicationVersion(GUI_VERSION); 14 | QLocale locale = QLocale::system(); 15 | QString languageCode = locale.name(); 16 | QTranslator translator; 17 | if (translator.load(":/translations/tr/HeadsetControl_GUI_" + languageCode + ".qm")) { 18 | app.installTranslator(&translator); 19 | } 20 | bool tray = false; 21 | for (int i=0; i 2 | 3 | icons/dark/png/battery-charging.png 4 | icons/dark/png/battery-fully-charged.png 5 | icons/dark/png/battery-level-full.png 6 | icons/dark/png/battery-low.png 7 | icons/dark/png/battery-medium.png 8 | icons/dark/png/battery-error.png 9 | icons/dark/png/headphones.png 10 | icons/dark/index.theme 11 | icons/light/index.theme 12 | icons/light/png/battery-charging.png 13 | icons/light/png/battery-fully-charged.png 14 | icons/light/png/battery-level-full.png 15 | icons/light/png/battery-low.png 16 | icons/light/png/battery-medium.png 17 | icons/light/png/battery-error.png 18 | icons/light/png/headphones.png 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | # push: 5 | # branches: [main] 6 | # paths: 7 | # - 'src/**' 8 | # - 'HeadsetControl-GUI.pro' 9 | pull_request: 10 | branches: [main] 11 | 12 | jobs: 13 | build-windows: 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Install Qt 20 | uses: jurplel/install-qt-action@v4 21 | with: 22 | version: "6.7.2" 23 | add-tools-to-path: true 24 | cache: true 25 | 26 | - name: Setup MSVC 27 | uses: ilammy/msvc-dev-cmd@v1 28 | 29 | - name: Build 30 | run: | 31 | mkdir build 32 | cd build 33 | qmake ../ 34 | nmake 35 | 36 | build-linux: 37 | runs-on: ubuntu-latest 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | 42 | - name: Install Qt 43 | uses: jurplel/install-qt-action@v4 44 | with: 45 | version: "6.7.2" 46 | host: "linux" 47 | add-tools-to-path: true 48 | cache: true 49 | 50 | - name: Install dependencies 51 | run: | 52 | sudo apt-get update 53 | sudo apt-get install -y build-essential libgl1-mesa-dev 54 | 55 | - name: Build with qmake 56 | run: | 57 | mkdir build 58 | cd build 59 | qmake ../HeadsetControl-GUI.pro CONFIG+=release 60 | make -j$(nproc) 61 | -------------------------------------------------------------------------------- /HeadsetControl-GUI.pro: -------------------------------------------------------------------------------- 1 | QT += core gui network 2 | greaterThan(QT_MAJOR_VERSION, 5): QT += widgets 3 | 4 | CONFIG += c++17 5 | 6 | INCLUDEPATH += \ 7 | src/DataTypes \ 8 | src/UI \ 9 | src/Utils 10 | 11 | SOURCES += \ 12 | src/UI/settingswindow.cpp \ 13 | src/Utils/headsetcontrolapi.cpp \ 14 | src/main.cpp \ 15 | src/DataTypes/device.cpp \ 16 | src/DataTypes/settings.cpp \ 17 | src/UI/dialoginfo.cpp \ 18 | src/UI/loaddevicewindow.cpp \ 19 | src/UI/mainwindow.cpp \ 20 | src/Utils/utils.cpp 21 | 22 | HEADERS += \ 23 | src/DataTypes/device.h \ 24 | src/DataTypes/settings.h \ 25 | src/UI/dialoginfo.h \ 26 | src/UI/loaddevicewindow.h \ 27 | src/UI/mainwindow.h \ 28 | src/UI/settingswindow.h \ 29 | src/Utils/headsetcontrolapi.h \ 30 | src/Utils/utils.h 31 | 32 | FORMS += \ 33 | src/UI/dialoginfo.ui \ 34 | src/UI/loaddevicewindow.ui \ 35 | src/UI/mainwindow.ui \ 36 | src/UI/settingswindow.ui 37 | 38 | TRANSLATIONS += \ 39 | src/Resources/tr/HeadsetControl_GUI_en.ts \ 40 | src/Resources/tr/HeadsetControl_GUI_it.ts 41 | 42 | RESOURCES += \ 43 | src/Resources/icons.qrc 44 | 45 | RC_FILE = src/Resources/appicon.rc 46 | 47 | DISTFILES += \ 48 | .gitignore 49 | 50 | CONFIG += lrelease 51 | QM_FILES_RESOURCE_PREFIX=/translations/tr 52 | CONFIG += embed_translations 53 | 54 | # Default rules for deployment. 55 | qnx: target.path = /tmp/$${TARGET}/bin 56 | else: unix:!android: target.path = /opt/$${TARGET}/bin 57 | !isEmpty(target.path): INSTALLS += target 58 | -------------------------------------------------------------------------------- /src/DataTypes/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef QT_DEBUG 11 | const QString PROGRAM_CONFIG_PATH = "./DEBUG-Config"; 12 | #else 13 | const QString PROGRAM_CONFIG_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) 14 | + QDir::separator() + "HeadsetControl-GUI"; 15 | #endif 16 | const QString PROGRAM_STYLES_PATH = PROGRAM_CONFIG_PATH + QDir::separator() + "styles"; 17 | const QString PROGRAM_SETTINGS_FILEPATH = PROGRAM_CONFIG_PATH + QDir::separator() + "settings.json"; 18 | const QString DEVICES_SETTINGS_FILEPATH = PROGRAM_CONFIG_PATH + QDir::separator() + "devices.json"; 19 | 20 | class Settings 21 | { 22 | public: 23 | Settings(); 24 | 25 | bool runOnstartup = false; 26 | 27 | bool notificationHeadsetDisconnected = true; 28 | bool notificationBatteryFull = true; 29 | bool notificationBatteryLow = true; 30 | int batteryLowThreshold = 15; 31 | bool audioNotification = true; 32 | 33 | int msecUpdateIntervalTime = 30000; 34 | 35 | QString styleName = "Default"; 36 | 37 | QString commandExe = ""; 38 | QString commandArgs = "{chatmix}"; 39 | int msecCommandIntervalTime = 1000; 40 | 41 | QString lastSelectedVendorID = "", lastSelectedProductID = ""; 42 | }; 43 | 44 | Settings loadSettingsFromFile(const QString &filePath); 45 | void saveSettingstoFile(const Settings &settings, const QString &filePath); 46 | 47 | #endif // SETTINGS_H 48 | -------------------------------------------------------------------------------- /update-winget-version.ps1: -------------------------------------------------------------------------------- 1 | # update-winget-version.ps1 2 | param( 3 | [Parameter(Mandatory=$true)] 4 | [string]$version 5 | ) 6 | 7 | # Function to get the latest release 8 | function Get-LatestGitHubRelease { 9 | param ( 10 | [string]$Owner, 11 | [string]$Repo 12 | ) 13 | 14 | $apiUrl = "https://api.github.com/repos/$Owner/$Repo/releases/latest" 15 | 16 | try { 17 | $response = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers @{ 18 | "Accept" = "application/vnd.github.v3+json" 19 | } 20 | 21 | return @{ 22 | Version = $response.tag_name 23 | Url = $response.assets[1].browser_download_url 24 | } 25 | } 26 | catch { 27 | Write-Error "Failed to fetch the latest release: $_" 28 | return $null 29 | } 30 | } 31 | 32 | # Check if the version matches the format *.*.* 33 | if ($version -notmatch '^\d+\.\d+\.\d+$') { 34 | Write-Error "Invalid version format. Please use the format x.y.z (e.g., 1.2.3)" 35 | exit 1 36 | } 37 | 38 | $owner = "LeoKlaus" 39 | $repo = "HeadsetControl-GUI" 40 | 41 | $latestRelease = Get-LatestGitHubRelease -Owner $owner -Repo $repo 42 | 43 | if (-not $latestRelease) { 44 | Write-Error "Failed to fetch the latest release information." 45 | exit 1 46 | } 47 | 48 | $latestVersion = $latestRelease.Version.TrimStart('v') # Remove 'v' prefix if present 49 | $downloadUrl = $latestRelease.Url 50 | 51 | if ($version -ne $latestVersion) { 52 | Write-Error "The vesrsion you gave as parameter($version) is diffferent from the latest released($latestVersion)" 53 | exit 1 54 | } 55 | 56 | # Set Manifest Path 57 | $manifestPath = ".\manifests\l\LeoKlaus\HeadsetControl-GUI\$version" 58 | 59 | # Get Latest Manifest 60 | wingetcreate update --urls $downloadUrl --version $version LeoKlaus.HeadsetControl-GUI 61 | # Validate New Manifest 62 | winget validate --manifest $manifestPath 63 | # Test Packet Installation 64 | winget install --manifest $manifestPath 65 | # Test Packet Uninstallation 66 | winget uninstall --manifest $manifestPath 67 | # Send PR with newer Manifest 68 | wingetcreate submit $manifestPath -------------------------------------------------------------------------------- /src/DataTypes/device.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICE_H 2 | #define DEVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Battery 9 | { 10 | public: 11 | Battery(); 12 | Battery(QString stat, int lev); 13 | QString status = "BATTERY_UNAVAILABLE"; 14 | int level = 0; 15 | }; 16 | 17 | class EqualizerPreset 18 | { 19 | public: 20 | QString name; 21 | QList values; 22 | }; 23 | 24 | class Equalizer 25 | { 26 | public: 27 | Equalizer(); 28 | Equalizer(int bands, int baseline, double step, int min, int max); 29 | 30 | int bands_number = 0; 31 | int band_baseline = 0; 32 | double band_step = 0; 33 | int band_min = 0; 34 | int band_max = 0; 35 | }; 36 | 37 | class Device 38 | { 39 | public: 40 | Device(); 41 | Device(const QJsonObject &jsonObj, QString jsonData); 42 | 43 | // Status 44 | QString status; 45 | 46 | // Basic info 47 | QString device; 48 | QString vendor; 49 | QString product; 50 | QString id_vendor; 51 | QString id_product; 52 | QSet capabilities; 53 | 54 | // Info to get from json and display 55 | Battery battery; 56 | int chatmix = 64; 57 | QList presets_list; 58 | Equalizer equalizer; 59 | bool notification_sound = false; 60 | 61 | // Info to set with gui and to save 62 | int lights = -1; 63 | int sidetone = -1; 64 | int previous_sidetone = -1; 65 | int voice_prompts = -1; 66 | int inactive_time = -1; 67 | int equalizer_preset = -1; 68 | QList equalizer_curve; 69 | int volume_limiter = -1; 70 | int rotate_to_mute = -1; 71 | int mic_mute_led_brightness = -1; 72 | int mic_volume = -1; 73 | int bt_when_powered_on = -1; 74 | int bt_call_volume = -1; 75 | 76 | bool operator!=(const Device &d) const; 77 | bool operator==(const Device &d) const; 78 | bool operator==(const Device *d) const; 79 | 80 | void copyConfig(Device* device); 81 | void updateConfig(const QList &list); 82 | 83 | void updateInfo(const Device *new_device); 84 | 85 | QJsonObject toJson() const; 86 | static Device *fromJson(const QJsonObject &json); 87 | }; 88 | 89 | void updateDeviceFromSource(QList &devicesToUpdate, const Device *sourceDevice); 90 | 91 | void serializeDevices(const QList &devices, const QString &filePath); 92 | QList deserializeDevices(const QString &filePath); 93 | 94 | void deleteDevices(QList deviceList); 95 | 96 | #endif // DEVICE_H 97 | -------------------------------------------------------------------------------- /src/Utils/headsetcontrolapi.h: -------------------------------------------------------------------------------- 1 | #ifndef HEADSETCONTROLAPI_H 2 | #define HEADSETCONTROLAPI_H 3 | 4 | #include "device.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class Action 11 | { 12 | public: 13 | bool success; 14 | QString capability; 15 | QString device; 16 | QString status; 17 | QString error_message; 18 | }; 19 | 20 | class HeadsetControlAPI : public QObject 21 | { 22 | Q_OBJECT 23 | public: 24 | HeadsetControlAPI(const QString& headsetcontrolDirectory = ""); 25 | 26 | bool areApiAvailable(); 27 | 28 | QString getName(); 29 | QString getVersion(); 30 | QString getApiVersion(); 31 | QString getHidApiVersion(); 32 | 33 | Device* getSelectedDevice(); 34 | 35 | void setSelectedDevice(const QString& vendorId, const QString& productId); 36 | void updateSelectedDevice(); 37 | 38 | QList getConnectedDevices(const QString& vendorId = "0", const QString& productId = "0"); 39 | 40 | private: 41 | QString headsetcontrolFilePath; 42 | QFile headsetcontrol; 43 | 44 | QString selectedVendorId; 45 | QString selectedProductId; 46 | Device *selectedDevice = nullptr; 47 | QString name; 48 | QString version; 49 | QString api_version; 50 | QString hidapi_version; 51 | 52 | QString sendCommand(const QStringList &args_list); 53 | Action sendAction(const QStringList &args_list); 54 | QJsonObject parseOutput(const QString &output); 55 | Device *getDevice(); 56 | 57 | public slots: 58 | void setSidetone(int level, bool emitSignal = true); 59 | void setLights(bool enabled, bool emitSignal = true); 60 | void setVoicePrompts(bool enabled, bool emitSignal = true); 61 | void setInactiveTime(int time, bool emitSignal = true); 62 | void playNotificationSound(int id, bool emitSignal = true); 63 | void setVolumeLimiter(bool enabled, bool emitSignal = true); 64 | void setEqualizer(QList equalizerValues, bool emitSignal = true); 65 | void setEqualizerPreset(int number, bool emitSignal = true); 66 | 67 | void setRotateToMute(bool enabled, bool emitSignal = true); 68 | void setMuteLedBrightness(int brightness, bool emitSignal = true); 69 | void setMicrophoneVolume(int volume, bool emitSignal = true); 70 | 71 | void setBluetoothWhenPoweredOn(bool enabled, bool emitSignal = true); 72 | void setBluetoothCallVolume(int option, bool emitSignal = true); 73 | 74 | void updateChatMix(); 75 | 76 | signals: 77 | void actionSuccesful(); 78 | }; 79 | 80 | #endif // HEADSETCONTROLAPI_H 81 | -------------------------------------------------------------------------------- /src/UI/loaddevicewindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | loaddevicewindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 184 10 | 114 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Select device to load 21 | 22 | 23 | 24 | 25 | 26 | QFrame::StyledPanel 27 | 28 | 29 | QFrame::Raised 30 | 31 | 32 | 33 | 34 | 35 | 36 | 0 37 | 0 38 | 39 | 40 | 41 | Select device: 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 99 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Qt::Horizontal 62 | 63 | 64 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | buttonBox 74 | accepted() 75 | loaddevicewindow 76 | accept() 77 | 78 | 79 | 248 80 | 254 81 | 82 | 83 | 157 84 | 274 85 | 86 | 87 | 88 | 89 | buttonBox 90 | rejected() 91 | loaddevicewindow 92 | reject() 93 | 94 | 95 | 316 96 | 260 97 | 98 | 99 | 286 100 | 274 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/UI/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include "device.h" 5 | #include "headsetcontrolapi.h" 6 | #include "settings.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | QT_BEGIN_NAMESPACE 19 | namespace Ui { 20 | class MainWindow; 21 | } 22 | QT_END_NAMESPACE 23 | 24 | class MainWindow : public QMainWindow 25 | { 26 | Q_OBJECT 27 | 28 | public: 29 | const QString PROGRAM_APP_DIRECTORY = qApp->applicationDirPath(); 30 | QString HEADSETCONTROL_DIRECTORY = ""; 31 | 32 | MainWindow(QWidget *parent = nullptr); 33 | ~MainWindow(); 34 | 35 | private: 36 | bool firstShow = true; 37 | bool firstRun = true; 38 | bool notified = true; 39 | 40 | QString defaultStyle; 41 | 42 | Ui::MainWindow *ui; 43 | QSystemTrayIcon *trayIcon; 44 | QString trayIconName = "headphones"; 45 | QMenu *trayMenu; 46 | QAction *ledOn; 47 | QAction *ledOff; 48 | QTimer *timerGUI; 49 | QTimer *timerCommand; 50 | 51 | void sendCommand(); 52 | 53 | Settings settings; 54 | 55 | int n_connected = 0, n_saved = 0; 56 | 57 | HeadsetControlAPI API; 58 | Device *selectedDevice = nullptr; 59 | 60 | QList equalizerSliders; 61 | bool equalizerLiveUpdate = false; 62 | 63 | void bindEvents(); 64 | 65 | //Tray Icon Section 66 | void changeTrayIconTo(QString iconName); 67 | void setupTrayIcon(); 68 | 69 | //Theme mode Section 70 | bool isAppDarkMode(); 71 | void updateIconsTheme(); 72 | void updateStyle(); 73 | 74 | void resetGUI(); 75 | 76 | //Window Position and Size Section 77 | void minimizeWindowSize(); 78 | void moveToBottomRight(); 79 | void rescaleAndMoveWindow(); 80 | void toggleWindow(); 81 | void showEvent(QShowEvent *event); 82 | 83 | //Utility 84 | void sendAppNotification(const QString &title, const QString &description, const QString &icon); 85 | 86 | //Devices Managing Section 87 | bool updateSelectedDevice(); 88 | void loadDevice(); 89 | void loadGUIValues(); 90 | QList getSavedDevices(); 91 | 92 | // Info Section Events 93 | void setBatteryStatus(); 94 | void setChatmixStatus(); 95 | 96 | //Equalizer Slidesrs Section 97 | void createEqualizerSliders(); 98 | void clearEqualizerSliders(); 99 | void setEqualizerSliders(double value); 100 | void setEqualizerSliders(QList values); 101 | 102 | private slots: 103 | void changeEvent(QEvent *e); 104 | 105 | //Tray Icon Section 106 | void trayIconActivated(QSystemTrayIcon::ActivationReason reason); 107 | 108 | //Devices Managing Section 109 | void saveDevicesSettings(); 110 | 111 | //Update GUI Section 112 | void updateGUI(); 113 | 114 | // Equalizer Section Events 115 | void equalizerPresetChanged(); 116 | void applyEqualizer(bool saveToFile = true); 117 | 118 | // Tool Bar Events 119 | void selectDevice(); 120 | void editProgramSetting(); 121 | void checkForUpdates(bool firstStart = false); 122 | void showAbout(); 123 | void showCredits(); 124 | }; 125 | #endif // MAINWINDOW_H 126 | -------------------------------------------------------------------------------- /src/DataTypes/settings.cpp: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | Settings::Settings() {} 8 | 9 | Settings loadSettingsFromFile(const QString &filePath) 10 | { 11 | Settings s; 12 | 13 | QFile file(filePath); 14 | 15 | if (file.open(QIODevice::ReadOnly)) { 16 | QByteArray saveData = file.readAll(); 17 | file.close(); 18 | 19 | QJsonDocument doc(QJsonDocument::fromJson(saveData)); 20 | QJsonObject json = doc.object(); 21 | 22 | if (json.contains("runOnStartup")) { 23 | s.runOnstartup = json["runOnStartup"].toBool(); 24 | } 25 | if (json.contains("notificationHeadsetDisconnected")) { 26 | s.batteryLowThreshold = json["notificationHeadsetDisconnected"].toInt(); 27 | } 28 | if (json.contains("notificationBatteryFull")) { 29 | s.batteryLowThreshold = json["notificationBatteryFull"].toInt(); 30 | } 31 | if (json.contains("notificationBatteryLow")) { 32 | s.batteryLowThreshold = json["notificationBatteryLow"].toInt(); 33 | } 34 | if (json.contains("audioNotification")) { 35 | s.batteryLowThreshold = json["audioNotification"].toInt(); 36 | } 37 | if (json.contains("batteryLowThreshold")) { 38 | s.batteryLowThreshold = json["batteryLowThreshold"].toInt(); 39 | } 40 | if (json.contains("msecUpdateIntervalTime")) { 41 | s.msecUpdateIntervalTime = json["msecUpdateIntervalTime"].toInt(); 42 | } 43 | if (json.contains("styleName")) { 44 | s.styleName = json["styleName"].toString(); 45 | } 46 | if (json.contains("commandExe")) { 47 | s.commandExe = json["commandExe"].toString(); 48 | } 49 | if (json.contains("commandArgs")) { 50 | s.commandArgs = json["commandArgs"].toString(); 51 | } 52 | if (json.contains("msecCommandIntervalTime")) { 53 | s.msecCommandIntervalTime = json["msecCommandIntervalTime"].toInt(); 54 | } 55 | if (json.contains("lastSelectedVendorID")) { 56 | s.lastSelectedVendorID = json["lastSelectedVendorID"].toString(); 57 | } 58 | if (json.contains("lastSelectedProductID")) { 59 | s.lastSelectedProductID = json["lastSelectedProductID"].toString(); 60 | } 61 | qDebug() << "Settings Loaded:\t" << json; 62 | qDebug(); 63 | } 64 | 65 | return s; 66 | } 67 | 68 | void saveSettingstoFile(const Settings &settings, const QString &filePath) 69 | { 70 | QJsonObject json; 71 | json["runOnStartup"] = settings.runOnstartup; 72 | json["notificationHeadsetDisconnected"] = settings.notificationHeadsetDisconnected; 73 | json["notificationBatteryFull"] = settings.notificationBatteryFull; 74 | json["notificationBatteryLow"] = settings.notificationBatteryLow; 75 | json["audioNotification"] = settings.audioNotification; 76 | json["batteryLowThreshold"] = settings.batteryLowThreshold; 77 | json["msecUpdateIntervalTime"] = settings.msecUpdateIntervalTime; 78 | json["styleName"] = settings.styleName; 79 | json["commandExe"] = settings.commandExe; 80 | json["commandArgs"] = settings.commandArgs; 81 | json["msecCommandIntervalTime"] = settings.msecCommandIntervalTime; 82 | json["lastSelectedVendorID"] = settings.lastSelectedVendorID; 83 | json["lastSelectedProductID"] = settings.lastSelectedProductID; 84 | 85 | QJsonDocument doc(json); 86 | QFile file(filePath); 87 | 88 | qDebug() << "Saving settings:"; 89 | qDebug() << "Destination:\t" << filePath; 90 | if (!file.open(QIODevice::WriteOnly)) { 91 | qWarning("Error:\tCouldn't open save file."); 92 | } 93 | file.write(doc.toJson()); 94 | file.close(); 95 | 96 | //qDebug() << "Content:\t" << json; 97 | qDebug(); 98 | } 99 | -------------------------------------------------------------------------------- /src/UI/dialoginfo.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | dialogInfo 4 | 5 | 6 | 7 | 0 8 | 0 9 | 305 10 | 86 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 22 | 23 | QFrame::StyledPanel 24 | 25 | 26 | QFrame::Raised 27 | 28 | 29 | 30 | 31 | 32 | 33 | 0 34 | 0 35 | 36 | 37 | 38 | 39 | 40 | 41 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 42 | 43 | 44 | true 45 | 46 | 47 | Qt::TextBrowserInteraction 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Qt::Horizontal 60 | 61 | 62 | 63 | 40 64 | 20 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Qt::Horizontal 76 | 77 | 78 | QDialogButtonBox::Close 79 | 80 | 81 | 82 | 83 | 84 | 85 | Qt::Horizontal 86 | 87 | 88 | 89 | 40 90 | 20 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | buttonBox 103 | accepted() 104 | dialogInfo 105 | accept() 106 | 107 | 108 | 248 109 | 254 110 | 111 | 112 | 157 113 | 274 114 | 115 | 116 | 117 | 118 | buttonBox 119 | rejected() 120 | dialogInfo 121 | reject() 122 | 123 | 124 | 316 125 | 260 126 | 127 | 128 | 286 129 | 274 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HeadsetControl-GUI [![Github All Releases](https://img.shields.io/github/downloads/LeoKlaus/headsetcontrol-gui/total.svg)]() [![license](https://img.shields.io/github/license/LeoKlaus/HeadsetControl-GUI)]() 2 | This is a GUI for [Sapds great HeadsetControl](https://github.com/Sapd/HeadsetControl/).
3 | It's just a frontend to graphically interact with the original HeadsetControl and has no functionality by itself.
4 | 5 | ## Platforms 6 | 7 | OS | Compiled | Tested 8 | :------------ | :-------------| :------------- 9 | Windows | ✅ | ✅ 10 | Linux | ✅ | ❌ 11 | MacOS | ❌ | ❌ 12 | 13 | If you are on Linux or Mac and try to build the app and test it, I'd be happy to hear if it did or didn't work. 14 | 15 | ## Installation (Windows) 16 | 17 | ### WinGet 18 | ```powershell 19 | winget install LeoKlaus.HeadsetControl-GUI 20 | ``` 21 | After that to you can reboot powershell an you can run the program with the command: 22 | ```powershell 23 | HeadsetControl-GUI 24 | ``` 25 | 26 | ### Manual 27 | 1. Download the [latest release](https://github.com/LeoKlaus/HeadsetControl-GUI/releases/latest/) of HeadsetControl-GUI from the [releases section](https://github.com/nicola02nb/HeadsetControl-GUI/releases) of this page. 28 | 2. Extract HeadsetControl-GUI to any folder. 29 | 30 | The finished folder should look something like this: 31 | 32 | ![361239376-0145ca37-6e59-4170-ba26-804e8856dbc8](https://github.com/user-attachments/assets/36233a85-1500-4789-9368-1573ff8f4fed) 33 | 34 | ## Usage 35 | Start HeadsetControl-GUI by double-clicking "HeadsetControl-GUI.exe", and if your headset is supported and everything was set up correctly, you will be greeted by the following screen HeadsetControl-GUI has.. 36 | 37 | If you don't find some features in you ui, probably it's not supported by your headset or it has not been implemented by [HeadsetControl](https://github.com/Sapd/HeadsetControl/). 38 | 39 | ![Videosenzatitolo-RealizzatoconClipchamp-ezgif com-crop](https://github.com/user-attachments/assets/9a25de13-deca-45e0-aeb5-2a9d3876e9b2) 40 | 41 | Here you can adjust all settings supported by your headset. 42 | Changes may or may not persist even after rebooting the system or turning the headset off(It depends on how headsets stores their own settings). 43 | 44 | If you have a wireless headset with support for battery levels, you can also minimize HeadsetControl-GUI to the system tray. 45 | 46 | ![338270796-ea327c0a-e39a-4035-aa99-bc6325724571](https://github.com/user-attachments/assets/b71d5cb6-c3f6-4ffb-b276-b4e8934ace2c) 47 | 48 | That way, you will be able to see the battery status at a glance and get a reminder when the batteries of your headset run low (below 15%). 49 | Hovering over the tray icon will show you the current battery percentage. You can also right-click the tray icon to bring up a context menu with quick access to the light control. You can also open or completely close the GUI through the context menu. 50 | 51 | ![Screenshot 2024-06-10 183803](https://github.com/user-attachments/assets/1bcf625a-e18c-4df9-b3a4-973075e3c335) 52 | 53 | ### Performance 54 | While the concept of calling another app for every single interaction has some inherit overhead, HeadsetControl-GUI is very light on ressources. 55 | Being open in the background, HeadsetControl-GUI consists of a single process that uses virtually no CPU time and about 8-10MB of system memory. 56 | 57 | ![349140526-3171e62d-8a0c-49b6-88bd-e5b03393c7fe](https://github.com/user-attachments/assets/a3b2af01-165e-46c1-90ec-75b579f95e33) 58 | 59 | ## Building from source 60 | To build HeadsetControl-GUI from source, you have to have a proper QT-ready development environment.
61 | I developed, built and tested the program with Qt 6.7.0 and [Qt Creator](https://www.qt.io/product/development-tools) as IDE.
62 | Clone the source code, import the project into [Qt Creator](https://www.qt.io/product/development-tools) or your favourite IDE and build it. 63 | 64 | ## Additional information 65 | This software comes with no warranty whatsoever.
66 | It's not properly tested for memory leakage and may or may not work with configurations other than those I've tested. 67 | 68 | **Disclaimer**: 69 | This program is in no way affiliated with Sapd or HeadsetControl. 70 | All issues regarding the functionality of HeadsetControl (like [compatiblity with devices](https://github.com/Sapd/HeadsetControl/?tab=readme-ov-file#supported-headsets)) are beyond the scope of this project. 71 | -------------------------------------------------------------------------------- /src/Utils/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | QString getLatestGitHubReleaseVersion(const QString &owner, const QString &repo) 13 | { 14 | QEventLoop loop; 15 | QNetworkAccessManager manager; 16 | QNetworkRequest request( 17 | QUrl(QString("https://api.github.com/repos/%1/%2/releases/latest").arg(owner, repo))); 18 | request.setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0"); 19 | 20 | QNetworkReply *reply = manager.get(request); 21 | QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); 22 | loop.exec(); 23 | 24 | if (reply->error() == QNetworkReply::NoError) { 25 | QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); 26 | QJsonObject jsonObj = doc.object(); 27 | QString latestVersion = jsonObj.value("tag_name").toString(); 28 | reply->deleteLater(); 29 | return latestVersion; 30 | } else { 31 | qDebug() << "Error:" << reply->errorString(); 32 | reply->deleteLater(); 33 | return QString("0.0.0"); 34 | } 35 | } 36 | 37 | bool fileExists(const QString &filePath) 38 | { 39 | QFileInfo checkFile(filePath); 40 | return checkFile.exists(); 41 | } 42 | 43 | bool openFileExplorer(const QString &path) 44 | { 45 | QDir dir(path); 46 | if (!dir.exists()) { 47 | qDebug() << "Path does not exist:" << path; 48 | return false; 49 | } 50 | 51 | QUrl url = QUrl::fromLocalFile(dir.absolutePath()); 52 | return QDesktopServices::openUrl(url); 53 | } 54 | 55 | bool setOSRunOnStartup(bool enable) 56 | { 57 | QString appName = QCoreApplication::applicationName(); 58 | QString appPath = QCoreApplication::applicationFilePath(); 59 | 60 | #ifdef Q_OS_WIN 61 | QString startupPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) 62 | + QDir::separator() + "Startup"; 63 | QString linkPath = startupPath + QDir::separator() + appName + ".lnk"; 64 | if (enable) { 65 | QFile::remove(linkPath); 66 | // Use Windows Script Host to create a shortcut with arguments 67 | QString script = 68 | "Set oWS = WScript.CreateObject(\"WScript.Shell\")\n" 69 | "sLinkFile = \"" + linkPath.replace("/", "\\") + "\"\n" 70 | "Set oLink = oWS.CreateShortcut(sLinkFile)\n" 71 | "oLink.TargetPath = \"" + appPath.replace("/", "\\") + "\"\n" 72 | "oLink.WorkingDirectory = \"" + QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + "\"\n" 73 | "oLink.Arguments = \"--tray\"\n" 74 | "oLink.Save\n"; 75 | QString vbsPath = QDir::temp().filePath("create_shortcut.vbs"); 76 | QFile vbsFile(vbsPath); 77 | if (vbsFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 78 | vbsFile.write(script.toUtf8()); 79 | vbsFile.close(); 80 | QProcess::execute("wscript", QStringList() << vbsPath); 81 | vbsFile.remove(); 82 | return QFile::exists(linkPath); 83 | } 84 | return false; 85 | } 86 | QFile::remove(linkPath); 87 | return false; 88 | 89 | #elif defined(Q_OS_LINUX) 90 | QString appDir = QCoreApplication::applicationDirPath(); 91 | QString configPath = QDir::homePath() + QDir::separator() + ".config"; 92 | QString autostartPath = configPath + QDir::separator() + "autostart"; 93 | QString desktopFilePath = autostartPath + QDir::separator() + appName + "-autostart.desktop"; 94 | 95 | QFile::remove(desktopFilePath); 96 | 97 | if (enable) { 98 | QFile::remove(desktopFilePath); 99 | QDir configDir(configPath); 100 | QFile desktopFile(desktopFilePath); 101 | if (!configDir.exists("autostart")) { 102 | configDir.mkdir("autostart"); 103 | } 104 | 105 | if (desktopFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 106 | QTextStream out(&desktopFile); 107 | out << "[Desktop Entry]\n"; 108 | out << "Path=" + appDir + "\n"; 109 | out << "Type=Application\n"; 110 | out << "Hidden=false\n"; 111 | out << "NoDisplay=false\n"; 112 | out << "X-GNOME-Autostart-enabled=true\n"; 113 | out << "Exec=" << appPath << " --tray\n"; 114 | out << "Name=" << appName << "\n"; 115 | out << "Icon=" << appName << "\n"; 116 | out << "Comment=Auto-starts " << appName << " on boot\n"; 117 | desktopFile.close(); 118 | return true; 119 | } 120 | } 121 | 122 | return false; 123 | #endif 124 | } 125 | -------------------------------------------------------------------------------- /src/UI/settingswindow.cpp: -------------------------------------------------------------------------------- 1 | #include "settingswindow.h" 2 | #include "ui_settingswindow.h" 3 | 4 | #include "utils.h" 5 | 6 | #include 7 | #include 8 | 9 | SettingsWindow::SettingsWindow(const Settings &programSettings, QWidget *parent) 10 | : QDialog(parent) 11 | , ui(new Ui::settingswindow) 12 | { 13 | setModal(true); 14 | ui->setupUi(this); 15 | 16 | connect(ui->runonstartupCheckBox, &QCheckBox::clicked, this, &SettingsWindow::setRunOnStartup); 17 | connect(ui->selectstyleComboBox, 18 | &QComboBox::currentTextChanged, 19 | this, 20 | [this](const QString &text) { 21 | this->ui->removestylePushButton->setEnabled(text != "Default"); 22 | }); 23 | connect(ui->loadstylePushButton, &QPushButton::clicked, this, &SettingsWindow::saveStyle); 24 | connect(ui->removestylePushButton, &QPushButton::clicked, this, &SettingsWindow::removeStyle); 25 | 26 | ui->runonstartupCheckBox->setChecked(programSettings.runOnstartup); 27 | 28 | ui->headsetdisconnectednotificationCheckBox->setChecked(programSettings.notificationHeadsetDisconnected); 29 | ui->batteryfullnotificationCheckBox->setChecked(programSettings.notificationBatteryFull); 30 | ui->batterylownotificationCheckBox->setChecked(programSettings.notificationBatteryLow); 31 | ui->batterylowtresholdSpinBox->setValue(programSettings.batteryLowThreshold); 32 | ui->enableaudioNotificationCheckBox->setChecked(programSettings.audioNotification); 33 | 34 | ui->updateintervaltimeDoubleSpinBox->setValue((double) programSettings.msecUpdateIntervalTime 35 | / 1000); 36 | 37 | loadStyles(); 38 | ui->selectstyleComboBox->setCurrentIndex( 39 | ui->selectstyleComboBox->findText(programSettings.styleName)); 40 | 41 | ui->commandexeLabel->setText(programSettings.commandExe); 42 | connect(ui->commandexePushButton, &QPushButton::clicked, this, &SettingsWindow::setCommandExe); 43 | connect(ui->commandclearPushButton, &QPushButton::clicked, this, &SettingsWindow::clearCommandExe); 44 | ui->commandargumentsValue->setText(programSettings.commandArgs); 45 | ui->commandintervaltimeDoubleSpinBox->setValue((double) programSettings.msecCommandIntervalTime 46 | / 1000); 47 | } 48 | 49 | Settings SettingsWindow::getSettings() 50 | { 51 | Settings settings; 52 | settings.runOnstartup = ui->runonstartupCheckBox->isChecked(); 53 | settings.notificationHeadsetDisconnected = ui->headsetdisconnectednotificationCheckBox->isChecked(); 54 | settings.notificationBatteryFull = ui->batteryfullnotificationCheckBox->isChecked(); 55 | settings.notificationBatteryLow = ui->batterylownotificationCheckBox->isChecked(); 56 | settings.batteryLowThreshold = ui->batterylowtresholdSpinBox->value(); 57 | settings.audioNotification = ui->enableaudioNotificationCheckBox->isChecked(); 58 | settings.msecUpdateIntervalTime = ui->updateintervaltimeDoubleSpinBox->value() * 1000; 59 | settings.styleName = ui->selectstyleComboBox->currentText(); 60 | settings.commandExe = ui->commandexeLabel->text(); 61 | settings.commandArgs = ui->commandargumentsValue->text(); 62 | settings.msecCommandIntervalTime = ui->commandintervaltimeDoubleSpinBox->value() * 1000; 63 | 64 | return settings; 65 | } 66 | 67 | void SettingsWindow::setRunOnStartup() 68 | { 69 | bool enabled = setOSRunOnStartup(ui->runonstartupCheckBox->isChecked()); 70 | ui->runonstartupCheckBox->setChecked(enabled); 71 | } 72 | 73 | void SettingsWindow::loadStyles() 74 | { 75 | QString destination = PROGRAM_STYLES_PATH; 76 | QDir directory = QDir(destination); 77 | QStringList list = directory.entryList(QStringList() << "*.qss", QDir::Files); 78 | ui->selectstyleComboBox->clear(); 79 | ui->selectstyleComboBox->addItem("Default"); 80 | ui->selectstyleComboBox->addItems(list); 81 | } 82 | 83 | void SettingsWindow::saveStyle() 84 | { 85 | QFileDialog dialog(this); 86 | dialog.setFileMode(QFileDialog::AnyFile); 87 | dialog.setNameFilter("QStyle (*.qss)"); 88 | 89 | if (dialog.exec()) 90 | { 91 | QStringList fileUrls = dialog.selectedFiles(); 92 | QUrl fileUrl = QUrl(fileUrls[0]); 93 | if (fileUrl.isValid()) { 94 | QString source = fileUrl.path().removeFirst(); 95 | QString destination = PROGRAM_STYLES_PATH; 96 | QDir().mkpath(destination); 97 | destination += "/" + fileUrl.fileName(); 98 | QFile file(destination); 99 | if (file.exists()) { 100 | file.remove(); 101 | } 102 | QFile::copy(source, destination); 103 | 104 | loadStyles(); 105 | } 106 | } 107 | } 108 | 109 | void SettingsWindow::removeStyle() 110 | { 111 | QString stylePath = PROGRAM_STYLES_PATH + "/" + ui->selectstyleComboBox->currentText(); 112 | QFile file(stylePath); 113 | if (file.exists()) { 114 | file.remove(); 115 | } 116 | 117 | loadStyles(); 118 | } 119 | 120 | void SettingsWindow::setCommandExe(){ 121 | QFileDialog dialog(this); 122 | dialog.setFileMode(QFileDialog::AnyFile); 123 | 124 | if (dialog.exec()) 125 | { 126 | QStringList fileUrls = dialog.selectedFiles(); 127 | QUrl fileUrl = QUrl(fileUrls[0]); 128 | if (fileUrl.isValid()) { 129 | QString source = fileUrl.toString(); 130 | ui->commandexeLabel->setText(source); 131 | } 132 | } 133 | } 134 | 135 | void SettingsWindow::clearCommandExe(){ 136 | ui->commandexeLabel->setText(""); 137 | } 138 | 139 | SettingsWindow::~SettingsWindow() 140 | { 141 | delete ui; 142 | } 143 | -------------------------------------------------------------------------------- /.github/workflows/build-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: "Release tag(e.g., 1.2.3) without starting 'v'" 11 | required: true 12 | default: "continuous" 13 | type: string 14 | prerelease: 15 | description: "Set as prerelease" 16 | required: true 17 | default: true 18 | type: boolean 19 | publish: 20 | description: "Publish release" 21 | required: true 22 | default: true 23 | type: boolean 24 | # push: 25 | # branches: [main] 26 | # paths: 27 | # - 'src/**' 28 | # - 'HeadsetControl-GUI.pro' 29 | 30 | jobs: 31 | build: 32 | runs-on: ${{ matrix.os }} 33 | defaults: 34 | run: 35 | shell: ${{ matrix.shell }} 36 | strategy: 37 | matrix: 38 | os: [windows-latest, ubuntu-latest] 39 | include: 40 | - os: windows-latest 41 | os_name: windows 42 | architecture: x64 43 | shell: pwsh 44 | headsetcontrol: "https://github.com/Sapd/HeadsetControl/releases/latest/download/headsetcontrol-windows-x86_64.zip" 45 | headsetcontrol_continuous: "https://github.com/Sapd/HeadsetControl/releases/download/continuous/headsetcontrol-windows-x86_64.zip" 46 | dependencies: | 47 | choco install zip 48 | Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" 49 | build: | 50 | mkdir build 51 | cd build 52 | qmake ../HeadsetControl-GUI.pro CONFIG+=release 53 | nmake 54 | - os: ubuntu-latest 55 | os_name: linux 56 | architecture: x64 57 | shell: bash 58 | headsetcontrol: "https://github.com/Sapd/HeadsetControl/releases/latest/download/headsetcontrol-linux-x86_64.zip" 59 | headsetcontrol_continuous: "https://github.com/Sapd/HeadsetControl/releases/download/continuous/headsetcontrol-linux-x86_64.zip" 60 | dependencies: | 61 | sudo apt-get update 62 | sudo apt-get install -y zip 63 | build: | 64 | mkdir build 65 | cd build 66 | qmake ../HeadsetControl-GUI.pro CONFIG+=release 67 | make -j$(nproc) 68 | steps: 69 | - uses: actions/checkout@v4 70 | - name: Install Qt 71 | uses: jurplel/install-qt-action@v4 72 | with: 73 | add-tools-to-path: true 74 | cache: true 75 | host: ${{ matrix.os_name }} 76 | 77 | - name: Install dependencies 78 | run: ${{ matrix.dependencies }} 79 | 80 | - uses: TheMrMilchmann/setup-msvc-dev@v3 81 | if: matrix.os_name == 'windows' 82 | with: 83 | arch: ${{ matrix.architecture }} 84 | 85 | - name: Build 86 | run: ${{ matrix.build }} 87 | 88 | - name: Deploy Qt Windows 89 | if: matrix.os_name == 'windows' 90 | run: | 91 | Get-ChildItem -Path build/ -Include *.cpp, *.h, *.obj, *.res, *.qrc, *.qm -Recurse | Remove-Item -Force 92 | Invoke-WebRequest -Uri ${{ inputs.prerelease && matrix.headsetcontrol_continuous || matrix.headsetcontrol }} -OutFile headsetcontrol-windows.zip 93 | Expand-Archive -Path headsetcontrol-windows.zip -DestinationPath build/release/ 94 | windeployqt6 --exclude-plugins qsvgicon,qsvg,qico,qjpeg,qgif,qnetworklistmanager,qtuiotouchplugin --no-opengl-sw --no-system-dxc-compiler --no-compiler-runtime --no-translations --no-system-d3d-compiler build/release/HeadsetControl-GUI.exe 95 | 96 | - name: Deploy Qt 97 | if: matrix.os_name == 'linux' 98 | run: | 99 | curl -L ${{ matrix.headsetcontrol_continuous }} -o headsetcontrol-linux-x86_64.zip 100 | mkdir -p build/release/ 101 | cp build/HeadsetControl-GUI build/release/ 102 | unzip headsetcontrol-linux-x86_64.zip -d build/release/ 103 | 104 | - name: zip binaries folder 105 | run: | 106 | cd build/release/ 107 | zip -r ../../HeadsetControl-GUI_${{ matrix.os_name }}_${{ matrix.architecture }}.zip . 108 | 109 | - name: Upload build artifacts 110 | uses: actions/upload-artifact@v4 111 | with: 112 | name: HeadsetControl-GUI_${{ matrix.os_name }}_${{ matrix.architecture }} 113 | path: HeadsetControl-GUI_${{ matrix.os_name }}_${{ matrix.architecture }}.zip 114 | 115 | create-release: 116 | if: ${{ inputs.publish }} 117 | needs: [build] 118 | runs-on: ubuntu-latest 119 | steps: 120 | - uses: actions/checkout@v4 121 | - name: Download Artifact 122 | uses: actions/download-artifact@v4 123 | with: 124 | merge-multiple: true 125 | - name: Deploy continuous 126 | uses: crowbarmaster/GH-Automatic-Releases@latest 127 | with: 128 | repo_token: ${{ secrets.GITHUB_TOKEN }} 129 | automatic_release_tag: ${{ inputs.version }} 130 | prerelease: ${{ inputs.prerelease }} 131 | title: Release ${{ inputs.version }} 132 | files: | 133 | HeadsetControl-GUI_* 134 | body: | 135 | ## Contributor 136 | @nicola02nb Mantainer 137 | 138 | ## Credits 139 | @Sapd for [HeadsetControl](https://github.com/Sapd/HeadsetControl) 140 | 141 | winget-release: 142 | if: ${{ inputs.publish && !(startsWith(github.ref, 'refs/tags/continuous') || inputs.prerelease) }} 143 | needs: [create-release] 144 | runs-on: ubuntu-latest 145 | steps: 146 | - name: Submit package to Windows Package Manager Community Repository 147 | uses: michidk/winget-updater@v1 148 | with: 149 | komac-token: ${{ secrets.WINGET_DEPLOY_TOKEN }} 150 | identifier: "LeoKlaus.HeadsetControl-GUI" 151 | repo: "LeoKlaus/HeadsetControl-GUI" 152 | url: "https://github.com/LeoKlaus/HeadsetControl-GUI/releases/download/{VERSION}/HeadsetControl-GUI_windows_x64.zip" -------------------------------------------------------------------------------- /src/DataTypes/device.cpp: -------------------------------------------------------------------------------- 1 | #include "device.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Battery::Battery() {} 10 | 11 | Battery::Battery(QString stat, int lev) 12 | { 13 | status = stat; 14 | level = lev; 15 | } 16 | 17 | Equalizer::Equalizer() {} 18 | 19 | Equalizer::Equalizer(int bands, int baseline, double step, int min, int max) 20 | { 21 | bands_number = bands; 22 | band_baseline = baseline; 23 | band_min = min; 24 | band_step = step; 25 | band_max = max; 26 | } 27 | 28 | Device::Device() {} 29 | 30 | Device::Device(const QJsonObject &jsonObj, QString jsonData) 31 | { 32 | status = jsonObj["status"].toString(); 33 | 34 | device = jsonObj["device"].toString(); 35 | vendor = jsonObj["vendor"].toString(); 36 | product = jsonObj["product"].toString(); 37 | id_vendor = jsonObj["id_vendor"].toString(); 38 | id_product = jsonObj["id_product"].toString(); 39 | 40 | QJsonArray caps = jsonObj["capabilities"].toArray(); 41 | for (const QJsonValue &value : std::as_const(caps)) { 42 | capabilities.insert(value.toString()); 43 | } 44 | if (capabilities.contains("CAP_BATTERY_STATUS")) { 45 | QJsonObject jEq = jsonObj["battery"].toObject(); 46 | battery = Battery(jEq["status"].toString(), jEq["level"].toInt()); 47 | } 48 | if (capabilities.contains("CAP_CHATMIX_STATUS")) { 49 | chatmix = jsonObj["chatmix"].toInt(); 50 | } 51 | 52 | if (capabilities.contains("CAP_EQUALIZER_PRESET")) { 53 | if (jsonObj.contains("equalizer_presets") && jsonObj["equalizer_presets"].isObject()) { 54 | QJsonObject equalizerPresets = jsonObj["equalizer_presets"].toObject(); 55 | 56 | // Parse the original JSON string to find the order of keys 57 | static QRegularExpression re("\"(\\w+)\":\\s*\\["); 58 | QRegularExpressionMatchIterator i = re.globalMatch(jsonData); 59 | while (i.hasNext()) { 60 | QRegularExpressionMatch match = i.next(); 61 | QString presetName = match.captured(1); 62 | if (equalizerPresets.contains(presetName)) { 63 | EqualizerPreset preset; 64 | preset.name = presetName; 65 | 66 | QJsonArray valuesArray = equalizerPresets[presetName].toArray(); 67 | for (const QJsonValue &value : std::as_const(valuesArray)) { 68 | preset.values.append(value.toDouble()); 69 | } 70 | 71 | presets_list.append(preset); 72 | } 73 | } 74 | } 75 | } 76 | if (capabilities.contains("CAP_EQUALIZER")) { 77 | QJsonObject jEq = jsonObj["equalizer"].toObject(); 78 | if (!jEq.isEmpty()) { 79 | equalizer = Equalizer(jEq["bands"].toInt(), 80 | jEq["baseline"].toInt(), 81 | jEq["step"].toDouble(), 82 | jEq["min"].toInt(), 83 | jEq["max"].toInt()); 84 | equalizer_curve = QVector(equalizer.bands_number, equalizer.band_baseline); 85 | } 86 | } 87 | } 88 | 89 | // Helper functions 90 | bool Device::operator!=(const Device &d) const 91 | { 92 | return this->id_vendor != d.id_vendor || this->id_product != d.id_product; 93 | } 94 | 95 | bool Device::operator==(const Device &d) const 96 | { 97 | return this->id_vendor == d.id_vendor && this->id_product == d.id_product; 98 | } 99 | 100 | bool Device::operator==(const Device *d) const 101 | { 102 | return this->id_vendor == d->id_vendor && this->id_product == d->id_product; 103 | } 104 | 105 | void Device::copyConfig(Device* device){ 106 | this->lights = device->lights; 107 | this->sidetone = device->sidetone; 108 | this->previous_sidetone = device->previous_sidetone; 109 | this->voice_prompts = device->voice_prompts; 110 | this->inactive_time = device->inactive_time; 111 | this->equalizer_preset = device->equalizer_preset; 112 | this->equalizer_curve = device->equalizer_curve; 113 | this->volume_limiter = device->volume_limiter; 114 | this->rotate_to_mute = device->rotate_to_mute; 115 | this->mic_mute_led_brightness = device->mic_mute_led_brightness; 116 | this->mic_volume = device->mic_volume; 117 | this->bt_when_powered_on = device->bt_when_powered_on; 118 | this->bt_call_volume = device->bt_call_volume; 119 | } 120 | 121 | void Device::updateConfig(const QList &list){ 122 | foreach (Device* device, list) { 123 | if(*this == device) 124 | this->copyConfig(device); 125 | } 126 | } 127 | 128 | void Device::updateInfo(const Device *new_device) 129 | { 130 | this->battery = new_device->battery; 131 | this->chatmix = new_device->chatmix; 132 | } 133 | 134 | QJsonObject Device::toJson() const 135 | { 136 | QJsonObject json; 137 | json["device"] = device; 138 | json["vendor"] = vendor; 139 | json["product"] = product; 140 | json["id_vendor"] = id_vendor; 141 | json["id_product"] = id_product; 142 | 143 | json["lights"] = lights; 144 | json["sidetone"] = sidetone; 145 | json["previous_sidetone"] = previous_sidetone; 146 | json["voice_prompts"] = voice_prompts; 147 | json["inactive_time"] = inactive_time; 148 | json["equalizer_preset"] = equalizer_preset; 149 | json["equalizer_curve"] = QJsonArray::fromVariantList( 150 | QVariantList(equalizer_curve.begin(), equalizer_curve.end())); 151 | json["volume_limiter"] = volume_limiter; 152 | json["rotate_to_mute"] = rotate_to_mute; 153 | json["mic_mute_led_brightness"] = mic_mute_led_brightness; 154 | json["mic_volume"] = mic_volume; 155 | json["bt_when_powered_on"] = bt_when_powered_on; 156 | json["bt_call_volume"] = bt_call_volume; 157 | 158 | return json; 159 | } 160 | 161 | Device *Device::fromJson( 162 | const QJsonObject &json) 163 | { 164 | Device *newDev = new Device(); 165 | newDev->device = json["device"].toString(); 166 | newDev->vendor = json["vendor"].toString(); 167 | newDev->product = json["product"].toString(); 168 | newDev->id_vendor = json["id_vendor"].toString(); 169 | newDev->id_product = json["id_product"].toString(); 170 | 171 | newDev->lights = json["lights"].toInt(); 172 | newDev->sidetone = json["sidetone"].toInt(); 173 | newDev->previous_sidetone = json["previous_sidetone"].toInt(); 174 | newDev->voice_prompts = json["voice_prompts"].toInt(); 175 | newDev->inactive_time = json["inactive_time"].toInt(); 176 | newDev->equalizer_preset = json["equalizer_preset"].toInt(); 177 | 178 | QJsonArray curveArray = json["equalizer_curve"].toArray(); 179 | for (const auto &value : std::as_const(curveArray)) { 180 | newDev->equalizer_curve.append(value.toDouble()); 181 | } 182 | 183 | newDev->volume_limiter = json["volume_limiter"].toInt(); 184 | newDev->rotate_to_mute = json["rotate_to_mute"].toInt(); 185 | newDev->mic_mute_led_brightness = json["mic_mute_led_brightness"].toInt(); 186 | newDev->mic_volume = json["mic_volume"].toInt(); 187 | newDev->bt_when_powered_on = json["bt_when_powered_on"].toInt(); 188 | newDev->bt_call_volume = json["bt_call_volume"].toInt(); 189 | 190 | return newDev; 191 | } 192 | 193 | void updateDeviceFromSource(QList &devicesToUpdate, const Device *sourceDevice) 194 | { 195 | bool found = false; 196 | for (Device *toUpdateDevice : devicesToUpdate) { 197 | if (*toUpdateDevice == sourceDevice) { 198 | // Update the saved device with connected device's information 199 | *toUpdateDevice = *sourceDevice; 200 | found = true; 201 | } 202 | }; 203 | if (!found) { 204 | Device *toAppend = new Device(); 205 | *toAppend = *sourceDevice; 206 | devicesToUpdate.append(toAppend); 207 | } 208 | } 209 | 210 | void serializeDevices(const QList &devices, const QString &filePath) 211 | { 212 | QJsonArray jsonArray; 213 | for (const auto *device : devices) { 214 | jsonArray.append(device->toJson()); 215 | } 216 | 217 | QJsonDocument doc(jsonArray); 218 | QFile file(filePath); 219 | if (file.open(QIODevice::WriteOnly)) { 220 | file.write(doc.toJson()); 221 | file.close(); 222 | qDebug() << "Devices Serialized" << jsonArray; 223 | qDebug(); 224 | } 225 | } 226 | 227 | QList deserializeDevices(const QString &filePath) 228 | { 229 | QList devices; 230 | QFile file(filePath); 231 | if (file.open(QIODevice::ReadOnly)) { 232 | QByteArray data = file.readAll(); 233 | QJsonDocument doc = QJsonDocument::fromJson(data); 234 | QJsonArray jsonArray = doc.array(); 235 | 236 | for (const auto &value : std::as_const(jsonArray)) { 237 | Device *device = Device::fromJson(value.toObject()); 238 | devices.append(device); 239 | } 240 | 241 | file.close(); 242 | qDebug() << "Devices Deserialized" << jsonArray; 243 | qDebug(); 244 | } 245 | return devices; 246 | } 247 | 248 | void deleteDevices( 249 | QList deviceList) 250 | { 251 | for (Device *device : deviceList) { 252 | delete device; 253 | } 254 | deviceList.clear(); 255 | } 256 | -------------------------------------------------------------------------------- /src/Utils/headsetcontrolapi.cpp: -------------------------------------------------------------------------------- 1 | #include "headsetcontrolapi.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | HeadsetControlAPI::HeadsetControlAPI(const QString& headsetcontrolDirectory) 10 | { 11 | if (headsetcontrolDirectory == ""){ 12 | this->headsetcontrolFilePath = "headsetcontrol"; 13 | #ifdef Q_OS_WIN 14 | this->headsetcontrolFilePath += ".exe"; 15 | #endif 16 | } else { 17 | this->headsetcontrolFilePath = headsetcontrolDirectory; 18 | } 19 | 20 | parseOutput(sendCommand(QStringList())); 21 | selectedVendorId="0"; 22 | selectedProductId="0"; 23 | } 24 | 25 | Device* HeadsetControlAPI::getSelectedDevice() 26 | { 27 | return selectedDevice; 28 | } 29 | 30 | bool HeadsetControlAPI::areApiAvailable(){ 31 | QString executableName = this->headsetcontrolFilePath; 32 | QFileInfo fileInfo(QDir::currentPath(),executableName); 33 | if (fileInfo.exists()) 34 | return true; 35 | else { 36 | QString path = QStandardPaths::findExecutable(executableName); 37 | return !path.isEmpty(); 38 | } 39 | } 40 | 41 | void HeadsetControlAPI::setSelectedDevice(const QString& vendorId, const QString& productId) 42 | { 43 | if(vendorId == "") 44 | selectedVendorId = "0"; 45 | else 46 | selectedVendorId=vendorId; 47 | if(productId == "") 48 | selectedProductId = "0"; 49 | else 50 | selectedProductId=productId; 51 | selectedDevice = getDevice(); 52 | } 53 | 54 | void HeadsetControlAPI::updateSelectedDevice(){ 55 | Device* newD = getDevice(); 56 | if (newD == nullptr){ 57 | selectedDevice = nullptr; 58 | } else { 59 | selectedDevice->updateInfo(newD); 60 | } 61 | } 62 | 63 | QString HeadsetControlAPI::getName() 64 | { 65 | return name; 66 | } 67 | 68 | QString HeadsetControlAPI::getVersion() 69 | { 70 | return version; 71 | } 72 | 73 | QString HeadsetControlAPI::getApiVersion() 74 | { 75 | return api_version; 76 | } 77 | 78 | QString HeadsetControlAPI::getHidApiVersion() 79 | { 80 | return hidapi_version; 81 | } 82 | 83 | Device *HeadsetControlAPI::getDevice() 84 | { 85 | QList connectedList = getConnectedDevices(selectedVendorId, selectedProductId); 86 | Device *connectedDevice = nullptr; 87 | if(selectedVendorId == "0" && selectedProductId == "0"){ 88 | if (connectedList.size() > 0){ 89 | connectedDevice = connectedList.at(0); 90 | } 91 | } else { 92 | foreach(Device *d, connectedList){ 93 | if(d->id_vendor == selectedVendorId && d->id_product == selectedProductId){ 94 | connectedDevice = d; 95 | } 96 | } 97 | } 98 | Device *device = nullptr; 99 | if (connectedDevice != nullptr) { 100 | device = new Device(); 101 | *device = *connectedDevice; 102 | selectedVendorId = device->id_vendor; 103 | selectedProductId = device->id_product; 104 | } 105 | deleteDevices(connectedList); 106 | 107 | return device; 108 | } 109 | 110 | QList HeadsetControlAPI::getConnectedDevices(const QString& vendorId, const QString& productId) 111 | { 112 | QStringList args = QStringList(); 113 | args << "--device" << vendorId+":"+productId; 114 | QString output = sendCommand(args); 115 | QJsonObject jsonInfo = parseOutput(output); 116 | 117 | int device_number = jsonInfo["device_count"].toInt(); 118 | qDebug() << "Found" << device_number << "devices:"; 119 | 120 | QList devices; 121 | QJsonArray jsonDevices = jsonInfo["devices"].toArray(); 122 | for (auto jsonDevice: jsonDevices) { 123 | Device *device = new Device(jsonDevice.toObject(), output); 124 | devices.append(device); 125 | qDebug() << device->device << "[" << device->id_vendor << ":" << device->id_vendor << "] "; 126 | } 127 | qDebug(); 128 | 129 | return devices; 130 | } 131 | 132 | // HC rleated functions 133 | QString HeadsetControlAPI::sendCommand(const QStringList &args_list) 134 | { 135 | QProcess *proc = new QProcess(); 136 | QStringList args = QStringList(); 137 | args << "--output" << "JSON"; 138 | //args << "--test-device"; //Uncomment this to enable test device 139 | args << args_list; 140 | 141 | proc->setProgram(headsetcontrolFilePath); 142 | proc->setArguments(args); 143 | 144 | proc->start(); 145 | proc->waitForFinished(); 146 | QString output = proc->readAllStandardOutput(); 147 | qDebug() << "Command:\t" << headsetcontrolFilePath; 148 | qDebug() << "Args:\t" << args; 149 | qDebug() << "ExitStatus:\t" << proc->exitStatus(); 150 | qDebug() << "Error:\t" << proc->error(); 151 | qDebug() << "ErrorMessage:\t" << proc->errorString(); 152 | qDebug() << "ExitCode:\t" << proc->exitCode(); 153 | 154 | //qDebug() << "Output:" << output; 155 | qDebug(); 156 | 157 | delete (proc); 158 | 159 | return output; 160 | } 161 | 162 | QJsonObject HeadsetControlAPI::parseOutput( 163 | const QString &output) 164 | { 165 | QJsonDocument jsonDoc = QJsonDocument::fromJson(output.toUtf8()); 166 | QJsonObject jsonInfo = jsonDoc.object(); 167 | 168 | name = jsonInfo["name"].toString(); 169 | version = jsonInfo["version"].toString(); 170 | api_version = jsonInfo["api_version"].toString(); 171 | hidapi_version = jsonInfo["hidapi_version"].toString(); 172 | 173 | return jsonInfo; 174 | } 175 | 176 | Action HeadsetControlAPI::sendAction(const QStringList &args_list) 177 | { 178 | QStringList args; 179 | args << "--device" << selectedVendorId+":"+selectedProductId << args_list; 180 | QString output = sendCommand(args); 181 | QJsonObject jsonInfo = parseOutput(output); 182 | QJsonArray actions = jsonInfo["actions"].toArray(); 183 | Action action; 184 | if (!actions.isEmpty()) { 185 | QJsonObject jaction = actions[0].toObject(); 186 | 187 | action.device = jaction["device"].toString(); 188 | action.capability = jaction["capability"].toString(); 189 | action.status = jaction["status"].toString(); 190 | action.error_message = jaction["error_message"].toString(); 191 | 192 | action.success = action.status == "success"; 193 | 194 | qDebug() << "Device:\t" << action.device; 195 | qDebug() << "Capability:" << action.capability; 196 | qDebug() << "Status:\t" << action.status; 197 | if (!action.success) { 198 | qDebug() << "Error:\t" << action.error_message; 199 | } 200 | qDebug(); 201 | } 202 | 203 | return action; 204 | } 205 | 206 | 207 | void HeadsetControlAPI::setSidetone( 208 | int level, bool emitSignal) 209 | { 210 | QStringList args = QStringList() << "--sidetone" << QString::number(level); 211 | Action a = sendAction(args); 212 | if (emitSignal && a.success) { 213 | selectedDevice->sidetone = level; 214 | emit actionSuccesful(); 215 | } 216 | } 217 | 218 | void HeadsetControlAPI::setLights( 219 | bool enabled, bool emitSignal) 220 | { 221 | QStringList args = QStringList() << "--light" << QString::number(enabled); 222 | Action a = sendAction(args); 223 | if (emitSignal && a.success) { 224 | selectedDevice->lights = enabled; 225 | emit actionSuccesful(); 226 | } 227 | } 228 | 229 | void HeadsetControlAPI::setVoicePrompts( 230 | bool enabled, bool emitSignal) 231 | { 232 | QStringList args = QStringList() << "--voice-prompt" << QString::number(enabled); 233 | Action a = sendAction(args); 234 | if (emitSignal && a.success) { 235 | selectedDevice->voice_prompts = enabled; 236 | emit actionSuccesful(); 237 | } 238 | } 239 | 240 | void HeadsetControlAPI::setInactiveTime( 241 | int time, bool emitSignal) 242 | { 243 | QStringList args = QStringList() << "--inactive-time" << QString::number(time); 244 | Action a = sendAction(args); 245 | if (emitSignal && a.success) { 246 | selectedDevice->inactive_time = time; 247 | emit actionSuccesful(); 248 | } 249 | } 250 | 251 | void HeadsetControlAPI::playNotificationSound( 252 | int id, bool emitSignal) 253 | { 254 | QStringList args = QStringList() << "--notificate" << QString::number(id); 255 | Action a = sendAction(args); 256 | if (emitSignal && a.success) { 257 | selectedDevice->notification_sound = id; 258 | emit actionSuccesful(); 259 | } 260 | } 261 | 262 | void HeadsetControlAPI::setVolumeLimiter( 263 | bool enabled, bool emitSignal) 264 | { 265 | QStringList args = QStringList() << "--volume-limiter" << QString::number(enabled); 266 | Action a = sendAction(args); 267 | if (emitSignal && a.success) { 268 | selectedDevice->volume_limiter = enabled; 269 | emit actionSuccesful(); 270 | } 271 | } 272 | 273 | void HeadsetControlAPI::setEqualizer( 274 | QList equalizerValues, bool emitSignal) 275 | { 276 | QString equalizer = ""; 277 | for (double value : equalizerValues) { 278 | equalizer += QString::number(value) + ","; 279 | } 280 | equalizer.removeLast(); 281 | QStringList args = QStringList() << "--equalizer" << equalizer; 282 | Action a = sendAction(args); 283 | if (emitSignal && a.success) { 284 | selectedDevice->equalizer_curve = equalizerValues; 285 | selectedDevice->equalizer_preset = -1; 286 | emit actionSuccesful(); 287 | } 288 | } 289 | 290 | void HeadsetControlAPI::setEqualizerPreset( 291 | int number, bool emitSignal) 292 | { 293 | QStringList args = QStringList() << "--equalizer-preset" << QString::number(number); 294 | Action a = sendAction(args); 295 | if (emitSignal && a.success) { 296 | selectedDevice->equalizer_preset = number; 297 | emit actionSuccesful(); 298 | } 299 | } 300 | 301 | void HeadsetControlAPI::setRotateToMute( 302 | bool enabled, bool emitSignal) 303 | { 304 | QStringList args = QStringList() << "--rotate-to-mute" << QString::number(enabled); 305 | Action a = sendAction(args); 306 | if (emitSignal && a.success) { 307 | selectedDevice->rotate_to_mute = enabled; 308 | emit actionSuccesful(); 309 | } 310 | } 311 | 312 | void HeadsetControlAPI::setMuteLedBrightness( 313 | int brightness, bool emitSignal) 314 | { 315 | QStringList args = QStringList() << "--microphone-mute-led-brightness" << QString::number(brightness); 316 | Action a = sendAction(args); 317 | if (emitSignal && a.success) { 318 | selectedDevice->mic_mute_led_brightness = brightness; 319 | emit actionSuccesful(); 320 | } 321 | } 322 | 323 | void HeadsetControlAPI::setMicrophoneVolume( 324 | int volume, bool emitSignal) 325 | { 326 | QStringList args = QStringList() << "--microphone-volume" << QString::number(volume); 327 | Action a = sendAction(args); 328 | if (emitSignal && a.success) { 329 | selectedDevice->mic_volume = volume; 330 | emit actionSuccesful(); 331 | } 332 | } 333 | 334 | void HeadsetControlAPI::setBluetoothWhenPoweredOn( 335 | bool enabled, bool emitSignal) 336 | { 337 | QStringList args = QStringList() << "--bt-when-powered-on" << QString::number(enabled); 338 | Action a = sendAction(args); 339 | if (emitSignal && a.success) { 340 | selectedDevice->bt_when_powered_on = enabled; 341 | emit actionSuccesful(); 342 | } 343 | } 344 | 345 | void HeadsetControlAPI::setBluetoothCallVolume( 346 | int option, bool emitSignal) 347 | { 348 | QStringList args = QStringList() << "--bt-call-volume" << QString::number(option); 349 | Action a = sendAction(args); 350 | if (emitSignal && a.success) { 351 | selectedDevice->bt_call_volume = option; 352 | emit actionSuccesful(); 353 | } 354 | } 355 | 356 | void HeadsetControlAPI::updateChatMix(){ 357 | if(selectedDevice->capabilities.contains("CAP_CHATMIX_STATUS")){ 358 | selectedDevice->chatmix = -1; 359 | return; 360 | } 361 | QStringList args = QStringList() << "--device" << selectedVendorId+":"+selectedProductId << "--chatmix" << "--output" << "STANDARD"; 362 | QString output = sendCommand(args); 363 | QString trimmed = output.trimmed(); 364 | int idx = trimmed.lastIndexOf("Chatmix: "); 365 | if (idx == -1) { 366 | selectedDevice->chatmix = -1; 367 | return; 368 | } 369 | 370 | QString after = trimmed.mid(idx + QStringLiteral("Chatmix: ").length()).trimmed(); 371 | 372 | selectedDevice->chatmix = after.toInt(); 373 | } 374 | -------------------------------------------------------------------------------- /src/UI/settingswindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | settingswindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 354 10 | 464 11 | 12 | 13 | 14 | Settings 15 | 16 | 17 | 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 0 25 | 0 26 | 27 | 28 | 29 | QFrame::Shape::StyledPanel 30 | 31 | 32 | QFrame::Shadow::Raised 33 | 34 | 35 | 36 | 37 | 38 | Run on Startup: 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::LayoutDirection::RightToLeft 46 | 47 | 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 0 63 | 0 64 | 65 | 66 | 67 | QFrame::Shape::StyledPanel 68 | 69 | 70 | QFrame::Shadow::Raised 71 | 72 | 73 | 74 | 75 | 76 | Qt::LayoutDirection::RightToLeft 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Qt::LayoutDirection::RightToLeft 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 0 98 | 0 99 | 100 | 101 | 102 | Battery full notification: 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 0 111 | 0 112 | 113 | 114 | 115 | Enable audio notifications: 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 0 124 | 0 125 | 126 | 127 | 128 | Battery low notification: 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 120 137 | 0 138 | 139 | 140 | 141 | 142 | 120 143 | 16777215 144 | 145 | 146 | 147 | 100 148 | 149 | 150 | 15 151 | 152 | 153 | 154 | 155 | 156 | 157 | Qt::LayoutDirection::RightToLeft 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 0 169 | 0 170 | 171 | 172 | 173 | Battery low threshold: 174 | 175 | 176 | 177 | 178 | 179 | 180 | Headset disconnected notification: 181 | 182 | 183 | 184 | 185 | 186 | 187 | Qt::LayoutDirection::RightToLeft 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 0 202 | 0 203 | 204 | 205 | 206 | QFrame::Shape::StyledPanel 207 | 208 | 209 | QFrame::Shadow::Raised 210 | 211 | 212 | 213 | 214 | 215 | Update info interval time (seconds): 216 | Default: 30 seconds 217 | DON'T PUT TOO LOW VALUES 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 120 226 | 0 227 | 228 | 229 | 230 | 231 | 120 232 | 16777215 233 | 234 | 235 | 236 | 0 237 | 238 | 239 | 1.000000000000000 240 | 241 | 242 | 99999.000000000000000 243 | 244 | 245 | 30.000000000000000 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | QFrame::Shape::StyledPanel 256 | 257 | 258 | QFrame::Shadow::Raised 259 | 260 | 261 | 262 | 263 | 264 | Custom Style: 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 0 273 | 0 274 | 275 | 276 | 277 | 99 278 | 279 | 280 | 281 | 282 | 283 | 284 | Load 285 | 286 | 287 | 288 | 289 | 290 | 291 | Remove 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | QFrame::Shape::StyledPanel 302 | 303 | 304 | QFrame::Shadow::Raised 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | CLI Exe: 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 0 321 | 0 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 0 334 | 0 335 | 336 | 337 | 338 | Select 339 | 340 | 341 | 342 | 343 | 344 | 345 | Clear 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 0 358 | 0 359 | 360 | 361 | 362 | CLI Arguments: 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 0 371 | 0 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | <html><head/><body><p>Send Command interval time (seconds):</p></body></html> 384 | 385 | 386 | 387 | 388 | 389 | 390 | 1 391 | 392 | 393 | 0.100000000000000 394 | 395 | 396 | 1.000000000000000 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | Qt::Orientation::Horizontal 409 | 410 | 411 | QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | buttonBox 421 | accepted() 422 | settingswindow 423 | accept() 424 | 425 | 426 | 248 427 | 254 428 | 429 | 430 | 157 431 | 274 432 | 433 | 434 | 435 | 436 | buttonBox 437 | rejected() 438 | settingswindow 439 | reject() 440 | 441 | 442 | 316 443 | 260 444 | 445 | 446 | 286 447 | 274 448 | 449 | 450 | 451 | 452 | 453 | -------------------------------------------------------------------------------- /src/Resources/tr/HeadsetControl_GUI_en.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWindow 6 | 7 | 8 | HeadsetControl-GUI 9 | 10 | 11 | 12 | 13 | <html><head/><body><p>Missing headsetcontrol!<br/>Download <a href="https://github.com/Sapd/HeadsetControl/releases/latest"><span style=" text-decoration: underline; color:#007af4;">headsetcontrol</span></a> in the program folder on in a $PATH directory.</p></body></html> 14 | 15 | 16 | 17 | 18 | Open Program Folder 19 | 20 | 21 | 22 | 23 | HeadsetControl couldn't find any compatible or working headsets. :( 24 | 25 | 26 | 27 | 28 | Device: 29 | Vendor: 30 | Model: 31 | 32 | 33 | 34 | 35 | No info of the device 36 | 37 | 38 | 39 | 40 | Battery: 41 | 42 | 43 | 44 | 45 | No compatible Device found! 46 | 47 | 48 | 49 | 50 | Other 51 | 52 | 53 | 54 | 55 | Lights: 56 | 57 | 58 | 59 | 60 | RGB OFF 61 | 62 | 63 | 64 | 65 | RGB ON 66 | 67 | 68 | 69 | 70 | Sidetone Level: 71 | 72 | 73 | 74 | 75 | Quiet (Off) 76 | 77 | 78 | 79 | 80 | 81 | Loud 82 | 83 | 84 | 85 | 86 | Voice Prompts: 87 | 88 | 89 | 90 | 91 | Voice Off 92 | 93 | 94 | 95 | 96 | Voice On 97 | 98 | 99 | 100 | 101 | Notification Sound: 102 | 103 | 104 | 105 | 106 | Test 0 107 | 108 | 109 | 110 | 111 | Test 1 112 | 113 | 114 | 115 | 116 | Inactivity Timer: 117 | 118 | 119 | 120 | 121 | 0 Minute (Off) 122 | 123 | 124 | 125 | 126 | 90 Minutes 127 | 128 | 129 | 130 | 131 | Chatmix: 132 | 133 | 134 | 135 | 136 | 137 | None 138 | 139 | 140 | 141 | 142 | Equalizer 143 | 144 | 145 | 146 | 147 | Equalizer preset: 148 | 149 | 150 | 151 | 152 | Equalizer: 153 | 154 | 155 | 156 | 157 | LiveUpdate 158 | 159 | 160 | 161 | 162 | Lows 163 | 164 | 165 | 166 | 167 | Mids 168 | 169 | 170 | 171 | 172 | Highs 173 | 174 | 175 | 176 | 177 | Apply Equalizer 178 | 179 | 180 | 181 | 182 | Volume Limiter: 183 | 184 | 185 | 186 | 187 | Limiter Off 188 | 189 | 190 | 191 | 192 | Limiter On 193 | 194 | 195 | 196 | 197 | Microphone 198 | 199 | 200 | 201 | 202 | Rotate to mute: 203 | 204 | 205 | 206 | 207 | Off 208 | 209 | 210 | 211 | 212 | On 213 | 214 | 215 | 216 | 217 | Muted led brightness: 218 | 219 | 220 | 221 | 222 | Low (Off) 223 | 224 | 225 | 226 | 227 | High 228 | 229 | 230 | 231 | 232 | Microphone volume: 233 | 234 | 235 | 236 | 237 | Quiet 238 | 239 | 240 | 241 | 242 | Bluetooth 243 | 244 | 245 | 246 | 247 | Bluetooth when powered on: 248 | 249 | 250 | 251 | 252 | Bluetooth Off 253 | 254 | 255 | 256 | 257 | Bluetooth On 258 | 259 | 260 | 261 | 262 | Bluetooth call volume: 263 | 264 | 265 | 266 | 267 | BT and PC 268 | 269 | 270 | 271 | 272 | PC -12dB 273 | 274 | 275 | 276 | 277 | BT only 278 | 279 | 280 | 281 | 282 | File 283 | 284 | 285 | 286 | 287 | Help 288 | 289 | 290 | 291 | 292 | Check Updates 293 | 294 | 295 | 296 | 297 | About 298 | 299 | 300 | 301 | 302 | 303 | Credits 304 | 305 | 306 | 307 | 308 | Load Device 309 | 310 | 311 | 312 | 313 | Settings 314 | 315 | 316 | 317 | 318 | Reload UI 319 | 320 | 321 | 322 | 323 | Hide/Show 324 | 325 | 326 | 327 | 328 | Turn Lights On 329 | 330 | 331 | 332 | 333 | Turn Lights Off 334 | 335 | 336 | 337 | 338 | Toggle Sidetone 339 | 340 | 341 | 342 | 343 | Exit 344 | 345 | 346 | 347 | 348 | Headset Off 349 | 350 | 351 | 352 | 353 | HeadsetControl 354 | Headset Off 355 | 356 | 357 | 358 | 359 | Headset Disconnected 360 | 361 | 362 | 363 | 364 | Your headset is disconnected or has been turned off 365 | 366 | 367 | 368 | 369 | % - Charging 370 | 371 | 372 | 373 | 374 | The battery has been charged to 100% 375 | 376 | 377 | 378 | 379 | HeadsetControl 380 | Battery: 381 | 382 | 383 | 384 | 385 | Battery Alert! 386 | 387 | 388 | 389 | 390 | The battery of your headset is running low 391 | 392 | 393 | 394 | 395 | Game 396 | 397 | 398 | 399 | 400 | Chat 401 | 402 | 403 | 404 | 405 | Neutral 406 | 407 | 408 | 409 | 410 | Check for updates 411 | 412 | 413 | 414 | 415 | 416 | up-to date v 417 | 418 | 419 | 420 | 421 | HeadsetControl 422 | Battery: Charging - 423 | 424 | 425 | 426 | 427 | Battery Charged! 428 | 429 | 430 | 431 | 432 | % - Descharging 433 | 434 | 435 | 436 | 437 | Error getting battery info: 438 | 439 | 440 | 441 | 442 | HeadsetControl 443 | Battery: Error 444 | 445 | 446 | 447 | 448 | Headset Error 449 | 450 | 451 | 452 | 453 | Error getting battery info, headset might be turned off 454 | 455 | 456 | 457 | 458 | 459 | Newer version 460 | 461 | 462 | 463 | 464 | About this program 465 | 466 | 467 | 468 | 469 | You can find HeadsetControl-GUI source code on <a href='https://github.com/LeoKlaus/HeadsetControl-GUI'>GitHub</a>.<br/>Made by:<br/> - <a href='https://github.com/LeoKlaus'>LeoKlaus</a><br/> - <a href='https://github.com/nicola02nb'>nicola02nb</a><br/>Version: 470 | 471 | 472 | 473 | 474 | Big shout-out to:<br/> - <a href='https://github.com/Sapd'>Sapd</a> for <a href='https://github.com/Sapd/HeadsetControl'>HeadsetCoontrol 475 | 476 | 477 | 478 | 479 | loaddevicewindow 480 | 481 | 482 | Select device to load 483 | 484 | 485 | 486 | 487 | Select device: 488 | 489 | 490 | 491 | 492 | settingswindow 493 | 494 | 495 | Settings 496 | 497 | 498 | 499 | 500 | Run on Startup: 501 | 502 | 503 | 504 | 505 | Battery low notification: 506 | 507 | 508 | 509 | 510 | Battery low threshold: 511 | 512 | 513 | 514 | 515 | Headset disconnected notification: 516 | 517 | 518 | 519 | 520 | Update info interval time (seconds): 521 | Default: 30 seconds 522 | DON'T PUT TOO LOW VALUES 523 | 524 | 525 | 526 | 527 | CLI Exe: 528 | 529 | 530 | 531 | 532 | Select 533 | 534 | 535 | 536 | 537 | Clear 538 | 539 | 540 | 541 | 542 | CLI Arguments: 543 | 544 | 545 | 546 | 547 | <html><head/><body><p>Send Command interval time (seconds):</p></body></html> 548 | 549 | 550 | 551 | 552 | Battery full notification: 553 | 554 | 555 | 556 | 557 | Enable audio notifications: 558 | 559 | 560 | 561 | 562 | Custom Style: 563 | 564 | 565 | 566 | 567 | Load 568 | 569 | 570 | 571 | 572 | Remove 573 | 574 | 575 | 576 | 577 | -------------------------------------------------------------------------------- /src/Resources/tr/HeadsetControl_GUI_it.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWindow 6 | 7 | 8 | HeadsetControl-GUI 9 | HeadsetControl-GUI 10 | 11 | 12 | 13 | <html><head/><body><p>Missing headsetcontrol!<br/>Download <a href="https://github.com/Sapd/HeadsetControl/releases/latest"><span style=" text-decoration: underline; color:#007af4;">headsetcontrol</span></a> in the program folder on in a $PATH directory.</p></body></html> 14 | <html><head/><body><p>Manca headsetcontrol!<br/>Scaricalo <a href="https://github.com/Sapd/HeadsetControl/releases/latest"><span style=" text-decoration: underline; color:#007af4;">headsetcontrol</span></a> nella cartella del programma o dentro un percorso $PATH.</p></body></html> 15 | 16 | 17 | 18 | Open Program Folder 19 | Apri Cartella del Programma 20 | 21 | 22 | 23 | HeadsetControl couldn't find any compatible or working headsets. :( 24 | HeadsetControl non è riuscito a trovare delle cuffie funizionanti o compatibili. :( 25 | 26 | 27 | 28 | Device: 29 | Vendor: 30 | Model: 31 | Dispositio: 32 | Distributore: 33 | Modello: 34 | 35 | 36 | 37 | No info of the device 38 | Nessuna informazione sul dipositivo 39 | 40 | 41 | 42 | Battery: 43 | Batteria: 44 | 45 | 46 | 47 | No compatible Device found! 48 | Nessun dispositivo compatibile è stato trovato! 49 | 50 | 51 | 52 | Other 53 | Altro 54 | 55 | 56 | 57 | Lights: 58 | Luci: 59 | 60 | 61 | 62 | RGB OFF 63 | RGB OFF 64 | 65 | 66 | 67 | RGB ON 68 | RGB ON 69 | 70 | 71 | 72 | Sidetone Level: 73 | Tono Laterale: 74 | 75 | 76 | 77 | Quiet (Off) 78 | Silenzioso (Spento) 79 | 80 | 81 | 82 | 83 | Loud 84 | Forte 85 | 86 | 87 | 88 | Voice Prompts: 89 | Istruzioni Vocali: 90 | 91 | 92 | 93 | Voice Off 94 | Voce Accesa 95 | 96 | 97 | 98 | Voice On 99 | Voce Spenta 100 | 101 | 102 | 103 | Notification Sound: 104 | Suono di Notifica: 105 | 106 | 107 | 108 | Test 0 109 | Prova 0 110 | 111 | 112 | 113 | Test 1 114 | Prova 1 115 | 116 | 117 | 118 | Inactivity Timer: 119 | Tempo di Inattività: 120 | 121 | 122 | 123 | 0 Minute (Off) 124 | 0 Minuti (Spento) 125 | 126 | 127 | 128 | 90 Minutes 129 | 90 Minuti 130 | 131 | 132 | 133 | Chatmix: 134 | Chatmix: 135 | 136 | 137 | 138 | 139 | None 140 | Nessun valore 141 | 142 | 143 | 144 | Equalizer 145 | Equalizzatore 146 | 147 | 148 | 149 | Equalizer preset: 150 | Preset Equalizzatore: 151 | 152 | 153 | 154 | Equalizer: 155 | Equalizzatore: 156 | 157 | 158 | 159 | LiveUpdate 160 | Aggiornamento Live 161 | 162 | 163 | 164 | Lows 165 | Bassi 166 | 167 | 168 | 169 | Mids 170 | Medi 171 | 172 | 173 | 174 | Highs 175 | Alti 176 | 177 | 178 | 179 | Apply Equalizer 180 | Applica Equalizzatore 181 | 182 | 183 | 184 | Volume Limiter: 185 | Limitatore Volume: 186 | 187 | 188 | 189 | Limiter Off 190 | Limitatore Spento 191 | 192 | 193 | 194 | Limiter On 195 | Limitatore Acceso 196 | 197 | 198 | 199 | Microphone 200 | Microfono 201 | 202 | 203 | 204 | Rotate to mute: 205 | Ruota per mutare: 206 | 207 | 208 | 209 | Off 210 | Spento 211 | 212 | 213 | 214 | On 215 | Acceso 216 | 217 | 218 | 219 | Muted led brightness: 220 | Luminosità microfono mutato: 221 | 222 | 223 | 224 | Low (Off) 225 | Basso (Spento) 226 | 227 | 228 | 229 | High 230 | Alto 231 | 232 | 233 | 234 | Microphone volume: 235 | Volume microfono: 236 | 237 | 238 | 239 | Quiet 240 | Basso 241 | 242 | 243 | 244 | Bluetooth 245 | Bluetooth 246 | 247 | 248 | 249 | Bluetooth when powered on: 250 | Bluetooth quando accese: 251 | 252 | 253 | 254 | Bluetooth Off 255 | Bluetooth Spento 256 | 257 | 258 | 259 | Bluetooth On 260 | Bluetooth Acceso 261 | 262 | 263 | 264 | Bluetooth call volume: 265 | Bluetoot volume chiamata: 266 | 267 | 268 | 269 | BT and PC 270 | BT e PC 271 | 272 | 273 | 274 | PC -12dB 275 | PC -12dB 276 | 277 | 278 | 279 | BT only 280 | Solo BT 281 | 282 | 283 | 284 | File 285 | File 286 | 287 | 288 | 289 | Help 290 | Aiuto 291 | 292 | 293 | 294 | Check Updates 295 | Controlla Aggiornamenti 296 | 297 | 298 | 299 | About 300 | About 301 | 302 | 303 | 304 | 305 | Credits 306 | Crediti 307 | 308 | 309 | 310 | Load Device 311 | Carica Dispositivo 312 | 313 | 314 | 315 | Settings 316 | Impostazioni 317 | 318 | 319 | 320 | Reload UI 321 | Ricarica UI 322 | 323 | 324 | 325 | Hide/Show 326 | Nascondi/Mostra 327 | 328 | 329 | 330 | Turn Lights On 331 | Accendi le Luci 332 | 333 | 334 | 335 | Turn Lights Off 336 | Spegni le Luci 337 | 338 | 339 | 340 | Toggle Sidetone 341 | Attiva/disattiva tono laterale 342 | 343 | 344 | 345 | Exit 346 | Esci 347 | 348 | 349 | 350 | Headset Off 351 | Cuffie Spente 352 | 353 | 354 | 355 | HeadsetControl 356 | Headset Off 357 | HeadsetControl 358 | Cuffie Spente 359 | 360 | 361 | 362 | Headset Disconnected 363 | Cuffie disconnesse 364 | 365 | 366 | 367 | Your headset is disconnected or has been turned off 368 | Le tue cuffie sono disconnesse o sono state spente 369 | 370 | 371 | 372 | % - Charging 373 | % - In Carica 374 | 375 | 376 | 377 | The battery has been charged to 100% 378 | La batteria è stata caricata al 100% 379 | 380 | 381 | 382 | % - Descharging 383 | % - Batteria in scarica 384 | 385 | 386 | 387 | HeadsetControl 388 | Battery: 389 | HeadsetControl 390 | Batteria: 391 | 392 | 393 | 394 | Battery Alert! 395 | Attenzione Batteria! 396 | 397 | 398 | 399 | The battery of your headset is running low 400 | La batteria delle tue cuffie è scarica 401 | 402 | 403 | 404 | Game 405 | Gioco 406 | 407 | 408 | 409 | Chat 410 | Chat 411 | 412 | 413 | 414 | Neutral 415 | Neutral 416 | 417 | 418 | 419 | Check for updates 420 | Controlla Aggirnamenti 421 | 422 | 423 | 424 | 425 | up-to date v 426 | aggiornato v 427 | 428 | 429 | 430 | HeadsetControl 431 | Battery: Charging - 432 | HeadsetControl 433 | Batteria: In Carica - 434 | 435 | 436 | 437 | Battery Charged! 438 | Batteria Carica! 439 | 440 | 441 | 442 | Error getting battery info: 443 | Errore leggendo info battteria: 444 | 445 | 446 | 447 | HeadsetControl 448 | Battery: Error 449 | HeadsetControl Batteria: Errore 450 | 451 | 452 | 453 | Headset Error 454 | Errore cuffie 455 | 456 | 457 | 458 | Error getting battery info, headset might be turned off 459 | Errore leggendo le info della batteria, le cuffie potrebbero essere spente 460 | 461 | 462 | 463 | 464 | Newer version 465 | Nuova versione 466 | 467 | 468 | 469 | About this program 470 | 471 | 472 | 473 | 474 | You can find HeadsetControl-GUI source code on <a href='https://github.com/LeoKlaus/HeadsetControl-GUI'>GitHub</a>.<br/>Made by:<br/> - <a href='https://github.com/LeoKlaus'>LeoKlaus</a><br/> - <a href='https://github.com/nicola02nb'>nicola02nb</a><br/>Version: 475 | Puoi trovare il codice sorgente di HeadsetControl-GUI su <a href='https://github.com/LeoKlaus/HeadsetControl-GUI'>GitHub</a>.<br/>Fatto da:<br/> - <a href='https://github.com/LeoKlaus'>LeoKlaus</a><br/> - <a href='https://github.com/nicola02nb'>nicola02nb</a><br/>Versione: 476 | 477 | 478 | 479 | Big shout-out to:<br/> - <a href='https://github.com/Sapd'>Sapd</a> for <a href='https://github.com/Sapd/HeadsetControl'>HeadsetCoontrol 480 | Un grande riconoscimento va a:<br/> - <a href='https://github.com/Sapd'>Sapd</a> per <a href='https://github.com/Sapd/HeadsetControl'>HeadsetCoontrol 481 | 482 | 483 | 484 | loaddevicewindow 485 | 486 | 487 | Select device to load 488 | Seleziona dispositivo da caricare 489 | 490 | 491 | 492 | Select device: 493 | Seleziona dispositivo: 494 | 495 | 496 | 497 | settingswindow 498 | 499 | 500 | Settings 501 | Impostazioni 502 | 503 | 504 | 505 | Run on Startup: 506 | Esecuzione all'avvio: 507 | 508 | 509 | 510 | Battery low notification: 511 | Notifica batteria scarica: 512 | 513 | 514 | 515 | Battery low threshold: 516 | Soglia batteria scarica: 517 | 518 | 519 | 520 | Headset disconnected notification: 521 | Notifica di disconnessione delle cuffie: 522 | 523 | 524 | 525 | Update info interval time (seconds): 526 | Default: 30 seconds 527 | DON'T PUT TOO LOW VALUES 528 | Intervallo di aggiornamento info (secondi): 529 | Predefinito: 30 secondi 530 | NON IMPOSTARE VALORI TROPPO BASSI 531 | 532 | 533 | 534 | CLI Exe: 535 | CLI Exe: 536 | 537 | 538 | 539 | Select 540 | Seleziona 541 | 542 | 543 | 544 | Clear 545 | Pulisci 546 | 547 | 548 | 549 | CLI Arguments: 550 | Argomenti CLI: 551 | 552 | 553 | 554 | <html><head/><body><p>Send Command interval time (seconds):</p></body></html> 555 | <html><head/><body><p>Tempo di intervallo invio comando (secondi):</p></body></html> 556 | 557 | 558 | 559 | Battery full notification: 560 | Notifica batteria carica: 561 | 562 | 563 | 564 | Enable audio notifications: 565 | Abillita notifica audio: 566 | 567 | 568 | 569 | Custom Style: 570 | Stile personalizzato: 571 | 572 | 573 | 574 | Load 575 | Carica 576 | 577 | 578 | 579 | Remove 580 | Rimuovi 581 | 582 | 583 | 584 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /src/UI/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include "device.h" 5 | #include "dialoginfo.h" 6 | #include "headsetcontrolapi.h" 7 | #include "loaddevicewindow.h" 8 | #include "settingswindow.h" 9 | #include "utils.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | MainWindow::MainWindow(QWidget *parent) 18 | : QMainWindow(parent) 19 | , ui(new Ui::MainWindow) 20 | , trayIcon(new QSystemTrayIcon(this)) 21 | , trayMenu(new QMenu(this)) 22 | , timerGUI(new QTimer(this)) 23 | , timerCommand(new QTimer(this)) 24 | , API(HeadsetControlAPI(HEADSETCONTROL_DIRECTORY)) 25 | { 26 | qDebug() << "Headsetcontrol"; 27 | qDebug() << "Name:" << API.getName(); 28 | qDebug() << "Version:" << API.getVersion(); 29 | qDebug() << "ApiVersion:" << API.getApiVersion(); 30 | qDebug() << "HidApiVersion:" << API.getHidApiVersion(); 31 | qDebug(); 32 | qDebug() << "Headsetcontrol-GUI"; 33 | qDebug() << "Version" << qApp->applicationVersion(); 34 | qDebug() << "AppPath" << PROGRAM_CONFIG_PATH; 35 | qDebug() << "ConfigPath" << PROGRAM_CONFIG_PATH; 36 | qDebug() << "SettingsPath" << PROGRAM_SETTINGS_FILEPATH; 37 | qDebug(); 38 | 39 | QDir().mkpath(PROGRAM_CONFIG_PATH); 40 | settings = loadSettingsFromFile(PROGRAM_SETTINGS_FILEPATH); 41 | API.setSelectedDevice(settings.lastSelectedVendorID, settings.lastSelectedProductID); 42 | defaultStyle = styleSheet(); 43 | 44 | setupTrayIcon(); 45 | ui->setupUi(this); 46 | bindEvents(); 47 | 48 | updateIconsTheme(); 49 | updateStyle(); 50 | 51 | updateGUI(); 52 | 53 | connect(&API, &HeadsetControlAPI::actionSuccesful, this, &::MainWindow::saveDevicesSettings); 54 | 55 | connect(timerGUI, &QTimer::timeout, this, &::MainWindow::updateGUI); 56 | timerGUI->start(settings.msecUpdateIntervalTime); 57 | connect(timerCommand, &QTimer::timeout, this, &::MainWindow::sendCommand); 58 | if(settings.commandExe != ""){ 59 | timerCommand->start(settings.msecCommandIntervalTime); 60 | } 61 | } 62 | 63 | void sobstituteArgs(QStringList &args, const Device* device){ 64 | for(int i=0; ichatmix)); 66 | } 67 | } 68 | 69 | void MainWindow::sendCommand(){ 70 | API.updateChatMix(); 71 | QProcess *proc = new QProcess(); 72 | QStringList args = QStringList(); 73 | if(settings.commandArgs != ""){ 74 | args = settings.commandArgs.split(" "); 75 | } 76 | 77 | sobstituteArgs(args, selectedDevice); 78 | proc->setProgram(settings.commandExe); 79 | proc->setArguments(args); 80 | 81 | proc->start(); 82 | proc->waitForFinished(); 83 | qDebug() << "Command Output:\t" << proc->readAllStandardOutput(); 84 | qDebug() << "ExitStatus:\t" << proc->exitStatus(); 85 | qDebug() << "ExitCode:\t" << proc->exitCode(); 86 | } 87 | 88 | MainWindow::~MainWindow() 89 | { 90 | timerGUI->stop(); 91 | timerCommand->stop(); 92 | delete timerGUI; 93 | delete timerCommand; 94 | delete trayMenu; 95 | delete trayIcon; 96 | delete ui; 97 | } 98 | 99 | void MainWindow::changeEvent(QEvent *e) 100 | { 101 | switch (e->type()) { 102 | case QEvent::ThemeChange: 103 | updateIconsTheme(); 104 | break; 105 | case QEvent::WindowStateChange: 106 | if (windowState() == Qt::WindowMinimized) { 107 | hide(); 108 | } 109 | break; 110 | default: 111 | break; 112 | } 113 | 114 | QMainWindow::changeEvent(e); 115 | } 116 | 117 | void MainWindow::bindEvents() 118 | { 119 | // Tool Bar 120 | connect(ui->actionLoad_Device, &QAction::triggered, this, &MainWindow::selectDevice); 121 | connect(ui->actionReload_UI, &QAction::triggered, this, &MainWindow::updateGUI); 122 | connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::editProgramSetting); 123 | connect(ui->actionCheck_Updates, &QAction::triggered, this, &MainWindow::checkForUpdates); 124 | 125 | connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAbout); 126 | connect(ui->actionCredits, &QAction::triggered, this, &MainWindow::showCredits); 127 | 128 | //Error frames 129 | connect(ui->openfolderPushButton, &QPushButton::clicked, this, [=]() { 130 | openFileExplorer(PROGRAM_APP_DIRECTORY); 131 | }); 132 | 133 | // Other Section 134 | connect(ui->onlightButton, &QPushButton::clicked, this, [=]() { 135 | API.setLights(true); 136 | }); 137 | connect(ui->offlightButton, &QPushButton::clicked, this, [=]() { 138 | API.setLights(false); 139 | }); 140 | connect(ui->sidetoneSlider, &QSlider::sliderReleased, this, [=]() { 141 | int sliderValue = ui->sidetoneSlider->value(); 142 | if (sliderValue != 0) selectedDevice->previous_sidetone = sliderValue; 143 | API.setSidetone(sliderValue); 144 | }); 145 | connect(ui->voiceOnButton, &QPushButton::clicked, this, [=]() { 146 | API.setVoicePrompts(true); 147 | }); 148 | connect(ui->voiceOffButton, &QPushButton::clicked, this, [=]() { 149 | API.setVoicePrompts(false); 150 | }); 151 | connect(ui->notification0Button, &QPushButton::clicked, this, [=]() { 152 | API.playNotificationSound(0); 153 | }); 154 | connect(ui->notification1Button, &QPushButton::clicked, this, [=]() { 155 | API.playNotificationSound(1); 156 | }); 157 | connect(ui->inactivitySlider, &QSlider::sliderReleased, this, [=]() { 158 | API.setInactiveTime(ui->inactivitySlider->value()); 159 | }); 160 | 161 | // Equalizer Section 162 | connect(ui->equalizerPresetcomboBox, 163 | &QComboBox::activated, 164 | this, 165 | &MainWindow::equalizerPresetChanged); 166 | connect(ui->equalizerliveupdateCheckBox, &QCheckBox::checkStateChanged, this, [=](int state) { 167 | equalizerLiveUpdate = (state == Qt::Checked); 168 | }); 169 | connect(ui->applyEqualizer, &QPushButton::clicked, this, [=]() { applyEqualizer(); }); 170 | connect(ui->volumelimiterOffButton, &QPushButton::clicked, this, [=]() { 171 | API.setVolumeLimiter(false); 172 | }); 173 | connect(ui->volumelimiterOnButton, &QPushButton::clicked, this, [=]() { 174 | API.setVolumeLimiter(true); 175 | }); 176 | 177 | // Microphone Section 178 | connect(ui->muteledbrightnessSlider, &QSlider::sliderReleased, this, [=]() { 179 | API.setMuteLedBrightness(ui->muteledbrightnessSlider->value()); 180 | }); 181 | connect(ui->micvolumeSlider, &QSlider::sliderReleased, this, [=]() { 182 | API.setMicrophoneVolume(ui->micvolumeSlider->value()); 183 | }); 184 | connect(ui->rotateOn, &QPushButton::clicked, this, [=]() { 185 | API.setRotateToMute(true); 186 | }); 187 | connect(ui->rotateOff, &QPushButton::clicked, this, [=]() { 188 | API.setRotateToMute(false); 189 | }); 190 | 191 | // Bluetooth Section 192 | connect(ui->btwhenonOffButton, &QPushButton::clicked, this, [=]() { 193 | API.setBluetoothWhenPoweredOn(false); 194 | }); 195 | connect(ui->btwhenonOnButton, &QPushButton::clicked, this, [=]() { 196 | API.setBluetoothWhenPoweredOn(true); 197 | }); 198 | connect(ui->btbothRadioButton, &QRadioButton::clicked, this, [=]() { 199 | API.setBluetoothCallVolume(0); 200 | }); 201 | connect(ui->btpcdbRadioButton, &QRadioButton::clicked, this, [=]() { 202 | API.setBluetoothCallVolume(1); 203 | }); 204 | connect(ui->btonlyRadioButton, &QRadioButton::clicked, this, [=]() { 205 | API.setBluetoothCallVolume(2); 206 | }); 207 | } 208 | 209 | //Tray Icon Section 210 | void MainWindow::changeTrayIconTo(QString iconName) 211 | { 212 | trayIconName = iconName; 213 | trayIcon->setIcon(QIcon::fromTheme(iconName)); 214 | } 215 | 216 | void MainWindow::setupTrayIcon() 217 | { 218 | changeTrayIconTo("headphones"); 219 | trayIcon->setToolTip("HeadsetControl"); 220 | 221 | trayMenu->addAction(tr("Hide/Show"), this, &MainWindow::toggleWindow); 222 | ledOn = trayMenu->addAction(tr("Turn Lights On"), &API, [=]() { 223 | API.setLights(true); 224 | }); 225 | ledOff = trayMenu->addAction(tr("Turn Lights Off"), &API, [=]() { 226 | API.setLights(false); 227 | }); 228 | trayMenu->addAction(tr("Toggle Sidetone"), &API, [=]() { 229 | int previousSidetone = selectedDevice->previous_sidetone; 230 | int currentSidetone = selectedDevice->sidetone; 231 | 232 | selectedDevice->previous_sidetone = currentSidetone; 233 | 234 | if (currentSidetone > 0) API.setSidetone(0, true); 235 | else if (previousSidetone <= 0 && currentSidetone == 0) { 236 | API.setSidetone(128, true); 237 | selectedDevice->previous_sidetone = 128; 238 | } 239 | else API.setSidetone(previousSidetone, true); 240 | 241 | ui->sidetoneSlider->setSliderPosition(API.getSelectedDevice()->sidetone); 242 | }); 243 | 244 | trayMenu->addAction(tr("Exit"), this, &QApplication::quit); 245 | 246 | trayIcon->setContextMenu(trayMenu); 247 | trayIcon->connect(trayIcon, 248 | SIGNAL(activated(QSystemTrayIcon::ActivationReason)), 249 | this, 250 | SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason))); 251 | trayIcon->show(); 252 | } 253 | 254 | void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) 255 | { 256 | if (reason == QSystemTrayIcon::ActivationReason::Trigger) { 257 | toggleWindow(); 258 | } 259 | } 260 | 261 | //Theme mode Section 262 | bool MainWindow::isAppDarkMode() 263 | { 264 | Qt::ColorScheme scheme = qApp->styleHints()->colorScheme(); 265 | if (scheme == Qt::ColorScheme::Dark) 266 | return true; 267 | return false; 268 | } 269 | 270 | void MainWindow::updateIconsTheme() 271 | { 272 | if (isAppDarkMode()) { 273 | QIcon::setThemeName("light"); 274 | } else { 275 | QIcon::setThemeName("dark"); 276 | } 277 | setWindowIcon(QIcon::fromTheme("headphones")); 278 | changeTrayIconTo(trayIconName); 279 | } 280 | 281 | void MainWindow::updateStyle() 282 | { 283 | if (settings.styleName != "Default") { 284 | QString destination = PROGRAM_STYLES_PATH + "/" + settings.styleName; 285 | QFile file(destination); 286 | if (file.open(QFile::ReadOnly)) { 287 | QString styleSheet = QLatin1String(file.readAll()); 288 | setStyleSheet(styleSheet); 289 | } 290 | } else { 291 | setStyleSheet(defaultStyle); 292 | } 293 | rescaleAndMoveWindow(); 294 | } 295 | 296 | void MainWindow::showEvent(QShowEvent *event) 297 | { 298 | QMainWindow::showEvent(event); 299 | rescaleAndMoveWindow(); 300 | if (firstShow) { 301 | checkForUpdates(firstShow); 302 | firstShow = false; 303 | } 304 | } 305 | 306 | //Window Position and Size Section 307 | void MainWindow::toggleWindow() 308 | { 309 | if (isHidden()) { 310 | show(); 311 | } else { 312 | hide(); 313 | } 314 | } 315 | 316 | void MainWindow::minimizeWindowSize() 317 | { 318 | adjustSize(); 319 | } 320 | 321 | void MainWindow::moveToBottomRight() 322 | { 323 | QScreen *screen = QGuiApplication::primaryScreen(); 324 | QSize screenSize = screen->availableSize(); 325 | QSize finalPosition = screenSize - sizeHint(); 326 | move(finalPosition.width() - 5, finalPosition.height() - 35); 327 | } 328 | 329 | void MainWindow::rescaleAndMoveWindow() 330 | { 331 | minimizeWindowSize(); 332 | moveToBottomRight(); 333 | } 334 | 335 | void MainWindow::resetGUI() 336 | { 337 | trayIcon->setIcon(QIcon::fromTheme("headphones")); 338 | trayIcon->setToolTip("HeadsetControl"); 339 | ledOn->setEnabled(false); 340 | ledOff->setEnabled(false); 341 | 342 | ui->missingheadsetcontrolFrame->setHidden(false); 343 | ui->notSupportedFrame->setHidden(false); 344 | 345 | ui->deviceinfoFrame->setHidden(true); 346 | ui->batteryFrame->setHidden(true); 347 | 348 | ui->tabWidget->hide(); 349 | ui->tabWidget->setTabEnabled(3, false); 350 | ui->tabWidget->setTabEnabled(2, false); 351 | ui->tabWidget->setTabEnabled(1, false); 352 | ui->tabWidget->setTabEnabled(0, false); 353 | 354 | ui->lightFrame->setHidden(true); 355 | ui->voicepromptFrame->setHidden(true); 356 | ui->notificationFrame->setHidden(true); 357 | ui->sidetoneFrame->setHidden(true); 358 | ui->inactivityFrame->setHidden(true); 359 | ui->chatmixFrame->setHidden(true); 360 | ui->volumelimiterFrame->setHidden(true); 361 | 362 | ui->equalizerpresetFrame->setHidden(true); 363 | ui->equalizerFrame->setHidden(true); 364 | ui->applyEqualizer->setEnabled(false); 365 | clearEqualizerSliders(); 366 | 367 | ui->rotatetomuteFrame->setHidden(true); 368 | ui->muteledbrightnessFrame->setHidden(true); 369 | ui->micvolumeFrame->setHidden(true); 370 | 371 | ui->btwhenonFrame->setHidden(true); 372 | ui->btcallvolumeFrame->setHidden(true); 373 | } 374 | 375 | //Utility Section 376 | void MainWindow::sendAppNotification(const QString &title, 377 | const QString &description, 378 | const QString &icon) 379 | { 380 | trayIcon->showMessage(title, description, QIcon::fromTheme(icon)); 381 | } 382 | 383 | //Devices Managing Section 384 | void MainWindow::loadDevice() 385 | { 386 | resetGUI(); 387 | 388 | if (firstRun){ 389 | notified = true; 390 | firstRun = false; 391 | } 392 | 393 | 394 | selectedDevice = API.getSelectedDevice(); 395 | if (selectedDevice == nullptr) { 396 | API.setSelectedDevice("0", "0"); 397 | ui->missingheadsetcontrolFrame->setHidden(true); 398 | rescaleAndMoveWindow(); 399 | return; 400 | } else { 401 | QList savedDevices = getSavedDevices(); 402 | selectedDevice->updateConfig(savedDevices); 403 | deleteDevices(savedDevices); 404 | } 405 | 406 | QSet &capabilities = selectedDevice->capabilities; 407 | 408 | ui->missingheadsetcontrolFrame->setHidden(true); 409 | ui->notSupportedFrame->setHidden(true); 410 | 411 | qDebug() << "Selected Device"; 412 | qDebug() << "Device:\t" << selectedDevice->device; 413 | qDebug() << "Caps:\t" << selectedDevice->capabilities; 414 | 415 | // Info section 416 | ui->deviceinfovalueLabel->setText(selectedDevice->device + "
" + selectedDevice->vendor 417 | + "
" + selectedDevice->product); 418 | ui->deviceinfoFrame->setHidden(false); 419 | if (capabilities.contains("CAP_BATTERY_STATUS")) { 420 | ui->batteryFrame->setHidden(false); 421 | setBatteryStatus(); 422 | qDebug() << "Battery:\t" << selectedDevice->battery.status 423 | << QString::number(selectedDevice->battery.level); 424 | } 425 | 426 | ui->tabWidget->show(); 427 | // Other Section 428 | if (capabilities.contains("CAP_LIGHTS")) { 429 | ui->lightFrame->setHidden(false); 430 | ui->tabWidget->setTabEnabled(0, true); 431 | ledOn->setEnabled(true); 432 | ledOff->setEnabled(true); 433 | } 434 | if (capabilities.contains("CAP_SIDETONE")) { 435 | ui->sidetoneFrame->setHidden(false); 436 | ui->tabWidget->setTabEnabled(0, true); 437 | } 438 | if (capabilities.contains("CAP_VOICE_PROMPTS")) { 439 | ui->voicepromptFrame->setHidden(false); 440 | ui->tabWidget->setTabEnabled(0, true); 441 | } 442 | if (capabilities.contains("CAP_NOTIFICATION_SOUND")) { 443 | ui->notificationFrame->setHidden(false); 444 | ui->tabWidget->setTabEnabled(0, true); 445 | } 446 | if (capabilities.contains("CAP_INACTIVE_TIME")) { 447 | ui->inactivityFrame->setHidden(false); 448 | ui->tabWidget->setTabEnabled(0, true); 449 | } 450 | if (capabilities.contains("CAP_CHATMIX_STATUS")) { 451 | ui->chatmixFrame->setHidden(false); 452 | ui->tabWidget->setTabEnabled(0, true); 453 | setChatmixStatus(); 454 | qDebug() << "Chatmix:\t" << QString::number(selectedDevice->chatmix); 455 | } 456 | // Equalizer Section 457 | if (capabilities.contains("CAP_EQUALIZER_PRESET") && !selectedDevice->presets_list.empty()) { 458 | ui->equalizerpresetFrame->setHidden(false); 459 | ui->tabWidget->setTabEnabled(1, true); 460 | } 461 | if (capabilities.contains("CAP_EQUALIZER") && selectedDevice->equalizer.bands_number > 0) { 462 | ui->equalizerFrame->setHidden(false); 463 | ui->tabWidget->setTabEnabled(1, true); 464 | } 465 | if (capabilities.contains("CAP_VOLUME_LIMITER")) { 466 | ui->volumelimiterFrame->setHidden(false); 467 | ui->tabWidget->setTabEnabled(1, true); 468 | } 469 | // Microphone Section 470 | if (capabilities.contains("CAP_ROTATE_TO_MUTE")) { 471 | ui->rotatetomuteFrame->setHidden(false); 472 | ui->tabWidget->setTabEnabled(2, true); 473 | } 474 | if (capabilities.contains("CAP_MICROPHONE_MUTE_LED_BRIGHTNESS")) { 475 | ui->muteledbrightnessFrame->setHidden(false); 476 | ui->tabWidget->setTabEnabled(2, true); 477 | } 478 | if (capabilities.contains("CAP_MICROPHONE_VOLUME")) { 479 | ui->micvolumeFrame->setHidden(false); 480 | ui->tabWidget->setTabEnabled(2, true); 481 | } 482 | // Bluetooth Section 483 | if (capabilities.contains("CAP_BT_WHEN_POWERED_ON")) { 484 | ui->btwhenonFrame->setHidden(false); 485 | ui->tabWidget->setTabEnabled(3, true); 486 | } 487 | if (capabilities.contains("CAP_BT_CALL_VOLUME")) { 488 | ui->btcallvolumeFrame->setHidden(false); 489 | ui->tabWidget->setTabEnabled(3, true); 490 | } 491 | 492 | qDebug(); 493 | loadGUIValues(); 494 | rescaleAndMoveWindow(); 495 | } 496 | 497 | void MainWindow::loadGUIValues() 498 | { 499 | if (selectedDevice->lights >= 0) { 500 | ui->onlightButton->setChecked(selectedDevice->lights); 501 | ui->offlightButton->setChecked(!selectedDevice->lights); 502 | } 503 | if (selectedDevice->sidetone >= 0) { 504 | ui->sidetoneSlider->setSliderPosition(selectedDevice->sidetone); 505 | } 506 | if (selectedDevice->voice_prompts >= 0) { 507 | ui->voiceOnButton->setChecked(selectedDevice->voice_prompts); 508 | ui->voiceOffButton->setChecked(!selectedDevice->voice_prompts); 509 | } 510 | if (selectedDevice->inactive_time >= 0) { 511 | ui->inactivitySlider->setSliderPosition(selectedDevice->inactive_time); 512 | } 513 | 514 | clearEqualizerSliders(); 515 | createEqualizerSliders(); 516 | 517 | ui->equalizerPresetcomboBox->clear(); 518 | for (EqualizerPreset &preset : selectedDevice->presets_list) { 519 | ui->equalizerPresetcomboBox->addItem(preset.name); 520 | } 521 | ui->equalizerPresetcomboBox->setCurrentIndex(-1); 522 | if (selectedDevice->equalizer_preset >= 0) { 523 | ui->equalizerPresetcomboBox->setCurrentIndex(selectedDevice->equalizer_preset); 524 | } else if (selectedDevice->equalizer_curve.length() == selectedDevice->equalizer.bands_number) { 525 | setEqualizerSliders(selectedDevice->equalizer_curve); 526 | } 527 | 528 | if (selectedDevice->volume_limiter >= 0) { 529 | ui->volumelimiterOnButton->setChecked(selectedDevice->volume_limiter); 530 | ui->volumelimiterOffButton->setChecked(!selectedDevice->volume_limiter); 531 | } 532 | 533 | if (selectedDevice->rotate_to_mute >= 0) { 534 | ui->rotateOn->setChecked(selectedDevice->rotate_to_mute); 535 | ui->rotateOff->setChecked(!selectedDevice->rotate_to_mute); 536 | } 537 | if (selectedDevice->mic_mute_led_brightness >= 0) { 538 | ui->muteledbrightnessSlider->setSliderPosition(selectedDevice->mic_mute_led_brightness); 539 | } 540 | if (selectedDevice->mic_volume >= 0) { 541 | ui->micvolumeSlider->setSliderPosition(selectedDevice->mic_volume); 542 | } 543 | 544 | if (selectedDevice->bt_call_volume >= 0) { 545 | switch (selectedDevice->bt_call_volume) { 546 | case 0: 547 | ui->btbothRadioButton->setChecked(true); 548 | break; 549 | case 1: 550 | ui->btpcdbRadioButton->setChecked(true); 551 | break; 552 | case 2: 553 | ui->btonlyRadioButton->setChecked(true); 554 | break; 555 | default: 556 | break; 557 | } 558 | } 559 | if (selectedDevice->bt_when_powered_on >= 0) { 560 | ui->btwhenonOnButton->setChecked(selectedDevice->bt_when_powered_on); 561 | ui->btwhenonOffButton->setChecked(!selectedDevice->bt_when_powered_on); 562 | } 563 | } 564 | 565 | void MainWindow::saveDevicesSettings() 566 | { 567 | QList toSave = getSavedDevices(); 568 | updateDeviceFromSource(toSave, selectedDevice); 569 | 570 | serializeDevices(toSave, DEVICES_SETTINGS_FILEPATH); 571 | 572 | deleteDevices(toSave); 573 | } 574 | 575 | QList MainWindow::getSavedDevices() 576 | { 577 | return deserializeDevices(DEVICES_SETTINGS_FILEPATH); 578 | } 579 | 580 | bool MainWindow::updateSelectedDevice() 581 | { 582 | QString lastStatus = ""; 583 | if (selectedDevice != nullptr){ 584 | lastStatus = selectedDevice->battery.status; 585 | } 586 | 587 | API.updateSelectedDevice(); 588 | bool stillTheSame = API.getSelectedDevice() != nullptr; 589 | if (stillTheSame) { 590 | API.setSelectedDevice("0","0"); 591 | selectedDevice = API.getSelectedDevice(); 592 | } 593 | 594 | if(selectedDevice != nullptr && lastStatus != selectedDevice->battery.status){ 595 | notified = false; 596 | } 597 | 598 | setBatteryStatus(); 599 | setChatmixStatus(); 600 | return stillTheSame; 601 | } 602 | 603 | //Update GUI Section 604 | void MainWindow::updateGUI() 605 | { 606 | if (!API.areApiAvailable()) { 607 | resetGUI(); 608 | ui->notSupportedFrame->setHidden(true); 609 | rescaleAndMoveWindow(); 610 | selectedDevice = nullptr; 611 | } else { 612 | if (selectedDevice == nullptr || !updateSelectedDevice()) { 613 | loadDevice(); 614 | } 615 | } 616 | } 617 | 618 | // Info Section Events 619 | void MainWindow::setBatteryStatus() 620 | { 621 | if (selectedDevice == nullptr) { 622 | changeTrayIconTo("headphones"); 623 | return; 624 | } 625 | 626 | QString status = selectedDevice->battery.status; 627 | int batteryLevel = selectedDevice->battery.level; 628 | QString level = QString::number(batteryLevel); 629 | 630 | if (batteryLevel >= 0) { 631 | ui->batteryProgressBar->show(); 632 | ui->batteryProgressBar->setValue(batteryLevel); 633 | } else { 634 | ui->batteryProgressBar->hide(); 635 | } 636 | 637 | if (status == "BATTERY_UNAVAILABLE") { 638 | ui->batteryPercentage->setText(tr("Headset Off")); 639 | trayIcon->setToolTip(tr("HeadsetControl \r\nHeadset Off")); 640 | changeTrayIconTo("headphones"); 641 | if (settings.notificationHeadsetDisconnected && !notified) { 642 | sendAppNotification(tr("Headset Disconnected"), 643 | tr("Your headset is disconnected or has been turned off"), 644 | "headphones"); 645 | if (settings.audioNotification) { 646 | API.playNotificationSound(0); 647 | } 648 | notified = true; 649 | } 650 | } else if (status == "BATTERY_CHARGING" && batteryLevel >= 0) { 651 | ui->batteryPercentage->setText(level + tr("% - Charging")); 652 | trayIcon->setToolTip(tr("HeadsetControl \r\nBattery: Charging - ") + level + "%"); 653 | if (batteryLevel >= 100) { 654 | changeTrayIconTo("battery-fully-charged"); 655 | if (settings.audioNotification) { 656 | API.playNotificationSound(1); 657 | } 658 | if (settings.notificationBatteryFull && !notified) { 659 | sendAppNotification(tr("Battery Charged!"), 660 | tr("The battery has been charged to 100%"), 661 | "battery-level-full"); 662 | notified = true; 663 | } 664 | } else { 665 | changeTrayIconTo("battery-charging"); 666 | } 667 | } else if (status == "BATTERY_AVAILABLE" && batteryLevel >= 0) { 668 | ui->batteryPercentage->setText(level + tr("% - Descharging")); 669 | trayIcon->setToolTip(tr("HeadsetControl \r\nBattery: ") + level + "%"); 670 | if (batteryLevel > 75) { 671 | changeTrayIconTo("battery-level-full"); 672 | notified = false; 673 | } else if (batteryLevel > settings.batteryLowThreshold) { 674 | changeTrayIconTo("battery-medium"); 675 | notified = false; 676 | } else { 677 | changeTrayIconTo("battery-low"); 678 | if (settings.audioNotification) { 679 | API.playNotificationSound(0); 680 | } 681 | if (settings.notificationBatteryLow && !notified) { 682 | sendAppNotification(tr("Battery Alert!"), 683 | tr("The battery of your headset is running low"), 684 | "battery-low"); 685 | notified = true; 686 | } 687 | } 688 | } else { 689 | ui->batteryPercentage->setText(tr("Error getting battery info: ") + level); 690 | trayIcon->setToolTip(tr("HeadsetControl \r\nBattery: Error ") + level); 691 | changeTrayIconTo("battery-error"); 692 | if (settings.notificationHeadsetDisconnected && !notified) { 693 | sendAppNotification(tr("Headset Error"), 694 | tr("Error getting battery info, headset might be turned off"), 695 | "battery-error"); 696 | if (settings.audioNotification) { 697 | API.playNotificationSound(0); 698 | } 699 | notified = true; 700 | } 701 | } 702 | } 703 | 704 | void MainWindow::setChatmixStatus() 705 | { 706 | QString chatmixStatus = tr("None"); 707 | 708 | if (selectedDevice == nullptr) { 709 | ui->chatmixvalueLabel->setText(chatmixStatus); 710 | return; 711 | } 712 | 713 | int chatmix = selectedDevice->chatmix; 714 | QString chatmixValue = QString::number(chatmix); 715 | if (chatmix < 64) 716 | chatmixStatus = tr("Game"); 717 | else if (chatmix > 64) 718 | chatmixStatus = tr("Chat"); 719 | else chatmixStatus = tr("Neutral"); 720 | 721 | ui->chatmixvalueLabel->setText(chatmixValue); 722 | ui->chatmixstatusLabel->setText(chatmixStatus); 723 | } 724 | 725 | // Equalizer Section Events 726 | void MainWindow::equalizerPresetChanged() 727 | { 728 | int index = ui->equalizerPresetcomboBox->currentIndex(); 729 | setEqualizerSliders(selectedDevice->presets_list.value(index).values); 730 | API.setEqualizerPreset(index); 731 | } 732 | 733 | void MainWindow::applyEqualizer( 734 | bool saveToFile) 735 | { 736 | ui->equalizerPresetcomboBox->setCurrentIndex(-1); 737 | QList values; 738 | for (QSlider *slider : std::as_const(equalizerSliders)) { 739 | values.append(slider->value() * selectedDevice->equalizer.band_step); 740 | } 741 | API.setEqualizer(values, saveToFile); 742 | } 743 | 744 | //Equalizer Slidesrs Section 745 | void MainWindow::createEqualizerSliders() 746 | { 747 | QHBoxLayout *layout = ui->equalizerLayout; 748 | int &bands_number = selectedDevice->equalizer.bands_number; 749 | if (bands_number > 0) { 750 | for (int i = 0; i < bands_number; ++i) { 751 | QSlider *s = new QSlider(Qt::Vertical); 752 | s->setMaximum(selectedDevice->equalizer.band_max / selectedDevice->equalizer.band_step); 753 | s->setMinimum(selectedDevice->equalizer.band_min / selectedDevice->equalizer.band_step); 754 | s->setSingleStep(1); 755 | s->setTickInterval(1 / selectedDevice->equalizer.band_step); 756 | if (selectedDevice->equalizer_curve.size() == bands_number) { 757 | s->setValue(selectedDevice->equalizer_curve.value(i)); 758 | } else { 759 | s->setValue(selectedDevice->equalizer.band_baseline); 760 | } 761 | 762 | equalizerSliders.append(s); 763 | layout->addWidget(s); 764 | connect(s, &QAbstractSlider::sliderReleased, this, [=]() { 765 | if (equalizerLiveUpdate) 766 | applyEqualizer(false); 767 | }); 768 | } 769 | ui->applyEqualizer->setEnabled(true); 770 | } 771 | } 772 | 773 | void MainWindow::clearEqualizerSliders() 774 | { 775 | QHBoxLayout *layout = ui->equalizerLayout; 776 | while (QLayoutItem *item = layout->takeAt(0)) { 777 | if (QWidget *widget = item->widget()) { 778 | widget->disconnect(); 779 | widget->deleteLater(); 780 | } 781 | delete item; 782 | } 783 | equalizerSliders.clear(); 784 | } 785 | 786 | void MainWindow::setEqualizerSliders( 787 | double value) 788 | { 789 | for (QSlider *slider : std::as_const(equalizerSliders)) { 790 | slider->setValue(value / selectedDevice->equalizer.band_step); 791 | } 792 | } 793 | 794 | void MainWindow::setEqualizerSliders(QList values) 795 | { 796 | int i = 0; 797 | if (values.length() == selectedDevice->equalizer.bands_number) { 798 | for (QSlider *slider : std::as_const(equalizerSliders)) { 799 | slider->setValue((int) (values[i++] / selectedDevice->equalizer.band_step)); 800 | } 801 | } else { 802 | setEqualizerSliders(0); 803 | qDebug() << "ERROR: Bad Equalizer Preset"; 804 | } 805 | } 806 | 807 | // Tool Bar Events 808 | void MainWindow::selectDevice() 809 | { 810 | QList connectedDevices = API.getConnectedDevices(); 811 | 812 | LoaddeviceWindow *loadDevWindow = new LoaddeviceWindow(connectedDevices, this); 813 | timerGUI->stop(); 814 | if (loadDevWindow->exec() == QDialog::Accepted) { 815 | int index = loadDevWindow->getDeviceIndex(); 816 | if (index >= 0 && index < connectedDevices.length()) { 817 | Device* d=connectedDevices[index]; 818 | API.setSelectedDevice(d->id_vendor, d->id_product); 819 | loadDevice(); 820 | settings.lastSelectedVendorID = selectedDevice->id_vendor; 821 | settings.lastSelectedProductID = selectedDevice->id_product; 822 | saveSettingstoFile(settings, PROGRAM_SETTINGS_FILEPATH); 823 | } 824 | } 825 | deleteDevices(connectedDevices); 826 | delete(loadDevWindow); 827 | timerGUI->start(); 828 | } 829 | 830 | void MainWindow::editProgramSetting() 831 | { 832 | SettingsWindow *settingsW = new SettingsWindow(settings, this); 833 | if (settingsW->exec() == QDialog::Accepted) { 834 | settings = settingsW->getSettings(); 835 | saveSettingstoFile(settings, PROGRAM_SETTINGS_FILEPATH); 836 | timerGUI->setInterval(settings.msecUpdateIntervalTime); 837 | if(settings.commandExe != ""){ 838 | timerCommand->stop(); 839 | timerCommand->start(settings.msecCommandIntervalTime); 840 | } 841 | updateStyle(); 842 | } 843 | delete (settingsW); 844 | } 845 | 846 | void MainWindow::checkForUpdates(bool firstStart) 847 | { 848 | bool needsUpdate = false; 849 | 850 | const QString &hcVersion = API.getVersion(); 851 | const QString &guiVersion = qApp->applicationVersion(); 852 | const QVersionNumber &local_hc = QVersionNumber::fromString(hcVersion); 853 | const QVersionNumber local_gui = QVersionNumber::fromString(guiVersion); 854 | QString v1 = getLatestGitHubReleaseVersion("Sapd", "HeadsetControl"); 855 | QString v2 = getLatestGitHubReleaseVersion("LeoKlaus", "HeadsetControl-GUI"); 856 | QVersionNumber remote_hc = QVersionNumber::fromString(v1); 857 | QVersionNumber remote_gui = QVersionNumber::fromString(v2); 858 | QString s1 = tr("up-to date v") + hcVersion; 859 | QString s2 = tr("up-to date v") + guiVersion; 860 | if (!(v1 == "") && remote_hc > local_hc && hcVersion != "continuous-modified") { 861 | s1 = tr("Newer version") 862 | + " -> " 864 | + remote_hc.toString() + ""; 865 | needsUpdate = true; 866 | } 867 | if (!(v2 == "") && remote_gui > local_gui) { 868 | s2 = tr("Newer version") 869 | + " -> " 872 | + remote_gui.toString() + ""; 873 | needsUpdate = true; 874 | } 875 | 876 | if ((needsUpdate && firstStart) || !firstStart) { 877 | DialogInfo *dialogWindow = new DialogInfo(this); 878 | dialogWindow->setTitle(tr("Check for updates")); 879 | QString text = "HeadesetControl: " + s1 + "
HeadesetControl-GUI: " + s2; 880 | dialogWindow->setLabel(text); 881 | 882 | dialogWindow->exec(); 883 | delete (dialogWindow); 884 | } 885 | } 886 | 887 | void MainWindow::showAbout() 888 | { 889 | DialogInfo *dialogWindow = new DialogInfo(this); 890 | dialogWindow->setTitle(tr("About this program")); 891 | QString text = tr("You can find HeadsetControl-GUI source code on " 892 | "GitHub.
" 893 | "Made by:
" 894 | " - LeoKlaus
" 895 | " - nicola02nb
" 896 | "Version: ") 897 | + qApp->applicationVersion(); 898 | dialogWindow->setLabel(text); 899 | 900 | dialogWindow->exec(); 901 | 902 | delete (dialogWindow); 903 | } 904 | 905 | void MainWindow::showCredits() 906 | { 907 | DialogInfo *dialogWindow = new DialogInfo(this); 908 | dialogWindow->setTitle(tr("Credits")); 909 | QString text = tr("Big shout-out to:
" 910 | " - Sapd for HeadsetCoontrol"); 912 | dialogWindow->setLabel(text); 913 | 914 | dialogWindow->exec(); 915 | 916 | delete (dialogWindow); 917 | } 918 | --------------------------------------------------------------------------------