├── .github └── workflows │ ├── build-and-release.yml │ └── build.yaml ├── .gitignore ├── HeadsetControl-GUI.pro ├── LICENSE ├── README.md ├── src ├── DataTypes │ ├── device.cpp │ ├── device.h │ ├── settings.cpp │ └── settings.h ├── Resources │ ├── appicon.rc │ ├── headphones-exe.ico │ ├── icons.qrc │ ├── icons │ │ ├── dark │ │ │ ├── index.theme │ │ │ └── png │ │ │ │ ├── battery-charging.png │ │ │ │ ├── battery-fully-charged.png │ │ │ │ ├── battery-level-full.png │ │ │ │ ├── battery-low.png │ │ │ │ ├── battery-medium.png │ │ │ │ └── headphones.png │ │ └── light │ │ │ ├── index.theme │ │ │ └── png │ │ │ ├── battery-charging.png │ │ │ ├── battery-fully-charged.png │ │ │ ├── battery-level-full.png │ │ │ ├── battery-low.png │ │ │ ├── battery-medium.png │ │ │ └── headphones.png │ └── tr │ │ ├── HeadsetControl_GUI_en.ts │ │ └── HeadsetControl_GUI_it.ts ├── UI │ ├── dialoginfo.cpp │ ├── dialoginfo.h │ ├── dialoginfo.ui │ ├── loaddevicewindow.cpp │ ├── loaddevicewindow.h │ ├── loaddevicewindow.ui │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ ├── settingswindow.cpp │ ├── settingswindow.h │ └── settingswindow.ui ├── Utils │ ├── headsetcontrolapi.cpp │ ├── headsetcontrolapi.h │ ├── utils.cpp │ └── utils.h └── main.cpp └── update-winget-version.ps1 /.github/workflows/build-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "Release tag(e.g., 1.2.3) without starting 'v'" 8 | required: true 9 | default: "continuous" 10 | type: string 11 | prerelease: 12 | description: "Set as prerelease" 13 | required: true 14 | default: true 15 | type: boolean 16 | push: 17 | branches: [main] 18 | paths: 19 | - 'src/**' 20 | - 'HeadsetControl-GUI.pro' 21 | 22 | jobs: 23 | build: 24 | runs-on: ${{ matrix.os }} 25 | defaults: 26 | run: 27 | shell: ${{ matrix.shell }} 28 | strategy: 29 | matrix: 30 | os: [windows-latest, ubuntu-latest] 31 | include: 32 | - os: windows-latest 33 | os_name: windows 34 | architecture: x64 35 | shell: pwsh 36 | headsetcontrol: "https://github.com/Sapd/HeadsetControl/releases/latest/download/headsetcontrol-windows-x86_64.zip" 37 | headsetcontrol_continuous: "https://github.com/Sapd/HeadsetControl/releases/download/continuous/headsetcontrol-windows-x86_64.zip" 38 | dependencies: | 39 | choco install zip 40 | Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" 41 | build: | 42 | mkdir build 43 | cd build 44 | qmake ../HeadsetControl-GUI.pro CONFIG+=release 45 | nmake 46 | - os: ubuntu-latest 47 | os_name: linux 48 | architecture: x64 49 | shell: bash 50 | headsetcontrol: "https://github.com/Sapd/HeadsetControl/releases/latest/download/headsetcontrol-linux-x86_64.zip" 51 | headsetcontrol_continuous: "https://github.com/Sapd/HeadsetControl/releases/download/continuous/headsetcontrol-linux-x86_64.zip" 52 | dependencies: | 53 | sudo apt-get update 54 | sudo apt-get install -y zip 55 | build: | 56 | mkdir build 57 | cd build 58 | qmake ../HeadsetControl-GUI.pro CONFIG+=release 59 | make -j$(nproc) 60 | steps: 61 | - uses: actions/checkout@v4 62 | - name: Install Qt 63 | uses: jurplel/install-qt-action@v4 64 | with: 65 | add-tools-to-path: true 66 | cache: true 67 | host: ${{ matrix.os_name }} 68 | 69 | - name: Install dependencies 70 | run: ${{ matrix.dependencies }} 71 | 72 | - uses: TheMrMilchmann/setup-msvc-dev@v3 73 | if: matrix.os_name == 'windows' 74 | with: 75 | arch: ${{ matrix.architecture }} 76 | 77 | - name: Build 78 | run: ${{ matrix.build }} 79 | 80 | - name: Deploy Qt Windows 81 | if: matrix.os_name == 'windows' 82 | run: | 83 | Get-ChildItem -Path build/ -Include *.cpp, *.h, *.obj, *.res, *.qrc, *.qm -Recurse | Remove-Item -Force 84 | Invoke-WebRequest -Uri ${{ inputs.prerelease && matrix.headsetcontrol_continuous || matrix.headsetcontrol }} -OutFile headsetcontrol-windows.zip 85 | Expand-Archive -Path headsetcontrol-windows.zip -DestinationPath build/release/ 86 | 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 87 | 88 | - name: Deploy Qt 89 | if: matrix.os_name == 'linux' 90 | run: | 91 | curl -L ${{ matrix.headsetcontrol_continuous }} -o headsetcontrol-linux-x86_64.zip 92 | mkdir -p build/release/ 93 | cp build/HeadsetControl-GUI build/release/ 94 | unzip headsetcontrol-linux-x86_64.zip -d build/release/ 95 | 96 | - name: zip binaries folder 97 | run: | 98 | cd build/release/ 99 | zip -r ../../HeadsetControl-GUI_${{ matrix.os_name }}_${{ matrix.architecture }}.zip . 100 | 101 | - name: Upload build artifacts 102 | uses: actions/upload-artifact@v4 103 | with: 104 | name: HeadsetControl-GUI_${{ matrix.os_name }}_${{ matrix.architecture }} 105 | path: HeadsetControl-GUI_${{ matrix.os_name }}_${{ matrix.architecture }}.zip 106 | 107 | create-release: 108 | needs: [build] 109 | runs-on: ubuntu-latest 110 | steps: 111 | - uses: actions/checkout@v4 112 | - name: Download Artifact 113 | uses: actions/download-artifact@v4 114 | with: 115 | merge-multiple: true 116 | - name: Deploy continuous 117 | uses: crowbarmaster/GH-Automatic-Releases@latest 118 | with: 119 | repo_token: ${{ secrets.GITHUB_TOKEN }} 120 | automatic_release_tag: ${{ inputs.version }} 121 | prerelease: ${{ inputs.prerelease }} 122 | title: Release ${{ inputs.version }} 123 | files: | 124 | HeadsetControl-GUI_* 125 | body: | 126 | ## Contributor 127 | @nicola02nb Mantainer 128 | 129 | ## Credits 130 | @Sapd for [HeadsetControl](https://github.com/Sapd/HeadsetControl) 131 | 132 | winget-release: 133 | if: ${{ !(startsWith(github.ref, 'refs/tags/continuous') || inputs.prerelease) }} 134 | needs: [create-release] 135 | runs-on: windows-latest 136 | continue-on-error: true 137 | steps: 138 | - name: Submit package to Windows Package Manager Community Repository 139 | run: | 140 | # Download latest wingetcreate 141 | Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe 142 | 143 | $packageId = "LeoKlaus.HeadsetControl-GUI" 144 | $installerUrl = "https://github.com/LeoKlaus/HeadsetControl-GUI/releases/download/${{ inputs.version }}/HeadsetControl_${{ inputs.version }}_windows_x64.zip" 145 | 146 | # Submit package update 147 | .\wingetcreate.exe update "$packageId" ` 148 | --version "${{ inputs.version }}" ` 149 | --urls "$installerUrl" ` 150 | --submit ` 151 | --token "${{ secrets.WINGET_DEPLOY_TOKEN }}" 152 | shell: pwsh -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 : 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 : 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->voice_prompts = device->voice_prompts; 109 | this->inactive_time = device->inactive_time; 110 | this->equalizer_preset = device->equalizer_preset; 111 | this->equalizer_curve = device->equalizer_curve; 112 | this->volume_limiter = device->volume_limiter; 113 | this->rotate_to_mute = device->rotate_to_mute; 114 | this->mic_mute_led_brightness = device->mic_mute_led_brightness; 115 | this->mic_volume = device->mic_volume; 116 | this->bt_when_powered_on = device->bt_when_powered_on; 117 | this->bt_call_volume = device->bt_call_volume; 118 | } 119 | 120 | void Device::updateConfig(const QList &list){ 121 | foreach (Device* device, list) { 122 | if(*this == device) 123 | this->copyConfig(device); 124 | } 125 | } 126 | 127 | void Device::updateInfo(const Device *new_device) 128 | { 129 | this->battery = new_device->battery; 130 | this->chatmix = new_device->chatmix; 131 | } 132 | 133 | QJsonObject Device::toJson() const 134 | { 135 | QJsonObject json; 136 | json["device"] = device; 137 | json["vendor"] = vendor; 138 | json["product"] = product; 139 | json["id_vendor"] = id_vendor; 140 | json["id_product"] = id_product; 141 | 142 | json["lights"] = lights; 143 | json["sidetone"] = sidetone; 144 | json["voice_prompts"] = voice_prompts; 145 | json["inactive_time"] = inactive_time; 146 | json["equalizer_preset"] = equalizer_preset; 147 | json["equalizer_curve"] = QJsonArray::fromVariantList( 148 | QVariantList(equalizer_curve.begin(), equalizer_curve.end())); 149 | json["volume_limiter"] = volume_limiter; 150 | json["rotate_to_mute"] = rotate_to_mute; 151 | json["mic_mute_led_brightness"] = mic_mute_led_brightness; 152 | json["mic_volume"] = mic_volume; 153 | json["bt_when_powered_on"] = bt_when_powered_on; 154 | json["bt_call_volume"] = bt_call_volume; 155 | 156 | return json; 157 | } 158 | 159 | Device *Device::fromJson( 160 | const QJsonObject &json) 161 | { 162 | Device *newDev = new Device(); 163 | newDev->device = json["device"].toString(); 164 | newDev->vendor = json["vendor"].toString(); 165 | newDev->product = json["product"].toString(); 166 | newDev->id_vendor = json["id_vendor"].toString(); 167 | newDev->id_product = json["id_product"].toString(); 168 | 169 | newDev->lights = json["lights"].toInt(); 170 | newDev->sidetone = json["sidetone"].toInt(); 171 | newDev->voice_prompts = json["voice_prompts"].toInt(); 172 | newDev->inactive_time = json["inactive_time"].toInt(); 173 | newDev->equalizer_preset = json["equalizer_preset"].toInt(); 174 | 175 | QJsonArray curveArray = json["equalizer_curve"].toArray(); 176 | for (const auto &value : curveArray) { 177 | newDev->equalizer_curve.append(value.toDouble()); 178 | } 179 | 180 | newDev->volume_limiter = json["volume_limiter"].toInt(); 181 | newDev->rotate_to_mute = json["rotate_to_mute"].toInt(); 182 | newDev->mic_mute_led_brightness = json["mic_mute_led_brightness"].toInt(); 183 | newDev->mic_volume = json["mic_volume"].toInt(); 184 | newDev->bt_when_powered_on = json["bt_when_powered_on"].toInt(); 185 | newDev->bt_call_volume = json["bt_call_volume"].toInt(); 186 | 187 | return newDev; 188 | } 189 | 190 | void updateDeviceFromSource(QList &devicesToUpdate, const Device *sourceDevice) 191 | { 192 | bool found = false; 193 | for (Device *toUpdateDevice : devicesToUpdate) { 194 | if (*toUpdateDevice == sourceDevice) { 195 | // Update the saved device with connected device's information 196 | *toUpdateDevice = *sourceDevice; 197 | found = true; 198 | } 199 | }; 200 | if (!found) { 201 | Device *toAppend = new Device(); 202 | *toAppend = *sourceDevice; 203 | devicesToUpdate.append(toAppend); 204 | } 205 | } 206 | 207 | void serializeDevices(const QList &devices, const QString &filePath) 208 | { 209 | QJsonArray jsonArray; 210 | for (const auto *device : devices) { 211 | jsonArray.append(device->toJson()); 212 | } 213 | 214 | QJsonDocument doc(jsonArray); 215 | QFile file(filePath); 216 | if (file.open(QIODevice::WriteOnly)) { 217 | file.write(doc.toJson()); 218 | file.close(); 219 | qDebug() << "Devices Serialized" << jsonArray; 220 | qDebug(); 221 | } 222 | } 223 | 224 | QList deserializeDevices(const QString &filePath) 225 | { 226 | QList devices; 227 | QFile file(filePath); 228 | if (file.open(QIODevice::ReadOnly)) { 229 | QByteArray data = file.readAll(); 230 | QJsonDocument doc = QJsonDocument::fromJson(data); 231 | QJsonArray jsonArray = doc.array(); 232 | 233 | for (const auto &value : jsonArray) { 234 | Device *device = Device::fromJson(value.toObject()); 235 | devices.append(device); 236 | } 237 | 238 | file.close(); 239 | qDebug() << "Devices Deserialized" << jsonArray; 240 | qDebug(); 241 | } 242 | return devices; 243 | } 244 | 245 | void deleteDevices( 246 | QList deviceList) 247 | { 248 | for (Device *device : deviceList) { 249 | delete device; 250 | } 251 | deviceList.clear(); 252 | } 253 | -------------------------------------------------------------------------------- /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 = 65; 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 voice_prompts = -1; 65 | int inactive_time = -1; 66 | int equalizer_preset = -1; 67 | QList equalizer_curve; 68 | int volume_limiter = -1; 69 | int rotate_to_mute = -1; 70 | int mic_mute_led_brightness = -1; 71 | int mic_volume = -1; 72 | int bt_when_powered_on = -1; 73 | int bt_call_volume = -1; 74 | 75 | bool operator!=(const Device &d) const; 76 | bool operator==(const Device &d) const; 77 | bool operator==(const Device *d) const; 78 | 79 | void copyConfig(Device* device); 80 | void updateConfig(const QList &list); 81 | 82 | void updateInfo(const Device *new_device); 83 | 84 | QJsonObject toJson() const; 85 | static Device *fromJson(const QJsonObject &json); 86 | }; 87 | 88 | void updateDeviceFromSource(QList &devicesToUpdate, const Device *sourceDevice); 89 | 90 | void serializeDevices(const QList &devices, const QString &filePath); 91 | QList deserializeDevices(const QString &filePath); 92 | 93 | void deleteDevices(QList deviceList); 94 | 95 | #endif // DEVICE_H 96 | -------------------------------------------------------------------------------- /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("notificationBatteryFull")) { 26 | s.batteryLowThreshold = json["notificationBatteryFull"].toInt(); 27 | } 28 | if (json.contains("notificationBatteryLow")) { 29 | s.batteryLowThreshold = json["notificationBatteryLow"].toInt(); 30 | } 31 | if (json.contains("audioNotification")) { 32 | s.batteryLowThreshold = json["audioNotification"].toInt(); 33 | } 34 | if (json.contains("batteryLowThreshold")) { 35 | s.batteryLowThreshold = json["batteryLowThreshold"].toInt(); 36 | } 37 | if (json.contains("msecUpdateIntervalTime")) { 38 | s.msecUpdateIntervalTime = json["msecUpdateIntervalTime"].toInt(); 39 | } 40 | if (json.contains("styleName")) { 41 | s.styleName = json["styleName"].toString(); 42 | } 43 | if (json.contains("lastSelectedVendorID")) { 44 | s.lastSelectedVendorID = json["lastSelectedVendorID"].toString(); 45 | } 46 | if (json.contains("lastSelectedProductID")) { 47 | s.lastSelectedProductID = json["lastSelectedProductID"].toString(); 48 | } 49 | qDebug() << "Settings Loaded:\t" << json; 50 | qDebug(); 51 | } 52 | 53 | return s; 54 | } 55 | 56 | void saveSettingstoFile(const Settings &settings, const QString &filePath) 57 | { 58 | QJsonObject json; 59 | json["runOnStartup"] = settings.runOnstartup; 60 | json["notificationBatteryFull"] = settings.notificationBatteryFull; 61 | json["notificationBatteryLow"] = settings.notificationBatteryLow; 62 | json["audioNotification"] = settings.audioNotification; 63 | json["batteryLowThreshold"] = settings.batteryLowThreshold; 64 | json["msecUpdateIntervalTime"] = settings.msecUpdateIntervalTime; 65 | json["styleName"] = settings.styleName; 66 | json["lastSelectedVendorID"] = settings.lastSelectedVendorID; 67 | json["lastSelectedProductID"] = settings.lastSelectedProductID; 68 | 69 | QJsonDocument doc(json); 70 | QFile file(filePath); 71 | 72 | qDebug() << "Saving settings:"; 73 | qDebug() << "Destination:\t" << filePath; 74 | if (!file.open(QIODevice::WriteOnly)) { 75 | qWarning("Error:\tCouldn't open save file."); 76 | } 77 | file.write(doc.toJson()); 78 | file.close(); 79 | 80 | //qDebug() << "Content:\t" << json; 81 | qDebug(); 82 | } 83 | -------------------------------------------------------------------------------- /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 notificationBatteryFull = true; 28 | bool notificationBatteryLow = true; 29 | int batteryLowThreshold = 15; 30 | bool audioNotification = true; 31 | 32 | int msecUpdateIntervalTime = 30000; 33 | 34 | QString styleName = "Default"; 35 | 36 | QString lastSelectedVendorID = "", lastSelectedProductID = ""; 37 | }; 38 | 39 | Settings loadSettingsFromFile(const QString &filePath); 40 | void saveSettingstoFile(const Settings &settings, const QString &filePath); 41 | 42 | #endif // SETTINGS_H 43 | -------------------------------------------------------------------------------- /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/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/headphones-exe.ico -------------------------------------------------------------------------------- /src/Resources/icons.qrc: -------------------------------------------------------------------------------- 1 | 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/headphones.png 9 | icons/dark/index.theme 10 | icons/light/index.theme 11 | icons/light/png/battery-charging.png 12 | icons/light/png/battery-fully-charged.png 13 | icons/light/png/battery-level-full.png 14 | icons/light/png/battery-low.png 15 | icons/light/png/battery-medium.png 16 | icons/light/png/headphones.png 17 | 18 | 19 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-charging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/dark/png/battery-charging.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-fully-charged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/dark/png/battery-fully-charged.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-level-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/dark/png/battery-level-full.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/dark/png/battery-low.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/battery-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/dark/png/battery-medium.png -------------------------------------------------------------------------------- /src/Resources/icons/dark/png/headphones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/dark/png/headphones.png -------------------------------------------------------------------------------- /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/Resources/icons/light/png/battery-charging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/light/png/battery-charging.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-fully-charged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/light/png/battery-fully-charged.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-level-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/light/png/battery-level-full.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/light/png/battery-low.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/battery-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/light/png/battery-medium.png -------------------------------------------------------------------------------- /src/Resources/icons/light/png/headphones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoKlaus/HeadsetControl-GUI/91cb58e5487a5e6add9db8423ca5533c95601e3a/src/Resources/icons/light/png/headphones.png -------------------------------------------------------------------------------- /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 | Hide/Show 319 | 320 | 321 | 322 | 323 | Turn Lights On 324 | 325 | 326 | 327 | 328 | Turn Lights Off 329 | 330 | 331 | 332 | 333 | Exit 334 | 335 | 336 | 337 | 338 | Headset Off 339 | 340 | 341 | 342 | 343 | HeadsetControl 344 | Headset Off 345 | 346 | 347 | 348 | 349 | % - Charging 350 | 351 | 352 | 353 | 354 | The battery has been charged to 100% 355 | 356 | 357 | 358 | 359 | % - Discharging 360 | 361 | 362 | 363 | 364 | HeadsetControl 365 | Battery: 366 | 367 | 368 | 369 | 370 | Battery Alert! 371 | 372 | 373 | 374 | 375 | The battery of your headset is running low 376 | 377 | 378 | 379 | 380 | No battery info 381 | 382 | 383 | 384 | 385 | Game 386 | 387 | 388 | 389 | 390 | Chat 391 | 392 | 393 | 394 | 395 | Check for updates 396 | 397 | 398 | 399 | 400 | 401 | up-to date v 402 | 403 | 404 | 405 | 406 | HeadsetControl 407 | Battery: Charging - 408 | 409 | 410 | 411 | 412 | Battery Charged! 413 | 414 | 415 | 416 | 417 | 418 | Newer version 419 | 420 | 421 | 422 | 423 | About this program 424 | 425 | 426 | 427 | 428 | 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: 429 | 430 | 431 | 432 | 433 | Big shout-out to:<br/> - <a href='https://github.com/Sapd'>Sapd</a> for <a href='https://github.com/Sapd/HeadsetControl'>HeadsetCoontrol 434 | 435 | 436 | 437 | 438 | loaddevicewindow 439 | 440 | 441 | Select device to load 442 | 443 | 444 | 445 | 446 | Select device: 447 | 448 | 449 | 450 | 451 | settingswindow 452 | 453 | 454 | Settings 455 | 456 | 457 | 458 | 459 | Run on Startup: 460 | 461 | 462 | 463 | 464 | Battery low notification: 465 | 466 | 467 | 468 | 469 | Battery low threshold: 470 | Battery low treshold: 471 | 472 | 473 | 474 | Battery full notification: 475 | 476 | 477 | 478 | 479 | Enable audio notifications: 480 | 481 | 482 | 483 | 484 | Update Info interval time (seconds): 485 | Default: 30 seconds 486 | DON'T PUT TOO LOW VALUES 487 | 488 | 489 | 490 | 491 | Custom Style: 492 | 493 | 494 | 495 | 496 | Load 497 | 498 | 499 | 500 | 501 | Remove 502 | 503 | 504 | 505 | 506 | -------------------------------------------------------------------------------- /src/Resources/tr/HeadsetControl_GUI_it.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWindow 6 | 7 | 8 | HeadsetControl-GUI 9 | HeadsetControl-GUI 10 | 11 | 12 | Missing headsetcontrol!<br/>Download <a href="https://github.com/Sapd/HeadsetControl/releases/latest">headsetcontrol</a> in the program folder. 13 | Manca headsetcontrol!<br/>Scarica <a href="https://github.com/Sapd/HeadsetControl/releases/latest">headsetcontrol</a> nella cartella del programma. 14 | 15 | 16 | 17 | <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> 18 | <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> 19 | 20 | 21 | 22 | Open Program Folder 23 | Apri Cartella del Programma 24 | 25 | 26 | 27 | HeadsetControl couldn't find any compatible or working headsets. :( 28 | HeadsetControl non è riuscito a trovare delle cuffie funizionanti o compatibili. :( 29 | 30 | 31 | 32 | Device: 33 | Vendor: 34 | Model: 35 | Dispositio: 36 | Distributore: 37 | Modello: 38 | 39 | 40 | 41 | No info of the device 42 | Nessuna informazione sul dipositivo 43 | 44 | 45 | 46 | Battery: 47 | Batteria: 48 | 49 | 50 | 51 | No compatible Device found! 52 | Nessun dispositivo compatibile è stato trovato! 53 | 54 | 55 | 56 | Other 57 | Altro 58 | 59 | 60 | 61 | Lights: 62 | Luci: 63 | 64 | 65 | 66 | RGB OFF 67 | RGB OFF 68 | 69 | 70 | 71 | RGB ON 72 | RGB ON 73 | 74 | 75 | 76 | Sidetone Level: 77 | Tono Laterale: 78 | 79 | 80 | 81 | Quiet (Off) 82 | Silenzioso (Spento) 83 | 84 | 85 | 86 | 87 | Loud 88 | Forte 89 | 90 | 91 | 92 | Voice Prompts: 93 | Istruzioni Vocali: 94 | 95 | 96 | 97 | Voice Off 98 | Voce Accesa 99 | 100 | 101 | 102 | Voice On 103 | Voce Spenta 104 | 105 | 106 | 107 | Notification Sound: 108 | Suono di Notifica: 109 | 110 | 111 | 112 | Test 0 113 | Prova 0 114 | 115 | 116 | 117 | Test 1 118 | Prova 1 119 | 120 | 121 | 122 | Inactivity Timer: 123 | Tempo di Inattività: 124 | 125 | 126 | 127 | 0 Minute (Off) 128 | 0 Minuti (Spento) 129 | 130 | 131 | 132 | 90 Minutes 133 | 90 Minuti 134 | 135 | 136 | 137 | Chatmix: 138 | Chatmix: 139 | 140 | 141 | 142 | 143 | None 144 | Nessun valore 145 | 146 | 147 | 148 | Equalizer 149 | Equalizzatore 150 | 151 | 152 | 153 | Equalizer preset: 154 | Preset Equalizzatore: 155 | 156 | 157 | 158 | Equalizer: 159 | Equalizzatore: 160 | 161 | 162 | 163 | LiveUpdate 164 | Aggiornamento Live 165 | 166 | 167 | 168 | Lows 169 | Bassi 170 | 171 | 172 | 173 | Mids 174 | Medi 175 | 176 | 177 | 178 | Highs 179 | Alti 180 | 181 | 182 | 183 | Apply Equalizer 184 | Applica Equalizzatore 185 | 186 | 187 | 188 | Volume Limiter: 189 | Limitatore Volume: 190 | 191 | 192 | 193 | Limiter Off 194 | Limitatore Spento 195 | 196 | 197 | 198 | Limiter On 199 | Limitatore Acceso 200 | 201 | 202 | 203 | Microphone 204 | Microfono 205 | 206 | 207 | 208 | Rotate to mute: 209 | Ruota per mutare: 210 | 211 | 212 | 213 | Off 214 | Spento 215 | 216 | 217 | 218 | On 219 | Acceso 220 | 221 | 222 | 223 | Muted led brightness: 224 | Luminosità microfono mutato: 225 | 226 | 227 | 228 | Low (Off) 229 | Basso (Spento) 230 | 231 | 232 | 233 | High 234 | Alto 235 | 236 | 237 | 238 | Microphone volume: 239 | Volume microfono: 240 | 241 | 242 | 243 | Quiet 244 | Basso 245 | 246 | 247 | 248 | Bluetooth 249 | Bluetooth 250 | 251 | 252 | 253 | Bluetooth when powered on: 254 | Bluetooth quando accese: 255 | 256 | 257 | 258 | Bluetooth Off 259 | Bluetooth Spento 260 | 261 | 262 | 263 | Bluetooth On 264 | Bluetooth Acceso 265 | 266 | 267 | 268 | Bluetooth call volume: 269 | Bluetoot volume chiamata: 270 | 271 | 272 | 273 | BT and PC 274 | BT e PC 275 | 276 | 277 | 278 | PC -12dB 279 | PC -12dB 280 | 281 | 282 | 283 | BT only 284 | Solo BT 285 | 286 | 287 | 288 | File 289 | File 290 | 291 | 292 | 293 | Help 294 | Aiuto 295 | 296 | 297 | 298 | Check Updates 299 | Controlla Aggiornamenti 300 | 301 | 302 | 303 | About 304 | About 305 | 306 | 307 | 308 | 309 | Credits 310 | Crediti 311 | 312 | 313 | 314 | Load Device 315 | Carica Dispositivo 316 | 317 | 318 | 319 | Settings 320 | Impostazioni 321 | 322 | 323 | 324 | Hide/Show 325 | Nascondi/Mostra 326 | 327 | 328 | 329 | Turn Lights On 330 | Accendi le Luci 331 | 332 | 333 | 334 | Turn Lights Off 335 | Spegni le Luci 336 | 337 | 338 | 339 | Exit 340 | Esci 341 | 342 | 343 | 344 | Headset Off 345 | Cuffie Spente 346 | 347 | 348 | 349 | HeadsetControl 350 | Headset Off 351 | HeadsetControl 352 | Cuffie Spente 353 | 354 | 355 | 356 | % - Charging 357 | % - In Carica 358 | 359 | 360 | 361 | The battery has been charged to 100% 362 | La batteria è stata caricata al 100% 363 | 364 | 365 | 366 | % - Descharging 367 | % - Batteria in scarica 368 | 369 | 370 | 371 | HeadsetControl 372 | Battery: 373 | HeadsetControl 374 | Batteria: 375 | 376 | 377 | 378 | Battery Alert! 379 | Attenzione Batteria! 380 | 381 | 382 | 383 | The battery of your headset is running low 384 | La batteria delle tue cuffie è scarica 385 | 386 | 387 | 388 | No battery info 389 | No informazioni sulla batteria 390 | 391 | 392 | 393 | Game 394 | Gioco 395 | 396 | 397 | 398 | Chat 399 | Chat 400 | 401 | 402 | 403 | Check for updates 404 | Controlla Aggirnamenti 405 | 406 | 407 | 408 | 409 | up-to date v 410 | aggiornato v 411 | 412 | 413 | 414 | HeadsetControl 415 | Battery: Charging - 416 | HeadsetControl 417 | Batteria: In Carica - 418 | 419 | 420 | 421 | Battery Charged! 422 | Batteria Carica! 423 | 424 | 425 | 426 | 427 | Newer version 428 | Nuova versione 429 | 430 | 431 | 432 | About this program 433 | 434 | 435 | 436 | 437 | 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: 438 | 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: 439 | 440 | 441 | 442 | Big shout-out to:<br/> - <a href='https://github.com/Sapd'>Sapd</a> for <a href='https://github.com/Sapd/HeadsetControl'>HeadsetCoontrol 443 | Un grande riconoscimento va a:<br/> - <a href='https://github.com/Sapd'>Sapd</a> per <a href='https://github.com/Sapd/HeadsetControl'>HeadsetCoontrol 444 | 445 | 446 | 447 | loaddevicewindow 448 | 449 | 450 | Select device to load 451 | Seleziona dispositivo da caricare 452 | 453 | 454 | 455 | Select device: 456 | Seleziona dispositivo: 457 | 458 | 459 | 460 | settingswindow 461 | 462 | 463 | Settings 464 | Impostazioni 465 | 466 | 467 | 468 | Run on Startup: 469 | Esecuzione all'avvio: 470 | 471 | 472 | 473 | Battery low notification: 474 | Notifica batteria scarica: 475 | 476 | 477 | 478 | Battery low threshold: 479 | Soglia batteria scarica: 480 | 481 | 482 | 483 | Battery full notification: 484 | Notifica batteria carica: 485 | 486 | 487 | 488 | Enable audio notifications: 489 | Abillita notifica audio: 490 | 491 | 492 | 493 | Update Info interval time (seconds): 494 | Default: 30 seconds 495 | DON'T PUT TOO LOW VALUES 496 | Intervallo di aggiornamento info (secondi): 497 | Predefinito: 30 secondi 498 | NON IMPOSTARE VALORI TROPPO BASSI 499 | 500 | 501 | 502 | Custom Style: 503 | Stile personalizzato: 504 | 505 | 506 | 507 | Load 508 | Carica 509 | 510 | 511 | 512 | Remove 513 | Rimuovi 514 | 515 | 516 | 517 | -------------------------------------------------------------------------------- /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/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.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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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.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 | 16 | MainWindow::MainWindow(QWidget *parent) 17 | : QMainWindow(parent) 18 | , ui(new Ui::MainWindow) 19 | , trayIcon(new QSystemTrayIcon(this)) 20 | , trayMenu(new QMenu(this)) 21 | , timerGUI(new QTimer(this)) 22 | , API(HeadsetControlAPI(HEADSETCONTROL_DIRECTORY)) 23 | { 24 | qDebug() << "Headsetcontrol"; 25 | qDebug() << "Name:" << API.getName(); 26 | qDebug() << "Version:" << API.getVersion(); 27 | qDebug() << "ApiVersion:" << API.getApiVersion(); 28 | qDebug() << "HidApiVersion:" << API.getHidApiVersion(); 29 | qDebug(); 30 | qDebug() << "Headsetcontrol-GUI"; 31 | qDebug() << "Version" << qApp->applicationVersion(); 32 | qDebug() << "AppPath" << PROGRAM_CONFIG_PATH; 33 | qDebug() << "ConfigPath" << PROGRAM_CONFIG_PATH; 34 | qDebug() << "SettingsPath" << PROGRAM_SETTINGS_FILEPATH; 35 | qDebug(); 36 | 37 | QDir().mkpath(PROGRAM_CONFIG_PATH); 38 | settings = loadSettingsFromFile(PROGRAM_SETTINGS_FILEPATH); 39 | API.setSelectedDevice(settings.lastSelectedVendorID, settings.lastSelectedProductID); 40 | defaultStyle = styleSheet(); 41 | 42 | setupTrayIcon(); 43 | ui->setupUi(this); 44 | bindEvents(); 45 | 46 | updateIconsTheme(); 47 | updateStyle(); 48 | 49 | updateGUI(); 50 | 51 | connect(&API, &HeadsetControlAPI::actionSuccesful, this, &::MainWindow::saveDevicesSettings); 52 | 53 | connect(timerGUI, &QTimer::timeout, this, &::MainWindow::updateGUI); 54 | timerGUI->start(settings.msecUpdateIntervalTime); 55 | 56 | //Small trick to make work theme style change (Won't work unless you show window once) 57 | show(); 58 | hide(); 59 | } 60 | 61 | MainWindow::~MainWindow() 62 | { 63 | timerGUI->stop(); 64 | delete timerGUI; 65 | delete trayMenu; 66 | delete trayIcon; 67 | delete ui; 68 | } 69 | 70 | void MainWindow::changeEvent(QEvent *e) 71 | { 72 | switch (e->type()) { 73 | case QEvent::ThemeChange: 74 | updateIconsTheme(); 75 | break; 76 | case QEvent::WindowStateChange: 77 | if (windowState() == Qt::WindowMinimized) { 78 | hide(); 79 | } 80 | break; 81 | default: 82 | break; 83 | } 84 | 85 | QMainWindow::changeEvent(e); 86 | } 87 | 88 | void MainWindow::bindEvents() 89 | { 90 | // Tool Bar 91 | connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::editProgramSetting); 92 | connect(ui->actionLoad_Device, &QAction::triggered, this, &MainWindow::selectDevice); 93 | connect(ui->actionCheck_Updates, &QAction::triggered, this, &MainWindow::checkForUpdates); 94 | 95 | connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAbout); 96 | connect(ui->actionCredits, &QAction::triggered, this, &MainWindow::showCredits); 97 | 98 | //Error frames 99 | connect(ui->openfolderPushButton, &QPushButton::clicked, this, [=]() { 100 | openFileExplorer(PROGRAM_APP_DIRECTORY); 101 | }); 102 | 103 | // Other Section 104 | connect(ui->onlightButton, &QPushButton::clicked, this, [=]() { 105 | API.setLights(true); 106 | }); 107 | connect(ui->offlightButton, &QPushButton::clicked, this, [=]() { 108 | API.setLights(false); 109 | }); 110 | connect(ui->sidetoneSlider, &QSlider::sliderReleased, this, [=]() { 111 | API.setSidetone(ui->sidetoneSlider->value()); 112 | }); 113 | connect(ui->voiceOnButton, &QPushButton::clicked, this, [=]() { 114 | API.setVoicePrompts(true); 115 | }); 116 | connect(ui->voiceOffButton, &QPushButton::clicked, this, [=]() { 117 | API.setVoicePrompts(true); 118 | }); 119 | connect(ui->notification0Button, &QPushButton::clicked, this, [=]() { 120 | API.playNotificationSound(0); 121 | }); 122 | connect(ui->notification1Button, &QPushButton::clicked, this, [=]() { 123 | API.playNotificationSound(1); 124 | }); 125 | connect(ui->inactivitySlider, &QSlider::sliderReleased, this, [=]() { 126 | API.setInactiveTime(ui->inactivitySlider->value()); 127 | }); 128 | 129 | // Equalizer Section 130 | connect(ui->equalizerPresetcomboBox, 131 | &QComboBox::activated, 132 | this, 133 | &MainWindow::equalizerPresetChanged); 134 | connect(ui->equalizerliveupdateCheckBox, &QCheckBox::checkStateChanged, this, [=](int state) { 135 | equalizerLiveUpdate = (state == Qt::Checked); 136 | }); 137 | connect(ui->applyEqualizer, &QPushButton::clicked, this, [=]() { applyEqualizer(); }); 138 | connect(ui->volumelimiterOffButton, &QPushButton::clicked, this, [=]() { 139 | API.setVolumeLimiter(false); 140 | }); 141 | connect(ui->volumelimiterOnButton, &QPushButton::clicked, this, [=]() { 142 | API.setVolumeLimiter(true); 143 | }); 144 | 145 | // Microphone Section 146 | connect(ui->muteledbrightnessSlider, &QSlider::sliderReleased, this, [=]() { 147 | API.setMuteLedBrightness(ui->muteledbrightnessSlider->value()); 148 | }); 149 | connect(ui->micvolumeSlider, &QSlider::sliderReleased, this, [=]() { 150 | API.setMicrophoneVolume(ui->micvolumeSlider->value()); 151 | }); 152 | connect(ui->rotateOn, &QPushButton::clicked, this, [=]() { 153 | API.setRotateToMute(true); 154 | }); 155 | connect(ui->rotateOff, &QPushButton::clicked, this, [=]() { 156 | API.setRotateToMute(false); 157 | }); 158 | 159 | // Bluetooth Section 160 | connect(ui->btwhenonOffButton, &QPushButton::clicked, this, [=]() { 161 | API.setBluetoothWhenPoweredOn(false); 162 | }); 163 | connect(ui->btwhenonOnButton, &QPushButton::clicked, this, [=]() { 164 | API.setBluetoothWhenPoweredOn(true); 165 | }); 166 | connect(ui->btbothRadioButton, &QRadioButton::clicked, this, [=]() { 167 | API.setBluetoothCallVolume(0); 168 | }); 169 | connect(ui->btpcdbRadioButton, &QRadioButton::clicked, this, [=]() { 170 | API.setBluetoothCallVolume(1); 171 | }); 172 | connect(ui->btonlyRadioButton, &QRadioButton::clicked, this, [=]() { 173 | API.setBluetoothCallVolume(2); 174 | }); 175 | } 176 | 177 | //Tray Icon Section 178 | void MainWindow::changeTrayIconTo(QString iconName) 179 | { 180 | trayIconName = iconName; 181 | trayIcon->setIcon(QIcon::fromTheme(iconName)); 182 | } 183 | 184 | void MainWindow::setupTrayIcon() 185 | { 186 | changeTrayIconTo("headphones"); 187 | trayIcon->setToolTip("HeadsetControl"); 188 | 189 | trayMenu->addAction(tr("Hide/Show"), this, &MainWindow::toggleWindow); 190 | ledOn = trayMenu->addAction(tr("Turn Lights On"), &API, [=]() { 191 | API.setLights(true); 192 | }); 193 | ledOff = trayMenu->addAction(tr("Turn Lights Off"), &API, [=]() { 194 | API.setLights(false); 195 | }); 196 | trayMenu->addAction(tr("Exit"), this, &QApplication::quit); 197 | 198 | trayIcon->setContextMenu(trayMenu); 199 | trayIcon->connect(trayIcon, 200 | SIGNAL(activated(QSystemTrayIcon::ActivationReason)), 201 | this, 202 | SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason))); 203 | trayIcon->show(); 204 | } 205 | 206 | void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) 207 | { 208 | if (reason == QSystemTrayIcon::ActivationReason::Trigger) { 209 | toggleWindow(); 210 | } 211 | } 212 | 213 | //Theme mode Section 214 | bool MainWindow::isAppDarkMode() 215 | { 216 | Qt::ColorScheme scheme = qApp->styleHints()->colorScheme(); 217 | if (scheme == Qt::ColorScheme::Dark) 218 | return true; 219 | return false; 220 | } 221 | 222 | void MainWindow::updateIconsTheme() 223 | { 224 | if (isAppDarkMode()) { 225 | QIcon::setThemeName("light"); 226 | } else { 227 | QIcon::setThemeName("dark"); 228 | } 229 | setWindowIcon(QIcon::fromTheme("headphones")); 230 | changeTrayIconTo(trayIconName); 231 | } 232 | 233 | void MainWindow::updateStyle() 234 | { 235 | if (settings.styleName != "Default") { 236 | QString destination = PROGRAM_STYLES_PATH + "/" + settings.styleName; 237 | QFile file(destination); 238 | if (file.open(QFile::ReadOnly)) { 239 | QString styleSheet = QLatin1String(file.readAll()); 240 | setStyleSheet(styleSheet); 241 | } 242 | } else { 243 | setStyleSheet(defaultStyle); 244 | } 245 | rescaleAndMoveWindow(); 246 | } 247 | 248 | //Window Position and Size Section 249 | void MainWindow::toggleWindow() 250 | { 251 | if (isHidden()) { 252 | show(); 253 | rescaleAndMoveWindow(); 254 | if (firstShow) { 255 | checkForUpdates(firstShow); 256 | firstShow = false; 257 | } 258 | } else { 259 | hide(); 260 | } 261 | } 262 | 263 | void MainWindow::minimizeWindowSize() 264 | { 265 | resize(sizeHint()); 266 | } 267 | 268 | void MainWindow::moveToBottomRight() 269 | { 270 | QScreen *screen = QGuiApplication::primaryScreen(); 271 | QSize screenSize = screen->availableSize(); 272 | QSize finalPosition = screenSize - sizeHint(); 273 | move(finalPosition.width() - 5, finalPosition.height() - 35); 274 | } 275 | 276 | void MainWindow::rescaleAndMoveWindow() 277 | { 278 | minimizeWindowSize(); 279 | moveToBottomRight(); 280 | } 281 | 282 | void MainWindow::resetGUI() 283 | { 284 | trayIcon->setIcon(QIcon(":/icons/light/png/headphones.png")); 285 | trayIcon->setToolTip("HeadsetControl"); 286 | ledOn->setEnabled(false); 287 | ledOff->setEnabled(false); 288 | 289 | ui->missingheadsetcontrolFrame->setHidden(false); 290 | ui->notSupportedFrame->setHidden(false); 291 | 292 | ui->deviceinfoFrame->setHidden(true); 293 | ui->batteryFrame->setHidden(true); 294 | 295 | ui->tabWidget->hide(); 296 | ui->tabWidget->setTabEnabled(3, false); 297 | ui->tabWidget->setTabEnabled(2, false); 298 | ui->tabWidget->setTabEnabled(1, false); 299 | ui->tabWidget->setTabEnabled(0, false); 300 | 301 | ui->lightFrame->setHidden(true); 302 | ui->voicepromptFrame->setHidden(true); 303 | ui->notificationFrame->setHidden(true); 304 | ui->sidetoneFrame->setHidden(true); 305 | ui->inactivityFrame->setHidden(true); 306 | ui->chatmixFrame->setHidden(true); 307 | ui->volumelimiterFrame->setHidden(true); 308 | 309 | ui->equalizerpresetFrame->setHidden(true); 310 | ui->equalizerFrame->setHidden(true); 311 | ui->applyEqualizer->setEnabled(false); 312 | clearEqualizerSliders(); 313 | 314 | ui->rotatetomuteFrame->setHidden(true); 315 | ui->muteledbrightnessFrame->setHidden(true); 316 | ui->micvolumeFrame->setHidden(true); 317 | 318 | ui->btwhenonFrame->setHidden(true); 319 | ui->btcallvolumeFrame->setHidden(true); 320 | } 321 | 322 | //Utility Section 323 | void MainWindow::sendAppNotification(const QString &title, 324 | const QString &description, 325 | const QIcon &icon) 326 | { 327 | trayIcon->showMessage(title, description, icon); 328 | } 329 | 330 | //Devices Managing Section 331 | void MainWindow::loadDevice() 332 | { 333 | resetGUI(); 334 | 335 | selectedDevice = API.getSelectedDevice(); 336 | if (selectedDevice == nullptr) { 337 | API.setSelectedDevice("0", "0"); 338 | ui->missingheadsetcontrolFrame->setHidden(true); 339 | rescaleAndMoveWindow(); 340 | return; 341 | } else { 342 | QList savedDevices = getSavedDevices(); 343 | selectedDevice->updateConfig(savedDevices); 344 | deleteDevices(savedDevices); 345 | } 346 | 347 | QSet &capabilities = selectedDevice->capabilities; 348 | 349 | ui->missingheadsetcontrolFrame->setHidden(true); 350 | ui->notSupportedFrame->setHidden(true); 351 | 352 | qDebug() << "Selected Device"; 353 | qDebug() << "Device:\t" << selectedDevice->device; 354 | qDebug() << "Caps:\t" << selectedDevice->capabilities; 355 | 356 | // Info section 357 | ui->deviceinfovalueLabel->setText(selectedDevice->device + "
" + selectedDevice->vendor 358 | + "
" + selectedDevice->product); 359 | ui->deviceinfoFrame->setHidden(false); 360 | if (capabilities.contains("CAP_BATTERY_STATUS")) { 361 | ui->batteryFrame->setHidden(false); 362 | setBatteryStatus(); 363 | qDebug() << "Battery:\t" << selectedDevice->battery.status 364 | << QString::number(selectedDevice->battery.level); 365 | } 366 | 367 | ui->tabWidget->show(); 368 | // Other Section 369 | if (capabilities.contains("CAP_LIGHTS")) { 370 | ui->lightFrame->setHidden(false); 371 | ui->tabWidget->setTabEnabled(0, true); 372 | ledOn->setEnabled(true); 373 | ledOff->setEnabled(true); 374 | } 375 | if (capabilities.contains("CAP_SIDETONE")) { 376 | ui->sidetoneFrame->setHidden(false); 377 | ui->tabWidget->setTabEnabled(0, true); 378 | } 379 | if (capabilities.contains("CAP_VOICE_PROMPTS")) { 380 | ui->voicepromptFrame->setHidden(false); 381 | ui->tabWidget->setTabEnabled(0, true); 382 | } 383 | if (capabilities.contains("CAP_NOTIFICATION_SOUND")) { 384 | ui->notificationFrame->setHidden(false); 385 | ui->tabWidget->setTabEnabled(0, true); 386 | } 387 | if (capabilities.contains("CAP_INACTIVE_TIME")) { 388 | ui->inactivityFrame->setHidden(false); 389 | ui->tabWidget->setTabEnabled(0, true); 390 | } 391 | if (capabilities.contains("CAP_CHATMIX_STATUS")) { 392 | ui->chatmixFrame->setHidden(false); 393 | ui->tabWidget->setTabEnabled(0, true); 394 | setChatmixStatus(); 395 | qDebug() << "Chatmix:\t" << QString::number(selectedDevice->chatmix); 396 | } 397 | // Equalizer Section 398 | if (capabilities.contains("CAP_EQUALIZER_PRESET") && !selectedDevice->presets_list.empty()) { 399 | ui->equalizerpresetFrame->setHidden(false); 400 | ui->tabWidget->setTabEnabled(1, true); 401 | } 402 | if (capabilities.contains("CAP_EQUALIZER") && selectedDevice->equalizer.bands_number > 0) { 403 | ui->equalizerFrame->setHidden(false); 404 | ui->tabWidget->setTabEnabled(1, true); 405 | } 406 | if (capabilities.contains("CAP_VOLUME_LIMITER")) { 407 | ui->volumelimiterFrame->setHidden(false); 408 | ui->tabWidget->setTabEnabled(1, true); 409 | } 410 | // Microphone Section 411 | if (capabilities.contains("CAP_ROTATE_TO_MUTE")) { 412 | ui->rotatetomuteFrame->setHidden(false); 413 | ui->tabWidget->setTabEnabled(2, true); 414 | } 415 | if (capabilities.contains("CAP_MICROPHONE_MUTE_LED_BRIGHTNESS")) { 416 | ui->muteledbrightnessFrame->setHidden(false); 417 | ui->tabWidget->setTabEnabled(2, true); 418 | } 419 | if (capabilities.contains("CAP_MICROPHONE_VOLUME")) { 420 | ui->micvolumeFrame->setHidden(false); 421 | ui->tabWidget->setTabEnabled(2, true); 422 | } 423 | // Bluetooth Section 424 | if (capabilities.contains("CAP_BT_WHEN_POWERED_ON")) { 425 | ui->btwhenonFrame->setHidden(false); 426 | ui->tabWidget->setTabEnabled(3, true); 427 | } 428 | if (capabilities.contains("CAP_BT_CALL_VOLUME")) { 429 | ui->btcallvolumeFrame->setHidden(false); 430 | ui->tabWidget->setTabEnabled(3, true); 431 | } 432 | 433 | qDebug(); 434 | loadGUIValues(); 435 | rescaleAndMoveWindow(); 436 | } 437 | 438 | void MainWindow::loadGUIValues() 439 | { 440 | if (selectedDevice->lights >= 0) { 441 | ui->onlightButton->setChecked(selectedDevice->lights); 442 | ui->offlightButton->setChecked(!selectedDevice->lights); 443 | } 444 | if (selectedDevice->sidetone >= 0) { 445 | ui->sidetoneSlider->setSliderPosition(selectedDevice->sidetone); 446 | } 447 | if (selectedDevice->voice_prompts >= 0) { 448 | ui->voiceOnButton->setChecked(selectedDevice->voice_prompts); 449 | ui->voiceOffButton->setChecked(!selectedDevice->voice_prompts); 450 | } 451 | if (selectedDevice->inactive_time >= 0) { 452 | ui->inactivitySlider->setSliderPosition(selectedDevice->inactive_time); 453 | } 454 | 455 | clearEqualizerSliders(); 456 | createEqualizerSliders(); 457 | 458 | ui->equalizerPresetcomboBox->clear(); 459 | for (EqualizerPreset &preset : selectedDevice->presets_list) { 460 | ui->equalizerPresetcomboBox->addItem(preset.name); 461 | } 462 | ui->equalizerPresetcomboBox->setCurrentIndex(-1); 463 | if (selectedDevice->equalizer_preset >= 0) { 464 | ui->equalizerPresetcomboBox->setCurrentIndex(selectedDevice->equalizer_preset); 465 | } else if (selectedDevice->equalizer_curve.length() == selectedDevice->equalizer.bands_number) { 466 | setEqualizerSliders(selectedDevice->equalizer_curve); 467 | } 468 | 469 | if (selectedDevice->volume_limiter >= 0) { 470 | ui->volumelimiterOnButton->setChecked(selectedDevice->volume_limiter); 471 | ui->volumelimiterOffButton->setChecked(!selectedDevice->volume_limiter); 472 | } 473 | 474 | if (selectedDevice->rotate_to_mute >= 0) { 475 | ui->rotateOn->setChecked(selectedDevice->rotate_to_mute); 476 | ui->rotateOff->setChecked(!selectedDevice->rotate_to_mute); 477 | } 478 | if (selectedDevice->mic_mute_led_brightness >= 0) { 479 | ui->muteledbrightnessSlider->setSliderPosition(selectedDevice->mic_mute_led_brightness); 480 | } 481 | if (selectedDevice->mic_volume >= 0) { 482 | ui->micvolumeSlider->setSliderPosition(selectedDevice->mic_volume); 483 | } 484 | 485 | if (selectedDevice->bt_call_volume >= 0) { 486 | switch (selectedDevice->bt_call_volume) { 487 | case 0: 488 | ui->btbothRadioButton->setChecked(true); 489 | break; 490 | case 1: 491 | ui->btpcdbRadioButton->setChecked(true); 492 | break; 493 | case 2: 494 | ui->btonlyRadioButton->setChecked(true); 495 | break; 496 | default: 497 | break; 498 | } 499 | } 500 | if (selectedDevice->bt_when_powered_on >= 0) { 501 | ui->btwhenonOnButton->setChecked(selectedDevice->bt_when_powered_on); 502 | ui->btwhenonOffButton->setChecked(!selectedDevice->bt_when_powered_on); 503 | } 504 | } 505 | 506 | void MainWindow::saveDevicesSettings() 507 | { 508 | QList toSave = getSavedDevices(); 509 | updateDeviceFromSource(toSave, selectedDevice); 510 | 511 | serializeDevices(toSave, DEVICES_SETTINGS_FILEPATH); 512 | 513 | deleteDevices(toSave); 514 | } 515 | 516 | QList MainWindow::getSavedDevices() 517 | { 518 | return deserializeDevices(DEVICES_SETTINGS_FILEPATH); 519 | } 520 | 521 | bool MainWindow::updateSelectedDevice() 522 | { 523 | API.updateSelectedDevice(); 524 | if (API.getSelectedDevice() == nullptr) { 525 | API.setSelectedDevice("0","0"); 526 | selectedDevice = API.getSelectedDevice(); 527 | return false; 528 | } 529 | 530 | setBatteryStatus(); 531 | setChatmixStatus(); 532 | return true; 533 | } 534 | 535 | //Update GUI Section 536 | void MainWindow::updateGUI() 537 | { 538 | if (!API.areApiAvailable()) { 539 | resetGUI(); 540 | rescaleAndMoveWindow(); 541 | ui->notSupportedFrame->setHidden(true); 542 | rescaleAndMoveWindow(); 543 | selectedDevice = nullptr; 544 | } else { 545 | if (selectedDevice == nullptr || !updateSelectedDevice()) { 546 | loadDevice(); 547 | } 548 | } 549 | } 550 | 551 | // Info Section Events 552 | void MainWindow::setBatteryStatus() 553 | { 554 | if (selectedDevice == nullptr) { 555 | changeTrayIconTo("headphones"); 556 | return; 557 | } 558 | 559 | QString status = selectedDevice->battery.status; 560 | int batteryLevel = selectedDevice->battery.level; 561 | QString level = QString::number(batteryLevel); 562 | 563 | if (batteryLevel >= 0) { 564 | ui->batteryProgressBar->show(); 565 | ui->batteryProgressBar->setValue(batteryLevel); 566 | } else { 567 | ui->batteryProgressBar->hide(); 568 | } 569 | 570 | if (status == "BATTERY_UNAVAILABLE") { 571 | ui->batteryPercentage->setText(tr("Headset Off")); 572 | trayIcon->setToolTip(tr("HeadsetControl \r\nHeadset Off")); 573 | changeTrayIconTo("headphones"); 574 | } else if (status == "BATTERY_CHARGING") { 575 | ui->batteryPercentage->setText(level + tr("% - Charging")); 576 | trayIcon->setToolTip(tr("HeadsetControl \r\nBattery: Charging - ") + level + "%"); 577 | if (batteryLevel < 100){ 578 | changeTrayIconTo("battery-charging"); 579 | } else { 580 | if (settings.notificationBatteryFull && !notified) { 581 | sendAppNotification(tr("Battery Charged!"), 582 | tr("The battery has been charged to 100%"), 583 | QIcon("battery-level-full")); 584 | if (settings.audioNotification) { 585 | API.playNotificationSound(1); 586 | } 587 | notified = true; 588 | } 589 | changeTrayIconTo("battery-fully-charged"); 590 | } 591 | } else if (status == "BATTERY_AVAILABLE") { 592 | ui->batteryPercentage->setText(level + tr("% - Descharging")); 593 | trayIcon->setToolTip(tr("HeadsetControl \r\nBattery: ") + level + "%"); 594 | if (batteryLevel > 75) { 595 | changeTrayIconTo("battery-level-full"); 596 | notified = false; 597 | } else if (batteryLevel > settings.batteryLowThreshold) { 598 | changeTrayIconTo("battery-medium"); 599 | notified = false; 600 | } else { 601 | changeTrayIconTo("battery-low"); 602 | if (settings.notificationBatteryLow && !notified) { 603 | sendAppNotification(tr("Battery Alert!"), 604 | tr("The battery of your headset is running low"), 605 | QIcon("battery-low")); 606 | if (settings.audioNotification) { 607 | API.playNotificationSound(0); 608 | } 609 | notified = true; 610 | } 611 | } 612 | } else { 613 | ui->batteryPercentage->setText(tr("No battery info")); 614 | trayIcon->setToolTip("HeadsetControl"); 615 | changeTrayIconTo("headphones"); 616 | } 617 | } 618 | 619 | void MainWindow::setChatmixStatus() 620 | { 621 | QString chatmixStatus = tr("None"); 622 | 623 | if (selectedDevice == nullptr) { 624 | ui->chatmixvalueLabel->setText(chatmixStatus); 625 | return; 626 | } 627 | 628 | int chatmix = selectedDevice->chatmix; 629 | QString chatmixValue = QString::number(chatmix); 630 | if (chatmix < 65) 631 | chatmixStatus = tr("Game"); 632 | else if (chatmix > 65) 633 | chatmixStatus = tr("Chat"); 634 | 635 | ui->chatmixvalueLabel->setText(chatmixValue); 636 | ui->chatmixstatusLabel->setText(chatmixStatus); 637 | } 638 | 639 | // Equalizer Section Events 640 | void MainWindow::equalizerPresetChanged() 641 | { 642 | int index = ui->equalizerPresetcomboBox->currentIndex(); 643 | setEqualizerSliders(selectedDevice->presets_list.value(index).values); 644 | API.setEqualizerPreset(index); 645 | } 646 | 647 | void MainWindow::applyEqualizer( 648 | bool saveToFile) 649 | { 650 | ui->equalizerPresetcomboBox->setCurrentIndex(-1); 651 | QList values; 652 | for (QSlider *slider : std::as_const(equalizerSliders)) { 653 | values.append(slider->value() * selectedDevice->equalizer.band_step); 654 | } 655 | API.setEqualizer(values, saveToFile); 656 | } 657 | 658 | //Equalizer Slidesrs Section 659 | void MainWindow::createEqualizerSliders() 660 | { 661 | QHBoxLayout *layout = ui->equalizerLayout; 662 | int &bands_number = selectedDevice->equalizer.bands_number; 663 | if (bands_number > 0) { 664 | for (int i = 0; i < bands_number; ++i) { 665 | QSlider *s = new QSlider(Qt::Vertical); 666 | s->setMaximum(selectedDevice->equalizer.band_max / selectedDevice->equalizer.band_step); 667 | s->setMinimum(selectedDevice->equalizer.band_min / selectedDevice->equalizer.band_step); 668 | s->setSingleStep(1); 669 | s->setTickInterval(1 / selectedDevice->equalizer.band_step); 670 | if (selectedDevice->equalizer_curve.size() == bands_number) { 671 | s->setValue(selectedDevice->equalizer_curve.value(i)); 672 | } else { 673 | s->setValue(selectedDevice->equalizer.band_baseline); 674 | } 675 | 676 | equalizerSliders.append(s); 677 | layout->addWidget(s); 678 | connect(s, &QAbstractSlider::sliderReleased, this, [=]() { 679 | if (equalizerLiveUpdate) 680 | applyEqualizer(false); 681 | }); 682 | } 683 | ui->applyEqualizer->setEnabled(true); 684 | } 685 | } 686 | 687 | void MainWindow::clearEqualizerSliders() 688 | { 689 | QHBoxLayout *layout = ui->equalizerLayout; 690 | while (QLayoutItem *item = layout->takeAt(0)) { 691 | if (QWidget *widget = item->widget()) { 692 | widget->disconnect(); 693 | widget->deleteLater(); 694 | } 695 | delete item; 696 | } 697 | equalizerSliders.clear(); 698 | } 699 | 700 | void MainWindow::setEqualizerSliders( 701 | double value) 702 | { 703 | for (QSlider *slider : std::as_const(equalizerSliders)) { 704 | slider->setValue(value / selectedDevice->equalizer.band_step); 705 | } 706 | } 707 | 708 | void MainWindow::setEqualizerSliders(QList values) 709 | { 710 | int i = 0; 711 | if (values.length() == selectedDevice->equalizer.bands_number) { 712 | for (QSlider *slider : std::as_const(equalizerSliders)) { 713 | slider->setValue((int) (values[i++] / selectedDevice->equalizer.band_step)); 714 | } 715 | } else { 716 | setEqualizerSliders(0); 717 | qDebug() << "ERROR: Bad Equalizer Preset"; 718 | } 719 | } 720 | 721 | // Tool Bar Events 722 | void MainWindow::selectDevice() 723 | { 724 | QList connectedDevices = API.getConnectedDevices(); 725 | 726 | LoaddeviceWindow *loadDevWindow = new LoaddeviceWindow(connectedDevices, this); 727 | timerGUI->stop(); 728 | if (loadDevWindow->exec() == QDialog::Accepted) { 729 | int index = loadDevWindow->getDeviceIndex(); 730 | if (index >= 0 && index < connectedDevices.length()) { 731 | Device* d=connectedDevices[index]; 732 | API.setSelectedDevice(d->id_vendor, d->id_product); 733 | loadDevice(); 734 | settings.lastSelectedVendorID = selectedDevice->id_vendor; 735 | settings.lastSelectedProductID = selectedDevice->id_product; 736 | saveSettingstoFile(settings, PROGRAM_SETTINGS_FILEPATH); 737 | } 738 | } 739 | deleteDevices(connectedDevices); 740 | delete(loadDevWindow); 741 | timerGUI->start(); 742 | } 743 | 744 | void MainWindow::editProgramSetting() 745 | { 746 | SettingsWindow *settingsW = new SettingsWindow(settings, this); 747 | if (settingsW->exec() == QDialog::Accepted) { 748 | settings = settingsW->getSettings(); 749 | saveSettingstoFile(settings, PROGRAM_SETTINGS_FILEPATH); 750 | timerGUI->setInterval(settings.msecUpdateIntervalTime); 751 | updateStyle(); 752 | } 753 | delete (settingsW); 754 | } 755 | 756 | void MainWindow::checkForUpdates(bool firstStart) 757 | { 758 | bool needsUpdate = false; 759 | 760 | const QString &hcVersion = API.getVersion(); 761 | const QString &guiVersion = qApp->applicationVersion(); 762 | const QVersionNumber &local_hc = QVersionNumber::fromString(hcVersion); 763 | const QVersionNumber local_gui = QVersionNumber::fromString(guiVersion); 764 | QString v1 = getLatestGitHubReleaseVersion("Sapd", "HeadsetControl"); 765 | QString v2 = getLatestGitHubReleaseVersion("LeoKlaus", "HeadsetControl-GUI"); 766 | QVersionNumber remote_hc = QVersionNumber::fromString(v1); 767 | QVersionNumber remote_gui = QVersionNumber::fromString(v2); 768 | QString s1 = tr("up-to date v") + hcVersion; 769 | QString s2 = tr("up-to date v") + guiVersion; 770 | if (!(v1 == "") && remote_hc > local_hc && hcVersion != "continuous-modified") { 771 | s1 = tr("Newer version") 772 | + " -> " 774 | + remote_hc.toString() + ""; 775 | needsUpdate = true; 776 | } 777 | if (!(v2 == "") && remote_gui > local_gui) { 778 | s2 = tr("Newer version") 779 | + " -> " 782 | + remote_gui.toString() + ""; 783 | needsUpdate = true; 784 | } 785 | 786 | if ((needsUpdate && firstStart) || !firstStart) { 787 | DialogInfo *dialogWindow = new DialogInfo(this); 788 | dialogWindow->setTitle(tr("Check for updates")); 789 | QString text = "HeadesetControl: " + s1 + "
HeadesetControl-GUI: " + s2; 790 | dialogWindow->setLabel(text); 791 | 792 | dialogWindow->exec(); 793 | delete (dialogWindow); 794 | } 795 | } 796 | 797 | void MainWindow::showAbout() 798 | { 799 | DialogInfo *dialogWindow = new DialogInfo(this); 800 | dialogWindow->setTitle(tr("About this program")); 801 | QString text = tr("You can find HeadsetControl-GUI source code on " 802 | "GitHub.
" 803 | "Made by:
" 804 | " - LeoKlaus
" 805 | " - nicola02nb
" 806 | "Version: ") 807 | + qApp->applicationVersion(); 808 | dialogWindow->setLabel(text); 809 | 810 | dialogWindow->exec(); 811 | 812 | delete (dialogWindow); 813 | } 814 | 815 | void MainWindow::showCredits() 816 | { 817 | DialogInfo *dialogWindow = new DialogInfo(this); 818 | dialogWindow->setTitle(tr("Credits")); 819 | QString text = tr("Big shout-out to:
" 820 | " - Sapd for HeadsetCoontrol"); 822 | dialogWindow->setLabel(text); 823 | 824 | dialogWindow->exec(); 825 | 826 | delete (dialogWindow); 827 | } 828 | -------------------------------------------------------------------------------- /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 notified = false; 38 | 39 | QString defaultStyle; 40 | 41 | Ui::MainWindow *ui; 42 | QSystemTrayIcon *trayIcon; 43 | QString trayIconName = "headphones"; 44 | QMenu *trayMenu; 45 | QAction *ledOn; 46 | QAction *ledOff; 47 | QTimer *timerGUI; 48 | 49 | Settings settings; 50 | 51 | int n_connected = 0, n_saved = 0; 52 | 53 | HeadsetControlAPI API; 54 | Device *selectedDevice = nullptr; 55 | 56 | QList equalizerSliders; 57 | bool equalizerLiveUpdate = false; 58 | 59 | void bindEvents(); 60 | 61 | //Tray Icon Section 62 | void changeTrayIconTo(QString iconName); 63 | void setupTrayIcon(); 64 | 65 | //Theme mode Section 66 | bool isAppDarkMode(); 67 | void updateIconsTheme(); 68 | void updateStyle(); 69 | 70 | void resetGUI(); 71 | 72 | //Window Position and Size Section 73 | void minimizeWindowSize(); 74 | void moveToBottomRight(); 75 | void rescaleAndMoveWindow(); 76 | void toggleWindow(); 77 | 78 | //Utility 79 | void sendAppNotification(const QString &title, const QString &description, const QIcon &icon); 80 | 81 | //Devices Managing Section 82 | bool updateSelectedDevice(); 83 | void loadDevice(); 84 | void loadGUIValues(); 85 | QList getSavedDevices(); 86 | 87 | // Info Section Events 88 | void setBatteryStatus(); 89 | void setChatmixStatus(); 90 | 91 | //Equalizer Slidesrs Section 92 | void createEqualizerSliders(); 93 | void clearEqualizerSliders(); 94 | void setEqualizerSliders(double value); 95 | void setEqualizerSliders(QList values); 96 | 97 | private slots: 98 | void changeEvent(QEvent *e); 99 | 100 | //Tray Icon Section 101 | void trayIconActivated(QSystemTrayIcon::ActivationReason reason); 102 | 103 | //Devices Managing Section 104 | void saveDevicesSettings(); 105 | 106 | //Update GUI Section 107 | void updateGUI(); 108 | 109 | // Equalizer Section Events 110 | void equalizerPresetChanged(); 111 | void applyEqualizer(bool saveToFile = true); 112 | 113 | // Tool Bar Events 114 | void selectDevice(); 115 | void editProgramSetting(); 116 | void checkForUpdates(bool firstStart = false); 117 | void showAbout(); 118 | void showCredits(); 119 | }; 120 | #endif // MAINWINDOW_H 121 | -------------------------------------------------------------------------------- /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->batteryfullnotificationCheckBox->setChecked(programSettings.notificationBatteryFull); 29 | ui->batterylownotificationCheckBox->setChecked(programSettings.notificationBatteryLow); 30 | ui->batterylowtresholdSpinBox->setValue(programSettings.batteryLowThreshold); 31 | ui->enableaudioNotificationCheckBox->setChecked(programSettings.audioNotification); 32 | 33 | ui->updateintervaltimeDoubleSpinBox->setValue((double) programSettings.msecUpdateIntervalTime 34 | / 1000); 35 | 36 | loadStyles(); 37 | ui->selectstyleComboBox->setCurrentIndex( 38 | ui->selectstyleComboBox->findText(programSettings.styleName)); 39 | } 40 | 41 | Settings SettingsWindow::getSettings() 42 | { 43 | Settings settings; 44 | settings.runOnstartup = ui->runonstartupCheckBox->isChecked(); 45 | settings.notificationBatteryFull = ui->batteryfullnotificationCheckBox->isChecked(); 46 | settings.notificationBatteryLow = ui->batterylownotificationCheckBox->isChecked(); 47 | settings.batteryLowThreshold = ui->batterylowtresholdSpinBox->value(); 48 | settings.audioNotification = ui->enableaudioNotificationCheckBox->isChecked(); 49 | settings.msecUpdateIntervalTime = ui->updateintervaltimeDoubleSpinBox->value() * 1000; 50 | settings.styleName = ui->selectstyleComboBox->currentText(); 51 | 52 | return settings; 53 | } 54 | 55 | void SettingsWindow::setRunOnStartup() 56 | { 57 | bool enabled = setOSRunOnStartup(ui->runonstartupCheckBox->isChecked()); 58 | ui->runonstartupCheckBox->setChecked(enabled); 59 | } 60 | 61 | void SettingsWindow::loadStyles() 62 | { 63 | QString destination = PROGRAM_STYLES_PATH; 64 | QDir directory = QDir(destination); 65 | QStringList list = directory.entryList(QStringList() << "*.qss", QDir::Files); 66 | ui->selectstyleComboBox->clear(); 67 | ui->selectstyleComboBox->addItem("Default"); 68 | ui->selectstyleComboBox->addItems(list); 69 | } 70 | 71 | void SettingsWindow::saveStyle() 72 | { 73 | QFileDialog dialog(this); 74 | dialog.setFileMode(QFileDialog::AnyFile); 75 | dialog.setNameFilter("QStyle (*.qss)"); 76 | 77 | if (dialog.exec()) 78 | { 79 | QStringList fileUrls = dialog.selectedFiles(); 80 | QUrl fileUrl = QUrl(fileUrls[0]); 81 | if (fileUrl.isValid()) { 82 | QString source = fileUrl.path().removeFirst(); 83 | QString destination = PROGRAM_STYLES_PATH; 84 | QDir().mkpath(destination); 85 | destination += "/" + fileUrl.fileName(); 86 | QFile file(destination); 87 | if (file.exists()) { 88 | file.remove(); 89 | } 90 | QFile::copy(source, destination); 91 | 92 | loadStyles(); 93 | } 94 | } 95 | } 96 | 97 | void SettingsWindow::removeStyle() 98 | { 99 | QString stylePath = PROGRAM_STYLES_PATH + "/" + ui->selectstyleComboBox->currentText(); 100 | QFile file(stylePath); 101 | if (file.exists()) { 102 | file.remove(); 103 | } 104 | 105 | loadStyles(); 106 | } 107 | 108 | SettingsWindow::~SettingsWindow() 109 | { 110 | delete ui; 111 | } 112 | -------------------------------------------------------------------------------- /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 | }; 30 | 31 | #endif // SETTINGSWINDOW_H 32 | -------------------------------------------------------------------------------- /src/UI/settingswindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | settingswindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 361 10 | 325 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 | 77 | 0 78 | 0 79 | 80 | 81 | 82 | Battery low notification: 83 | 84 | 85 | 86 | 87 | 88 | 89 | Qt::LayoutDirection::RightToLeft 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 0 101 | 0 102 | 103 | 104 | 105 | Battery low threshold: 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 0 114 | 0 115 | 116 | 117 | 118 | Battery full notification: 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 120 127 | 0 128 | 129 | 130 | 131 | 132 | 120 133 | 16777215 134 | 135 | 136 | 137 | 100 138 | 139 | 140 | 15 141 | 142 | 143 | 144 | 145 | 146 | 147 | Qt::LayoutDirection::RightToLeft 148 | 149 | 150 | 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 | Enable audio notifications: 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 0 185 | 0 186 | 187 | 188 | 189 | QFrame::Shape::StyledPanel 190 | 191 | 192 | QFrame::Shadow::Raised 193 | 194 | 195 | 196 | 197 | 198 | Update Info interval time (seconds): 199 | Default: 30 seconds 200 | DON'T PUT TOO LOW VALUES 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 120 209 | 0 210 | 211 | 212 | 213 | 214 | 120 215 | 16777215 216 | 217 | 218 | 219 | 0 220 | 221 | 222 | 1.000000000000000 223 | 224 | 225 | 99999.000000000000000 226 | 227 | 228 | 30.000000000000000 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | QFrame::Shape::StyledPanel 239 | 240 | 241 | QFrame::Shadow::Raised 242 | 243 | 244 | 245 | 246 | 247 | Custom Style: 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 258 | 259 | 260 | 99 261 | 262 | 263 | 264 | 265 | 266 | 267 | Load 268 | 269 | 270 | 271 | 272 | 273 | 274 | Remove 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | Qt::Orientation::Horizontal 285 | 286 | 287 | QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | buttonBox 297 | accepted() 298 | settingswindow 299 | accept() 300 | 301 | 302 | 248 303 | 254 304 | 305 | 306 | 157 307 | 274 308 | 309 | 310 | 311 | 312 | buttonBox 313 | rejected() 314 | settingswindow 315 | reject() 316 | 317 | 318 | 316 319 | 260 320 | 321 | 322 | 286 323 | 274 324 | 325 | 326 | 327 | 328 | 329 | -------------------------------------------------------------------------------- /src/Utils/headsetcontrolapi.cpp: -------------------------------------------------------------------------------- 1 | #include "headsetcontrolapi.h" 2 | #include "utils.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | HeadsetControlAPI::HeadsetControlAPI(const QString& headsetcontrolDirectory) 11 | { 12 | if (headsetcontrolDirectory == ""){ 13 | this->headsetcontrolFilePath = "headsetcontrol"; 14 | #ifdef Q_OS_WIN 15 | this->headsetcontrolFilePath += ".exe"; 16 | #endif 17 | } else { 18 | this->headsetcontrolFilePath = headsetcontrolDirectory; 19 | } 20 | 21 | parseOutput(sendCommand(QStringList())); 22 | selectedVendorId="0"; 23 | selectedProductId="0"; 24 | } 25 | 26 | Device* HeadsetControlAPI::getSelectedDevice() 27 | { 28 | return selectedDevice; 29 | } 30 | 31 | bool HeadsetControlAPI::areApiAvailable(){ 32 | QString executableName = this->headsetcontrolFilePath; 33 | QFileInfo fileInfo(QDir::currentPath(),executableName); 34 | if (fileInfo.exists()) 35 | return true; 36 | else { 37 | QString path = QStandardPaths::findExecutable(executableName); 38 | return !path.isEmpty(); 39 | } 40 | } 41 | 42 | void HeadsetControlAPI::setSelectedDevice(const QString& vendorId, const QString& productId) 43 | { 44 | if(vendorId == "") 45 | selectedVendorId = "0"; 46 | else 47 | selectedVendorId=vendorId; 48 | if(productId == "") 49 | selectedProductId = "0"; 50 | else 51 | selectedProductId=productId; 52 | selectedDevice = getDevice(); 53 | } 54 | 55 | void HeadsetControlAPI::updateSelectedDevice(){ 56 | Device* newD = getDevice(); 57 | if (newD == nullptr){ 58 | selectedDevice = nullptr; 59 | } else { 60 | selectedDevice->updateInfo(newD); 61 | } 62 | } 63 | 64 | QString HeadsetControlAPI::getName() 65 | { 66 | return name; 67 | } 68 | 69 | QString HeadsetControlAPI::getVersion() 70 | { 71 | return version; 72 | } 73 | 74 | QString HeadsetControlAPI::getApiVersion() 75 | { 76 | return api_version; 77 | } 78 | 79 | QString HeadsetControlAPI::getHidApiVersion() 80 | { 81 | return hidapi_version; 82 | } 83 | 84 | Device *HeadsetControlAPI::getDevice() 85 | { 86 | QList connectedList = getConnectedDevices(selectedVendorId, selectedProductId); 87 | Device *connectedDevice = nullptr; 88 | if(selectedVendorId == "0" && selectedProductId == "0"){ 89 | if (connectedList.size() > 0){ 90 | connectedDevice = connectedList.at(0); 91 | } 92 | } else { 93 | foreach(Device *d, connectedList){ 94 | if(d->id_vendor == selectedVendorId && d->id_product == selectedProductId){ 95 | connectedDevice = d; 96 | } 97 | } 98 | } 99 | Device *device = nullptr; 100 | if (connectedDevice != nullptr) { 101 | device = new Device(); 102 | *device = *connectedDevice; 103 | selectedVendorId = device->id_vendor; 104 | selectedProductId = device->id_product; 105 | } 106 | deleteDevices(connectedList); 107 | 108 | return device; 109 | } 110 | 111 | QList HeadsetControlAPI::getConnectedDevices(const QString& vendorId, const QString& productId) 112 | { 113 | QStringList args = QStringList(); 114 | args << QString("--device") << QString(vendorId+":"+productId); 115 | QString output = sendCommand(args); 116 | QJsonObject jsonInfo = parseOutput(output); 117 | 118 | int device_number = jsonInfo["device_count"].toInt(); 119 | qDebug() << "Found" << device_number << "devices:"; 120 | 121 | QList devices; 122 | QJsonArray jsonDevices = jsonInfo["devices"].toArray(); 123 | for (auto jsonDevice: jsonDevices) { 124 | Device *device = new Device(jsonDevice.toObject(), output); 125 | devices.append(device); 126 | qDebug() << device->device << "[" << device->id_vendor << ":" << device->id_vendor << "] "; 127 | } 128 | qDebug(); 129 | 130 | return devices; 131 | } 132 | 133 | // HC rleated functions 134 | QString HeadsetControlAPI::sendCommand(const QStringList &args_list) 135 | { 136 | QProcess *proc = new QProcess(); 137 | QStringList args = QStringList(); 138 | args << QString("--output") << QString("JSON"); 139 | //args << QString("--test-device"); //Uncomment this to enable test device 140 | args << args_list; 141 | 142 | proc->setProgram(headsetcontrolFilePath); 143 | proc->setArguments(args); 144 | 145 | proc->start(); 146 | proc->waitForFinished(); 147 | QString output = proc->readAllStandardOutput(); 148 | qDebug() << "Command:\t" << headsetcontrolFilePath; 149 | qDebug() << "Args:\t" << args; 150 | qDebug() << "ExitStatus:\t" << proc->exitStatus(); 151 | qDebug() << "Error:\t" << proc->error(); 152 | qDebug() << "ErrorMessage:\t" << proc->errorString(); 153 | qDebug() << "ExitCode:\t" << proc->exitCode(); 154 | 155 | //qDebug() << "Output:" << output; 156 | qDebug(); 157 | 158 | delete (proc); 159 | 160 | return output; 161 | } 162 | 163 | QJsonObject HeadsetControlAPI::parseOutput( 164 | const QString &output) 165 | { 166 | QJsonDocument jsonDoc = QJsonDocument::fromJson(output.toUtf8()); 167 | QJsonObject jsonInfo = jsonDoc.object(); 168 | 169 | name = jsonInfo["name"].toString(); 170 | version = jsonInfo["version"].toString(); 171 | api_version = jsonInfo["api_version"].toString(); 172 | hidapi_version = jsonInfo["hidapi_version"].toString(); 173 | 174 | return jsonInfo; 175 | } 176 | 177 | Action HeadsetControlAPI::sendAction(const QStringList &args_list) 178 | { 179 | QStringList args; 180 | args << QString("--device") << QString(selectedVendorId+":"+selectedProductId) << args_list; 181 | QString output = sendCommand(args); 182 | QJsonObject jsonInfo = parseOutput(output); 183 | QJsonArray actions = jsonInfo["actions"].toArray(); 184 | Action action; 185 | if (!actions.isEmpty()) { 186 | QJsonObject jaction = actions[0].toObject(); 187 | 188 | action.device = jaction["device"].toString(); 189 | action.capability = jaction["capability"].toString(); 190 | action.status = jaction["status"].toString(); 191 | action.error_message = jaction["error_message"].toString(); 192 | 193 | action.success = action.status == "success"; 194 | 195 | qDebug() << "Device:\t" << action.device; 196 | qDebug() << "Capability:" << action.capability; 197 | qDebug() << "Status:\t" << action.status; 198 | if (!action.success) { 199 | qDebug() << "Error:\t" << action.error_message; 200 | } 201 | qDebug(); 202 | } 203 | 204 | return action; 205 | } 206 | 207 | 208 | void HeadsetControlAPI::setSidetone( 209 | int level, bool emitSignal) 210 | { 211 | QStringList args = QStringList() << QString("--sidetone") << QString::number(level); 212 | Action a = sendAction(args); 213 | if (emitSignal && a.success) { 214 | selectedDevice->sidetone = level; 215 | emit actionSuccesful(); 216 | } 217 | } 218 | 219 | void HeadsetControlAPI::setLights( 220 | bool enabled, bool emitSignal) 221 | { 222 | QStringList args = QStringList() << QString("--light") << QString::number(enabled); 223 | Action a = sendAction(args); 224 | if (emitSignal && a.success) { 225 | selectedDevice->lights = enabled; 226 | emit actionSuccesful(); 227 | } 228 | } 229 | 230 | void HeadsetControlAPI::setVoicePrompts( 231 | bool enabled, bool emitSignal) 232 | { 233 | QStringList args = QStringList() << QString("--voice-prompt") << QString::number(enabled); 234 | Action a = sendAction(args); 235 | if (emitSignal && a.success) { 236 | selectedDevice->voice_prompts = enabled; 237 | emit actionSuccesful(); 238 | } 239 | } 240 | 241 | void HeadsetControlAPI::setInactiveTime( 242 | int time, bool emitSignal) 243 | { 244 | QStringList args = QStringList() << QString("--inactive-time") << QString::number(time); 245 | Action a = sendAction(args); 246 | if (emitSignal && a.success) { 247 | selectedDevice->inactive_time = time; 248 | emit actionSuccesful(); 249 | } 250 | } 251 | 252 | void HeadsetControlAPI::playNotificationSound( 253 | int id, bool emitSignal) 254 | { 255 | QStringList args = QStringList() << QString("--notificate") << QString::number(id); 256 | Action a = sendAction(args); 257 | if (emitSignal && a.success) { 258 | selectedDevice->notification_sound = id; 259 | emit actionSuccesful(); 260 | } 261 | } 262 | 263 | void HeadsetControlAPI::setVolumeLimiter( 264 | bool enabled, bool emitSignal) 265 | { 266 | QStringList args = QStringList() << QString("--volume-limiter") << QString::number(enabled); 267 | Action a = sendAction(args); 268 | if (emitSignal && a.success) { 269 | selectedDevice->volume_limiter = enabled; 270 | emit actionSuccesful(); 271 | } 272 | } 273 | 274 | void HeadsetControlAPI::setEqualizer( 275 | QList equalizerValues, bool emitSignal) 276 | { 277 | QString equalizer = ""; 278 | for (double value : equalizerValues) { 279 | equalizer += QString::number(value) + ","; 280 | } 281 | equalizer.removeLast(); 282 | QStringList args = QStringList() << QString("--equalizer") << equalizer; 283 | Action a = sendAction(args); 284 | if (emitSignal && a.success) { 285 | selectedDevice->equalizer_curve = equalizerValues; 286 | selectedDevice->equalizer_preset = -1; 287 | emit actionSuccesful(); 288 | } 289 | } 290 | 291 | void HeadsetControlAPI::setEqualizerPreset( 292 | int number, bool emitSignal) 293 | { 294 | QStringList args = QStringList() << QString("--equalizer-preset") << QString::number(number); 295 | Action a = sendAction(args); 296 | if (emitSignal && a.success) { 297 | selectedDevice->equalizer_preset = number; 298 | emit actionSuccesful(); 299 | } 300 | } 301 | 302 | void HeadsetControlAPI::setRotateToMute( 303 | bool enabled, bool emitSignal) 304 | { 305 | QStringList args = QStringList() << QString("--rotate-to-mute") << QString::number(enabled); 306 | Action a = sendAction(args); 307 | if (emitSignal && a.success) { 308 | selectedDevice->rotate_to_mute = enabled; 309 | emit actionSuccesful(); 310 | } 311 | } 312 | 313 | void HeadsetControlAPI::setMuteLedBrightness( 314 | int brightness, bool emitSignal) 315 | { 316 | QStringList args = QStringList() << QString("--microphone-mute-led-brightness") 317 | << QString::number(brightness); 318 | Action a = sendAction(args); 319 | if (emitSignal && a.success) { 320 | selectedDevice->mic_mute_led_brightness = brightness; 321 | emit actionSuccesful(); 322 | } 323 | } 324 | 325 | void HeadsetControlAPI::setMicrophoneVolume( 326 | int volume, bool emitSignal) 327 | { 328 | QStringList args = QStringList() << QString("--microphone-volume") << QString::number(volume); 329 | Action a = sendAction(args); 330 | if (emitSignal && a.success) { 331 | selectedDevice->mic_volume = volume; 332 | emit actionSuccesful(); 333 | } 334 | } 335 | 336 | void HeadsetControlAPI::setBluetoothWhenPoweredOn( 337 | bool enabled, bool emitSignal) 338 | { 339 | QStringList args = QStringList() << QString("--bt-when-powered-on") << QString::number(enabled); 340 | Action a = sendAction(args); 341 | if (emitSignal && a.success) { 342 | selectedDevice->bt_when_powered_on = enabled; 343 | emit actionSuccesful(); 344 | } 345 | } 346 | 347 | void HeadsetControlAPI::setBluetoothCallVolume( 348 | int option, bool emitSignal) 349 | { 350 | QStringList args = QStringList() << QString("--bt-call-volume") << QString::number(option); 351 | Action a = sendAction(args); 352 | if (emitSignal && a.success) { 353 | selectedDevice->bt_call_volume = option; 354 | emit actionSuccesful(); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /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 | signals: 75 | void actionSuccesful(); 76 | }; 77 | 78 | #endif // HEADSETCONTROLAPI_H 79 | -------------------------------------------------------------------------------- /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 | return QFile::link(appPath, linkPath); 67 | } 68 | QFile::remove(linkPath); 69 | return false; 70 | 71 | #elif defined(Q_OS_LINUX) 72 | QString appDir = QCoreApplication::applicationDirPath(); 73 | QString autostartPath = QDir::homePath() + "/.config/autostart/"; 74 | QString desktopFilePath = autostartPath + appName + ".desktop"; 75 | 76 | if (enable) { 77 | QFile::remove(desktopFilePath); 78 | QFile desktopFile(desktopFilePath); 79 | if (desktopFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 80 | QTextStream out(&desktopFile); 81 | out << "[Desktop Entry]\n"; 82 | out << "Path=" + appDir + "\n"; 83 | out << "Type=Application\n"; 84 | out << "Exec=" << appPath << "\n"; 85 | out << "Name=" << appName << "\n"; 86 | out << "Comment=Auto-starts " << appName << " on boot\n"; 87 | desktopFile.close(); 88 | return true; 89 | } 90 | } 91 | QFile::remove(desktopFilePath); 92 | 93 | return false; 94 | #endif 95 | } 96 | -------------------------------------------------------------------------------- /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/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.18.4"; 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 | MainWindow window; 21 | 22 | return app.exec(); 23 | } 24 | -------------------------------------------------------------------------------- /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 --------------------------------------------------------------------------------