├── version.latest ├── SndCtrl.pdb ├── app-icon.ico ├── app-icon.png ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── question.md │ ├── enhancement.md │ └── bug_report.md ├── src ├── main │ ├── cpp │ │ ├── .gitignore │ │ ├── init.bat │ │ ├── cpdebug.bat │ │ ├── cpdll.bat │ │ ├── header.bat │ │ ├── SndCtrl │ │ │ ├── framework.h │ │ │ ├── FocusListener.h │ │ │ ├── SndCtrl.vcxproj.user │ │ │ ├── pch.cpp │ │ │ ├── Listeners.cpp │ │ │ ├── helpers.h │ │ │ ├── helpers.cpp │ │ │ ├── dllmain.cpp │ │ │ ├── AudioSession.h │ │ │ ├── pch.h │ │ │ ├── JniCaller.cpp │ │ │ ├── FocusListener.cpp │ │ │ └── AudioDevice.h │ │ └── SndCtrlTest │ │ │ ├── SndCtrlTest.vcxproj.user │ │ │ └── SndCtrlTest.cpp │ ├── java │ │ └── com │ │ │ └── getpcpanel │ │ │ ├── cpp │ │ │ ├── lombok.config │ │ │ ├── EventType.java │ │ │ ├── AudioDeviceEvent.java │ │ │ ├── AudioSessionEvent.java │ │ │ ├── windows │ │ │ │ ├── WindowFocusChangedEvent.java │ │ │ │ ├── WindowsAudioSession.java │ │ │ │ ├── SndCtrlNative.java │ │ │ │ └── ProcessHelper.java │ │ │ ├── Role.java │ │ │ ├── DataFlow.java │ │ │ ├── MuteType.java │ │ │ ├── linux │ │ │ │ ├── LinuxAudioSession.java │ │ │ │ ├── SndCtrlLinuxDebug.java │ │ │ │ ├── LinuxAudioDevice.java │ │ │ │ └── LinuxProcessHelper.java │ │ │ ├── ISndCtrl.java │ │ │ ├── AudioDevice.java │ │ │ └── SetNewSessionVolumeService.java │ │ │ ├── mqtt │ │ │ └── MqttStatusEvent.java │ │ │ ├── obs │ │ │ ├── OBSConnectEvent.java │ │ │ ├── OBSMuteEvent.java │ │ │ └── ObsConnectedVolumeService.java │ │ │ ├── voicemeeter │ │ │ ├── VoiceMeeterDirtyEvent.java │ │ │ ├── VoiceMeeterConnectedEvent.java │ │ │ ├── VoiceMeeterMuteEvent.java │ │ │ ├── VoicemeeterException.java │ │ │ └── VoiceMeeterConnectedVolumeService.java │ │ │ ├── profile │ │ │ ├── OSCConnectionInfo.java │ │ │ ├── OSCBinding.java │ │ │ ├── KnobSetting.java │ │ │ ├── ProfileWindowFocusService.java │ │ │ ├── SingleLogoLightingConfig.java │ │ │ ├── MqttSettings.java │ │ │ ├── SingleSliderLabelLightingConfig.java │ │ │ ├── KnobSettingMapDeserializer.java │ │ │ ├── SingleSliderLightingConfig.java │ │ │ └── SingleKnobLightingConfig.java │ │ │ ├── ui │ │ │ ├── LightingChangedToDefaultEvent.java │ │ │ ├── OverlayPosition.java │ │ │ ├── UIInitializer.java │ │ │ ├── command │ │ │ │ ├── CommandContext.java │ │ │ │ ├── ObsEnabled.java │ │ │ │ ├── ButtonCommandController.java │ │ │ │ ├── VoiceMeeterEnabled.java │ │ │ │ ├── DialCommandController.java │ │ │ │ ├── Cmd.java │ │ │ │ ├── button │ │ │ │ │ ├── BtnNoopController.java │ │ │ │ │ ├── BtnShortcutController.java │ │ │ │ │ ├── BtnDefaultDeviceAdvancedController.java │ │ │ │ │ ├── BtnProfileController.java │ │ │ │ │ ├── BtnDeviceToggleAdvancedController.java │ │ │ │ │ ├── BtnMediaController.java │ │ │ │ │ └── BtnFocusMuteController.java │ │ │ │ ├── dial │ │ │ │ │ ├── DialBrightnessController.java │ │ │ │ │ ├── DialVolumeFocusController.java │ │ │ │ │ └── DialObsController.java │ │ │ │ └── CommandController.java │ │ │ ├── colorpicker │ │ │ │ ├── WebColorField.java │ │ │ │ ├── IntegerField.java │ │ │ │ └── IntegerFieldSkin.java │ │ │ ├── UIHelper.java │ │ │ ├── LimitedTextField.java │ │ │ └── graphviewer │ │ │ │ └── GraphViewer.java │ │ │ ├── util │ │ │ ├── version │ │ │ │ ├── package-info.java │ │ │ │ └── Version.java │ │ │ ├── ProcessHelper.java │ │ │ ├── coloroverride │ │ │ │ ├── IOverrideColorProvider.java │ │ │ │ ├── IOverrideColorProviderProvider.java │ │ │ │ └── OverrideColorService.java │ │ │ ├── FileUtil.java │ │ │ └── ExtractUtil.java │ │ │ ├── hid │ │ │ ├── ButtonClickEvent.java │ │ │ ├── DialValue.java │ │ │ ├── DeviceCommunicationHandlerFactory.java │ │ │ ├── ByteWriter.java │ │ │ └── HidDebug.java │ │ │ ├── commands │ │ │ ├── command │ │ │ │ ├── CommandObs.java │ │ │ │ ├── CommandVoiceMeeter.java │ │ │ │ ├── DeviceAction.java │ │ │ │ ├── CommandVolume.java │ │ │ │ ├── ButtonAction.java │ │ │ │ ├── CommandVolumeFocusMute.java │ │ │ │ ├── CommandVolumeFocus.java │ │ │ │ ├── CommandKeystroke.java │ │ │ │ ├── CommandNoOp.java │ │ │ │ ├── CommandRun.java │ │ │ │ ├── CommandObsSetScene.java │ │ │ │ ├── CommandVolumeProcessMute.java │ │ │ │ ├── CommandShortcut.java │ │ │ │ ├── CommandVolumeDeviceMute.java │ │ │ │ ├── CommandProfile.java │ │ │ │ ├── CommandVolumeDefaultDevice.java │ │ │ │ ├── CommandEndProgram.java │ │ │ │ ├── Command.java │ │ │ │ ├── CommandObsSetSourceVolume.java │ │ │ │ ├── CommandVoiceMeeterBasicButton.java │ │ │ │ ├── CommandBrightness.java │ │ │ │ ├── DialAction.java │ │ │ │ ├── CommandObsMuteSource.java │ │ │ │ ├── CommandVolumeDevice.java │ │ │ │ ├── CommandVoiceMeeterAdvanced.java │ │ │ │ ├── CommandVoiceMeeterBasic.java │ │ │ │ ├── CommandVoiceMeeterAdvancedButton.java │ │ │ │ ├── CommandVolumeDefaultDeviceAdvanced.java │ │ │ │ └── CommandVolumeProcess.java │ │ │ ├── DeviceSet.java │ │ │ ├── CommandsType.java │ │ │ ├── PCPanelControlEvent.java │ │ │ ├── Commands.java │ │ │ └── AbstractNewXVolumeService.java │ │ │ ├── iconextract │ │ │ ├── SIZEByValue.java │ │ │ ├── IconServiceLinux.java │ │ │ ├── IconServiceWindows.java │ │ │ ├── IShellItemImageFactory.java │ │ │ ├── Shell32Extra.java │ │ │ └── IIconService.java │ │ │ ├── RestTemplateConfig.java │ │ │ ├── spring │ │ │ ├── Prototype.java │ │ │ ├── ConditionalOnLinux.java │ │ │ ├── ConditionalOnWindows.java │ │ │ └── OsHelper.java │ │ │ ├── device │ │ │ ├── DeviceType.java │ │ │ └── DeviceFactory.java │ │ │ ├── Json.java │ │ │ ├── Main.java │ │ │ ├── CachingConfig.java │ │ │ └── MainFX.java │ └── resources │ │ ├── SndCtrl.dll │ │ ├── assets │ │ ├── obs.png │ │ ├── 32x32.png │ │ ├── device.png │ │ ├── dots.png │ │ ├── 256x256.png │ │ ├── lighting.png │ │ ├── voicemeeter.png │ │ ├── systemsounds.ico │ │ ├── DefaultExeIcon.ico │ │ ├── apex-mk2.regular.otf │ │ ├── background_image.png │ │ ├── PCPanelPro │ │ │ ├── preview.png │ │ │ ├── Pro_Cutout.png │ │ │ ├── PCPanelPro.css │ │ │ ├── knob.fxml │ │ │ └── slider.fxml │ │ ├── PCPanelRGB │ │ │ ├── preview.png │ │ │ ├── PCPanelRGB.css │ │ │ └── knob.fxml │ │ ├── PCPanelMini │ │ │ ├── preview.png │ │ │ ├── PCPanelMini.css │ │ │ └── knob.fxml │ │ ├── icons │ │ │ ├── chevron-down.svg │ │ │ ├── chevron-up.svg │ │ │ ├── content-copy.svg │ │ │ ├── magnify.svg │ │ │ ├── close-circle-outline.svg │ │ │ └── lightbulb-on-outline.svg │ │ ├── PickProcesses.fxml │ │ ├── command │ │ │ ├── button │ │ │ │ ├── DefaultDeviceAdvanced.fxml │ │ │ │ ├── Noop.fxml │ │ │ │ ├── Profile.fxml │ │ │ │ ├── DefaultDevice.fxml │ │ │ │ ├── DeviceToggleAdvanced.fxml │ │ │ │ ├── Keystroke.fxml │ │ │ │ ├── Shortcut.fxml │ │ │ │ ├── VolumeProcessMute.fxml │ │ │ │ ├── FocusMute.fxml │ │ │ │ ├── EndProgram.fxml │ │ │ │ ├── ApplicationDeviceToggle.fxml │ │ │ │ ├── Media.fxml │ │ │ │ ├── DeviceMute.fxml │ │ │ │ └── DefaultDeviceToggle.fxml │ │ │ ├── dial │ │ │ │ ├── Brightness.fxml │ │ │ │ ├── Obs.fxml │ │ │ │ ├── VolumeFocus.fxml │ │ │ │ └── VolumeDevice.fxml │ │ │ └── Button.fxml │ │ ├── AdvancedDevices.fxml │ │ ├── AppFinderDialog.fxml │ │ ├── OSCSettingsDialog.fxml │ │ ├── dark_theme.css │ │ └── Overlay.fxml │ │ └── application.properties ├── test │ ├── resources │ │ └── junit-platform.properties │ └── java │ │ └── com │ │ └── getpcpanel │ │ ├── util │ │ └── version │ │ │ └── VersionCheckerTest.java │ │ └── hid │ │ ├── DialValueTest.java │ │ └── DialValueCalculatorTest.java ├── lombok.config └── packaging │ ├── linux-jpackage.txt │ ├── windows-jpackage.txt │ └── add-launch-to-msi.js ├── .gitattributes ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .idea ├── vcs.xml ├── codeStyles │ └── codeStyleConfig.xml ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── encodings.xml ├── git_toolbox_prj.xml └── runConfigurations │ └── PCPanel.xml ├── EnableFullDump.reg └── .gitignore /version.latest: -------------------------------------------------------------------------------- 1 | 1.6 2 | -------------------------------------------------------------------------------- /SndCtrl.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/SndCtrl.pdb -------------------------------------------------------------------------------- /app-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/app-icon.ico -------------------------------------------------------------------------------- /app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/app-icon.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nvdweem 2 | custom: ["https://paypal.me/nvdweem"] 3 | -------------------------------------------------------------------------------- /src/main/cpp/.gitignore: -------------------------------------------------------------------------------- 1 | /SndCtrl/.vs/ 2 | /SndCtrl/x64 3 | /SndCtrlTest/x64 4 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/lombok.config: -------------------------------------------------------------------------------- 1 | lombok.accessors.fluent=true 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.testinstance.lifecycle.default=per_class 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/SndCtrl.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/SndCtrl.dll -------------------------------------------------------------------------------- /src/main/cpp/init.bat: -------------------------------------------------------------------------------- 1 | del ..\..\..\SndCtrl.dll 2 | mklink /h ..\..\..\SndCtrl.dll SndCtrl\x64\Debug\SndCtrl.dll 3 | -------------------------------------------------------------------------------- /src/main/resources/assets/obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/obs.png -------------------------------------------------------------------------------- /src/main/resources/assets/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/32x32.png -------------------------------------------------------------------------------- /src/main/resources/assets/device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/device.png -------------------------------------------------------------------------------- /src/main/resources/assets/dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/dots.png -------------------------------------------------------------------------------- /src/main/resources/assets/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/256x256.png -------------------------------------------------------------------------------- /src/main/resources/assets/lighting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/lighting.png -------------------------------------------------------------------------------- /src/main/resources/assets/voicemeeter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/voicemeeter.png -------------------------------------------------------------------------------- /src/main/resources/assets/systemsounds.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/systemsounds.ico -------------------------------------------------------------------------------- /src/main/resources/assets/DefaultExeIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/DefaultExeIcon.ico -------------------------------------------------------------------------------- /src/main/resources/assets/apex-mk2.regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/apex-mk2.regular.otf -------------------------------------------------------------------------------- /src/main/resources/assets/background_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/background_image.png -------------------------------------------------------------------------------- /src/main/resources/assets/PCPanelPro/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/PCPanelPro/preview.png -------------------------------------------------------------------------------- /src/main/resources/assets/PCPanelRGB/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/PCPanelRGB/preview.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip 2 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/mqtt/MqttStatusEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.mqtt; 2 | 3 | public record MqttStatusEvent(boolean connected) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/obs/OBSConnectEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.obs; 2 | 3 | public record OBSConnectEvent(boolean connected) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/assets/PCPanelMini/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/PCPanelMini/preview.png -------------------------------------------------------------------------------- /src/main/resources/assets/PCPanelPro/Pro_Cutout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvdweem/PCPanel/HEAD/src/main/resources/assets/PCPanelPro/Pro_Cutout.png -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/obs/OBSMuteEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.obs; 2 | 3 | public record OBSMuteEvent(String input, boolean muted) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterDirtyEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.voicemeeter; 2 | 3 | public record VoiceMeeterDirtyEvent() { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/cpp/cpdebug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | copy SndCtrl\x64\Debug\SndCtrl.dll ..\resources 3 | del ..\..\..\SndCtrl.pdb 4 | copy SndCtrl\x64\Debug\SndCtrl.pdb ..\..\..\ 5 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/EventType.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | public enum EventType { 4 | ADDED, 5 | REMOVED, 6 | CHANGED 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/OSCConnectionInfo.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | public record OSCConnectionInfo(String host, int port) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/cpp/cpdll.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | copy SndCtrl\x64\Release\SndCtrl.dll ..\resources 3 | del ..\..\..\SndCtrl.pdb 4 | copy SndCtrl\x64\Release\SndCtrl.pdb ..\..\..\ 5 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/LightingChangedToDefaultEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui; 2 | 3 | public enum LightingChangedToDefaultEvent { 4 | INSTANCE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/util/version/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullApi 2 | package com.getpcpanel.util.version; 3 | 4 | import org.springframework.lang.NonNullApi; 5 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterConnectedEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.voicemeeter; 2 | 3 | public record VoiceMeeterConnectedEvent() { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/AudioDeviceEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | public record AudioDeviceEvent(AudioDevice device, EventType eventType) { 4 | } 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Not a bug or a feature, just a question. 4 | title: '' 5 | labels: 'question' 6 | assignees: '' 7 | --- 8 | 9 | -------------------------------------------------------------------------------- /src/main/cpp/header.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | javac -h ..\cpp\SndCtrl\ ..\java\com\getpcpanel\cpp\windows\SndCtrlNative.java 3 | del ..\java\com\getpcpanel\cpp\windows\SndCtrlNative.class 4 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/hid/ButtonClickEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.hid; 2 | 3 | public record ButtonClickEvent(String serialNum, int button, boolean dblClick) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/AudioSessionEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | public record AudioSessionEvent(AudioSession session, EventType eventType) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/windows/WindowFocusChangedEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp.windows; 2 | 3 | public record WindowFocusChangedEvent(String application) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | // Windows Header Files 5 | #include 6 | -------------------------------------------------------------------------------- /src/main/resources/assets/icons/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/assets/icons/chevron-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EnableFullDump.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\PCPanel.exe] 4 | "DumpCount"=dword:a 5 | "DumpType"=dword:2 6 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/FocusListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "JniCaller.h" 3 | 4 | class FocusListener { 5 | public: 6 | FocusListener(shared_ptr& jniCaller); 7 | ~FocusListener(); 8 | }; 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/SndCtrl.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandObs.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public abstract class CommandObs extends Command { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrlTest/SndCtrlTest.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/OverlayPosition.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui; 2 | 3 | public enum OverlayPosition { 4 | topLeft, topMiddle, topRight, 5 | middleLeft, middleMiddle, middleRight, 6 | bottomLeft, bottomMiddle, bottomRight 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/OSCBinding.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | public record OSCBinding(String address, float min, float max, boolean toggle) { 4 | public static final OSCBinding EMPTY = new OSCBinding("", 0, 1, false); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/assets/icons/content-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeter.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | 6 | @Getter 7 | @ToString(callSuper = true) 8 | public abstract class CommandVoiceMeeter extends Command { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/UIInitializer.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | public interface UIInitializer { 6 | default void initUI(@Nonnull T args) { 7 | } 8 | 9 | record SingleParamInitializer(TT param) { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/assets/PickProcesses.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/assets/PCPanelMini/PCPanelMini.css: -------------------------------------------------------------------------------- 1 | #dial_button { 2 | -fx-border-color: transparent; 3 | -fx-background-radius: 0; 4 | -fx-background-color: transparent; 5 | -fx-background-size: 100px 100px; 6 | -fx-background-repeat: no-repeat; 7 | -fx-background-position: center; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/assets/PCPanelPro/PCPanelPro.css: -------------------------------------------------------------------------------- 1 | #dial_button { 2 | -fx-border-color: transparent; 3 | -fx-background-radius: 0; 4 | -fx-background-color: transparent; 5 | -fx-background-size: 100px 100px; 6 | -fx-background-repeat: no-repeat; 7 | -fx-background-position: center; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/PCPanelRGB/PCPanelRGB.css: -------------------------------------------------------------------------------- 1 | #dial_button { 2 | -fx-border-color: transparent; 3 | -fx-background-radius: 0; 4 | -fx-background-color: transparent; 5 | -fx-background-size: 100px 100px; 6 | -fx-background-repeat: no-repeat; 7 | -fx-background-position: center; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/CommandContext.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command; 2 | 3 | import com.getpcpanel.profile.DeviceSave; 4 | import com.getpcpanel.profile.Profile; 5 | 6 | import javafx.stage.Stage; 7 | 8 | public record CommandContext(Stage stage, DeviceSave deviceSave, Profile profile) { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterMuteEvent.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.voicemeeter; 2 | 3 | import com.getpcpanel.voicemeeter.Voicemeeter.ButtonType; 4 | import com.getpcpanel.voicemeeter.Voicemeeter.ControlType; 5 | 6 | public record VoiceMeeterMuteEvent(ControlType ct, int idx, ButtonType button, boolean state) { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/DeviceSet.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands; 2 | 3 | public record DeviceSet(String name, 4 | String mediaPlayback, 5 | String mediaRecord, 6 | String communicationPlayback, 7 | String communicationRecord) { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/iconextract/SIZEByValue.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.iconextract; 2 | 3 | import com.sun.jna.Structure.ByValue; 4 | import com.sun.jna.platform.win32.WinUser.SIZE; 5 | 6 | public class SIZEByValue extends SIZE implements ByValue { 7 | public SIZEByValue(int w, int h) { 8 | super(w, h); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/KnobSetting.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class KnobSetting { 7 | private int minTrim; 8 | private int maxTrim = 100; 9 | private boolean logarithmic; 10 | private String overlayIcon; 11 | private int buttonDebounce = 50; 12 | } 13 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/Role.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public enum Role { 9 | roleConsole, roleMultimedia, roleCommunications; 10 | 11 | public static Role from(int ord) { 12 | return values()[ord]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/assets/icons/magnify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Want to see a new feature? 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | 9 | Describe what you want to happen and how it should work. Try to be complete in which software 10 | should be controlled, which device you have to control it with (PCPanel pro/mini/rgb) and what you expect the result to be. 11 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/util/ProcessHelper.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.util; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class ProcessHelper { 7 | public ProcessBuilder builder(String... command) { 8 | var result = new ProcessBuilder(command); 9 | result.environment().put("LC_ALL", "C"); 10 | return result; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/DeviceAction.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | public interface DeviceAction { 4 | void execute(DeviceActionParameters context); 5 | 6 | default Runnable toRunnable(DeviceActionParameters context) { 7 | return () -> execute(context); 8 | } 9 | 10 | record DeviceActionParameters(String device) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/ObsEnabled.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command; 2 | 3 | import com.getpcpanel.MainFX; 4 | import com.getpcpanel.obs.OBS; 5 | import com.getpcpanel.ui.command.Cmd.CmdEnabled; 6 | 7 | public class ObsEnabled extends CmdEnabled { 8 | @Override 9 | public boolean isEnabled() { 10 | return MainFX.getBean(OBS.class).isConnected(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/Listeners.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Listeners.h" 3 | 4 | EDataFlow getDataFlow(IMMDevice& device) { 5 | CComPtr cpEndPoint = NULL; 6 | device.QueryInterface(__uuidof(IMMEndpoint), (void**)&cpEndPoint); 7 | 8 | EDataFlow dataflow = eRender; 9 | if (cpEndPoint) { 10 | cpEndPoint->GetDataFlow(&dataflow); 11 | } 12 | return dataflow; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/CommandsType.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | 5 | @RequiredArgsConstructor 6 | public enum CommandsType { 7 | allAtOnce("All at once"), 8 | sequential("In sequence"); 9 | 10 | private final String label; 11 | 12 | @Override 13 | public String toString() { 14 | return label; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVolume.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.getpcpanel.MainFX; 4 | import com.getpcpanel.cpp.ISndCtrl; 5 | 6 | import lombok.ToString; 7 | 8 | @ToString(callSuper = true) 9 | public abstract class CommandVolume extends Command { 10 | protected ISndCtrl getSndCtrl() { 11 | return MainFX.getBean(ISndCtrl.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/assets/icons/close-circle-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | @Configuration 8 | public class RestTemplateConfig { 9 | @Bean 10 | public RestTemplate restTemplate() { 11 | return new RestTemplate(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/ButtonCommandController.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command; 2 | 3 | import com.getpcpanel.commands.command.Command; 4 | 5 | public abstract class ButtonCommandController extends CommandController { 6 | public abstract Command buildCommand(); 7 | 8 | @Override 9 | protected Command buildLabelCommand() { 10 | return buildCommand(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/VoiceMeeterEnabled.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command; 2 | 3 | import com.getpcpanel.MainFX; 4 | import com.getpcpanel.ui.command.Cmd.CmdEnabled; 5 | import com.getpcpanel.voicemeeter.Voicemeeter; 6 | 7 | public class VoiceMeeterEnabled extends CmdEnabled { 8 | @Override 9 | public boolean isEnabled() { 10 | return MainFX.getBean(Voicemeeter.class).login(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/DefaultDeviceAdvanced.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/DataFlow.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public enum DataFlow { 9 | dfRender(false, true), dfCapture(true, false), dfAll(true, true); 10 | private final boolean input; 11 | private final boolean output; 12 | 13 | public static DataFlow from(int ord) { 14 | return values()[ord]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/MuteType.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | import java.util.function.Function; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @RequiredArgsConstructor 8 | public enum MuteType { 9 | mute(x -> true), 10 | unmute(x -> false), 11 | toggle(x -> !x); 12 | 13 | private final Function convert; 14 | 15 | public boolean convert(boolean value) { 16 | return convert.apply(value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lombok.config: -------------------------------------------------------------------------------- 1 | config.stopBubbling = true 2 | lombok.accessors.chain=true 3 | lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value 4 | 5 | 6 | lombok.log.custom.flagUsage=error 7 | lombok.log.apacheCommons.flagUsage=error 8 | lombok.log.flogger.flagUsage=error 9 | lombok.log.jbosslog.flagUsage=error 10 | lombok.log.javaUtilLogging.flagUsage=error 11 | lombok.log.log4j.flagUsage=error 12 | lombok.log.slf4j.flagUsage=error 13 | lombok.log.xslf4j.flagUsage=error 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 2 | hs_err_pid* 3 | 4 | # Maven 5 | target/ 6 | !.mvn/wrapper/maven-wrapper.jar 7 | !**/src/main/**/target/ 8 | !**/src/test/**/target/ 9 | 10 | ### IntelliJ IDEA ### 11 | .idea/modules.xml 12 | .idea/jarRepositories.xml 13 | .idea/compiler.xml 14 | .idea/libraries/ 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | # Application files 20 | /SndCtrl.dll 21 | /src/main/resources/application-default.properties 22 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/iconextract/IconServiceLinux.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.iconextract; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.getpcpanel.spring.ConditionalOnLinux; 9 | 10 | @Service 11 | @ConditionalOnLinux 12 | public class IconServiceLinux implements IIconService { 13 | @Override 14 | public BufferedImage getIconForFile(int width, int height, File file) { 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/DialCommandController.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command; 2 | 3 | import com.getpcpanel.commands.command.Command; 4 | import com.getpcpanel.commands.command.DialAction.DialCommandParams; 5 | 6 | public abstract class DialCommandController extends CommandController { 7 | public abstract Command buildCommand(DialCommandParams params); 8 | 9 | @Override 10 | protected Command buildLabelCommand() { 11 | return buildCommand(DialCommandParams.DEFAULT); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/voicemeeter/VoicemeeterException.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.voicemeeter; 2 | 3 | import lombok.Getter; 4 | 5 | public class VoicemeeterException extends Exception { 6 | @Getter private final boolean disconnected; 7 | 8 | public VoicemeeterException(String message) { 9 | super(message); 10 | disconnected = false; 11 | } 12 | 13 | public VoicemeeterException(String message, boolean disconnected) { 14 | super(message); 15 | this.disconnected = disconnected; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/assets/icons/lightbulb-on-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/iconextract/IconServiceWindows.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.iconextract; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.getpcpanel.spring.ConditionalOnWindows; 9 | 10 | @Service 11 | @ConditionalOnWindows 12 | public class IconServiceWindows implements IIconService { 13 | @Override 14 | public BufferedImage getIconForFile(int width, int height, File file) { 15 | return JIconExtract.getIconForFile(width, height, file); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug:** 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps to reproduce:** 14 | 15 | Steps to reproduce the issue. 16 | 17 | **Expected behavior:** 18 | 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Additional context:** 22 | 23 | Add context for the issue, Windows/Linux, which version of the application and what device do you use 24 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | DWORD GetFocusProcessId(); 5 | wstring GetProcessName(DWORD procId); 6 | 7 | #undef DEBUG 8 | 9 | #define NULLRETURN(x) if (!x) return; 10 | #define NULLCONTINUE(x) if (!x) continue; 11 | 12 | template T* notNull(T* t, int line, const char* file) { 13 | cout << t << ": " << file << " (" << line << ")" << endl; 14 | return t; 15 | } 16 | 17 | template T& notNull(T& t, int line, const char* file) { 18 | cout << t << ": " << file << " (" << line << ")" << endl; 19 | return t; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | application.version=@project.version@ 2 | application.build=@github.build@ 3 | application.github.user-and-repo=@github.repo@ 4 | application.root=${user.home}/.pcpanel/ 5 | 6 | spring.main.web-application-type=none 7 | spring.output.ansi.enabled=ALWAYS 8 | logging.file.name=${application.root}/logs/logging.log 9 | logging.logback.rollingpolicy.max-file-size=5MB 10 | logging.logback.rollingpolicy.total-size-cap=30MB 11 | logging.level.org.jnativehook=off 12 | spring.main.banner-mode=off 13 | spring.jackson.deserialization.fail-on-unknown-properties=false 14 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "helpers.h" 3 | 4 | DWORD GetFocusProcessId() { 5 | DWORD procId; 6 | GetWindowThreadProcessId(GetForegroundWindow(), &procId); 7 | return (int)procId; 8 | } 9 | 10 | wstring GetProcessName(DWORD procId) { 11 | DWORD buffSize = MAX_PATH; 12 | WCHAR buffer[MAX_PATH] = { 0 }; 13 | HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, procId); 14 | if (!hProc) { 15 | return wstring(); 16 | } 17 | QueryFullProcessImageName(hProc, NULL, buffer, &buffSize); 18 | CloseHandle(hProc); 19 | return wstring(buffer); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/spring/Prototype.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.spring; 2 | 3 | import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | import org.springframework.context.annotation.Scope; 12 | 13 | @Documented 14 | @Scope(SCOPE_PROTOTYPE) 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface Prototype { 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/iconextract/IShellItemImageFactory.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.iconextract; 2 | 3 | import com.sun.jna.Pointer; 4 | import com.sun.jna.platform.win32.COM.Unknown; 5 | import com.sun.jna.platform.win32.WinNT.HRESULT; 6 | import com.sun.jna.ptr.PointerByReference; 7 | 8 | public class IShellItemImageFactory extends Unknown { 9 | public IShellItemImageFactory(Pointer pvInstance) { 10 | super(pvInstance); 11 | } 12 | 13 | public HRESULT GetImage(SIZEByValue size, int flags, PointerByReference bitmap) { 14 | return (HRESULT) _invokeNativeObject(3, new Object[] { getPointer(), size, flags, bitmap }, HRESULT.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/Noop.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/ButtonAction.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import com.fasterxml.jackson.annotation.JsonInclude; 8 | 9 | public interface ButtonAction { 10 | void execute(); 11 | 12 | default Runnable toRunnable() { 13 | return this::execute; 14 | } 15 | 16 | default boolean hasOverlay() { 17 | return StringUtils.isNotBlank(getOverlayText()); 18 | } 19 | 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | default @Nullable String getOverlayText() { 22 | return null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/iconextract/Shell32Extra.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.iconextract; 2 | 3 | import com.sun.jna.Native; 4 | import com.sun.jna.Pointer; 5 | import com.sun.jna.WString; 6 | import com.sun.jna.platform.win32.Guid.REFIID; 7 | import com.sun.jna.platform.win32.Shell32; 8 | import com.sun.jna.platform.win32.WinNT.HRESULT; 9 | import com.sun.jna.ptr.PointerByReference; 10 | 11 | public interface Shell32Extra extends Shell32 { 12 | Shell32Extra INSTANCE = Native.load("shell32", Shell32Extra.class); 13 | 14 | HRESULT SHCreateItemFromParsingName(WString paramWString, Pointer paramPointer, REFIID paramREFIID, PointerByReference paramPointerByReference); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/dial/Brightness.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/Cmd.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import com.getpcpanel.commands.command.Command; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | public @interface Cmd { 10 | String name(); 11 | 12 | String fxml(); 13 | 14 | Class[] cmds(); 15 | 16 | String os() default "*"; 17 | 18 | Class enabled() default CmdEnabled.class; 19 | 20 | enum Type { 21 | button, dial 22 | } 23 | 24 | class CmdEnabled { 25 | public boolean isEnabled() { 26 | return true; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/linux/LinuxAudioSession.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp.linux; 2 | 3 | import java.io.File; 4 | 5 | import org.springframework.context.ApplicationEventPublisher; 6 | 7 | import com.getpcpanel.cpp.AudioSession; 8 | 9 | import lombok.EqualsAndHashCode; 10 | import lombok.Getter; 11 | 12 | @Getter 13 | @EqualsAndHashCode(callSuper = true) 14 | public class LinuxAudioSession extends AudioSession { 15 | private final int index; 16 | 17 | public LinuxAudioSession(ApplicationEventPublisher eventPublisher, int index, int pid, File executable, String title, String icon, float volume, boolean muted) { 18 | super(eventPublisher, pid, executable, title, icon, volume, muted); 19 | this.index = index; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/assets/AdvancedDevices.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /src/packaging/linux-jpackage.txt: -------------------------------------------------------------------------------- 1 | --name @app.name@ 2 | --icon "@project.basedir@/app-icon.png" 3 | --dest "@project.build.directory@" 4 | --main-jar @project.build.finalName@.jar 5 | --main-class @start-class@ 6 | --input "@project.build.directory@/dependency" 7 | --app-version @project.version@ 8 | --runtime-image "@project.build.directory@/jvm-image" 9 | --temp "@project.build.directory@/installer-work" 10 | --java-options "--add-exports javafx.controls/com.sun.javafx.scene.control.skin.resources=@module.name@" 11 | --java-options "--add-exports javafx.base/com.sun.javafx.event=@module.name@" 12 | --about-url "https://github.com/nvdweem/PCPanel" 13 | --linux-menu-group PCPanel 14 | --linux-package-deps pulseaudio-utils 15 | --linux-package-deps xdotool 16 | --linux-shortcut 17 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include "pch.h" 3 | #include "JniCaller.h" 4 | #include "sndctrl.h" 5 | 6 | BOOL APIENTRY DllMain( HMODULE hModule, 7 | DWORD ul_reason_for_call, 8 | LPVOID lpReserved 9 | ) 10 | { 11 | switch (ul_reason_for_call) 12 | { 13 | case DLL_PROCESS_ATTACH: 14 | case DLL_PROCESS_DETACH: 15 | case DLL_THREAD_ATTACH: 16 | case DLL_THREAD_DETACH: 17 | break; 18 | } 19 | return TRUE; 20 | } 21 | 22 | jint JNI_OnLoad(JavaVM* vm, void* reserved) { 23 | pJvm = vm; 24 | return JNI_VERSION_10; 25 | } 26 | 27 | void JNI_OnUnload(JavaVM* vm, void* reserved) { 28 | pSndCtrl.release(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/hid/DialValue.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.hid; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import com.getpcpanel.commands.command.Command; 6 | import com.getpcpanel.profile.KnobSetting; 7 | 8 | public record DialValue( 9 | DialValueCalculator settings, 10 | int value 11 | ) { 12 | public DialValue(@Nullable KnobSetting settings, int value) { 13 | this(new DialValueCalculator(settings), value); 14 | } 15 | 16 | @SuppressWarnings("NumericCastThatLosesPrecision") 17 | public int getValue(@Nullable Command cmd) { 18 | return (int) getValue(cmd, 0f, 100f); 19 | } 20 | 21 | public float getValue(@Nullable Command cmd, float min, float max) { 22 | return settings.calcValue(cmd, value, min, max); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/ProfileWindowFocusService.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | import org.springframework.context.event.EventListener; 4 | import org.springframework.stereotype.Service; 5 | 6 | import com.getpcpanel.cpp.windows.WindowFocusChangedEvent; 7 | import com.getpcpanel.hid.DeviceHolder; 8 | 9 | import lombok.RequiredArgsConstructor; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | public class ProfileWindowFocusService { 14 | private final DeviceHolder devices; 15 | private String previousApplication = ""; 16 | 17 | @EventListener 18 | public void onFocusChanged(WindowFocusChangedEvent event) { 19 | devices.values().forEach(d -> d.focusChanged(previousApplication, event.application())); 20 | previousApplication = event.application(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/iconextract/IIconService.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.iconextract; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | 6 | import javax.annotation.Nullable; 7 | 8 | import org.springframework.cache.annotation.Cacheable; 9 | 10 | import javafx.embed.swing.SwingFXUtils; 11 | import javafx.scene.image.Image; 12 | 13 | public interface IIconService { 14 | @Cacheable("icon") 15 | BufferedImage getIconForFile(int width, int height, File file); 16 | 17 | @Nullable 18 | @Cacheable("icon") 19 | default Image getIconImageForFile(int width, int height, File file) { 20 | var image = getIconForFile(width, height, file); 21 | if (image != null) { 22 | return SwingFXUtils.toFXImage(image, null); 23 | } 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/dial/Obs.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/device/DeviceType.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.device; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import lombok.AccessLevel; 7 | import lombok.Getter; 8 | import lombok.RequiredArgsConstructor; 9 | 10 | @Getter 11 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) 12 | public enum DeviceType { 13 | PCPANEL_RGB("PCPanel RGB", 1240, 60242, 4, 4, false), 14 | PCPANEL_MINI("PCPanel Mini", 1155, 41924, 4, 4, false), 15 | PCPANEL_PRO("PCPanel Pro", 1155, 41925, 9, 5, true); 16 | 17 | public static final List ALL = Arrays.asList(values()); 18 | 19 | private final String niceName; 20 | private final int vid; 21 | private final int pid; 22 | private final int analogCount; 23 | private final int buttonCount; 24 | private final boolean hasLogoLed; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/util/coloroverride/IOverrideColorProvider.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.util.coloroverride; 2 | 3 | import java.util.Optional; 4 | 5 | import com.getpcpanel.profile.SingleKnobLightingConfig; 6 | import com.getpcpanel.profile.SingleLogoLightingConfig; 7 | import com.getpcpanel.profile.SingleSliderLabelLightingConfig; 8 | import com.getpcpanel.profile.SingleSliderLightingConfig; 9 | 10 | public interface IOverrideColorProvider { 11 | Optional getDialOverride(String deviceSerial, int dial); 12 | 13 | Optional getSliderOverride(String deviceSerial, int slider); 14 | 15 | Optional getSliderLabelOverride(String deviceSerial, int slider); 16 | 17 | Optional getLogoOverride(String deviceSerial); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/Profile.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/AudioSession.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "JniCaller.h" 3 | #include "Listeners.h" 4 | 5 | class AudioSession 6 | { 7 | private: 8 | CComPtr cpSession; 9 | CComQIPtr cpVolumeControl; 10 | 11 | int pid; 12 | wstring name; 13 | 14 | StoppingHandle cpListener; 15 | public: 16 | AudioSession(CComPtr ctrl); 17 | AudioSession(const AudioSession&) = delete; 18 | 19 | void Init(JniCaller& audioDevice, AudioSessionListenerCB& callback); 20 | void SetVolume(float volume); 21 | void Mute(bool muted); 22 | 23 | int GetPid() const { 24 | return pid; 25 | } 26 | const wstring& GetName() const { 27 | return name; 28 | } 29 | 30 | private: 31 | basic_string GetProductName(); 32 | }; 33 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.util; 2 | 3 | import java.io.File; 4 | 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Service; 7 | 8 | import jakarta.annotation.PostConstruct; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.extern.log4j.Log4j2; 11 | 12 | @Log4j2 13 | @Service 14 | @RequiredArgsConstructor 15 | public class FileUtil { 16 | @Value("${application.root}") private final File root; 17 | 18 | @PostConstruct 19 | void ensureRoot() { 20 | log.info("Using root: {}", root); 21 | if (!root.exists() && !root.mkdirs()) { 22 | log.error("Unable to create file root: {}", root); 23 | } 24 | } 25 | 26 | public File getFile(String file) { 27 | return new File(root, file); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/DefaultDevice.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import java.util.Set; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.getpcpanel.cpp.MuteType; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | @Getter 12 | @ToString(callSuper = true) 13 | public class CommandVolumeFocusMute extends CommandVolume implements ButtonAction { 14 | private final MuteType muteType; 15 | 16 | public CommandVolumeFocusMute(@JsonProperty("muteType") MuteType muteType) { 17 | this.muteType = muteType; 18 | } 19 | 20 | @Override 21 | public void execute() { 22 | getSndCtrl().muteProcesses(Set.of(getSndCtrl().getFocusApplication()), muteType); 23 | } 24 | 25 | @Override 26 | public String buildLabel() { 27 | return String.valueOf(muteType); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/DeviceToggleAdvanced.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/button/BtnNoopController.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command.button; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import com.getpcpanel.commands.command.Command; 6 | import com.getpcpanel.commands.command.CommandNoOp; 7 | import com.getpcpanel.spring.Prototype; 8 | import com.getpcpanel.ui.command.ButtonCommandController; 9 | import com.getpcpanel.ui.command.Cmd; 10 | import com.getpcpanel.ui.command.CommandContext; 11 | 12 | import javafx.beans.Observable; 13 | import lombok.RequiredArgsConstructor; 14 | import lombok.extern.log4j.Log4j2; 15 | 16 | @Log4j2 17 | @Component 18 | @Prototype 19 | @RequiredArgsConstructor 20 | @Cmd(name = "No action", fxml = "Noop", cmds = CommandNoOp.class) 21 | public class BtnNoopController extends ButtonCommandController { 22 | @Override 23 | public void postInit(CommandContext context) { 24 | } 25 | 26 | @Override 27 | public Command buildCommand() { 28 | return CommandNoOp.NOOP; 29 | } 30 | 31 | @Override 32 | protected Observable[] determineDependencies() { 33 | return new Observable[0]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/SingleSliderLabelLightingConfig.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.getpcpanel.util.Util; 5 | 6 | import javafx.scene.paint.Color; 7 | import lombok.Data; 8 | 9 | @Data 10 | public class SingleSliderLabelLightingConfig { 11 | private SINGLE_SLIDER_LABEL_MODE mode; 12 | 13 | private String color; 14 | private String muteOverrideDeviceOrFollow; 15 | private String muteOverrideColor; 16 | 17 | public SingleSliderLabelLightingConfig() { 18 | mode = SINGLE_SLIDER_LABEL_MODE.NONE; 19 | } 20 | 21 | public enum SINGLE_SLIDER_LABEL_MODE { 22 | NONE, STATIC 23 | } 24 | 25 | @JsonIgnore 26 | public void setColorFromColor(Color color) { 27 | this.color = Util.formatHexString(color); 28 | } 29 | 30 | @JsonIgnore 31 | public void setMuteOverrideColorFromColor(Color color) { 32 | muteOverrideColor = Util.formatHexString(color); 33 | } 34 | 35 | public void set(SingleSliderLabelLightingConfig c) { 36 | color = c.color; 37 | mode = c.mode; 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/ISndCtrl.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | import java.io.File; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | public interface ISndCtrl { 10 | Map getDevicesMap(); 11 | 12 | Collection getDevices(); 13 | 14 | Collection getAllSessions(); 15 | 16 | AudioDevice getDevice(String id); 17 | 18 | void setDeviceVolume(String deviceId, float volume); 19 | 20 | void muteDevice(String deviceId, MuteType mute); 21 | 22 | void setDefaultDevice(String deviceId); 23 | 24 | void setProcessVolume(String fileName, String device, float volume); 25 | 26 | void setFocusVolume(float volume); 27 | 28 | void muteProcesses(Set fileName, MuteType mute); 29 | 30 | String getFocusApplication(); 31 | 32 | List getRunningApplications(); 33 | 34 | String defaultDeviceOnEmpty(String deviceId); 35 | 36 | String defaultPlayer(); 37 | 38 | String defaultRecorder(); 39 | 40 | record RunningApplication(int pid, File file, String name) { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/windows/SndCtrlNative.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp.windows; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | /** 6 | * Generate headers using: javac.exe -h . SndCtrlNative.java 7 | */ 8 | enum SndCtrlNative { 9 | instance; 10 | 11 | public static native void start(Object target); 12 | 13 | public native void setDeviceVolume(String id, float volume); 14 | 15 | public native void setProcessVolume(String deviceId, int pid, float volume); 16 | 17 | public native void setFocusVolume(float volume); 18 | 19 | public native void setDefaultDevice(String device, int dataFlow, int role); 20 | 21 | public native void muteDevice(String deviceId, boolean muted); 22 | 23 | public native void muteSession(String id, int pid, boolean muted); 24 | 25 | public native String getFocusApplication(); 26 | 27 | public native boolean setPersistedDefaultAudioEndpoint(int pid, int dataFlow, @Nullable String deviceId); 28 | 29 | public native String getPersistedDefaultAudioEndpoint(int pid, int dataFlow); 30 | 31 | public native boolean hasAudioPolicyConfigFactory(); 32 | 33 | public native void triggerAv(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandProfile.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import com.fasterxml.jackson.annotation.JsonCreator; 8 | import com.fasterxml.jackson.annotation.JsonProperty; 9 | import com.getpcpanel.MainFX; 10 | import com.getpcpanel.hid.DeviceHolder; 11 | 12 | import javafx.application.Platform; 13 | import lombok.Getter; 14 | import lombok.ToString; 15 | 16 | @ToString(callSuper = true) 17 | public class CommandProfile extends Command implements DeviceAction { 18 | @Getter private final String profile; 19 | 20 | @JsonCreator 21 | public CommandProfile(@Nullable @JsonProperty("profile") String profile) { 22 | this.profile = profile; 23 | } 24 | 25 | @Override 26 | public void execute(DeviceActionParameters context) { 27 | Platform.runLater(() -> MainFX.getBean(DeviceHolder.class).getDevice(context.device()).ifPresent(device -> device.setProfile(profile))); 28 | } 29 | 30 | @Override 31 | public String buildLabel() { 32 | return StringUtils.defaultString(profile); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import com.fasterxml.jackson.annotation.JsonCreator; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | @Getter 12 | @ToString(callSuper = true) 13 | public class CommandVolumeDefaultDevice extends CommandVolume implements ButtonAction { 14 | private final String deviceId; 15 | 16 | @JsonCreator 17 | public CommandVolumeDefaultDevice(@JsonProperty("deviceId") String deviceId) { 18 | this.deviceId = deviceId; 19 | } 20 | 21 | @Override 22 | public void execute() { 23 | getSndCtrl().setDefaultDevice(deviceId); 24 | } 25 | 26 | @Override 27 | public @Nullable String getOverlayText() { 28 | var targetDevice = getSndCtrl().getDevicesMap().get(deviceId); 29 | if (targetDevice != null) { 30 | return targetDevice.name(); 31 | } 32 | return null; 33 | } 34 | 35 | @Override 36 | public String buildLabel() { 37 | return ""; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.util.IPlatformCommand; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | import lombok.extern.log4j.Log4j2; 11 | 12 | @Getter 13 | @Log4j2 14 | @ToString(callSuper = true) 15 | public class CommandEndProgram extends Command implements ButtonAction { 16 | private static final Runtime rt = Runtime.getRuntime(); 17 | private final boolean specific; 18 | private final String name; 19 | 20 | @JsonCreator 21 | public CommandEndProgram(@JsonProperty("specific") boolean specific, @JsonProperty("name") String name) { 22 | this.specific = specific; 23 | this.name = name; 24 | } 25 | 26 | @Override 27 | public void execute() { 28 | MainFX.getBean(IPlatformCommand.class).kill(specific ? name : IPlatformCommand.FOCUS); 29 | } 30 | 31 | @Override 32 | public String buildLabel() { 33 | return specific ? name : "Focus"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/Command.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 6 | import com.getpcpanel.hid.DialValue; 7 | 8 | import lombok.ToString; 9 | import lombok.extern.log4j.Log4j2; 10 | 11 | @Log4j2 12 | @ToString 13 | @SuppressWarnings("InstanceofThis") 14 | @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "_type") 15 | public abstract class Command { 16 | public Runnable toRunnable(boolean initial, String deviceId, @Nullable DialValue vol) { 17 | if (this instanceof ButtonAction ba) { 18 | return ba.toRunnable(); 19 | } 20 | if (this instanceof DeviceAction da) { 21 | return da.toRunnable(new DeviceAction.DeviceActionParameters(deviceId)); 22 | } 23 | if (vol != null && this instanceof DialAction da) { 24 | return da.toRunnable(new DialAction.DialActionParameters(deviceId, initial, vol)); 25 | } 26 | log.error("Unable to convert {} to Runnable ({}, {})", this, deviceId, vol); 27 | return () -> { 28 | }; 29 | } 30 | 31 | public abstract String buildLabel(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterConnectedVolumeService.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.voicemeeter; 2 | 3 | import org.springframework.context.ApplicationEventPublisher; 4 | import org.springframework.context.event.EventListener; 5 | import org.springframework.stereotype.Service; 6 | 7 | import com.getpcpanel.commands.AbstractNewXVolumeService; 8 | import com.getpcpanel.commands.command.CommandVoiceMeeter; 9 | import com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced; 10 | import com.getpcpanel.commands.command.CommandVoiceMeeterBasic; 11 | import com.getpcpanel.hid.DeviceHolder; 12 | import com.getpcpanel.spring.ConditionalOnWindows; 13 | 14 | @Service 15 | @ConditionalOnWindows 16 | public class VoiceMeeterConnectedVolumeService extends AbstractNewXVolumeService { 17 | public VoiceMeeterConnectedVolumeService(DeviceHolder devices, ApplicationEventPublisher eventPublisher) { 18 | super(devices, eventPublisher); 19 | } 20 | 21 | @EventListener(VoiceMeeterConnectedEvent.class) 22 | public void onVoiceMeeterConnected() { 23 | triggerCommandsOf(CommandVoiceMeeter.class, 24 | s -> s.filterValues(cmd -> cmd instanceof CommandVoiceMeeterBasic || cmd instanceof CommandVoiceMeeterAdvanced) 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/assets/AppFinderDialog.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandObsSetSourceVolume.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.obs.OBS; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | @Getter 12 | @ToString(callSuper = true) 13 | public class CommandObsSetSourceVolume extends CommandObs implements DialAction { 14 | private final String sourceName; 15 | private final DialCommandParams dialParams; 16 | 17 | @JsonCreator 18 | public CommandObsSetSourceVolume( 19 | @JsonProperty("sourceName") String sourceName, 20 | @JsonProperty("dialParams") DialCommandParams dialParams) { 21 | this.sourceName = sourceName; 22 | this.dialParams = dialParams; 23 | } 24 | 25 | @Override 26 | public void execute(DialActionParameters context) { 27 | var obs = MainFX.getBean(OBS.class); 28 | if (obs.isConnected()) { 29 | obs.setSourceVolume(sourceName, context.dial().getValue(this)); 30 | } 31 | } 32 | 33 | @Override 34 | public String buildLabel() { 35 | return "Source volume: " + sourceName; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/dial/DialBrightnessController.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command.dial; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import com.getpcpanel.commands.command.Command; 6 | import com.getpcpanel.commands.command.CommandBrightness; 7 | import com.getpcpanel.commands.command.DialAction.DialCommandParams; 8 | import com.getpcpanel.spring.Prototype; 9 | import com.getpcpanel.ui.command.Cmd; 10 | import com.getpcpanel.ui.command.CommandContext; 11 | import com.getpcpanel.ui.command.DialCommandController; 12 | 13 | import javafx.beans.Observable; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.log4j.Log4j2; 16 | 17 | @Log4j2 18 | @Component 19 | @Prototype 20 | @RequiredArgsConstructor 21 | @Cmd(name = "Device Brightness", fxml = "Brightness", cmds = CommandBrightness.class) 22 | public class DialBrightnessController extends DialCommandController { 23 | 24 | @Override 25 | public void postInit(CommandContext context) { 26 | } 27 | 28 | @Override 29 | public Command buildCommand(DialCommandParams params) { 30 | return new CommandBrightness(params); 31 | } 32 | 33 | @Override 34 | protected Observable[] determineDependencies() { 35 | return new Observable[0]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/KnobSettingMapDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import com.fasterxml.jackson.core.JsonParser; 8 | import com.fasterxml.jackson.core.JsonToken; 9 | import com.fasterxml.jackson.core.type.TypeReference; 10 | import com.fasterxml.jackson.databind.DeserializationContext; 11 | import com.fasterxml.jackson.databind.JsonDeserializer; 12 | 13 | public class KnobSettingMapDeserializer extends JsonDeserializer> { 14 | @Override 15 | public Map deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 16 | if (p.currentToken() == JsonToken.START_ARRAY) { 17 | return readOldFormat(p); 18 | } 19 | 20 | var type = new TypeReference>() { 21 | }; 22 | return p.readValueAs(type); 23 | } 24 | 25 | private Map readOldFormat(JsonParser p) throws IOException { 26 | var result = new HashMap(); 27 | while (p.nextToken() == JsonToken.START_OBJECT) { 28 | result.put(result.size(), p.readValueAs(KnobSetting.class)); 29 | } 30 | return result; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/dial/DialVolumeFocusController.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command.dial; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import com.getpcpanel.commands.command.Command; 6 | import com.getpcpanel.commands.command.CommandVolumeFocus; 7 | import com.getpcpanel.commands.command.DialAction.DialCommandParams; 8 | import com.getpcpanel.spring.Prototype; 9 | import com.getpcpanel.ui.command.Cmd; 10 | import com.getpcpanel.ui.command.CommandContext; 11 | import com.getpcpanel.ui.command.DialCommandController; 12 | 13 | import javafx.beans.Observable; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.log4j.Log4j2; 16 | 17 | @Log4j2 18 | @Component 19 | @Prototype 20 | @RequiredArgsConstructor 21 | @Cmd(name = "Focus Volume", fxml = "VolumeFocus", cmds = CommandVolumeFocus.class) 22 | public class DialVolumeFocusController extends DialCommandController { 23 | 24 | @Override 25 | public void postInit(CommandContext context) { 26 | } 27 | 28 | 29 | @Override 30 | public Command buildCommand(DialCommandParams params) { 31 | return new CommandVolumeFocus(params); 32 | } 33 | 34 | @Override 35 | protected Observable[] determineDependencies() { 36 | return new Observable[0]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/cpp/SndCtrl/FocusListener.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "FocusListener.h" 3 | #include "helpers.h" 4 | #include 5 | #include "JniCaller.h" 6 | 7 | unique_ptr pJThread; 8 | unique_ptr pThread; 9 | shared_ptr pJni; 10 | 11 | VOID CALLBACK WinEventProcCallback(HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { 12 | auto name = GetProcessName(GetFocusProcessId()); 13 | auto nameStr = pJThread->jstr(name.c_str()); 14 | pJni->CallVoid(*pJThread, "focusChanged", "(Ljava/lang/String;)V", nameStr); 15 | pJThread->jstr(nameStr); 16 | } 17 | 18 | FocusListener::FocusListener(shared_ptr& pJniCaller) { 19 | pJni = pJniCaller; 20 | 21 | pThread = make_unique([this]() { 22 | pJThread = make_unique(); 23 | SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, NULL, WinEventProcCallback, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); 24 | MSG msg; 25 | while (GetMessage(&msg, NULL, 0, 0)) { 26 | TranslateMessage(&msg); 27 | DispatchMessage(&msg); 28 | } 29 | }); 30 | } 31 | 32 | FocusListener::~FocusListener() { 33 | pThread.release(); 34 | pJni.reset(); 35 | pJThread.release(); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/util/version/Version.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.util.version; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | import org.apache.commons.lang3.math.NumberUtils; 6 | 7 | public record Version(int id, 8 | String html_url, 9 | String name, 10 | boolean prerelease) { 11 | static final String SNAPSHOT_POSTFIX = "-SNAPSHOT"; 12 | private static final Pattern versionPattern = Pattern.compile("v([\\d.]+)"); 13 | private static final Pattern buildPattern = Pattern.compile("\\((\\d+)\\)"); 14 | 15 | public String getRawVersion() { 16 | var matcher = versionPattern.matcher(name); 17 | if (matcher.find()) { 18 | return matcher.group(1) + (prerelease ? SNAPSHOT_POSTFIX : ""); 19 | } 20 | throw new IllegalStateException("'" + name + "' is not a parsable version name."); 21 | } 22 | 23 | public int getBuild() { 24 | if (prerelease) { 25 | var matcher = buildPattern.matcher(name); 26 | if (matcher.find()) { 27 | return NumberUtils.toInt(matcher.group(1), -1); 28 | } 29 | } 30 | return -1; 31 | } 32 | 33 | public String versionDisplay() { 34 | return getRawVersion() + (prerelease ? "-" + getBuild() : ""); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/VolumeProcessMute.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/command/CommandController.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.command; 2 | 3 | import java.util.Arrays; 4 | 5 | import com.getpcpanel.commands.command.Command; 6 | 7 | import javafx.beans.Observable; 8 | import javafx.beans.binding.Bindings; 9 | import javafx.beans.property.BooleanProperty; 10 | import javafx.beans.property.SimpleBooleanProperty; 11 | import javafx.beans.property.SimpleStringProperty; 12 | import javafx.beans.property.StringProperty; 13 | 14 | public abstract class CommandController { 15 | private final BooleanProperty initialized = new SimpleBooleanProperty(false); 16 | 17 | public abstract void postInit(CommandContext context); 18 | 19 | public void initFromCommand(T cmd) { 20 | initialized.set(true); 21 | } 22 | 23 | public StringProperty additionalLabelText() { 24 | var old = determineDependencies(); 25 | var dependencies = Arrays.copyOf(old, old.length + 1); 26 | dependencies[old.length] = initialized; 27 | 28 | var result = new SimpleStringProperty(); 29 | result.bind(Bindings.createStringBinding(() -> buildLabelCommand().buildLabel(), dependencies)); 30 | return result; 31 | } 32 | 33 | protected abstract Observable[] determineDependencies(); 34 | 35 | protected abstract Command buildLabelCommand(); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/SingleSliderLightingConfig.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.getpcpanel.util.Util; 5 | 6 | import javafx.scene.paint.Color; 7 | import lombok.Data; 8 | 9 | @Data 10 | public class SingleSliderLightingConfig { 11 | private SINGLE_SLIDER_MODE mode; 12 | private String color1; 13 | private String color2; 14 | private String muteOverrideDeviceOrFollow; 15 | private String muteOverrideColor; 16 | 17 | public SingleSliderLightingConfig() { 18 | mode = SINGLE_SLIDER_MODE.NONE; 19 | } 20 | 21 | public enum SINGLE_SLIDER_MODE { 22 | NONE, STATIC, STATIC_GRADIENT, VOLUME_GRADIENT 23 | } 24 | 25 | @JsonIgnore 26 | public void setColor1FromColor(Color color1) { 27 | this.color1 = Util.formatHexString(color1); 28 | } 29 | 30 | @JsonIgnore 31 | public void setColor2FromColor(Color color2) { 32 | this.color2 = Util.formatHexString(color2); 33 | } 34 | 35 | @JsonIgnore 36 | public void setMuteOverrideColorFromColor(Color color) { 37 | muteOverrideColor = Util.formatHexString(color); 38 | } 39 | 40 | public void set(SingleSliderLightingConfig c) { 41 | color1 = c.color1; 42 | color2 = c.color2; 43 | mode = c.mode; 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasicButton.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.voicemeeter.Voicemeeter; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | @Getter 12 | @ToString(callSuper = true) 13 | public class CommandVoiceMeeterBasicButton extends CommandVoiceMeeter implements ButtonAction { 14 | private final Voicemeeter.ControlType ct; 15 | private final int index; 16 | private final Voicemeeter.ButtonType bt; 17 | 18 | @JsonCreator 19 | public CommandVoiceMeeterBasicButton(@JsonProperty("ct") Voicemeeter.ControlType ct, @JsonProperty("index") int index, @JsonProperty("bt") Voicemeeter.ButtonType bt) { 20 | this.ct = ct; 21 | this.index = index; 22 | this.bt = bt; 23 | } 24 | 25 | @Override 26 | public void execute() { 27 | var voiceMeeter = MainFX.getBean(Voicemeeter.class); 28 | if (voiceMeeter.login()) { 29 | voiceMeeter.controlButton(ct, index, bt, null); 30 | } 31 | } 32 | 33 | @Override 34 | public String buildLabel() { 35 | return "Basic " + ct + " - " + index + " - " + (bt == null ? "-" : bt.getParameterName()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandBrightness.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.hid.DeviceHolder; 7 | import com.getpcpanel.profile.SaveService; 8 | 9 | import lombok.Getter; 10 | import lombok.ToString; 11 | import lombok.extern.log4j.Log4j2; 12 | 13 | @Getter 14 | @Log4j2 15 | @ToString(callSuper = true) 16 | public class CommandBrightness extends Command implements DialAction { 17 | private final DialCommandParams dialParams; 18 | 19 | @JsonCreator 20 | public CommandBrightness(@JsonProperty("dialParams") DialCommandParams dialParams) { 21 | this.dialParams = dialParams; 22 | } 23 | 24 | @Override 25 | public void execute(DialActionParameters context) { 26 | MainFX.getBean(DeviceHolder.class).getDevice(context.device()).ifPresent(device -> { 27 | var lightingConfig = device.getLightingConfig(); 28 | lightingConfig.setGlobalBrightness(context.dial().getValue(this)); 29 | device.setLighting(lightingConfig, false); 30 | 31 | MainFX.getBean(SaveService.class).debouncedSave(); 32 | }); 33 | } 34 | 35 | @Override 36 | public String buildLabel() { 37 | return ""; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/DialAction.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import com.getpcpanel.hid.DialValue; 6 | 7 | public interface DialAction { 8 | void execute(DialActionParameters context); 9 | 10 | default Runnable toRunnable(DialActionParameters context) { 11 | return () -> execute(context); 12 | } 13 | 14 | default boolean hasOverlay() { 15 | return true; 16 | } 17 | 18 | @Nullable DialCommandParams getDialParams(); 19 | 20 | default boolean isInvert() { 21 | return getDialParams() != null && getDialParams().invert; 22 | } 23 | 24 | record DialActionParameters(String device, boolean initial, DialValue dial) { 25 | } 26 | 27 | record DialCommandParams(boolean invert, @Nullable Integer moveStart, @Nullable Integer moveEnd) { 28 | public static final DialCommandParams DEFAULT = new DialCommandParams(false, null, null); 29 | 30 | public int moveStartNonNull() { 31 | return moveStart == null ? 0 : moveStart; 32 | } 33 | 34 | public int moveEndNonNull() { 35 | return moveEnd == null ? 0 : moveEnd; 36 | } 37 | 38 | public DialCommandParams withInvert(Boolean newValue) { 39 | return new DialCommandParams(newValue, moveStart, moveEnd); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandObsMuteSource.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.obs.OBS; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | @Getter 12 | @ToString(callSuper = true) 13 | public class CommandObsMuteSource extends CommandObs implements ButtonAction { 14 | public enum MuteType { 15 | toggle, mute, unmute 16 | } 17 | 18 | private final String source; 19 | private final MuteType type; 20 | 21 | @JsonCreator 22 | public CommandObsMuteSource(@JsonProperty("source") String source, @JsonProperty("type") MuteType type) { 23 | this.source = source; 24 | this.type = type; 25 | } 26 | 27 | @Override 28 | public void execute() { 29 | var obs = MainFX.getBean(OBS.class); 30 | if (obs.isConnected()) { 31 | switch (type) { 32 | case toggle -> obs.toggleSourceMute(source); 33 | case mute -> obs.setSourceMute(source, true); 34 | case unmute -> obs.setSourceMute(source, false); 35 | } 36 | } 37 | } 38 | 39 | @Override 40 | public String buildLabel() { 41 | return "Mute source: " + source + " (" + type + ")"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/ui/colorpicker/IntegerField.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.ui.colorpicker; 2 | 3 | import javafx.beans.property.IntegerProperty; 4 | import javafx.beans.property.SimpleIntegerProperty; 5 | import javafx.scene.control.Skin; 6 | 7 | class IntegerField extends InputField { 8 | private final IntegerProperty value = new SimpleIntegerProperty(this, "value"); 9 | 10 | public final int getValue() { 11 | return value.get(); 12 | } 13 | 14 | public final void setValue(int value) { 15 | this.value.set(value); 16 | } 17 | 18 | public final IntegerProperty valueProperty() { 19 | return value; 20 | } 21 | 22 | private final IntegerProperty maxValue = new SimpleIntegerProperty(this, "maxValue", -1); 23 | 24 | public final int getMaxValue() { 25 | return maxValue.get(); 26 | } 27 | 28 | public final void setMaxValue(int maxVal) { 29 | maxValue.set(maxVal); 30 | } 31 | 32 | public final IntegerProperty maxValueProperty() { 33 | return maxValue; 34 | } 35 | 36 | public IntegerField() { 37 | this(-1); 38 | } 39 | 40 | public IntegerField(int maxVal) { 41 | getStyleClass().setAll("integer-field"); 42 | setMaxValue(maxVal); 43 | } 44 | 45 | @Override 46 | protected Skin createDefaultSkin() { 47 | return new IntegerFieldSkin(this); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/getpcpanel/util/version/VersionCheckerTest.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.util.version; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.CsvSource; 9 | 10 | class VersionCheckerTest { 11 | private VersionChecker sut; 12 | 13 | @BeforeEach 14 | void setUp() { 15 | sut = new VersionChecker(null, null, null, null, null, -1, true); 16 | } 17 | 18 | @ParameterizedTest 19 | @CsvSource(delimiter = '|', value = { 20 | "1|1|false", 21 | "1.1|1.1|false", 22 | "1.1.1|1.1.1|false", 23 | "1|0.9|false", 24 | "1.1|1.0|false", 25 | "1.1.1|1|false", 26 | "1.1.1|1.0|false", 27 | "1.1.1|1.0.0|false", 28 | "1.2|1.1.1|false", 29 | "1.2-SNAPSHOT|1.1.1|false", 30 | "1.2-SNAPSHOT|1.2|true", 31 | }) 32 | void isVersionNewer(String current, String latest, boolean shouldBeNewer) { 33 | assertEquals(shouldBeNewer, sut.isVersionNewer(current, latest)); 34 | if (!StringUtils.equals(current, latest) && !StringUtils.contains(current, '-')) { 35 | assertEquals(!shouldBeNewer, sut.isVersionNewer(latest, current)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/util/coloroverride/IOverrideColorProviderProvider.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.util.coloroverride; 2 | 3 | import java.util.Optional; 4 | 5 | import com.getpcpanel.profile.SingleKnobLightingConfig; 6 | import com.getpcpanel.profile.SingleLogoLightingConfig; 7 | import com.getpcpanel.profile.SingleSliderLabelLightingConfig; 8 | import com.getpcpanel.profile.SingleSliderLightingConfig; 9 | 10 | public interface IOverrideColorProviderProvider extends IOverrideColorProvider { 11 | IOverrideColorProvider getOverrideColorProvider(); 12 | 13 | @Override 14 | default Optional getDialOverride(String deviceSerial, int dial) { 15 | return getOverrideColorProvider().getDialOverride(deviceSerial, dial); 16 | } 17 | 18 | @Override 19 | default Optional getSliderOverride(String deviceSerial, int slider) { 20 | return getOverrideColorProvider().getSliderOverride(deviceSerial, slider); 21 | } 22 | 23 | @Override 24 | default Optional getSliderLabelOverride(String deviceSerial, int slider) { 25 | return getOverrideColorProvider().getSliderLabelOverride(deviceSerial, slider); 26 | } 27 | 28 | @Override 29 | default Optional getLogoOverride(String deviceSerial) { 30 | return getOverrideColorProvider().getLogoOverride(deviceSerial); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/AudioDevice.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.springframework.context.ApplicationEventPublisher; 6 | 7 | import lombok.AccessLevel; 8 | import lombok.Data; 9 | import lombok.Setter; 10 | import lombok.extern.log4j.Log4j2; 11 | 12 | @Data 13 | @Log4j2 14 | @Setter(AccessLevel.PROTECTED) 15 | @SuppressWarnings("unused") // Methods called from JNI 16 | public class AudioDevice implements Serializable { 17 | protected final transient ApplicationEventPublisher eventPublisher; 18 | private final String name; 19 | private final String id; 20 | private float volume; 21 | private boolean muted; 22 | private DataFlow dataflow; 23 | 24 | public AudioDevice(ApplicationEventPublisher eventPublisher, String name, String id) { 25 | this.eventPublisher = eventPublisher; 26 | this.name = name; 27 | this.id = id; 28 | } 29 | 30 | private void setState(float volume, boolean muted) { 31 | volume(volume).muted(muted); 32 | eventPublisher.publishEvent(new AudioDeviceEvent(this, EventType.CHANGED)); 33 | log.trace("State changed: {}", this); 34 | } 35 | 36 | public boolean isOutput() { 37 | return dataflow.output(); 38 | } 39 | 40 | public boolean isInput() { 41 | return dataflow.input(); 42 | } 43 | 44 | public String toString() { 45 | return name; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/util/ExtractUtil.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Objects; 6 | 7 | import org.apache.commons.io.FileUtils; 8 | import org.apache.commons.io.FilenameUtils; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.stereotype.Service; 11 | 12 | import lombok.extern.log4j.Log4j2; 13 | 14 | @Log4j2 15 | @Service 16 | public class ExtractUtil { 17 | @Value("${application.version}") private String version; 18 | @Value("${application.build}") private String build; 19 | 20 | public File extractAndDeleteOnExit(String file) { 21 | var name = FilenameUtils.getBaseName(file); 22 | var ext = FilenameUtils.getExtension(file); 23 | 24 | var extracted = new File(System.getProperty("java.io.tmpdir"), String.join(".", name, version, build, ext)); 25 | if (extracted.exists() && !extracted.delete()) { 26 | log.info("{} already exists, not updating", extracted); 27 | return extracted; 28 | } 29 | 30 | try { 31 | var resource = Util.class.getResource("/" + file); 32 | FileUtils.copyURLToFile(Objects.requireNonNull(resource), extracted); 33 | } catch (IOException e) { 34 | throw new RuntimeException(e); 35 | } 36 | extracted.deleteOnExit(); 37 | return extracted; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/FocusMute.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/assets/command/button/EndProgram.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.cpp.MuteType; 6 | 7 | import lombok.Getter; 8 | import lombok.ToString; 9 | 10 | @Getter 11 | @ToString(callSuper = true) 12 | public class CommandVolumeDevice extends CommandVolume implements DialAction { 13 | private final String deviceId; 14 | private final boolean unMuteOnVolumeChange; 15 | private final DialCommandParams dialParams; 16 | 17 | @JsonCreator 18 | public CommandVolumeDevice( 19 | @JsonProperty("deviceId") String deviceId, 20 | @JsonProperty("isUnMuteOnVolumeChange") boolean unMuteOnVolumeChange, 21 | @JsonProperty("dialParams") DialCommandParams dialParams) { 22 | this.deviceId = deviceId; 23 | this.unMuteOnVolumeChange = unMuteOnVolumeChange; 24 | this.dialParams = dialParams; 25 | } 26 | 27 | @Override 28 | public void execute(DialActionParameters context) { 29 | if (!context.initial() && unMuteOnVolumeChange) { 30 | getSndCtrl().muteDevice(deviceId, MuteType.unmute); 31 | } 32 | getSndCtrl().setDeviceVolume(deviceId, context.dial().getValue(this, 0, 1)); 33 | } 34 | 35 | @Override 36 | public String buildLabel() { 37 | return ("".equals(deviceId) ? "Default" : "Specific") + (unMuteOnVolumeChange ? " (unmute)" : ""); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvanced.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.voicemeeter.Voicemeeter; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | @Getter 12 | @ToString(callSuper = true) 13 | public class CommandVoiceMeeterAdvanced extends CommandVoiceMeeter implements DialAction { 14 | private final String fullParam; 15 | private final Voicemeeter.DialControlMode ct; 16 | 17 | private final DialCommandParams dialParams; 18 | @JsonCreator 19 | public CommandVoiceMeeterAdvanced( 20 | @JsonProperty("fullParam") String fullParam, 21 | @JsonProperty("ct") Voicemeeter.DialControlMode ct, 22 | @JsonProperty("dialParams") DialCommandParams dialParams) { 23 | this.fullParam = fullParam; 24 | this.ct = ct; 25 | this.dialParams = dialParams; 26 | } 27 | 28 | @Override 29 | public void execute(DialActionParameters context) { 30 | if (ct == null) { 31 | return; 32 | } 33 | var voiceMeeter = MainFX.getBean(Voicemeeter.class); 34 | if (voiceMeeter.login()) { 35 | voiceMeeter.controlLevel(fullParam, ct, context.dial().getValue(this)); 36 | } 37 | } 38 | 39 | @Override 40 | public String buildLabel() { 41 | return "Advanced " + fullParam + " - " + ct; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/Commands.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import javax.annotation.Nonnull; 7 | import javax.annotation.Nullable; 8 | 9 | import com.fasterxml.jackson.annotation.JsonIgnore; 10 | import com.getpcpanel.commands.command.Command; 11 | import com.getpcpanel.commands.command.CommandNoOp; 12 | 13 | import lombok.Data; 14 | import one.util.streamex.StreamEx; 15 | 16 | @Data 17 | public class Commands { 18 | public static final Commands EMPTY = new Commands(List.of(), CommandsType.allAtOnce); 19 | private final @Nonnull List commands; 20 | private final @Nonnull CommandsType type; 21 | @JsonIgnore private int sequenceIdx = -1; 22 | 23 | public static @Nonnull List cmds(@Nullable Commands buttonData) { 24 | if (buttonData != null) { 25 | return buttonData.getCommands(); 26 | } 27 | return List.of(); 28 | } 29 | 30 | public Commands(@Nonnull List commands, @Nullable CommandsType type) { 31 | this.commands = StreamEx.of(commands).remove(CommandNoOp.class::isInstance).toImmutableList(); 32 | this.type = type != null ? type : CommandsType.allAtOnce; 33 | } 34 | 35 | public Optional getCommand(Class cmd) { 36 | return StreamEx.of(commands).select(cmd).findFirst(); 37 | } 38 | 39 | public static boolean hasCommands(@Nullable Commands cmds) { 40 | return cmds != null && !cmds.commands.isEmpty(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/profile/SingleKnobLightingConfig.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.profile; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import com.fasterxml.jackson.annotation.JsonIgnore; 6 | import com.getpcpanel.util.Util; 7 | 8 | import javafx.scene.paint.Color; 9 | import lombok.Data; 10 | 11 | @Data 12 | public class SingleKnobLightingConfig { 13 | private SINGLE_KNOB_MODE mode; 14 | private String color1; 15 | private String color2; 16 | @Nullable private String muteOverrideDeviceOrFollow; 17 | @Nullable private String muteOverrideColor; 18 | 19 | public SingleKnobLightingConfig() { 20 | mode = SINGLE_KNOB_MODE.NONE; 21 | } 22 | 23 | public enum SINGLE_KNOB_MODE { 24 | NONE, STATIC, VOLUME_GRADIENT 25 | } 26 | 27 | @JsonIgnore 28 | public void setColor1FromColor(Color color1) { 29 | this.color1 = Util.formatHexString(color1); 30 | } 31 | 32 | @JsonIgnore 33 | public void setColor2FromColor(Color color2) { 34 | this.color2 = Util.formatHexString(color2); 35 | } 36 | 37 | @JsonIgnore 38 | public void setMuteOverrideColorFromColor(Color color) { 39 | if (color == null) { 40 | muteOverrideColor = null; 41 | } else { 42 | muteOverrideColor = Util.formatHexString(color); 43 | } 44 | } 45 | 46 | public void set(SingleKnobLightingConfig c) { 47 | color1 = c.color1; 48 | color2 = c.color2; 49 | muteOverrideColor = c.muteOverrideColor; 50 | mode = c.mode; 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/MainFX.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.context.ConfigurableApplicationContext; 5 | 6 | import com.getpcpanel.ui.HomePage; 7 | 8 | import javafx.application.Application; 9 | import javafx.application.Platform; 10 | import javafx.stage.Stage; 11 | import lombok.extern.log4j.Log4j2; 12 | 13 | @Log4j2 14 | public class MainFX extends Application { 15 | @SuppressWarnings("StaticNonFinalField") private static ConfigurableApplicationContext context; 16 | 17 | @Override 18 | @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod") 19 | public void init() throws Exception { 20 | context = new SpringApplicationBuilder(Main.class) 21 | .headless(false) 22 | .run(getParameters().getRaw().toArray(new String[0])); 23 | } 24 | 25 | @Override 26 | public void start(Stage primaryStage) throws Exception { 27 | log.info("Starting v{}", context.getEnvironment().getProperty("application.version")); 28 | context.getBean(HomePage.class).start(primaryStage, getParameters().getRaw().contains("quiet")); 29 | } 30 | 31 | @Override 32 | public void stop() throws Exception { 33 | context.close(); 34 | Platform.exit(); 35 | } 36 | 37 | public static T getBean(Class clazz) { 38 | return context.getBean(clazz); 39 | } 40 | 41 | public static ConfigurableApplicationContext getContext() { 42 | return context; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasic.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.voicemeeter.Voicemeeter; 7 | 8 | import lombok.Getter; 9 | import lombok.ToString; 10 | 11 | @Getter 12 | @ToString(callSuper = true) 13 | public class CommandVoiceMeeterBasic extends CommandVoiceMeeter implements DialAction { 14 | private final Voicemeeter.ControlType ct; 15 | private final int index; 16 | private final Voicemeeter.DialType dt; 17 | private final DialCommandParams dialParams; 18 | 19 | @JsonCreator 20 | public CommandVoiceMeeterBasic( 21 | @JsonProperty("ct") Voicemeeter.ControlType ct, 22 | @JsonProperty("index") int index, 23 | @JsonProperty("dt") Voicemeeter.DialType dt, 24 | @JsonProperty("dialParams") DialCommandParams dialParams) { 25 | this.ct = ct; 26 | this.index = index; 27 | this.dt = dt; 28 | this.dialParams = dialParams; 29 | } 30 | 31 | @Override 32 | public void execute(DialActionParameters context) { 33 | var voiceMeeter = MainFX.getBean(Voicemeeter.class); 34 | if (voiceMeeter.login()) { 35 | voiceMeeter.controlLevel(ct, index, dt, context.dial().getValue(this)); 36 | } 37 | } 38 | 39 | @Override 40 | public String buildLabel() { 41 | return "Basic - " + ct + " - " + index + " - " + dt; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvancedButton.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import com.fasterxml.jackson.annotation.JsonCreator; 8 | import com.fasterxml.jackson.annotation.JsonProperty; 9 | import com.getpcpanel.MainFX; 10 | import com.getpcpanel.voicemeeter.Voicemeeter; 11 | 12 | import lombok.Getter; 13 | import lombok.ToString; 14 | 15 | @Getter 16 | @ToString(callSuper = true) 17 | public class CommandVoiceMeeterAdvancedButton extends CommandVoiceMeeter implements ButtonAction { 18 | private final String fullParam; 19 | private final Voicemeeter.ButtonControlMode bt; 20 | private final String stringValue; 21 | 22 | @JsonCreator 23 | public CommandVoiceMeeterAdvancedButton( 24 | @JsonProperty("fullParam") String fullParam, 25 | @JsonProperty("bt") Voicemeeter.ButtonControlMode bt, 26 | @Nullable @JsonProperty("stringValue") String stringValue) { 27 | this.fullParam = fullParam; 28 | this.bt = bt; 29 | this.stringValue = stringValue; 30 | } 31 | 32 | @Override 33 | public void execute() { 34 | var voiceMeeter = MainFX.getBean(Voicemeeter.class); 35 | if (voiceMeeter.login()) { 36 | voiceMeeter.controlButton(fullParam, bt, stringValue); 37 | } 38 | } 39 | 40 | @Override 41 | public String buildLabel() { 42 | return "Advanced " + StringUtils.removeEnd(fullParam + " - " + bt + " - " + stringValue, " - "); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/cpp/windows/ProcessHelper.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.cpp.windows; 2 | 3 | import java.io.File; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | 7 | import com.getpcpanel.cpp.ISndCtrl; 8 | import com.sun.jna.platform.win32.Kernel32; 9 | import com.sun.jna.platform.win32.PsapiUtil; 10 | import com.sun.jna.platform.win32.WinDef; 11 | import com.sun.jna.ptr.IntByReference; 12 | 13 | import one.util.streamex.IntStreamEx; 14 | 15 | final class ProcessHelper { 16 | private static final int PROCESS_QUERY_INFORMATION = 0x0400; 17 | 18 | private ProcessHelper() { 19 | } 20 | 21 | public static List getRunningApplications() { 22 | return IntStreamEx.of(PsapiUtil.enumProcesses()) 23 | .mapToEntry(Integer::valueOf, ProcessHelper::getFile) 24 | .nonNullValues() 25 | .mapKeyValue((pid, file) -> new ISndCtrl.RunningApplication(pid, file, file.getName())) 26 | .sorted(Comparator.comparing(ISndCtrl.RunningApplication::name)) 27 | .toImmutableList(); 28 | } 29 | 30 | static File getFile(int pid) { 31 | var handle = Kernel32.INSTANCE.OpenProcess(PROCESS_QUERY_INFORMATION, false, pid); 32 | var buffer = new char[WinDef.MAX_PATH]; 33 | var size = new IntByReference(buffer.length); 34 | if (!Kernel32.INSTANCE.QueryFullProcessImageName(handle, 0, buffer, size)) { 35 | return null; 36 | } 37 | return new File(new String(buffer, 0, size.getValue())); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/packaging/add-launch-to-msi.js: -------------------------------------------------------------------------------- 1 | // run with command 2 | // cscript add-change.js 3 | var installer = WScript.CreateObject("WindowsInstaller.Installer"); 4 | var database = installer.OpenDatabase("@app.name@-@project.version@.msi", 1); 5 | var sql 6 | var view 7 | 8 | sql = "SELECT File, Component_ FROM File WHERE FileName='@app.name@.exe'"; 9 | view = database.OpenView(sql); 10 | view.Execute(); 11 | var viewRow = view.Fetch(); 12 | var file = viewRow.StringData(1) 13 | var component = viewRow.StringData(2) 14 | WScript.StdErr.WriteLine(file); 15 | view.Close(); 16 | 17 | try { 18 | sql = "INSERT INTO `CustomAction` (`Action`,`Type`,`Source`) VALUES ('ExecuteAfterFinalize','2258','" + file + "')" 19 | WScript.StdErr.WriteLine(sql); 20 | view = database.OpenView(sql); 21 | view.Execute(); 22 | view.Close(); 23 | 24 | sql = "INSERT INTO `InstallExecuteSequence` (`Action`,`Condition`,`Sequence`) VALUES ('ExecuteAfterFinalize','NOT Installed','6700')" 25 | WScript.StdErr.WriteLine(sql); 26 | view = database.OpenView(sql); 27 | view.Execute(); 28 | view.Close(); 29 | 30 | sql = "INSERT INTO `Registry` (`Registry`,`Root`,`Key`, `Name`, `Value`, `Component_`) VALUES ('1337','-1','Software\\Microsoft\\Windows\\CurrentVersion\\Run', 'PCPanel', '\"[#" + file + "]\" quiet', '" + component + "')" 31 | WScript.StdErr.WriteLine(sql); 32 | view = database.OpenView(sql); 33 | view.Execute(); 34 | view.Close(); 35 | 36 | WScript.StdErr.WriteLine("Committing changes"); 37 | database.Commit(); 38 | } catch (e) { 39 | WScript.StdErr.WriteLine(e); 40 | WScript.Quit(1); 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/getpcpanel/hid/DialValueTest.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.hid; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.CsvSource; 7 | 8 | import com.getpcpanel.commands.command.CommandBrightness; 9 | import com.getpcpanel.commands.command.DialAction.DialCommandParams; 10 | import com.getpcpanel.profile.KnobSetting; 11 | 12 | class DialValueTest { 13 | 14 | @CsvSource({ 15 | // Linear 16 | "false, false, 255, 0, 1, 1", 17 | "false, false, 0, 0, 1, 0", 18 | "false, false, 127, 0, 1, 0.50", 19 | "true, false, 255, 0, 1, 0", 20 | "true, false, 0, 0, 1, 1", 21 | "true, false, 127, 0, 1, 0.50", 22 | 23 | // Logarithmic 24 | "false, true, 255, 0, 1, 1", 25 | "false, true, 0, 0, 1, 0", 26 | "false, true, 217, 0, 1, 0.50", 27 | "true, true, 255, 0, 1, 0", 28 | "true, true, 0, 0, 1, 1", 29 | "true, true, 217, 0, 1, 0.50", 30 | }) 31 | @ParameterizedTest 32 | void testGetValue(boolean invert, boolean log, int value, float min, float max, float expected) { 33 | var dialValue = new DialValue(new KnobSetting().setLogarithmic(log), value); 34 | var calculated = dialValue.getValue(new CommandBrightness(new DialCommandParams(invert, null, null)), min, max); 35 | var rounded = Math.round(calculated * 100) / 100d; // Round to make the expected more usable 36 | 37 | assertEquals(expected, rounded); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.commands.command; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import com.getpcpanel.MainFX; 6 | import com.getpcpanel.cpp.DataFlow; 7 | import com.getpcpanel.cpp.Role; 8 | import com.getpcpanel.cpp.windows.SndCtrlWindows; 9 | 10 | import lombok.AllArgsConstructor; 11 | import lombok.Builder; 12 | import lombok.Getter; 13 | import lombok.ToString; 14 | import lombok.extern.jackson.Jacksonized; 15 | 16 | @Getter 17 | @Builder 18 | @Jacksonized 19 | @AllArgsConstructor 20 | @ToString(callSuper = true) 21 | public class CommandVolumeDefaultDeviceAdvanced extends CommandVolume implements ButtonAction { 22 | private final String name; 23 | private final String mediaPb; 24 | private final String mediaRec; 25 | private final String communicationPb; 26 | private final String communicationRec; 27 | 28 | @Override 29 | public void execute() { 30 | var windowsSndCtrl = MainFX.getBean(SndCtrlWindows.class); 31 | windowsSndCtrl.setDefaultDevice(mediaPb, DataFlow.dfRender, Role.roleMultimedia); 32 | windowsSndCtrl.setDefaultDevice(mediaRec, DataFlow.dfCapture, Role.roleMultimedia); 33 | windowsSndCtrl.setDefaultDevice(communicationPb, DataFlow.dfRender, Role.roleCommunications); 34 | windowsSndCtrl.setDefaultDevice(communicationRec, DataFlow.dfCapture, Role.roleCommunications); 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public String getOverlayText() { 40 | return name; 41 | } 42 | 43 | @Override 44 | public String buildLabel() { 45 | return name; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/getpcpanel/hid/DialValueCalculatorTest.java: -------------------------------------------------------------------------------- 1 | package com.getpcpanel.hid; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.getpcpanel.commands.command.CommandBrightness; 9 | import com.getpcpanel.commands.command.DialAction; 10 | import com.getpcpanel.profile.KnobSetting; 11 | import com.getpcpanel.util.Util; 12 | 13 | class DialValueCalculatorTest { 14 | 15 | @Test 16 | void calcLineair() { 17 | var calculator = new DialValueCalculator(new KnobSetting()); 18 | for (var i = 0; i < 255; i++) { 19 | var result = Math.round(calculator.calcValue(null, i, 0, 100)); 20 | var expected = Math.round(i / 2.55f); 21 | assertEquals(expected, result); 22 | } 23 | } 24 | 25 | @Test 26 | void calcUsingStartEnd() { 27 | var calculator = new DialValueCalculator(new KnobSetting()); 28 | var cmd = new CommandBrightness(new DialAction.DialCommandParams(false, 25, 25)); 29 | 30 | for (var i = 0; i < 255; i++) { 31 | var result = Math.round(calculator.calcValue(cmd, i, 0, 100)); 32 | var position = Util.map(i, 0, 255, 0, 100); 33 | 34 | if (position < 25) { 35 | assertEquals(0, result); 36 | } else if (position > 75) { 37 | assertEquals(100, result); 38 | } else { 39 | var expected = Util.map(position, 25, 75, 0, 100); 40 | assertTrue(result > expected - 3 && result < expected + 3); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/assets/OSCSettingsDialog.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 |