From 84060568f2cc03b2cb32d21d08b7e5bb182cf493 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 11:05:58 +0200 Subject: [PATCH 01/49] docs: design for feature-module (plugin-style) restructure Analysis of the current command/integration scatter and a target structure where each integration is a self-contained module discovered via CDI + annotations. Captures the Id.CLASS persisted-type constraint, the ~20 registries an integration command touches today, and a phased migration. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/feature-module-structure.md | 224 +++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 docs/feature-module-structure.md diff --git a/docs/feature-module-structure.md b/docs/feature-module-structure.md new file mode 100644 index 00000000..284da907 --- /dev/null +++ b/docs/feature-module-structure.md @@ -0,0 +1,224 @@ +# Feature-module structure (plugin-style refactor) + +Status: **design** — proposed target structure and migration plan. Implementation is phased; this +document is the source of truth for the end state and the order we get there. + +## Goal + +Make each *integration / feature* (VoiceMeeter, OBS, Wave Link, Discord, Home Assistant, OSC, MQTT, +and any future one) a **self-contained module**: its code lives in its own package, scoped as local +as possible, exposing a small public seam to the rest of the app. Adding a new feature should mean +*creating one package and implementing a few SPIs* — not editing a dozen shared registries. + +We are **not** supporting dynamically-loaded third-party plugins (no separate classloaders / jars). +"Plugin-style" here means *internal modularity*: features are discovered through CDI and a handful of +annotations rather than hand-wired into central lists. + +## Where we are today + +Two patterns coexist: + +- **Good (the template):** Wave Link, Discord, Home Assistant. Command classes live in + `com.getpcpanel..command`, REST under `rest/` (`rest/wavelink`, `rest/discord`), + low-level clients under `dev.niels.`, settings records inside the feature package, icon via + the `IIconHandler` SPI, mute-colour via the `MuteStateResolver` SPI. They leak into only a few + shared registries. +- **Scattered (the problem):** VoiceMeeter and OBS (and the generic outputs OSC/MQTT). Their + `Command*` classes sit in the shared `commands/command/` pile; their REST resources are flat in + `rest/`; VoiceMeeter's mute resolver lives in the generic `mutecolor/` package and exposes a public + `VM_PATTERN` regex that `NamedDeviceMuteResolver` reaches into; their icons are hard-coded in + `IconService.init()`. + +### The real cost: adding one integration command touches ~20 places + +Verified by a fan-out audit of every subsystem. The central registries that must change when a +feature/command is added or removed: + +| # | Registry | Location | Avoidable? | +|---|----------|----------|------------| +| 1 | Frontend command catalog | `webui/.../features/commands/command-catalog.ts` (`COMMANDS[]`) | **Generate from Java** | +| 2 | Backend picker list + category enum | `rest/CommandsResource` static list + `rest/model/dto/CommandType.CommandCategory` | **Derive from annotations** | +| 3 | Icon handler map | `commands/IconService.init()` hard-coded `imageHandlers.put(...)` | **Move to `IIconHandler` SPI** | +| 4 | Native-image reflection | `graalvm/NativeImageConfig` `classes[]`/`classNames[]` | Guarded by coverage test; can be derived | +| 5 | Native build args (×2, parity-locked) | `pom.xml` + `application.properties` `additional-build-args` | Only for JNA features | +| 6 | TS-generator class patterns | `pom.xml` `` | **Single glob** instead of per-feature line | +| 7 | Settings schema | `profile/Save` fields | Local to feature (record) | +| 8 | Settings DTO mapping | `rest/model/dto/SettingsDto` `from`/`applyTo` | Local to feature | +| 9 | Settings REST + tabs | `rest/SettingsResource`, frontend `settings.component.*` | Partly local | +| 10 | Platform capability flag | `rest/PlatformResource`, frontend `platform.service.ts` | For OS-gated features | +| 11 | Mute-colour resolver | `mutecolor/*Resolver` (SPI already) | Local to feature | +| 12 | Frontend live-picker data | `features/commands/integration-data.service.ts` + `command-fields.ts` `liveOptions()` | Inherently per-feature (frontend) | +| 13 | Frontend picker integration list | `features/commands/command-picker.component.ts` `INTEGRATIONS` | Inherently per-feature (frontend) | +| 14 | Legacy migration switch | `commands/command/CommandConverter` | Legacy-only; untouched by new features | +| 15 | Events catalogue | `docs/events.md` | Doc; keep current | + +Items 1–4 and 6 are the high-value collapses. 11–13 stay per-feature but become **local to the +feature module** instead of edits to shared files. + +## The cornerstone constraint: `Command` polymorphism is `Id.CLASS` + +`commands/command/Command.java` is annotated `@JsonTypeInfo(use = Id.CLASS, property = "_type")`. That +means **the persisted discriminator in every user's `profiles.json` is the literal fully-qualified +class name**, e.g. `com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced`. The frontend +`command-catalog.ts` keys on the same FQCN strings. `CommandMapDeserializer` does **no** FQCN +remapping (`CommandConverter` only migrates the ancient v1.6 `String[]` format, never `_type`s). + +**Therefore: moving any command class to a new package silently breaks every existing saved profile** +unless we decouple the persisted type id from the class location first. The commands actually at risk +(still in `commands/command/`, so a move changes their FQCN) are exactly: + +- OBS: `CommandObs`, `CommandObsAction`, `CommandObsSetScene`, `CommandObsMuteSource`, `CommandObsSetSourceVolume` +- VoiceMeeter: `CommandVoiceMeeter` (+ `Basic`, `Advanced`, `BasicButton`, `AdvancedButton`) +- MQTT: `CommandMqttPublish` +- OSC: `CommandOscSend` +- (generic) `CommandHttpRequest` + +Wave Link / Discord / Home Assistant commands also persist their FQCN, but they already live in their +feature `.command` packages, so they are frozen-in-place and not at move-risk. + +### Decision: stable logical type IDs (decouple `_type` from FQCN) + +Introduce a stable, location-independent type id per command — the same pattern `WsEvent` already uses +(`@JsonTypeInfo(use = Id.NAME)` + logical names). Each command declares a stable id (e.g. +`voicemeeter.advanced`) via the new `@CommandMeta` annotation (below). A custom `@JsonTypeIdResolver` +configured on `Command` (wired through `com.getpcpanel.Json`, the single shared `ObjectMapper`) maps +**stable-id ↔ class**, so commands become freely movable forever. + +Back-compat is mandatory: the resolver also recognises **legacy FQCN `_type` strings** as aliases for +their command (a small `legacy-fqcn → stable-id` table, seeded with the current FQCNs of the at-risk +commands). Old saves load; new saves write the stable id. The frontend catalog then keys on stable ids +instead of FQCN strings. + +This single mechanism is the foundation of the annotation-driven registry: the same `@CommandMeta` +that carries the stable id carries the UI metadata. + +## The annotation-driven command registry (single source of truth) + +Today the frontend `command-catalog.ts` is hand-maintained and duplicates metadata that mostly already +exists on the Java command classes. Replace it with metadata declared **once, in Java, next to each +command**, and generated into TypeScript at build time (consistent with the existing +typescript-generator pipeline; keeps the frontend statically typed). + +New annotations in `com.getpcpanel.commands.meta`: + +```java +@CommandMeta( + id = "voicemeeter.advanced", // stable persisted type id + UI _type key + label = "Voicemeeter — parameter", + category = CommandCategory.INTEGRATION, + feature = "voicemeeter", // ties to a Feature/enablement key; omit for core + kinds = { CommandKind.DIAL }, + icon = "sliders") // must be an IconName the UI knows +@FieldMeta(key = "fullParam", label = "Parameter", kind = SELECT_LIVE, source = "vm-advanced") +@FieldMeta(key = "ct", label = "Range", kind = SELECT, optionsEnum = ControlType.class) +public final class CommandVoiceMeeterAdvanced extends CommandVoiceMeeter implements DialAction { ... } +``` + +A build-time generator (an extension of the typescript-generator step, or a small companion Maven +generator) emits `webui/.../models/generated/command-catalog.generated.ts` carrying the `CommandDef` +list — type id, label, category, kinds, feature, icon, the `buildEmpty()` default shape (derived from +the class's fields/`@JsonProperty` wire names), and the simple `fields[]`. + +**Escape hatches stay hand-written.** Composite field editors that are not 1:1 with a Java field — +`keystroke`, `wavelink-target`, `analog-bands`, and the `mute`/`apps`/`device` live pickers — and the +`LiveSource` → `IntegrationDataService` wiring cannot be generated. The generated catalog references +them by a stable `kind`, and `command-fields.component.ts` keeps its bespoke renderers. The generator +must faithfully reproduce two existing quirks: the `@JsonProperty("isUnMuteOnVolumeChange")` wire-name +divergence, and the dial `invert` + `dialParams{invert,moveStart,moveEnd}` duplication. + +The same annotation metadata feeds `CommandsResource` (picker list + per-feature `enabled()` gate), +collapsing registry #2. (Note: that backend list is *already* non-authoritative — Discord/HA are +missing from it today — confirming the frontend catalog is the real registry to replace.) + +## Target package layout + +``` +com.getpcpanel +├── commands/ # the command ENGINE only (framework) +│ ├── command/ # Command, DialAction/ButtonAction/DeviceAction SPIs, +│ │ # CommandNoOp, CommandConverter (legacy), and the genuinely-core +│ │ # commands: volume/*, media, keystroke, shortcut, run, end-program, +│ │ # brightness, profile, CommandValueOutput + CommandHttpRequest +│ ├── meta/ # NEW: @CommandMeta, @FieldMeta, CommandKind, the type-id resolver +│ ├── Commands, CommandsType, CommandDispatcher, DeviceSet, PCPanelControlEvent +│ └── IconService, IIconHandler +│ +├── voicemeeter/ # ← consolidate +│ ├── command/ # CommandVoiceMeeter{,Basic,Advanced,BasicButton,AdvancedButton} +│ ├── model/ # the enums extracted from the Voicemeeter facade (ControlType, …) +│ ├── VoiceMeeterMuteResolver (MuteStateResolver SPI impl; VM_PATTERN no longer leaks) +│ ├── VoiceMeeterIconHandler (IIconHandler SPI impl) +│ ├── VoiceMeeterSettings (record; persisted via Save) +│ └── Voicemeeter, VoicemeeterAPI, VoicemeeterInstance, … (engine, unchanged) +├── obs/ + obs/command, obs/ObsIconHandler, ObsSettings +├── osc/ + osc/command +├── mqtt/ + mqtt/command +├── wavelink/ (already good) +├── discord/ (already good) +├── homeassistant/ (already good — the action-package reference) +│ +└── rest/ + ├── (shared bridge only: Device/Audio/Settings/Process/Serial/Midi/System/Overlay/Icon/Platform + │ resources, EventWebSocket, EventBroadcaster, LocalHttpGuard, model/{dto,ws}) + ├── voicemeeter/ ← move VoiceMeeterResource (+dto) + ├── obs/ ← move ObsResource (+dto) + ├── osc/ ← move OscResource + ├── wavelink/ (already) + └── discord/ (already) +``` + +Per-feature settings records move into the feature package (Wave Link/Discord pattern); `Save` keeps +the fields or holds the records, and `SettingsDto` mapping shrinks accordingly. Per-feature +`MuteStateResolver` impls move into their feature package (the `@All List` discovery +already makes location irrelevant). + +### `pom.xml` classPatterns → one glob + +Replace the per-feature lines (`wavelink.command.**`, `discord.command.**`, …) with +`com.getpcpanel.**.command.**` so any feature's command package is picked up automatically — removing a +per-integration edit to the build config. + +## Out of scope (platform core, not pluggable features) + +These packages are core infrastructure, not integrations, and are **not** restructured by this work +(flagged here so they are not mistaken for omissions): `cpp/` (OS-audio facade `ISndCtrl` + per-OS +impls), `overlay/`, `sleepdetection/`, `iconextract/`, `volume/`, `analogbands/`, `util/**` (incl. +`tray/`, `version/`, `coloroverride/`), and top-level `Main`/`Json`/`CachingConfig`. Note `Json.java` +is the single Jackson-config seam where the command type-id resolver must be registered. Several of +these already expose sibling SPIs (`IOverrideColorProvider`, `IFocusRedirector`) that confirm the +CDI-discovery pattern this refactor leans on. + +## Phased implementation + +Each phase is independently committable and keeps the build + coverage/parity tests green. + +1. **Type-id infrastructure (no moves yet).** Add `@CommandMeta`/`@FieldMeta`, `CommandKind`, and the + `@JsonTypeIdResolver` with the legacy-FQCN alias table. Switch `Command` to it. Add a test that + every existing saved-FQCN still deserializes. *No class moves — purely additive.* +2. **Annotation backfill.** Add `@CommandMeta` (+ `@FieldMeta` where simple) to every command, + mirroring `command-catalog.ts` exactly. Add the generator; emit `command-catalog.generated.ts`; + switch the frontend to it (keeping the hand-written composite renderers). Delete the hand-built + catalog. `CommandsResource` derives from the annotations. +3. **VoiceMeeter consolidation** (git-mv): commands → `voicemeeter.command`; resolver → `voicemeeter`; + resource → `rest/voicemeeter`; enums → `voicemeeter.model`; icon → `IIconHandler`; settings record + local. Update NativeImageConfig + classPatterns glob. Verify saves load. +4. **OBS consolidation** (same shape). +5. **OSC + MQTT consolidation** (same shape). +6. **Cleanup + parity:** move deserializers next to their types; fix `docs/events.md` (add the missing + Discord events); confirm `ReflectionRegistrationCoverageTest`, `ProxyRegistrationCoverageTest`, + `NativeBuildArgsParityTest` green; build native if feasible. + +Git history is preserved throughout by using `git mv` for every relocation. + +## Risks & guards + +- **Saved-profile breakage** — mitigated by the legacy-FQCN alias table + a deserialization test + seeded with the current FQCNs (phase 1, before any move). +- **Native-image 500s** — every concrete command + nested record (and `[]` form for `List`/`Set`) + must stay registered; `ReflectionRegistrationCoverageTest` enforces it. The `File`/`FileSerializer` + hazard on `ISndCtrl.RunningApplication` is pre-existing and unaffected. +- **TS contract drift** — the classPatterns glob must actually match new packages; the build fails + loudly if `backend.types.ts` regenerates differently. +- **Commands are plain data, not beans** — they reach services via `CdiHelper.getBean(...)`. The + metadata registry hangs off the *class*, never an instance. +- **`docs/events.md` is stale** (missing Discord events) — fix as part of phase 6. From a6eee8804699b4592d57326f0b97b839fa3b98df Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 11:37:37 +0200 Subject: [PATCH 02/49] refactor(commands): stable Id.NAME type registry so commands can move package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch Command polymorphism from Jackson Id.CLASS to Id.NAME + an explicit @JsonSubTypes registry. Each subtype's name is a stable, location-independent string equal to the class's historical FQCN — so saved profiles.json, the generated TypeScript _type union, and the frontend command catalog are all unchanged, while a command class is now free to move into its own feature package (e.g. com.getpcpanel.voicemeeter.command) without breaking any of them: only the @Type(value=…) reference moves, the name stays frozen. This is the seam that lets each integration own its command classes. - CommandSubtypeRegistryTest guards the two invariants: every concrete Command subtype is registered exactly once (the 'forgot to register' guard), and every persisted id still deserializes to its class (the save-migration guard). - typescript-generator now emits a CommandUnion tagged union (it keys off @JsonSubTypes); control.component.ts casts its two loose command-build sites to CommandUnion. backend.types.ts is otherwise unchanged except abstract base classes correctly drop out of the concrete _type literal unions. Pre-existing FocusVolumeOverrideServiceTest failures (3, env-specific focus detection) are unrelated and unchanged by this commit. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../getpcpanel/commands/command/Command.java | 92 ++++++++++++- .../src/app/models/generated/backend.types.ts | 24 ++-- .../app/pages/control/control.component.ts | 6 +- .../command/CommandSubtypeRegistryTest.java | 121 ++++++++++++++++++ 4 files changed, 228 insertions(+), 15 deletions(-) create mode 100644 src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java diff --git a/src/main/java/com/getpcpanel/commands/command/Command.java b/src/main/java/com/getpcpanel/commands/command/Command.java index 3e2b10a8..709dddb2 100644 --- a/src/main/java/com/getpcpanel/commands/command/Command.java +++ b/src/main/java/com/getpcpanel/commands/command/Command.java @@ -2,16 +2,106 @@ import javax.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.getpcpanel.discord.command.CommandDiscordJoinVoice; +import com.getpcpanel.discord.command.CommandDiscordLeaveVoice; +import com.getpcpanel.discord.command.CommandDiscordMute; +import com.getpcpanel.discord.command.CommandDiscordScreenShare; +import com.getpcpanel.discord.command.CommandDiscordSelfDeafen; +import com.getpcpanel.discord.command.CommandDiscordSelfInputVolume; +import com.getpcpanel.discord.command.CommandDiscordSelfMute; +import com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume; +import com.getpcpanel.discord.command.CommandDiscordToggleVideo; +import com.getpcpanel.discord.command.CommandDiscordUserMute; +import com.getpcpanel.discord.command.CommandDiscordUserVolume; +import com.getpcpanel.discord.command.CommandDiscordVolume; import com.getpcpanel.hid.DialValue; +import com.getpcpanel.homeassistant.command.CommandHomeAssistantAction; +import com.getpcpanel.homeassistant.command.CommandHomeAssistantValue; +import com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel; +import com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel; +import com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute; +import com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect; +import com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput; import lombok.ToString; import lombok.extern.log4j.Log4j2; +/** + * Base type for every assignable action. + * + *

Persisted type id (the {@code _type} discriminator). The polymorphism uses + * {@link JsonTypeInfo.Id#NAME} with an explicit {@link JsonSubTypes} registry rather than + * {@code Id.CLASS}. Each subtype's {@code name} is a stable, location-independent string + * that happens to equal the class's historical fully-qualified name — so saved {@code profiles.json} + * files, the generated TypeScript {@code _type} union, and the frontend command catalog are all + * unchanged by this scheme, yet a command class is now free to move to its own feature package + * (e.g. {@code com.getpcpanel.voicemeeter.command}) without breaking any of them: only the + * {@code @Type(value = …)} reference moves, the {@code name} stays frozen. + * + *

This is the seam that lets each integration own its command classes. To add a command: create + * the class and register one {@code @Type} line here. (A future build-time generator may derive this + * list and the frontend catalog from per-command {@code @CommandMeta} annotations — see + * {@code docs/feature-module-structure.md}.) + */ @Log4j2 @ToString @SuppressWarnings("InstanceofThis") -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "_type") +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "_type") +@JsonSubTypes({ + @Type(value = CommandAnalogBands.class, name = "com.getpcpanel.commands.command.CommandAnalogBands"), + @Type(value = CommandBrightness.class, name = "com.getpcpanel.commands.command.CommandBrightness"), + @Type(value = CommandEndProgram.class, name = "com.getpcpanel.commands.command.CommandEndProgram"), + @Type(value = CommandHttpRequest.class, name = "com.getpcpanel.commands.command.CommandHttpRequest"), + @Type(value = CommandKeystroke.class, name = "com.getpcpanel.commands.command.CommandKeystroke"), + @Type(value = CommandMedia.class, name = "com.getpcpanel.commands.command.CommandMedia"), + @Type(value = CommandMqttPublish.class, name = "com.getpcpanel.commands.command.CommandMqttPublish"), + @Type(value = CommandNoOp.class, name = "com.getpcpanel.commands.command.CommandNoOp"), + @Type(value = CommandObsAction.class, name = "com.getpcpanel.commands.command.CommandObsAction"), + @Type(value = CommandObsMuteSource.class, name = "com.getpcpanel.commands.command.CommandObsMuteSource"), + @Type(value = CommandObsSetScene.class, name = "com.getpcpanel.commands.command.CommandObsSetScene"), + @Type(value = CommandObsSetSourceVolume.class, name = "com.getpcpanel.commands.command.CommandObsSetSourceVolume"), + @Type(value = CommandOscSend.class, name = "com.getpcpanel.commands.command.CommandOscSend"), + @Type(value = CommandProfile.class, name = "com.getpcpanel.commands.command.CommandProfile"), + @Type(value = CommandRun.class, name = "com.getpcpanel.commands.command.CommandRun"), + @Type(value = CommandShortcut.class, name = "com.getpcpanel.commands.command.CommandShortcut"), + @Type(value = CommandVoiceMeeterAdvanced.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced"), + @Type(value = CommandVoiceMeeterAdvancedButton.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton"), + @Type(value = CommandVoiceMeeterBasic.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterBasic"), + @Type(value = CommandVoiceMeeterBasicButton.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton"), + @Type(value = CommandVolumeApplicationDeviceToggle.class, name = "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle"), + @Type(value = CommandVolumeDefaultDevice.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDevice"), + @Type(value = CommandVolumeDefaultDeviceAdvanced.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced"), + @Type(value = CommandVolumeDefaultDeviceToggle.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle"), + @Type(value = CommandVolumeDefaultDeviceToggleAdvanced.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced"), + @Type(value = CommandVolumeDevice.class, name = "com.getpcpanel.commands.command.CommandVolumeDevice"), + @Type(value = CommandVolumeDeviceMute.class, name = "com.getpcpanel.commands.command.CommandVolumeDeviceMute"), + @Type(value = CommandVolumeFocus.class, name = "com.getpcpanel.commands.command.CommandVolumeFocus"), + @Type(value = CommandVolumeFocusMute.class, name = "com.getpcpanel.commands.command.CommandVolumeFocusMute"), + @Type(value = CommandVolumeProcess.class, name = "com.getpcpanel.commands.command.CommandVolumeProcess"), + @Type(value = CommandVolumeProcessMute.class, name = "com.getpcpanel.commands.command.CommandVolumeProcessMute"), + @Type(value = CommandDiscordJoinVoice.class, name = "com.getpcpanel.discord.command.CommandDiscordJoinVoice"), + @Type(value = CommandDiscordLeaveVoice.class, name = "com.getpcpanel.discord.command.CommandDiscordLeaveVoice"), + @Type(value = CommandDiscordMute.class, name = "com.getpcpanel.discord.command.CommandDiscordMute"), + @Type(value = CommandDiscordScreenShare.class, name = "com.getpcpanel.discord.command.CommandDiscordScreenShare"), + @Type(value = CommandDiscordSelfDeafen.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfDeafen"), + @Type(value = CommandDiscordSelfInputVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume"), + @Type(value = CommandDiscordSelfMute.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfMute"), + @Type(value = CommandDiscordSelfOutputVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume"), + @Type(value = CommandDiscordToggleVideo.class, name = "com.getpcpanel.discord.command.CommandDiscordToggleVideo"), + @Type(value = CommandDiscordUserMute.class, name = "com.getpcpanel.discord.command.CommandDiscordUserMute"), + @Type(value = CommandDiscordUserVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordUserVolume"), + @Type(value = CommandDiscordVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordVolume"), + @Type(value = CommandHomeAssistantAction.class, name = "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction"), + @Type(value = CommandHomeAssistantValue.class, name = "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue"), + @Type(value = CommandWaveLinkAddFocusToChannel.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel"), + @Type(value = CommandWaveLinkChangeLevel.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel"), + @Type(value = CommandWaveLinkChangeMute.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute"), + @Type(value = CommandWaveLinkChannelEffect.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect"), + @Type(value = CommandWaveLinkMainOutput.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"), +}) public abstract class Command { public Runnable toRunnable(boolean initial, String deviceId, @Nullable DialValue vol) { // A command that is both a dial and a button action (e.g. the generic HTTP/MQTT/OSC outputs) diff --git a/src/main/webui/src/app/models/generated/backend.types.ts b/src/main/webui/src/app/models/generated/backend.types.ts index 67d6cbde..88ad2a60 100644 --- a/src/main/webui/src/app/models/generated/backend.types.ts +++ b/src/main/webui/src/app/models/generated/backend.types.ts @@ -39,7 +39,7 @@ export interface ButtonAction { } export interface Command { - _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandObs" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandValueOutput" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandVoiceMeeter" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.commands.command.CommandVolume" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.discord.command.CommandDiscord" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistant" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.wavelink.command.CommandWaveLink" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChange" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; } export interface CommandAnalogBands extends Command, DialAction { @@ -55,7 +55,7 @@ export interface CommandConverter { } export interface CommandDiscord extends Command { - _type: "com.getpcpanel.discord.command.CommandDiscord" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume"; + _type: "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume"; } export interface CommandDiscordJoinVoice extends CommandDiscord, ButtonAction { @@ -128,7 +128,7 @@ export interface CommandEndProgram extends Command, ButtonAction { } export interface CommandHomeAssistant extends Command { - _type: "com.getpcpanel.homeassistant.command.CommandHomeAssistant" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue"; + _type: "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue"; server?: string; } @@ -177,7 +177,7 @@ export interface CommandNoOp extends Command, ButtonAction, DialAction { } export interface CommandObs extends Command { - _type: "com.getpcpanel.commands.command.CommandObs" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume"; + _type: "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume"; } export interface CommandObsAction extends CommandObs, ButtonAction { @@ -217,7 +217,7 @@ export interface CommandRun extends Command, ButtonAction { } export interface Commands { - commands: Command[]; + commands: CommandUnion[]; type?: CommandsType; } @@ -234,14 +234,14 @@ export interface CommandType { } export interface CommandValueOutput extends Command, DialAction, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandValueOutput" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend"; + _type: "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend"; formula?: string; max?: number; min?: number; } export interface CommandVoiceMeeter extends Command { - _type: "com.getpcpanel.commands.command.CommandVoiceMeeter" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton"; + _type: "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton"; } export interface CommandVoiceMeeterAdvanced extends CommandVoiceMeeter, DialAction { @@ -272,7 +272,7 @@ export interface CommandVoiceMeeterBasicButton extends CommandVoiceMeeter, Butto } export interface CommandVolume extends Command { - _type: "com.getpcpanel.commands.command.CommandVolume" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute"; + _type: "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute"; } export interface CommandVolumeApplicationDeviceToggle extends CommandVolume, ButtonAction { @@ -349,7 +349,7 @@ export interface CommandVolumeProcessMute extends CommandVolume, ButtonAction { } export interface CommandWaveLink extends Command { - _type: "com.getpcpanel.wavelink.command.CommandWaveLink" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChange" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; } export interface CommandWaveLinkAddFocusToChannel extends CommandWaveLink, ButtonAction { @@ -359,7 +359,7 @@ export interface CommandWaveLinkAddFocusToChannel extends CommandWaveLink, Butto } export interface CommandWaveLinkChange extends CommandWaveLink { - _type: "com.getpcpanel.wavelink.command.CommandWaveLinkChange" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute"; + _type: "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute"; commandType: WaveLinkCommandTarget; id1?: string; id2?: string; @@ -541,7 +541,7 @@ export interface FocusVolumeOverride { } export interface FocusVolumeTarget { - command: Command; + command: CommandUnion; } export interface GlobalLightingSpec { @@ -915,6 +915,8 @@ export type CommandCategory = "standard" | "voicemeeter" | "obs" | "wavelink"; export type CommandsType = "allAtOnce" | "sequential"; +export type CommandUnion = CommandAnalogBands | CommandBrightness | CommandEndProgram | CommandHttpRequest | CommandKeystroke | CommandMedia | CommandMqttPublish | CommandNoOp | CommandObsAction | CommandObsMuteSource | CommandObsSetScene | CommandObsSetSourceVolume | CommandOscSend | CommandProfile | CommandRun | CommandShortcut | CommandVoiceMeeterAdvanced | CommandVoiceMeeterAdvancedButton | CommandVoiceMeeterBasic | CommandVoiceMeeterBasicButton | CommandVolumeApplicationDeviceToggle | CommandVolumeDefaultDevice | CommandVolumeDefaultDeviceAdvanced | CommandVolumeDefaultDeviceToggle | CommandVolumeDefaultDeviceToggleAdvanced | CommandVolumeDevice | CommandVolumeDeviceMute | CommandVolumeFocus | CommandVolumeFocusMute | CommandVolumeProcess | CommandVolumeProcessMute | CommandDiscordJoinVoice | CommandDiscordLeaveVoice | CommandDiscordMute | CommandDiscordScreenShare | CommandDiscordSelfDeafen | CommandDiscordSelfInputVolume | CommandDiscordSelfMute | CommandDiscordSelfOutputVolume | CommandDiscordToggleVideo | CommandDiscordUserMute | CommandDiscordUserVolume | CommandDiscordVolume | CommandHomeAssistantAction | CommandHomeAssistantValue | CommandWaveLinkAddFocusToChannel | CommandWaveLinkChangeLevel | CommandWaveLinkChangeMute | CommandWaveLinkChannelEffect | CommandWaveLinkMainOutput; + export type ControlType = "STRIP" | "BUS"; export type DeviceType = "PCPANEL_RGB" | "PCPANEL_MINI" | "PCPANEL_PRO"; diff --git a/src/main/webui/src/app/pages/control/control.component.ts b/src/main/webui/src/app/pages/control/control.component.ts index cdd8fca6..cd772845 100644 --- a/src/main/webui/src/app/pages/control/control.component.ts +++ b/src/main/webui/src/app/pages/control/control.component.ts @@ -6,7 +6,7 @@ import { DeviceStateService } from '../../services/device-state.service'; import { DeviceService } from '../../services/device.service'; import { IntegrationDataService } from '../../features/commands/integration-data.service'; import { DeviceCapabilitiesService } from '../../services/device-capabilities.service'; -import { Command, Commands, KnobSetting } from '../../models/generated/backend.types'; +import { Command, CommandUnion, Commands, KnobSetting } from '../../models/generated/backend.types'; import { AppPickerComponent, IconComponent, ToggleComponent, ToastService, } from '../../ui'; @@ -169,7 +169,7 @@ export class ControlComponent { // ── mutations ──────────────────────────────────────────────────────────── addCommand(def: CommandDef): void { const sig = this.currentSlotSignal(); - const next = { ...sig(), commands: [...sig().commands, def.buildEmpty() as Command] }; + const next = { ...sig(), commands: [...sig().commands, def.buildEmpty() as CommandUnion] }; sig.set(next); this.expanded.set(next.commands.length - 1); this.save(); @@ -184,7 +184,7 @@ export class ControlComponent { updateCommand(i: number, cmd: Record): void { const sig = this.currentSlotSignal(); const cmds = [...sig().commands]; - cmds[i] = cmd as unknown as Command; + cmds[i] = cmd as unknown as CommandUnion; sig.set({ ...sig(), commands: cmds }); this.save(); } diff --git a/src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java b/src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java new file mode 100644 index 00000000..444e3e4e --- /dev/null +++ b/src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java @@ -0,0 +1,121 @@ +package com.getpcpanel.commands.command; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Guards the {@link Command} polymorphic type registry. + * + *

{@code Command} uses {@code Id.NAME} + {@link JsonSubTypes}: each subtype's {@code name} is a + * stable, location-independent id that equals the class's historical fully-qualified name, which is + * exactly what existing {@code profiles.json} files persist. This lets a command class move into its + * own feature package without changing the persisted {@code _type}. These tests enforce the two + * invariants that keep that safe: + * + *

    + *
  1. Completeness — every concrete {@code Command} subtype on the classpath is registered + * (a missing entry would make Jackson throw at serialization time), and there are no stale + * entries. This is the "you forgot to register your new command" guard.
  2. + *
  3. Migration — every registered persisted id still deserializes to its class, so saved + * profiles keep loading after a command moves package.
  4. + *
+ */ +@DisplayName("Command @JsonSubTypes registry") +class CommandSubtypeRegistryTest { + // Mirror the Quarkus production mapper, which does not fail on unknown properties — commands + // serialize extra getters (e.g. a dial command's top-level `invert`) that have no creator param. + private final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + @Test + @DisplayName("every concrete Command subtype is registered exactly once, with a unique id") + void registryIsCompleteAndConsistent() throws Exception { + var registered = registeredSubtypes(); + var concrete = findConcreteCommandSubtypes(); + + var missing = concrete.stream().filter(c -> !registered.containsKey(c)).map(Class::getName).sorted().toList(); + assertTrue(missing.isEmpty(), "Concrete Command subtypes missing from Command's @JsonSubTypes (add an @Type line): " + missing); + + var stale = registered.keySet().stream().filter(c -> !concrete.contains(c)).map(Class::getName).sorted().toList(); + assertTrue(stale.isEmpty(), "@JsonSubTypes entries that are not concrete Command subtypes (remove them): " + stale); + + var names = new HashSet(); + for (var name : registered.values()) { + assertFalse(name.isBlank(), "Blank @JsonSubTypes name"); + assertTrue(names.add(name), "Duplicate @JsonSubTypes name: " + name); + } + } + + @Test + @DisplayName("every persisted type id deserializes back to its command class") + void persistedTypeIdsResolve() { + registeredSubtypes().forEach((clazz, name) -> { + var json = "{\"_type\":\"" + name + "\"}"; + try { + var cmd = mapper.readValue(json, Command.class); + assertInstanceOf(clazz, cmd, () -> "id '" + name + "' resolved to the wrong class"); + } catch (Exception e) { + fail("Persisted type id '" + name + "' (for " + clazz.getName() + ") no longer deserializes: " + e.getMessage()); + } + }); + } + + @Test + @DisplayName("a CommandVolumeProcess persists and reloads under its frozen id") + void roundTripFrozenId() throws Exception { + var json = mapper.writeValueAsString(new CommandVolumeProcess(java.util.List.of("foo.exe"), "", false, DialAction.DialCommandParams.DEFAULT)); + assertTrue(json.contains("\"com.getpcpanel.commands.command.CommandVolumeProcess\""), "frozen id not emitted: " + json); + assertInstanceOf(CommandVolumeProcess.class, mapper.readValue(json, Command.class)); + } + + private static Map, String> registeredSubtypes() { + var ann = Command.class.getAnnotation(JsonSubTypes.class); + assertEquals(JsonSubTypes.class, ann.annotationType()); + var result = new HashMap, String>(); + for (var t : ann.value()) { + result.put(t.value(), t.name()); + } + return result; + } + + /** Discovers every concrete {@code com.getpcpanel.**} subclass of {@link Command} from the built classes. */ + private static Set> findConcreteCommandSubtypes() throws Exception { + var root = Path.of(Command.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + var loader = CommandSubtypeRegistryTest.class.getClassLoader(); + var result = new HashSet>(); + try (Stream walk = Files.walk(root.resolve("com").resolve("getpcpanel"))) { + for (var classFile : walk.filter(p -> p.toString().endsWith(".class")).toList()) { + var binary = root.relativize(classFile).toString(); + binary = binary.substring(0, binary.length() - ".class".length()).replace(java.io.File.separatorChar, '.'); + Class clazz; + try { + clazz = Class.forName(binary, false, loader); + } catch (Throwable e) { + continue; + } + if (Command.class.isAssignableFrom(clazz) && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && clazz != Command.class) { + result.add(clazz); + } + } + } + return result; + } +} From 4453906295ed552122f328b0efcc8ad7e83f6596 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 11:43:48 +0200 Subject: [PATCH 03/49] refactor(voicemeeter): move command classes into com.getpcpanel.voicemeeter.command git-mv the five CommandVoiceMeeter* classes from the shared commands/command pile into the VoiceMeeter module, matching the WaveLink/Discord/HomeAssistant convention. Enabled by the stable Id.NAME registry: the persisted _type names stay the historical FQCN, so saved profiles.json, the generated TypeScript _type union, and the frontend command catalog are unchanged (the union diff is a pure reordering of the same literal set). - @JsonSubTypes / CommandConverter / NativeImageConfig / IconService / CommandsResource / VoiceMeeterConnectedVolumeService / VoiceMeeterMuteResolver updated to import from the new package. - reachability-metadata.json: stale VoiceMeeter FQCNs repointed. - pom typescript-generator: collapsed the per-feature command classPatterns to one glob com.getpcpanel.**.command.** so future feature command packages are picked up with no build-config edit. History preserved via git mv. ReflectionRegistrationCoverageTest and the new CommandSubtypeRegistryTest stay green; frontend tsc unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- pom.xml | 9 +++++---- src/main/java/com/getpcpanel/commands/IconService.java | 2 +- .../java/com/getpcpanel/commands/command/Command.java | 4 ++++ .../getpcpanel/commands/command/CommandConverter.java | 4 ++++ .../java/com/getpcpanel/graalvm/NativeImageConfig.java | 10 +++++----- .../getpcpanel/mutecolor/VoiceMeeterMuteResolver.java | 4 ++-- .../java/com/getpcpanel/rest/CommandsResource.java | 8 ++++---- .../voicemeeter/VoiceMeeterConnectedVolumeService.java | 6 +++--- .../command/CommandVoiceMeeter.java | 4 +++- .../command/CommandVoiceMeeterAdvanced.java | 3 ++- .../command/CommandVoiceMeeterAdvancedButton.java | 3 ++- .../command/CommandVoiceMeeterBasic.java | 3 ++- .../command/CommandVoiceMeeterBasicButton.java | 3 ++- .../META-INF/native-image/reachability-metadata.json | 4 ++-- .../webui/src/app/models/generated/backend.types.ts | 2 +- 15 files changed, 42 insertions(+), 27 deletions(-) rename src/main/java/com/getpcpanel/{commands => voicemeeter}/command/CommandVoiceMeeter.java (60%) rename src/main/java/com/getpcpanel/{commands => voicemeeter}/command/CommandVoiceMeeterAdvanced.java (93%) rename src/main/java/com/getpcpanel/{commands => voicemeeter}/command/CommandVoiceMeeterAdvancedButton.java (93%) rename src/main/java/com/getpcpanel/{commands => voicemeeter}/command/CommandVoiceMeeterBasic.java (93%) rename src/main/java/com/getpcpanel/{commands => voicemeeter}/command/CommandVoiceMeeterBasicButton.java (92%) diff --git a/pom.xml b/pom.xml index faec6ef1..479b3db8 100644 --- a/pom.xml +++ b/pom.xml @@ -375,10 +375,11 @@ com.getpcpanel.rest.model.** com.getpcpanel.commands.Commands - com.getpcpanel.commands.command.** - com.getpcpanel.wavelink.command.** - com.getpcpanel.discord.command.** - com.getpcpanel.homeassistant.command.** + + com.getpcpanel.**.command.** com.getpcpanel.device.descriptor.** **.dto.** diff --git a/src/main/java/com/getpcpanel/commands/IconService.java b/src/main/java/com/getpcpanel/commands/IconService.java index 23b73346..1d460f7e 100644 --- a/src/main/java/com/getpcpanel/commands/IconService.java +++ b/src/main/java/com/getpcpanel/commands/IconService.java @@ -18,7 +18,7 @@ import com.getpcpanel.commands.command.Command; import com.getpcpanel.commands.command.CommandBrightness; import com.getpcpanel.commands.command.CommandObs; -import com.getpcpanel.commands.command.CommandVoiceMeeter; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; import com.getpcpanel.commands.command.CommandVolumeDefaultDevice; import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced; import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle; diff --git a/src/main/java/com/getpcpanel/commands/command/Command.java b/src/main/java/com/getpcpanel/commands/command/Command.java index 709dddb2..13d788e0 100644 --- a/src/main/java/com/getpcpanel/commands/command/Command.java +++ b/src/main/java/com/getpcpanel/commands/command/Command.java @@ -20,6 +20,10 @@ import com.getpcpanel.hid.DialValue; import com.getpcpanel.homeassistant.command.CommandHomeAssistantAction; import com.getpcpanel.homeassistant.command.CommandHomeAssistantValue; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; import com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel; import com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel; import com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandConverter.java b/src/main/java/com/getpcpanel/commands/command/CommandConverter.java index 9df02904..a54626b3 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandConverter.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandConverter.java @@ -16,6 +16,10 @@ import com.getpcpanel.voicemeeter.Voicemeeter.ControlType; import com.getpcpanel.voicemeeter.Voicemeeter.DialControlMode; import com.getpcpanel.voicemeeter.Voicemeeter.DialType; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; import lombok.extern.log4j.Log4j2; import one.util.streamex.StreamEx; diff --git a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java index 2a7b34e3..cbe92d52 100644 --- a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java +++ b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java @@ -34,11 +34,11 @@ import com.getpcpanel.commands.command.CommandRun; import com.getpcpanel.commands.command.CommandShortcut; import com.getpcpanel.commands.command.CommandValueOutput; -import com.getpcpanel.commands.command.CommandVoiceMeeter; -import com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced; -import com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton; -import com.getpcpanel.commands.command.CommandVoiceMeeterBasic; -import com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; import com.getpcpanel.commands.command.CommandVolume; import com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle; import com.getpcpanel.commands.command.CommandVolumeDefaultDevice; diff --git a/src/main/java/com/getpcpanel/mutecolor/VoiceMeeterMuteResolver.java b/src/main/java/com/getpcpanel/mutecolor/VoiceMeeterMuteResolver.java index 7a50824c..122bd2a4 100644 --- a/src/main/java/com/getpcpanel/mutecolor/VoiceMeeterMuteResolver.java +++ b/src/main/java/com/getpcpanel/mutecolor/VoiceMeeterMuteResolver.java @@ -9,8 +9,8 @@ import org.apache.commons.lang3.math.NumberUtils; import com.getpcpanel.commands.Commands; -import com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced; -import com.getpcpanel.commands.command.CommandVoiceMeeterBasic; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; import com.getpcpanel.voicemeeter.VoiceMeeterMuteEvent; import com.getpcpanel.voicemeeter.Voicemeeter.ButtonType; import com.getpcpanel.voicemeeter.Voicemeeter.ControlType; diff --git a/src/main/java/com/getpcpanel/rest/CommandsResource.java b/src/main/java/com/getpcpanel/rest/CommandsResource.java index e35e1cdc..ba67f55f 100644 --- a/src/main/java/com/getpcpanel/rest/CommandsResource.java +++ b/src/main/java/com/getpcpanel/rest/CommandsResource.java @@ -12,10 +12,10 @@ import com.getpcpanel.commands.command.CommandObsSetSourceVolume; import com.getpcpanel.commands.command.CommandRun; import com.getpcpanel.commands.command.CommandShortcut; -import com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced; -import com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton; -import com.getpcpanel.commands.command.CommandVoiceMeeterBasic; -import com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; import com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle; import com.getpcpanel.commands.command.CommandVolumeDefaultDevice; import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced; diff --git a/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterConnectedVolumeService.java b/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterConnectedVolumeService.java index 35df4d38..a37fc7bc 100644 --- a/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterConnectedVolumeService.java +++ b/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterConnectedVolumeService.java @@ -1,8 +1,8 @@ package com.getpcpanel.voicemeeter; -import com.getpcpanel.commands.command.CommandVoiceMeeter; -import com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced; -import com.getpcpanel.commands.command.CommandVoiceMeeterBasic; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; import com.getpcpanel.hid.DeviceHolder; import com.getpcpanel.platform.WindowsBuild; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeter.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeter.java similarity index 60% rename from src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeter.java rename to src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeter.java index 51d95e6e..887930ea 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeter.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeter.java @@ -1,4 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.voicemeeter.command; + +import com.getpcpanel.commands.command.Command; import lombok.Getter; import lombok.ToString; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvanced.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java similarity index 93% rename from src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvanced.java rename to src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java index 251b54ab..065ce995 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvanced.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java @@ -1,7 +1,8 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.voicemeeter.command; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.voicemeeter.Voicemeeter; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvancedButton.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java similarity index 93% rename from src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvancedButton.java rename to src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java index 14c8c4c2..0dd8320d 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterAdvancedButton.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java @@ -1,4 +1,4 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.voicemeeter.command; import javax.annotation.Nullable; @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.voicemeeter.Voicemeeter; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasic.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasic.java similarity index 93% rename from src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasic.java rename to src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasic.java index 7063ac97..0cb71b03 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasic.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasic.java @@ -1,7 +1,8 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.voicemeeter.command; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.voicemeeter.Voicemeeter; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasicButton.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasicButton.java similarity index 92% rename from src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasicButton.java rename to src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasicButton.java index ef27f022..2dbbbafc 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVoiceMeeterBasicButton.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasicButton.java @@ -1,7 +1,8 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.voicemeeter.command; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.voicemeeter.Voicemeeter; diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json index d74eb3bb..8e00d5bd 100644 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -217,10 +217,10 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandVoiceMeeter" + "type": "com.getpcpanel.voicemeeter.command.CommandVoiceMeeter" }, { - "type": "com.getpcpanel.commands.command.CommandVoiceMeeterBasic", + "type": "com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic", "methods": [ { "name": "", diff --git a/src/main/webui/src/app/models/generated/backend.types.ts b/src/main/webui/src/app/models/generated/backend.types.ts index 88ad2a60..8430dc1f 100644 --- a/src/main/webui/src/app/models/generated/backend.types.ts +++ b/src/main/webui/src/app/models/generated/backend.types.ts @@ -39,7 +39,7 @@ export interface ButtonAction { } export interface Command { - _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; } export interface CommandAnalogBands extends Command, DialAction { From 4c99990dd91832f4f631c7c5cdbefcbe03c34575 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 11:55:57 +0200 Subject: [PATCH 04/49] refactor(obs): move command classes into com.getpcpanel.obs.command Same pattern as the VoiceMeeter move: git-mv the CommandObs* classes out of the shared commands/command pile into the OBS module. Persisted _type names stay the historical FQCN (frozen via @JsonSubTypes), so saves, the TS _type union, and the frontend catalog are unchanged. Importers (@JsonSubTypes, CommandConverter, NativeImageConfig, IconService, ObsMuteResolver, ObsConnectedVolumeService, CommandsResource) and reachability-metadata repointed. classPatterns glob already covers obs.command. Guards green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../java/com/getpcpanel/commands/IconService.java | 2 +- .../com/getpcpanel/commands/command/Command.java | 4 ++++ .../commands/command/CommandConverter.java | 3 +++ .../com/getpcpanel/graalvm/NativeImageConfig.java | 12 ++++++------ .../com/getpcpanel/mutecolor/ObsMuteResolver.java | 4 ++-- .../getpcpanel/obs/ObsConnectedVolumeService.java | 2 +- .../{commands => obs}/command/CommandObs.java | 4 +++- .../{commands => obs}/command/CommandObsAction.java | 3 ++- .../command/CommandObsMuteSource.java | 3 ++- .../command/CommandObsSetScene.java | 3 ++- .../command/CommandObsSetSourceVolume.java | 3 ++- .../java/com/getpcpanel/rest/CommandsResource.java | 6 +++--- .../META-INF/native-image/reachability-metadata.json | 6 +++--- .../webui/src/app/models/generated/backend.types.ts | 2 +- 14 files changed, 35 insertions(+), 22 deletions(-) rename src/main/java/com/getpcpanel/{commands => obs}/command/CommandObs.java (50%) rename src/main/java/com/getpcpanel/{commands => obs}/command/CommandObsAction.java (96%) rename src/main/java/com/getpcpanel/{commands => obs}/command/CommandObsMuteSource.java (92%) rename src/main/java/com/getpcpanel/{commands => obs}/command/CommandObsSetScene.java (89%) rename src/main/java/com/getpcpanel/{commands => obs}/command/CommandObsSetSourceVolume.java (92%) diff --git a/src/main/java/com/getpcpanel/commands/IconService.java b/src/main/java/com/getpcpanel/commands/IconService.java index 1d460f7e..c2dc8cb5 100644 --- a/src/main/java/com/getpcpanel/commands/IconService.java +++ b/src/main/java/com/getpcpanel/commands/IconService.java @@ -17,7 +17,7 @@ import com.getpcpanel.commands.command.Command; import com.getpcpanel.commands.command.CommandBrightness; -import com.getpcpanel.commands.command.CommandObs; +import com.getpcpanel.obs.command.CommandObs; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; import com.getpcpanel.commands.command.CommandVolumeDefaultDevice; import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced; diff --git a/src/main/java/com/getpcpanel/commands/command/Command.java b/src/main/java/com/getpcpanel/commands/command/Command.java index 13d788e0..a5f55eb6 100644 --- a/src/main/java/com/getpcpanel/commands/command/Command.java +++ b/src/main/java/com/getpcpanel/commands/command/Command.java @@ -20,6 +20,10 @@ import com.getpcpanel.hid.DialValue; import com.getpcpanel.homeassistant.command.CommandHomeAssistantAction; import com.getpcpanel.homeassistant.command.CommandHomeAssistantValue; +import com.getpcpanel.obs.command.CommandObsAction; +import com.getpcpanel.obs.command.CommandObsMuteSource; +import com.getpcpanel.obs.command.CommandObsSetScene; +import com.getpcpanel.obs.command.CommandObsSetSourceVolume; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandConverter.java b/src/main/java/com/getpcpanel/commands/command/CommandConverter.java index a54626b3..bcafc14b 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandConverter.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandConverter.java @@ -11,6 +11,9 @@ import com.getpcpanel.commands.command.CommandMedia.VolumeButton; import com.getpcpanel.commands.command.DialAction.DialCommandParams; import com.getpcpanel.cpp.MuteType; +import com.getpcpanel.obs.command.CommandObsMuteSource; +import com.getpcpanel.obs.command.CommandObsSetScene; +import com.getpcpanel.obs.command.CommandObsSetSourceVolume; import com.getpcpanel.voicemeeter.Voicemeeter.ButtonControlMode; import com.getpcpanel.voicemeeter.Voicemeeter.ButtonType; import com.getpcpanel.voicemeeter.Voicemeeter.ControlType; diff --git a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java index cbe92d52..a8882a5a 100644 --- a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java +++ b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java @@ -23,12 +23,12 @@ import com.getpcpanel.commands.command.CommandMedia.VolumeButton; import com.getpcpanel.commands.command.CommandMqttPublish; import com.getpcpanel.commands.command.CommandNoOp; -import com.getpcpanel.commands.command.CommandObs; -import com.getpcpanel.commands.command.CommandObsAction; -import com.getpcpanel.commands.command.CommandObsAction.ObsActionType; -import com.getpcpanel.commands.command.CommandObsMuteSource; -import com.getpcpanel.commands.command.CommandObsSetScene; -import com.getpcpanel.commands.command.CommandObsSetSourceVolume; +import com.getpcpanel.obs.command.CommandObs; +import com.getpcpanel.obs.command.CommandObsAction; +import com.getpcpanel.obs.command.CommandObsAction.ObsActionType; +import com.getpcpanel.obs.command.CommandObsMuteSource; +import com.getpcpanel.obs.command.CommandObsSetScene; +import com.getpcpanel.obs.command.CommandObsSetSourceVolume; import com.getpcpanel.commands.command.CommandOscSend; import com.getpcpanel.commands.command.CommandProfile; import com.getpcpanel.commands.command.CommandRun; diff --git a/src/main/java/com/getpcpanel/mutecolor/ObsMuteResolver.java b/src/main/java/com/getpcpanel/mutecolor/ObsMuteResolver.java index cba2dde3..89f84b48 100644 --- a/src/main/java/com/getpcpanel/mutecolor/ObsMuteResolver.java +++ b/src/main/java/com/getpcpanel/mutecolor/ObsMuteResolver.java @@ -5,8 +5,8 @@ import org.apache.commons.lang3.StringUtils; import com.getpcpanel.commands.Commands; -import com.getpcpanel.commands.command.CommandObsMuteSource; -import com.getpcpanel.commands.command.CommandObsSetSourceVolume; +import com.getpcpanel.obs.command.CommandObsMuteSource; +import com.getpcpanel.obs.command.CommandObsSetSourceVolume; import com.getpcpanel.obs.OBS; import jakarta.enterprise.context.ApplicationScoped; diff --git a/src/main/java/com/getpcpanel/obs/ObsConnectedVolumeService.java b/src/main/java/com/getpcpanel/obs/ObsConnectedVolumeService.java index 4570c183..bcd426d0 100644 --- a/src/main/java/com/getpcpanel/obs/ObsConnectedVolumeService.java +++ b/src/main/java/com/getpcpanel/obs/ObsConnectedVolumeService.java @@ -1,7 +1,7 @@ package com.getpcpanel.obs; import java.util.function.Function; -import com.getpcpanel.commands.command.CommandObsSetSourceVolume; +import com.getpcpanel.obs.command.CommandObsSetSourceVolume; import com.getpcpanel.hid.DeviceHolder; import com.getpcpanel.platform.WindowsBuild; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandObs.java b/src/main/java/com/getpcpanel/obs/command/CommandObs.java similarity index 50% rename from src/main/java/com/getpcpanel/commands/command/CommandObs.java rename to src/main/java/com/getpcpanel/obs/command/CommandObs.java index 87110f15..f217ff6b 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandObs.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObs.java @@ -1,4 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.obs.command; + +import com.getpcpanel.commands.command.Command; import lombok.Getter; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandObsAction.java b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java similarity index 96% rename from src/main/java/com/getpcpanel/commands/command/CommandObsAction.java rename to src/main/java/com/getpcpanel/obs/command/CommandObsAction.java index ee125786..bcc1b068 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandObsAction.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java @@ -1,8 +1,9 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.obs.OBS; +import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; import lombok.Getter; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandObsMuteSource.java b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java similarity index 92% rename from src/main/java/com/getpcpanel/commands/command/CommandObsMuteSource.java rename to src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java index 7bb99479..e5ffa4ac 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandObsMuteSource.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java @@ -1,9 +1,10 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; import com.getpcpanel.obs.OBS; +import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; import lombok.Getter; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandObsSetScene.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java similarity index 89% rename from src/main/java/com/getpcpanel/commands/command/CommandObsSetScene.java rename to src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java index 047944d7..355fe04b 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandObsSetScene.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java @@ -1,7 +1,8 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.obs.OBS; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandObsSetSourceVolume.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java similarity index 92% rename from src/main/java/com/getpcpanel/commands/command/CommandObsSetSourceVolume.java rename to src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java index 2cfd1f59..ad09eb30 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandObsSetSourceVolume.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java @@ -1,7 +1,8 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.obs.OBS; diff --git a/src/main/java/com/getpcpanel/rest/CommandsResource.java b/src/main/java/com/getpcpanel/rest/CommandsResource.java index ba67f55f..cb1bbedc 100644 --- a/src/main/java/com/getpcpanel/rest/CommandsResource.java +++ b/src/main/java/com/getpcpanel/rest/CommandsResource.java @@ -7,9 +7,9 @@ import com.getpcpanel.commands.command.CommandEndProgram; import com.getpcpanel.commands.command.CommandKeystroke; import com.getpcpanel.commands.command.CommandMedia; -import com.getpcpanel.commands.command.CommandObsMuteSource; -import com.getpcpanel.commands.command.CommandObsSetScene; -import com.getpcpanel.commands.command.CommandObsSetSourceVolume; +import com.getpcpanel.obs.command.CommandObsMuteSource; +import com.getpcpanel.obs.command.CommandObsSetScene; +import com.getpcpanel.obs.command.CommandObsSetSourceVolume; import com.getpcpanel.commands.command.CommandRun; import com.getpcpanel.commands.command.CommandShortcut; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json index 8e00d5bd..2b0ac7d5 100644 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -179,10 +179,10 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandObs" + "type": "com.getpcpanel.obs.command.CommandObs" }, { - "type": "com.getpcpanel.commands.command.CommandObsSetScene", + "type": "com.getpcpanel.obs.command.CommandObsSetScene", "methods": [ { "name": "", @@ -197,7 +197,7 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandObsSetSourceVolume", + "type": "com.getpcpanel.obs.command.CommandObsSetSourceVolume", "methods": [ { "name": "", diff --git a/src/main/webui/src/app/models/generated/backend.types.ts b/src/main/webui/src/app/models/generated/backend.types.ts index 8430dc1f..c4be252d 100644 --- a/src/main/webui/src/app/models/generated/backend.types.ts +++ b/src/main/webui/src/app/models/generated/backend.types.ts @@ -39,7 +39,7 @@ export interface ButtonAction { } export interface Command { - _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; } export interface CommandAnalogBands extends Command, DialAction { From a14698c8606d897bbf793ac3ca10ae611fbf4d77 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 11:57:26 +0200 Subject: [PATCH 05/49] refactor(osc,mqtt): move generic-output commands into feature .command packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-mv CommandOscSend -> com.getpcpanel.osc.command and CommandMqttPublish -> com.getpcpanel.mqtt.command. Both extend the generic CommandValueOutput base (which stays in commands/command). Frozen @JsonSubTypes names keep saves/TS/UI unchanged; NativeImageConfig + @JsonSubTypes imports repointed. Guards green. With this, all integration commands (VoiceMeeter, OBS, OSC, MQTT — plus the already-modular WaveLink/Discord/HomeAssistant) live in their feature's .command package; commands/command now holds only the engine + genuinely-core commands (volume/media/keystroke/run/brightness/profile/HTTP/value-output). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/main/java/com/getpcpanel/commands/command/Command.java | 2 ++ src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java | 4 ++-- .../{commands => mqtt}/command/CommandMqttPublish.java | 3 ++- .../getpcpanel/{commands => osc}/command/CommandOscSend.java | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) rename src/main/java/com/getpcpanel/{commands => mqtt}/command/CommandMqttPublish.java (94%) rename src/main/java/com/getpcpanel/{commands => osc}/command/CommandOscSend.java (94%) diff --git a/src/main/java/com/getpcpanel/commands/command/Command.java b/src/main/java/com/getpcpanel/commands/command/Command.java index a5f55eb6..1cac36f8 100644 --- a/src/main/java/com/getpcpanel/commands/command/Command.java +++ b/src/main/java/com/getpcpanel/commands/command/Command.java @@ -20,6 +20,8 @@ import com.getpcpanel.hid.DialValue; import com.getpcpanel.homeassistant.command.CommandHomeAssistantAction; import com.getpcpanel.homeassistant.command.CommandHomeAssistantValue; +import com.getpcpanel.mqtt.command.CommandMqttPublish; +import com.getpcpanel.osc.command.CommandOscSend; import com.getpcpanel.obs.command.CommandObsAction; import com.getpcpanel.obs.command.CommandObsMuteSource; import com.getpcpanel.obs.command.CommandObsSetScene; diff --git a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java index a8882a5a..4d894f32 100644 --- a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java +++ b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java @@ -21,7 +21,7 @@ import com.getpcpanel.commands.command.CommandKeystroke; import com.getpcpanel.commands.command.CommandMedia; import com.getpcpanel.commands.command.CommandMedia.VolumeButton; -import com.getpcpanel.commands.command.CommandMqttPublish; +import com.getpcpanel.mqtt.command.CommandMqttPublish; import com.getpcpanel.commands.command.CommandNoOp; import com.getpcpanel.obs.command.CommandObs; import com.getpcpanel.obs.command.CommandObsAction; @@ -29,7 +29,7 @@ import com.getpcpanel.obs.command.CommandObsMuteSource; import com.getpcpanel.obs.command.CommandObsSetScene; import com.getpcpanel.obs.command.CommandObsSetSourceVolume; -import com.getpcpanel.commands.command.CommandOscSend; +import com.getpcpanel.osc.command.CommandOscSend; import com.getpcpanel.commands.command.CommandProfile; import com.getpcpanel.commands.command.CommandRun; import com.getpcpanel.commands.command.CommandShortcut; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandMqttPublish.java b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java similarity index 94% rename from src/main/java/com/getpcpanel/commands/command/CommandMqttPublish.java rename to src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java index 251df67a..25361a34 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandMqttPublish.java +++ b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java @@ -1,4 +1,4 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.mqtt.command; import javax.annotation.Nullable; @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.mqtt.MqttService; +import com.getpcpanel.commands.command.CommandValueOutput; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.util.ValueInterpolator; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandOscSend.java b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java similarity index 94% rename from src/main/java/com/getpcpanel/commands/command/CommandOscSend.java rename to src/main/java/com/getpcpanel/osc/command/CommandOscSend.java index 95d1e65a..4f7199d3 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandOscSend.java +++ b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java @@ -1,4 +1,4 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.osc.command; import javax.annotation.Nullable; @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.osc.OSCService; +import com.getpcpanel.commands.command.CommandValueOutput; import com.getpcpanel.util.CdiHelper; import lombok.Getter; From bdf9a9805a36b5fc158c53302bd6b0dcabfd3d36 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 11:59:50 +0200 Subject: [PATCH 06/49] refactor(voicemeeter): move VoiceMeeterMuteResolver into the voicemeeter module The VoiceMeeter-specific mute resolver lived in the generic mutecolor package and exposed its VM_PATTERN regex for NamedDeviceMuteResolver to reach into. Move it to com.getpcpanel.voicemeeter; it implements the shared MuteStateResolver SPI so it is still discovered by MuteColorService via @All List regardless of package. NamedDeviceMuteResolver now imports it; the resolver imports the mutecolor SPI + dirty-event types. MuteColorServiceTest stays green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../com/getpcpanel/mutecolor/NamedDeviceMuteResolver.java | 1 + .../{mutecolor => voicemeeter}/VoiceMeeterMuteResolver.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) rename src/main/java/com/getpcpanel/{mutecolor => voicemeeter}/VoiceMeeterMuteResolver.java (95%) diff --git a/src/main/java/com/getpcpanel/mutecolor/NamedDeviceMuteResolver.java b/src/main/java/com/getpcpanel/mutecolor/NamedDeviceMuteResolver.java index 294e6dc2..2b2acf98 100644 --- a/src/main/java/com/getpcpanel/mutecolor/NamedDeviceMuteResolver.java +++ b/src/main/java/com/getpcpanel/mutecolor/NamedDeviceMuteResolver.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.getpcpanel.commands.Commands; +import com.getpcpanel.voicemeeter.VoiceMeeterMuteResolver; import com.getpcpanel.cpp.ISndCtrl; import jakarta.annotation.Priority; diff --git a/src/main/java/com/getpcpanel/mutecolor/VoiceMeeterMuteResolver.java b/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterMuteResolver.java similarity index 95% rename from src/main/java/com/getpcpanel/mutecolor/VoiceMeeterMuteResolver.java rename to src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterMuteResolver.java index 122bd2a4..76c9a1cc 100644 --- a/src/main/java/com/getpcpanel/mutecolor/VoiceMeeterMuteResolver.java +++ b/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterMuteResolver.java @@ -1,4 +1,4 @@ -package com.getpcpanel.mutecolor; +package com.getpcpanel.voicemeeter; import java.util.Map; import java.util.Optional; @@ -9,9 +9,10 @@ import org.apache.commons.lang3.math.NumberUtils; import com.getpcpanel.commands.Commands; +import com.getpcpanel.mutecolor.MuteOverridesDirtyEvent; +import com.getpcpanel.mutecolor.MuteStateResolver; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; -import com.getpcpanel.voicemeeter.VoiceMeeterMuteEvent; import com.getpcpanel.voicemeeter.Voicemeeter.ButtonType; import com.getpcpanel.voicemeeter.Voicemeeter.ControlType; From b0160d20d84da79fcbaec0519b9ccc56e329c63c Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 12:01:37 +0200 Subject: [PATCH 07/49] docs: update feature-module plan with implemented status Record the frozen-FQCN-id foundation, the four command relocations + the VoiceMeeterMuteResolver move as done, and the remaining work (annotation-driven catalog + pretty ids, resource/icon/settings consolidation, dead-code + events.md cleanup). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/feature-module-structure.md | 80 +++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/docs/feature-module-structure.md b/docs/feature-module-structure.md index 284da907..b8c5d176 100644 --- a/docs/feature-module-structure.md +++ b/docs/feature-module-structure.md @@ -1,7 +1,37 @@ # Feature-module structure (plugin-style refactor) -Status: **design** — proposed target structure and migration plan. Implementation is phased; this -document is the source of truth for the end state and the order we get there. +Status: **in progress** — the type-id foundation and the command relocations are implemented and +green; the frontend-catalog generator and the remaining non-command consolidation are not yet done. +This document is the source of truth for the end state and the order we get there. + +### Implemented so far + +- **Stable type-id foundation.** `Command` now uses `@JsonTypeInfo(Id.NAME)` + an explicit + `@JsonSubTypes` registry. Each subtype's `name` is its *historical fully-qualified class name*, + frozen as a location-independent logical id. Saved `profiles.json`, the generated TS `_type` + union, and the frontend catalog are byte-for-byte unchanged, but command classes can now move + package freely. `CommandSubtypeRegistryTest` guards completeness (every concrete command + registered) and migration (every persisted id still deserializes). +- **All integration commands relocated** out of the shared `commands/command` pile into their + feature `*.command` packages via `git mv`: VoiceMeeter, OBS, OSC, MQTT (WaveLink/Discord/Home + Assistant were already there). `commands/command` now holds only the engine + genuinely-core + commands. +- **`VoiceMeeterMuteResolver`** moved from `mutecolor/` into the `voicemeeter` module (still found via + the `@All List` SPI). +- **pom `classPatterns`** collapsed to one glob `com.getpcpanel.**.command.**` — new feature command + packages need no build-config edit. + +Full backend suite green except 3 pre-existing, environment-specific `FocusVolumeOverrideServiceTest` +failures unrelated to this work; frontend `tsc` clean. + +> **Note on the id scheme vs. the original plan.** The approved design called for *pretty* ids +> (`voicemeeter.advanced`) with the old FQCN as a legacy alias. The implemented foundation instead +> **freezes the current FQCN as the stable logical id** — this is the strictly safer stepping stone: +> it requires *zero* change to saves, the generated TS, or the frontend, while still decoupling the +> persisted id from the class's package (the whole point). Switching to pretty ids later is a clean, +> separable follow-up: add `legacyTypes = { "" }` to each command's future `@CommandMeta`, +> emit the pretty id as the primary `@JsonSubTypes` name, and regenerate the frontend catalog from the +> annotations. That step *does* touch the frontend, so it is best done alongside the generator below. ## Goal @@ -192,28 +222,36 @@ CDI-discovery pattern this refactor leans on. Each phase is independently committable and keeps the build + coverage/parity tests green. -1. **Type-id infrastructure (no moves yet).** Add `@CommandMeta`/`@FieldMeta`, `CommandKind`, and the - `@JsonTypeIdResolver` with the legacy-FQCN alias table. Switch `Command` to it. Add a test that - every existing saved-FQCN still deserializes. *No class moves — purely additive.* -2. **Annotation backfill.** Add `@CommandMeta` (+ `@FieldMeta` where simple) to every command, - mirroring `command-catalog.ts` exactly. Add the generator; emit `command-catalog.generated.ts`; - switch the frontend to it (keeping the hand-written composite renderers). Delete the hand-built - catalog. `CommandsResource` derives from the annotations. -3. **VoiceMeeter consolidation** (git-mv): commands → `voicemeeter.command`; resolver → `voicemeeter`; - resource → `rest/voicemeeter`; enums → `voicemeeter.model`; icon → `IIconHandler`; settings record - local. Update NativeImageConfig + classPatterns glob. Verify saves load. -4. **OBS consolidation** (same shape). -5. **OSC + MQTT consolidation** (same shape). -6. **Cleanup + parity:** move deserializers next to their types; fix `docs/events.md` (add the missing - Discord events); confirm `ReflectionRegistrationCoverageTest`, `ProxyRegistrationCoverageTest`, - `NativeBuildArgsParityTest` green; build native if feasible. - -Git history is preserved throughout by using `git mv` for every relocation. +1. **✅ DONE — Type-id infrastructure (no moves).** `Command` switched to `Id.NAME` + `@JsonSubTypes` + with frozen-FQCN names; `CommandSubtypeRegistryTest` guards completeness + migration. Purely + additive — no behaviour change. (Implemented as frozen FQCN ids rather than the custom + `@JsonTypeIdResolver`/`@CommandMeta` form — see the note in *Implemented so far* above.) +2. **✅ DONE — VoiceMeeter commands → `voicemeeter.command`** (git-mv). `VoiceMeeterMuteResolver` also + moved into the module. classPatterns collapsed to the glob. +3. **✅ DONE — OBS commands → `obs.command`** (git-mv). +4. **✅ DONE — OSC + MQTT commands → `osc.command` / `mqtt.command`** (git-mv). +5. **TODO — Annotation-driven catalog + pretty ids.** Add `@CommandMeta` (+ `@FieldMeta` where simple) + to every command, mirroring `command-catalog.ts`. Build a generator that emits the command-level + metadata into a generated TS catalog; the frontend keeps its hand-written composite-field renderers + and live-source wiring. Switch the `@JsonSubTypes` primary names to pretty ids with the old FQCNs as + `legacyTypes` aliases, and regenerate the catalog. (This is the step that touches the frontend, so + verify with `tsc`/`npm run build`.) +6. **TODO — Remaining non-command consolidation.** `VoiceMeeterResource`/`ObsResource`/`OscResource` + → `rest/`; migrate the hardcoded `IconService` VoiceMeeter/OBS handlers to the + `IIconHandler` SPI (mirror `WaveLinkIconHandler`); per-feature settings records local; delete the + dead `CommandsResource` + `CommandType` DTO (and prune their `reachability-metadata.json` entries); + fix `docs/events.md` (add the missing Discord events). Confirm `ReflectionRegistrationCoverageTest`, + `ProxyRegistrationCoverageTest`, `NativeBuildArgsParityTest`; build native if feasible. + +Git history is preserved throughout by using `git mv` for every relocation. Phases 1–4 are committed +and green (one commit each). ## Risks & guards -- **Saved-profile breakage** — mitigated by the legacy-FQCN alias table + a deserialization test - seeded with the current FQCNs (phase 1, before any move). +- **Saved-profile breakage** — eliminated by freezing each command's persisted id to its historical + FQCN, so a package move never changes the wire string; `CommandSubtypeRegistryTest` deserializes + every registered id. (When phase 5 introduces pretty ids, the old FQCNs become `legacyTypes` + aliases and the same test must cover them.) - **Native-image 500s** — every concrete command + nested record (and `[]` form for `List`/`Set`) must stay registered; `ReflectionRegistrationCoverageTest` enforces it. The `File`/`FileSerializer` hazard on `ISndCtrl.RunningApplication` is pre-existing and unaffected. From e05cfe4b77343c239557af739d20fbca8c377f31 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 12:35:50 +0200 Subject: [PATCH 08/49] refactor(commands): decentralize the command type registry (no central list) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the central @JsonSubTypes block on Command with fully decentralized, per-package self-registration so adding a command (or a whole plugin) never touches anything outside its own feature package: - Command keeps @JsonTypeInfo(Id.NAME) but drops @JsonSubTypes entirely. - Each concrete command declares its own stable id via @JsonTypeName in its own file (the id stays the historical FQCN, so saves/TS/frontend are unchanged). - New CommandModule CDI SPI: each feature has an @ApplicationScoped module that lists only its own commands. CommandSubtypeRegistrar (an ObjectMapperCustomizer) collects them via @All and registers them with Jackson — mirroring the existing @All-discovered SPIs (MuteStateResolver, IIconHandler, DeviceProvider). - typescript-generator emits the _type union from @JsonTypeName (verified): the generated literal set is byte-identical, CommandUnion is no longer synthesised (control.component reverts its two casts to Command), WsEventUnion is unaffected. Tests: CommandSubtypeRegistryTest now guards the decentralized invariants (every command self-identifies with a unique @JsonTypeName; the CommandModule SPI covers exactly the concrete set; every id resolves). CommandSubtypeRegistrarTest checks the registrar wiring. CommandKeystrokeTest registers its subtype on its bare mapper. CoreCommandModule is a temporary single module for the still-shared core commands; the next commits split it into per-family modules. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../getpcpanel/commands/CommandModule.java | 18 +++ .../commands/CommandSubtypeRegistrar.java | 36 ++++++ .../getpcpanel/commands/command/Command.java | 105 ++-------------- .../commands/command/CommandAnalogBands.java | 2 + .../commands/command/CommandBrightness.java | 2 + .../commands/command/CommandEndProgram.java | 2 + .../commands/command/CommandHttpRequest.java | Bin 5233 -> 5355 bytes .../commands/command/CommandKeystroke.java | 2 + .../commands/command/CommandMedia.java | 2 + .../commands/command/CommandNoOp.java | 2 + .../commands/command/CommandProfile.java | 2 + .../commands/command/CommandRun.java | 2 + .../commands/command/CommandShortcut.java | 2 + .../CommandVolumeApplicationDeviceToggle.java | 2 + .../command/CommandVolumeDefaultDevice.java | 2 + .../CommandVolumeDefaultDeviceAdvanced.java | 2 + .../CommandVolumeDefaultDeviceToggle.java | 2 + ...mandVolumeDefaultDeviceToggleAdvanced.java | 2 + .../commands/command/CommandVolumeDevice.java | 2 + .../command/CommandVolumeDeviceMute.java | 2 + .../commands/command/CommandVolumeFocus.java | 2 + .../command/CommandVolumeFocusMute.java | 2 + .../command/CommandVolumeProcess.java | 2 + .../command/CommandVolumeProcessMute.java | 2 + .../commands/command/CoreCommandModule.java | 40 +++++++ .../command/CommandDiscordJoinVoice.java | 2 + .../command/CommandDiscordLeaveVoice.java | 2 + .../discord/command/CommandDiscordMute.java | 2 + .../command/CommandDiscordScreenShare.java | 2 + .../command/CommandDiscordSelfDeafen.java | 2 + .../CommandDiscordSelfInputVolume.java | 2 + .../command/CommandDiscordSelfMute.java | 2 + .../CommandDiscordSelfOutputVolume.java | 2 + .../command/CommandDiscordToggleVideo.java | 2 + .../command/CommandDiscordUserMute.java | 2 + .../command/CommandDiscordUserVolume.java | 2 + .../discord/command/CommandDiscordVolume.java | 2 + .../discord/command/DiscordCommandModule.java | 32 +++++ .../command/CommandHomeAssistantAction.java | 2 + .../command/CommandHomeAssistantValue.java | 2 + .../command/HomeAssistantCommandModule.java | 22 ++++ .../mqtt/command/CommandMqttPublish.java | 2 + .../mqtt/command/MqttCommandModule.java | 21 ++++ .../obs/command/CommandObsAction.java | 2 + .../obs/command/CommandObsMuteSource.java | 2 + .../obs/command/CommandObsSetScene.java | 2 + .../command/CommandObsSetSourceVolume.java | 2 + .../obs/command/ObsCommandModule.java | 24 ++++ .../osc/command/CommandOscSend.java | 2 + .../osc/command/OscCommandModule.java | 21 ++++ .../command/CommandVoiceMeeterAdvanced.java | 2 + .../CommandVoiceMeeterAdvancedButton.java | 2 + .../command/CommandVoiceMeeterBasic.java | 2 + .../CommandVoiceMeeterBasicButton.java | 2 + .../command/VoiceMeeterCommandModule.java | 24 ++++ .../CommandWaveLinkAddFocusToChannel.java | 2 + .../command/CommandWaveLinkChangeLevel.java | 2 + .../command/CommandWaveLinkChangeMute.java | 2 + .../command/CommandWaveLinkChannelEffect.java | 2 + .../command/CommandWaveLinkMainOutput.java | 2 + .../command/WaveLinkCommandModule.java | 25 ++++ .../src/app/models/generated/backend.types.ts | 33 ++++- .../app/pages/control/control.component.ts | 6 +- .../commands/CommandSubtypeRegistrarTest.java | 42 +++++++ .../command/CommandKeystrokeTest.java | 6 + .../command/CommandSubtypeRegistryTest.java | 113 +++++++++--------- 66 files changed, 510 insertions(+), 156 deletions(-) create mode 100644 src/main/java/com/getpcpanel/commands/CommandModule.java create mode 100644 src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java create mode 100644 src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java create mode 100644 src/main/java/com/getpcpanel/discord/command/DiscordCommandModule.java create mode 100644 src/main/java/com/getpcpanel/homeassistant/command/HomeAssistantCommandModule.java create mode 100644 src/main/java/com/getpcpanel/mqtt/command/MqttCommandModule.java create mode 100644 src/main/java/com/getpcpanel/obs/command/ObsCommandModule.java create mode 100644 src/main/java/com/getpcpanel/osc/command/OscCommandModule.java create mode 100644 src/main/java/com/getpcpanel/voicemeeter/command/VoiceMeeterCommandModule.java create mode 100644 src/main/java/com/getpcpanel/wavelink/command/WaveLinkCommandModule.java create mode 100644 src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java diff --git a/src/main/java/com/getpcpanel/commands/CommandModule.java b/src/main/java/com/getpcpanel/commands/CommandModule.java new file mode 100644 index 00000000..e631d4d6 --- /dev/null +++ b/src/main/java/com/getpcpanel/commands/CommandModule.java @@ -0,0 +1,18 @@ +package com.getpcpanel.commands; + +import java.util.List; + +import com.getpcpanel.commands.command.Command; + +/** + * A feature module's contribution of assignable command types. + * + *

Each module is an {@code @ApplicationScoped} bean that lives in its own feature package + * and lists only the {@link Command} subclasses it owns. {@link CommandSubtypeRegistrar} collects every + * {@code CommandModule} via CDI {@code @All} and registers the classes with Jackson (each class carries + * its own {@code @JsonTypeName}). Adding a command — or an entire new feature/plugin — therefore never + * touches anything outside the feature's package: drop the command class in, list it here, done. + */ +public interface CommandModule { + List> commandTypes(); +} diff --git a/src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java b/src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java new file mode 100644 index 00000000..1d12aecc --- /dev/null +++ b/src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java @@ -0,0 +1,36 @@ +package com.getpcpanel.commands; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.All; +import io.quarkus.jackson.ObjectMapperCustomizer; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.extern.log4j.Log4j2; + +/** + * Registers every feature module's command classes as Jackson polymorphic subtypes of + * {@link com.getpcpanel.commands.command.Command}. The subtypes are discovered entirely through the + * {@link CommandModule} CDI SPI ({@code @All}), so no central list of command classes exists anywhere — + * a new command/plugin only declares itself in its own package. Jackson reads each class's + * {@code @JsonTypeName} for the stable persisted id. + */ +@Log4j2 +@Singleton +public class CommandSubtypeRegistrar implements ObjectMapperCustomizer { + @Inject + @All + List modules; + + @Override + public void customize(ObjectMapper mapper) { + var registered = modules.stream() + .flatMap(m -> m.commandTypes().stream()) + .distinct() + .peek(mapper::registerSubtypes) + .count(); + log.debug("Registered {} command subtypes from {} module(s)", registered, modules.size()); + } +} diff --git a/src/main/java/com/getpcpanel/commands/command/Command.java b/src/main/java/com/getpcpanel/commands/command/Command.java index 1cac36f8..dc4312aa 100644 --- a/src/main/java/com/getpcpanel/commands/command/Command.java +++ b/src/main/java/com/getpcpanel/commands/command/Command.java @@ -2,39 +2,8 @@ import javax.annotation.Nullable; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.getpcpanel.discord.command.CommandDiscordJoinVoice; -import com.getpcpanel.discord.command.CommandDiscordLeaveVoice; -import com.getpcpanel.discord.command.CommandDiscordMute; -import com.getpcpanel.discord.command.CommandDiscordScreenShare; -import com.getpcpanel.discord.command.CommandDiscordSelfDeafen; -import com.getpcpanel.discord.command.CommandDiscordSelfInputVolume; -import com.getpcpanel.discord.command.CommandDiscordSelfMute; -import com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume; -import com.getpcpanel.discord.command.CommandDiscordToggleVideo; -import com.getpcpanel.discord.command.CommandDiscordUserMute; -import com.getpcpanel.discord.command.CommandDiscordUserVolume; -import com.getpcpanel.discord.command.CommandDiscordVolume; import com.getpcpanel.hid.DialValue; -import com.getpcpanel.homeassistant.command.CommandHomeAssistantAction; -import com.getpcpanel.homeassistant.command.CommandHomeAssistantValue; -import com.getpcpanel.mqtt.command.CommandMqttPublish; -import com.getpcpanel.osc.command.CommandOscSend; -import com.getpcpanel.obs.command.CommandObsAction; -import com.getpcpanel.obs.command.CommandObsMuteSource; -import com.getpcpanel.obs.command.CommandObsSetScene; -import com.getpcpanel.obs.command.CommandObsSetSourceVolume; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; -import com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel; -import com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel; -import com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute; -import com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect; -import com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput; import lombok.ToString; import lombok.extern.log4j.Log4j2; @@ -42,76 +11,22 @@ /** * Base type for every assignable action. * - *

Persisted type id (the {@code _type} discriminator). The polymorphism uses - * {@link JsonTypeInfo.Id#NAME} with an explicit {@link JsonSubTypes} registry rather than - * {@code Id.CLASS}. Each subtype's {@code name} is a stable, location-independent string - * that happens to equal the class's historical fully-qualified name — so saved {@code profiles.json} - * files, the generated TypeScript {@code _type} union, and the frontend command catalog are all - * unchanged by this scheme, yet a command class is now free to move to its own feature package - * (e.g. {@code com.getpcpanel.voicemeeter.command}) without breaking any of them: only the - * {@code @Type(value = …)} reference moves, the {@code name} stays frozen. + *

Polymorphism is fully decentralized. The discriminator uses {@link JsonTypeInfo.Id#NAME}, + * and each concrete command declares its own stable id with a {@code @JsonTypeName} in its own + * package — there is deliberately no central {@code @JsonSubTypes} list here, so adding a command + * (or a whole new feature module) never touches this class. The id is a stable, location-independent + * string equal to the command's historical fully-qualified name, so saved {@code profiles.json}, the + * generated TS {@code _type} union, and the frontend catalog are unaffected by where the class lives. * - *

This is the seam that lets each integration own its command classes. To add a command: create - * the class and register one {@code @Type} line here. (A future build-time generator may derive this - * list and the frontend catalog from per-command {@code @CommandMeta} annotations — see - * {@code docs/feature-module-structure.md}.) + *

Each feature module contributes its command classes through the {@link com.getpcpanel.commands.CommandModule} + * CDI SPI; {@link com.getpcpanel.commands.CommandSubtypeRegistrar} collects them via {@code @All} and + * registers them with Jackson for deserialization. {@code typescript-generator} discovers the subtypes + * from the {@code **.command.**} class scan and reads each {@code @JsonTypeName}. */ @Log4j2 @ToString @SuppressWarnings("InstanceofThis") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "_type") -@JsonSubTypes({ - @Type(value = CommandAnalogBands.class, name = "com.getpcpanel.commands.command.CommandAnalogBands"), - @Type(value = CommandBrightness.class, name = "com.getpcpanel.commands.command.CommandBrightness"), - @Type(value = CommandEndProgram.class, name = "com.getpcpanel.commands.command.CommandEndProgram"), - @Type(value = CommandHttpRequest.class, name = "com.getpcpanel.commands.command.CommandHttpRequest"), - @Type(value = CommandKeystroke.class, name = "com.getpcpanel.commands.command.CommandKeystroke"), - @Type(value = CommandMedia.class, name = "com.getpcpanel.commands.command.CommandMedia"), - @Type(value = CommandMqttPublish.class, name = "com.getpcpanel.commands.command.CommandMqttPublish"), - @Type(value = CommandNoOp.class, name = "com.getpcpanel.commands.command.CommandNoOp"), - @Type(value = CommandObsAction.class, name = "com.getpcpanel.commands.command.CommandObsAction"), - @Type(value = CommandObsMuteSource.class, name = "com.getpcpanel.commands.command.CommandObsMuteSource"), - @Type(value = CommandObsSetScene.class, name = "com.getpcpanel.commands.command.CommandObsSetScene"), - @Type(value = CommandObsSetSourceVolume.class, name = "com.getpcpanel.commands.command.CommandObsSetSourceVolume"), - @Type(value = CommandOscSend.class, name = "com.getpcpanel.commands.command.CommandOscSend"), - @Type(value = CommandProfile.class, name = "com.getpcpanel.commands.command.CommandProfile"), - @Type(value = CommandRun.class, name = "com.getpcpanel.commands.command.CommandRun"), - @Type(value = CommandShortcut.class, name = "com.getpcpanel.commands.command.CommandShortcut"), - @Type(value = CommandVoiceMeeterAdvanced.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced"), - @Type(value = CommandVoiceMeeterAdvancedButton.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton"), - @Type(value = CommandVoiceMeeterBasic.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterBasic"), - @Type(value = CommandVoiceMeeterBasicButton.class, name = "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton"), - @Type(value = CommandVolumeApplicationDeviceToggle.class, name = "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle"), - @Type(value = CommandVolumeDefaultDevice.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDevice"), - @Type(value = CommandVolumeDefaultDeviceAdvanced.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced"), - @Type(value = CommandVolumeDefaultDeviceToggle.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle"), - @Type(value = CommandVolumeDefaultDeviceToggleAdvanced.class, name = "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced"), - @Type(value = CommandVolumeDevice.class, name = "com.getpcpanel.commands.command.CommandVolumeDevice"), - @Type(value = CommandVolumeDeviceMute.class, name = "com.getpcpanel.commands.command.CommandVolumeDeviceMute"), - @Type(value = CommandVolumeFocus.class, name = "com.getpcpanel.commands.command.CommandVolumeFocus"), - @Type(value = CommandVolumeFocusMute.class, name = "com.getpcpanel.commands.command.CommandVolumeFocusMute"), - @Type(value = CommandVolumeProcess.class, name = "com.getpcpanel.commands.command.CommandVolumeProcess"), - @Type(value = CommandVolumeProcessMute.class, name = "com.getpcpanel.commands.command.CommandVolumeProcessMute"), - @Type(value = CommandDiscordJoinVoice.class, name = "com.getpcpanel.discord.command.CommandDiscordJoinVoice"), - @Type(value = CommandDiscordLeaveVoice.class, name = "com.getpcpanel.discord.command.CommandDiscordLeaveVoice"), - @Type(value = CommandDiscordMute.class, name = "com.getpcpanel.discord.command.CommandDiscordMute"), - @Type(value = CommandDiscordScreenShare.class, name = "com.getpcpanel.discord.command.CommandDiscordScreenShare"), - @Type(value = CommandDiscordSelfDeafen.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfDeafen"), - @Type(value = CommandDiscordSelfInputVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume"), - @Type(value = CommandDiscordSelfMute.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfMute"), - @Type(value = CommandDiscordSelfOutputVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume"), - @Type(value = CommandDiscordToggleVideo.class, name = "com.getpcpanel.discord.command.CommandDiscordToggleVideo"), - @Type(value = CommandDiscordUserMute.class, name = "com.getpcpanel.discord.command.CommandDiscordUserMute"), - @Type(value = CommandDiscordUserVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordUserVolume"), - @Type(value = CommandDiscordVolume.class, name = "com.getpcpanel.discord.command.CommandDiscordVolume"), - @Type(value = CommandHomeAssistantAction.class, name = "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction"), - @Type(value = CommandHomeAssistantValue.class, name = "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue"), - @Type(value = CommandWaveLinkAddFocusToChannel.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel"), - @Type(value = CommandWaveLinkChangeLevel.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel"), - @Type(value = CommandWaveLinkChangeMute.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute"), - @Type(value = CommandWaveLinkChannelEffect.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect"), - @Type(value = CommandWaveLinkMainOutput.class, name = "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"), -}) public abstract class Command { public Runnable toRunnable(boolean initial, String deviceId, @Nullable DialValue vol) { // A command that is both a dial and a button action (e.g. the generic HTTP/MQTT/OSC outputs) diff --git a/src/main/java/com/getpcpanel/commands/command/CommandAnalogBands.java b/src/main/java/com/getpcpanel/commands/command/CommandAnalogBands.java index a7b272c0..682c9e0a 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandAnalogBands.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandAnalogBands.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.analogbands.AnalogBandColorService; @@ -35,6 +36,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandAnalogBands") public class CommandAnalogBands extends Command implements DialAction { private static final int MAX_RAW = 255; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandBrightness.java b/src/main/java/com/getpcpanel/commands/command/CommandBrightness.java index 56067b6a..266b2003 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandBrightness.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandBrightness.java @@ -1,6 +1,7 @@ package com.getpcpanel.commands.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.hid.DeviceHolder; @@ -12,6 +13,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandBrightness") public class CommandBrightness extends Command implements DialAction { private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java b/src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java index ba6d5df4..8c22b64a 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java @@ -1,6 +1,7 @@ package com.getpcpanel.commands.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.util.IPlatformCommand; @@ -12,6 +13,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandEndProgram") public class CommandEndProgram extends Command implements ButtonAction { private static final Runtime rt = Runtime.getRuntime(); private final boolean specific; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandHttpRequest.java b/src/main/java/com/getpcpanel/commands/command/CommandHttpRequest.java index f899194fd1110e142812203b36c039d817fc5213..196b56e052ac05a67d1b0476bbd034f9670d60e6 100644 GIT binary patch delta 66 zcmeyU@mh1kHb#z+%7RqC#N5=)9gM=v{0?5l`FSuQ4W)@3@*;Z9`MJ4?c_|(xB?UpL Qg{7&*B}$uTvfSVY04!)20{{R3 delta 17 ZcmaE@`B7uTHpb0w7@sn27G=H04**Y02hIQh diff --git a/src/main/java/com/getpcpanel/commands/command/CommandKeystroke.java b/src/main/java/com/getpcpanel/commands/command/CommandKeystroke.java index f05e93fc..753c0283 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandKeystroke.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandKeystroke.java @@ -3,6 +3,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.KeyMacro; @@ -23,6 +24,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandKeystroke") public class CommandKeystroke extends Command implements ButtonAction { public enum KeystrokeType { /** Single key combination (modifiers + one key). */ diff --git a/src/main/java/com/getpcpanel/commands/command/CommandMedia.java b/src/main/java/com/getpcpanel/commands/command/CommandMedia.java index 6f0a8967..aa130ecc 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandMedia.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandMedia.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.SystemUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.util.OsxMediaControl; @@ -31,6 +32,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandMedia") public class CommandMedia extends Command implements ButtonAction { private static final int WM_APPCOMMAND = 0x0319; private final VolumeButton button; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandNoOp.java b/src/main/java/com/getpcpanel/commands/command/CommandNoOp.java index ee7cf80e..69e9db1f 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandNoOp.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandNoOp.java @@ -1,8 +1,10 @@ package com.getpcpanel.commands.command; +import com.fasterxml.jackson.annotation.JsonTypeName; import lombok.ToString; @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandNoOp") public final class CommandNoOp extends Command implements ButtonAction, DialAction { public static final CommandNoOp NOOP = new CommandNoOp(); diff --git a/src/main/java/com/getpcpanel/commands/command/CommandProfile.java b/src/main/java/com/getpcpanel/commands/command/CommandProfile.java index 0b98b04a..a0720311 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandProfile.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandProfile.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.hid.DeviceHolder; import com.getpcpanel.util.CdiHelper; @@ -13,6 +14,7 @@ import lombok.ToString; @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandProfile") public class CommandProfile extends Command implements DeviceAction { @Getter private final String profile; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandRun.java b/src/main/java/com/getpcpanel/commands/command/CommandRun.java index 3b49932c..400746b5 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandRun.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandRun.java @@ -1,6 +1,7 @@ package com.getpcpanel.commands.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.util.IPlatformCommand; @@ -12,6 +13,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandRun") public class CommandRun extends Command implements ButtonAction { private final String command; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandShortcut.java b/src/main/java/com/getpcpanel/commands/command/CommandShortcut.java index 31d77282..ecfa4192 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandShortcut.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandShortcut.java @@ -1,6 +1,7 @@ package com.getpcpanel.commands.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.util.IPlatformCommand; @@ -12,6 +13,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandShortcut") public class CommandShortcut extends Command implements ButtonAction { private static final Runtime rt = Runtime.getRuntime(); private final String shortcut; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeApplicationDeviceToggle.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeApplicationDeviceToggle.java index 48cd2bc7..b03075bd 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeApplicationDeviceToggle.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeApplicationDeviceToggle.java @@ -10,6 +10,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.DeviceSet; import com.getpcpanel.cpp.AudioDevice; @@ -24,6 +25,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle") public class CommandVolumeApplicationDeviceToggle extends CommandVolume implements ButtonAction { private final List processes; private final boolean followFocus; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java index acdbc1bd..edc213aa 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @@ -10,6 +11,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDefaultDevice") public class CommandVolumeDefaultDevice extends CommandVolume implements ButtonAction { private final String deviceId; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java index 841192a6..52095f93 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java @@ -1,5 +1,6 @@ package com.getpcpanel.commands.command; +import com.fasterxml.jackson.annotation.JsonTypeName; import javax.annotation.Nullable; import com.getpcpanel.util.CdiHelper; @@ -20,6 +21,7 @@ @Jacksonized @AllArgsConstructor @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced") public class CommandVolumeDefaultDeviceAdvanced extends CommandVolume implements ButtonAction { private final String name; private final String mediaPb; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggle.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggle.java index 45e04ff6..0ef1016b 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggle.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggle.java @@ -8,6 +8,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.AudioDevice; import com.getpcpanel.cpp.DataFlow; @@ -20,6 +21,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle") public class CommandVolumeDefaultDeviceToggle extends CommandVolume implements ButtonAction { private final List devices; private int currentIdx; // Used as a fallback for when the current idx cannot be found diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggleAdvanced.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggleAdvanced.java index 17357927..0e094a48 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggleAdvanced.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggleAdvanced.java @@ -10,6 +10,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.DeviceSet; import com.getpcpanel.cpp.AudioDevice; @@ -26,6 +27,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced") public class CommandVolumeDefaultDeviceToggleAdvanced extends CommandVolume implements ButtonAction { private final List devices; private int currentIdx = -1; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java index b8f218ad..ffb1ac7c 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java @@ -1,6 +1,7 @@ package com.getpcpanel.commands.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; @@ -9,6 +10,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDevice") public class CommandVolumeDevice extends CommandVolume implements DialAction { private final String deviceId; private final boolean unMuteOnVolumeChange; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDeviceMute.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDeviceMute.java index 191f8fc1..dbe798e3 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDeviceMute.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeDeviceMute.java @@ -1,6 +1,7 @@ package com.getpcpanel.commands.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; @@ -9,6 +10,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDeviceMute") public class CommandVolumeDeviceMute extends CommandVolume implements ButtonAction { private final String deviceId; private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocus.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocus.java index 29c77d35..80e30203 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocus.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocus.java @@ -1,6 +1,7 @@ package com.getpcpanel.commands.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.volume.VolumeCoordinatorService; import com.getpcpanel.util.CdiHelper; @@ -10,6 +11,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeFocus") public class CommandVolumeFocus extends CommandVolume implements DialAction { private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java index bf71cc3f..b5de7136 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java @@ -3,6 +3,7 @@ import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.getpcpanel.cpp.MuteType; import lombok.Getter; @@ -10,6 +11,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeFocusMute") public class CommandVolumeFocusMute extends CommandVolume implements ButtonAction { private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcess.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcess.java index 598b0046..ba78ee50 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcess.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcess.java @@ -4,6 +4,7 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; @@ -12,6 +13,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeProcess") public class CommandVolumeProcess extends CommandVolume implements DialAction { private final List processName; private final String device; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcessMute.java b/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcessMute.java index 20d6b717..f0300f2d 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcessMute.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcessMute.java @@ -3,6 +3,7 @@ import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.getpcpanel.cpp.MuteType; import lombok.Getter; @@ -10,6 +11,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeProcessMute") public class CommandVolumeProcessMute extends CommandVolume implements ButtonAction { private final Set processName; private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java b/src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java new file mode 100644 index 00000000..9e330018 --- /dev/null +++ b/src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java @@ -0,0 +1,40 @@ +package com.getpcpanel.commands.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Core feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class CoreCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandAnalogBands.class, + CommandBrightness.class, + CommandEndProgram.class, + CommandHttpRequest.class, + CommandKeystroke.class, + CommandMedia.class, + CommandNoOp.class, + CommandProfile.class, + CommandRun.class, + CommandShortcut.class, + CommandVolumeApplicationDeviceToggle.class, + CommandVolumeDefaultDevice.class, + CommandVolumeDefaultDeviceAdvanced.class, + CommandVolumeDefaultDeviceToggle.class, + CommandVolumeDefaultDeviceToggleAdvanced.class, + CommandVolumeDevice.class, + CommandVolumeDeviceMute.class, + CommandVolumeFocus.class, + CommandVolumeFocusMute.class, + CommandVolumeProcess.class, + CommandVolumeProcessMute.class); + } +} diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java index dd192953..9295c5a1 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -16,6 +17,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordJoinVoice") public final class CommandDiscordJoinVoice extends CommandDiscord implements ButtonAction { @Nullable private final String channelId; /** Cosmetic: the channel's name at configure time, for the button label (the id is what we join). */ diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java index 2296c8cb..aaedf6c0 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -14,6 +15,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordLeaveVoice") public final class CommandDiscordLeaveVoice extends CommandDiscord implements ButtonAction { @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java index 41ae4b38..dc1dc067 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -21,6 +22,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordMute") public final class CommandDiscordMute extends CommandDiscord implements ButtonAction { public static final String SELF = "self"; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java index 306f6547..bfe256f1 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java @@ -9,6 +9,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.discord.DiscordService; @@ -27,6 +28,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordScreenShare") public final class CommandDiscordScreenShare extends CommandDiscord implements ButtonAction { public enum Mode { SCREEN, PROCESS, FOCUS } diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java index 1045a55b..cb6b2af9 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -16,6 +17,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordSelfDeafen") public final class CommandDiscordSelfDeafen extends CommandDiscord implements ButtonAction { private final MuteType muteType; @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfInputVolume.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfInputVolume.java index b71cedcb..600c41ee 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfInputVolume.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfInputVolume.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; @@ -14,6 +15,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordSelfInputVolume") public final class CommandDiscordSelfInputVolume extends CommandDiscord implements DialAction { @Nullable private final DialCommandParams dialParams; private final boolean unmuteOnChange; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfMute.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfMute.java index 73cf8d1d..ad30aece 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfMute.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfMute.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -16,6 +17,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordSelfMute") public final class CommandDiscordSelfMute extends CommandDiscord implements ButtonAction { private final MuteType muteType; @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfOutputVolume.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfOutputVolume.java index f1b5f0aa..17b24862 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfOutputVolume.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfOutputVolume.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; @@ -14,6 +15,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume") public final class CommandDiscordSelfOutputVolume extends CommandDiscord implements DialAction { @Nullable private final DialCommandParams dialParams; private final boolean undeafenOnChange; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java index a0fd77e2..79db504e 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java @@ -3,6 +3,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -14,6 +15,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordToggleVideo") public final class CommandDiscordToggleVideo extends CommandDiscord implements ButtonAction { @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserMute.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserMute.java index 316c0cdd..c573325e 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserMute.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserMute.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -18,6 +19,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordUserMute") public final class CommandDiscordUserMute extends CommandDiscord implements ButtonAction { @Nullable private final String username; private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserVolume.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserVolume.java index 7387162d..7c78578d 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserVolume.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordUserVolume.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; @@ -16,6 +17,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordUserVolume") public final class CommandDiscordUserVolume extends CommandDiscord implements DialAction { @Nullable private final String username; @Nullable private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java index d25b76da..b34d0da9 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; @@ -21,6 +22,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordVolume") public final class CommandDiscordVolume extends CommandDiscord implements DialAction { public static final String MIC = "mic"; public static final String OUTPUT = "output"; diff --git a/src/main/java/com/getpcpanel/discord/command/DiscordCommandModule.java b/src/main/java/com/getpcpanel/discord/command/DiscordCommandModule.java new file mode 100644 index 00000000..838e6d69 --- /dev/null +++ b/src/main/java/com/getpcpanel/discord/command/DiscordCommandModule.java @@ -0,0 +1,32 @@ +package com.getpcpanel.discord.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Discord feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class DiscordCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandDiscordJoinVoice.class, + CommandDiscordLeaveVoice.class, + CommandDiscordMute.class, + CommandDiscordScreenShare.class, + CommandDiscordSelfDeafen.class, + CommandDiscordSelfInputVolume.class, + CommandDiscordSelfMute.class, + CommandDiscordSelfOutputVolume.class, + CommandDiscordToggleVideo.class, + CommandDiscordUserMute.class, + CommandDiscordUserVolume.class, + CommandDiscordVolume.class); + } +} diff --git a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java index 9b5b00cd..927d2978 100644 --- a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java +++ b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -19,6 +20,7 @@ */ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.homeassistant.command.CommandHomeAssistantAction") public class CommandHomeAssistantAction extends CommandHomeAssistant implements ButtonAction { private final String action; @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java index 5f473361..76e8c7ee 100644 --- a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java +++ b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.ValueInterpolator; @@ -22,6 +23,7 @@ */ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.homeassistant.command.CommandHomeAssistantValue") public class CommandHomeAssistantValue extends CommandHomeAssistant implements DialAction { private final String action; @Nullable private final Double min; diff --git a/src/main/java/com/getpcpanel/homeassistant/command/HomeAssistantCommandModule.java b/src/main/java/com/getpcpanel/homeassistant/command/HomeAssistantCommandModule.java new file mode 100644 index 00000000..5f16af55 --- /dev/null +++ b/src/main/java/com/getpcpanel/homeassistant/command/HomeAssistantCommandModule.java @@ -0,0 +1,22 @@ +package com.getpcpanel.homeassistant.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * HomeAssistant feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class HomeAssistantCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandHomeAssistantAction.class, + CommandHomeAssistantValue.class); + } +} diff --git a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java index 25361a34..b8239ebd 100644 --- a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java +++ b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.mqtt.MqttService; import com.getpcpanel.commands.command.CommandValueOutput; @@ -21,6 +22,7 @@ */ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandMqttPublish") public class CommandMqttPublish extends CommandValueOutput { private final String topic; private final String payload; diff --git a/src/main/java/com/getpcpanel/mqtt/command/MqttCommandModule.java b/src/main/java/com/getpcpanel/mqtt/command/MqttCommandModule.java new file mode 100644 index 00000000..f0baa713 --- /dev/null +++ b/src/main/java/com/getpcpanel/mqtt/command/MqttCommandModule.java @@ -0,0 +1,21 @@ +package com.getpcpanel.mqtt.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Mqtt feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class MqttCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandMqttPublish.class); + } +} diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java index bcc1b068..f6fc0e29 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java @@ -1,6 +1,7 @@ package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.obs.OBS; import com.getpcpanel.commands.command.ButtonAction; @@ -15,6 +16,7 @@ */ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandObsAction") public class CommandObsAction extends CommandObs implements ButtonAction { private final ObsActionType action; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java index e5ffa4ac..180021fc 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java @@ -1,6 +1,7 @@ package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; import com.getpcpanel.obs.OBS; @@ -12,6 +13,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandObsMuteSource") public class CommandObsMuteSource extends CommandObs implements ButtonAction { private final String source; private final MuteType type; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java index 355fe04b..e1c27cac 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java @@ -1,6 +1,7 @@ package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; @@ -11,6 +12,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandObsSetScene") public class CommandObsSetScene extends CommandObs implements ButtonAction { private final String scene; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java index ad09eb30..f5a259da 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java @@ -1,6 +1,7 @@ package com.getpcpanel.obs.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.CdiHelper; @@ -11,6 +12,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandObsSetSourceVolume") public class CommandObsSetSourceVolume extends CommandObs implements DialAction { private final String sourceName; private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/obs/command/ObsCommandModule.java b/src/main/java/com/getpcpanel/obs/command/ObsCommandModule.java new file mode 100644 index 00000000..4e22e43c --- /dev/null +++ b/src/main/java/com/getpcpanel/obs/command/ObsCommandModule.java @@ -0,0 +1,24 @@ +package com.getpcpanel.obs.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Obs feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class ObsCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandObsAction.class, + CommandObsMuteSource.class, + CommandObsSetScene.class, + CommandObsSetSourceVolume.class); + } +} diff --git a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java index 4f7199d3..7f8d9bcf 100644 --- a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java +++ b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.osc.OSCService; import com.getpcpanel.commands.command.CommandValueOutput; @@ -20,6 +21,7 @@ */ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandOscSend") public class CommandOscSend extends CommandValueOutput { private final String address; diff --git a/src/main/java/com/getpcpanel/osc/command/OscCommandModule.java b/src/main/java/com/getpcpanel/osc/command/OscCommandModule.java new file mode 100644 index 00000000..244a00cd --- /dev/null +++ b/src/main/java/com/getpcpanel/osc/command/OscCommandModule.java @@ -0,0 +1,21 @@ +package com.getpcpanel.osc.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Osc feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class OscCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandOscSend.class); + } +} diff --git a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java index 065ce995..8951c6fa 100644 --- a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java @@ -1,6 +1,7 @@ package com.getpcpanel.voicemeeter.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.CdiHelper; @@ -11,6 +12,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced") public class CommandVoiceMeeterAdvanced extends CommandVoiceMeeter implements DialAction { private final String fullParam; private final Voicemeeter.DialControlMode ct; diff --git a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java index 0dd8320d..1b24987b 100644 --- a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; @@ -15,6 +16,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton") public class CommandVoiceMeeterAdvancedButton extends CommandVoiceMeeter implements ButtonAction { private final String fullParam; private final Voicemeeter.ButtonControlMode bt; diff --git a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasic.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasic.java index 0cb71b03..66901699 100644 --- a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasic.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasic.java @@ -1,6 +1,7 @@ package com.getpcpanel.voicemeeter.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.CdiHelper; @@ -11,6 +12,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVoiceMeeterBasic") public class CommandVoiceMeeterBasic extends CommandVoiceMeeter implements DialAction { private final Voicemeeter.ControlType ct; private final int index; diff --git a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasicButton.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasicButton.java index 2dbbbafc..53f611b1 100644 --- a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasicButton.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterBasicButton.java @@ -1,6 +1,7 @@ package com.getpcpanel.voicemeeter.command; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; @@ -11,6 +12,7 @@ @Getter @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton") public class CommandVoiceMeeterBasicButton extends CommandVoiceMeeter implements ButtonAction { private final Voicemeeter.ControlType ct; private final int index; diff --git a/src/main/java/com/getpcpanel/voicemeeter/command/VoiceMeeterCommandModule.java b/src/main/java/com/getpcpanel/voicemeeter/command/VoiceMeeterCommandModule.java new file mode 100644 index 00000000..9a6c3d86 --- /dev/null +++ b/src/main/java/com/getpcpanel/voicemeeter/command/VoiceMeeterCommandModule.java @@ -0,0 +1,24 @@ +package com.getpcpanel.voicemeeter.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * VoiceMeeter feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class VoiceMeeterCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandVoiceMeeterAdvanced.class, + CommandVoiceMeeterAdvancedButton.class, + CommandVoiceMeeterBasic.class, + CommandVoiceMeeterBasicButton.class); + } +} diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java index f8832f3b..2fa16da1 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java @@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -15,6 +16,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel") public final class CommandWaveLinkAddFocusToChannel extends CommandWaveLink implements ButtonAction { @Nullable private final String id; @Nullable private final String name; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java index f537a9f5..39410a96 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; @@ -15,6 +16,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel") public final class CommandWaveLinkChangeLevel extends CommandWaveLinkChange implements DialAction { @Nullable private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java index ab6b6691..91612a07 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -17,6 +18,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute") public final class CommandWaveLinkChangeMute extends CommandWaveLinkChange implements ButtonAction { private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChannelEffect.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChannelEffect.java index 394c151f..89c98146 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChannelEffect.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChannelEffect.java @@ -6,6 +6,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -19,6 +20,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect") public final class CommandWaveLinkChannelEffect extends CommandWaveLink implements ButtonAction { @Nullable private final String channelId; @Nullable private final String channelName; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java index 04079b64..b931bbe4 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -15,6 +16,7 @@ @Getter @Log4j2 @ToString(callSuper = true) +@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput") public final class CommandWaveLinkMainOutput extends CommandWaveLink implements ButtonAction { @Nullable private final String id; @Nullable private final String name; diff --git a/src/main/java/com/getpcpanel/wavelink/command/WaveLinkCommandModule.java b/src/main/java/com/getpcpanel/wavelink/command/WaveLinkCommandModule.java new file mode 100644 index 00000000..5976f90e --- /dev/null +++ b/src/main/java/com/getpcpanel/wavelink/command/WaveLinkCommandModule.java @@ -0,0 +1,25 @@ +package com.getpcpanel.wavelink.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * WaveLink feature module: registers its own command types via the + * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class WaveLinkCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandWaveLinkAddFocusToChannel.class, + CommandWaveLinkChangeLevel.class, + CommandWaveLinkChangeMute.class, + CommandWaveLinkChannelEffect.class, + CommandWaveLinkMainOutput.class); + } +} diff --git a/src/main/webui/src/app/models/generated/backend.types.ts b/src/main/webui/src/app/models/generated/backend.types.ts index c4be252d..2cf8ab9d 100644 --- a/src/main/webui/src/app/models/generated/backend.types.ts +++ b/src/main/webui/src/app/models/generated/backend.types.ts @@ -166,6 +166,9 @@ export interface CommandMedia extends Command, ButtonAction { spotify: boolean; } +export interface CommandModule { +} + export interface CommandMqttPublish extends CommandValueOutput { _type: "com.getpcpanel.commands.command.CommandMqttPublish"; payload: string; @@ -217,7 +220,7 @@ export interface CommandRun extends Command, ButtonAction { } export interface Commands { - commands: CommandUnion[]; + commands: Command[]; type?: CommandsType; } @@ -397,6 +400,9 @@ export interface ControlAssignmentsUpdateDto { releaseButton?: Commands; } +export interface CoreCommandModule extends CommandModule { +} + export interface DeviceAction { } @@ -498,6 +504,9 @@ export interface DiscordAuth { userName?: string; } +export interface DiscordCommandModule extends CommandModule { +} + export interface DiscordSeenUser { displayName: string; id: string; @@ -541,7 +550,7 @@ export interface FocusVolumeOverride { } export interface FocusVolumeTarget { - command: CommandUnion; + command: Command; } export interface GlobalLightingSpec { @@ -552,6 +561,9 @@ export interface GlobalLightingSpec { supportedModes: string[]; } +export interface HomeAssistantCommandModule extends CommandModule { +} + export interface HomeAssistantServer { id: string; name: string; @@ -623,6 +635,9 @@ export interface MidiDeviceDto { name: string; } +export interface MqttCommandModule extends CommandModule { +} + export interface MqttSettings { baseTopic: string; enabled: boolean; @@ -634,6 +649,9 @@ export interface MqttSettings { username: string; } +export interface ObsCommandModule extends CommandModule { +} + export interface OnboardingDto { changelogUrl: string; intent: string; @@ -647,6 +665,9 @@ export interface OSCBinding { toggle: boolean; } +export interface OscCommandModule extends CommandModule { +} + export interface OSCConnectionInfo { host: string; port: number; @@ -766,6 +787,9 @@ export interface SingleSliderLightingConfig { muteOverrideDeviceOrFollow: string; } +export interface VoiceMeeterCommandModule extends CommandModule { +} + export interface WaveLinkAppDto { id: string; name: string; @@ -781,6 +805,9 @@ export interface WaveLinkChannelDto { type?: string; } +export interface WaveLinkCommandModule extends CommandModule { +} + export interface WaveLinkEffectDto { id: string; isEnabled: boolean; @@ -915,8 +942,6 @@ export type CommandCategory = "standard" | "voicemeeter" | "obs" | "wavelink"; export type CommandsType = "allAtOnce" | "sequential"; -export type CommandUnion = CommandAnalogBands | CommandBrightness | CommandEndProgram | CommandHttpRequest | CommandKeystroke | CommandMedia | CommandMqttPublish | CommandNoOp | CommandObsAction | CommandObsMuteSource | CommandObsSetScene | CommandObsSetSourceVolume | CommandOscSend | CommandProfile | CommandRun | CommandShortcut | CommandVoiceMeeterAdvanced | CommandVoiceMeeterAdvancedButton | CommandVoiceMeeterBasic | CommandVoiceMeeterBasicButton | CommandVolumeApplicationDeviceToggle | CommandVolumeDefaultDevice | CommandVolumeDefaultDeviceAdvanced | CommandVolumeDefaultDeviceToggle | CommandVolumeDefaultDeviceToggleAdvanced | CommandVolumeDevice | CommandVolumeDeviceMute | CommandVolumeFocus | CommandVolumeFocusMute | CommandVolumeProcess | CommandVolumeProcessMute | CommandDiscordJoinVoice | CommandDiscordLeaveVoice | CommandDiscordMute | CommandDiscordScreenShare | CommandDiscordSelfDeafen | CommandDiscordSelfInputVolume | CommandDiscordSelfMute | CommandDiscordSelfOutputVolume | CommandDiscordToggleVideo | CommandDiscordUserMute | CommandDiscordUserVolume | CommandDiscordVolume | CommandHomeAssistantAction | CommandHomeAssistantValue | CommandWaveLinkAddFocusToChannel | CommandWaveLinkChangeLevel | CommandWaveLinkChangeMute | CommandWaveLinkChannelEffect | CommandWaveLinkMainOutput; - export type ControlType = "STRIP" | "BUS"; export type DeviceType = "PCPANEL_RGB" | "PCPANEL_MINI" | "PCPANEL_PRO"; diff --git a/src/main/webui/src/app/pages/control/control.component.ts b/src/main/webui/src/app/pages/control/control.component.ts index cd772845..cdd8fca6 100644 --- a/src/main/webui/src/app/pages/control/control.component.ts +++ b/src/main/webui/src/app/pages/control/control.component.ts @@ -6,7 +6,7 @@ import { DeviceStateService } from '../../services/device-state.service'; import { DeviceService } from '../../services/device.service'; import { IntegrationDataService } from '../../features/commands/integration-data.service'; import { DeviceCapabilitiesService } from '../../services/device-capabilities.service'; -import { Command, CommandUnion, Commands, KnobSetting } from '../../models/generated/backend.types'; +import { Command, Commands, KnobSetting } from '../../models/generated/backend.types'; import { AppPickerComponent, IconComponent, ToggleComponent, ToastService, } from '../../ui'; @@ -169,7 +169,7 @@ export class ControlComponent { // ── mutations ──────────────────────────────────────────────────────────── addCommand(def: CommandDef): void { const sig = this.currentSlotSignal(); - const next = { ...sig(), commands: [...sig().commands, def.buildEmpty() as CommandUnion] }; + const next = { ...sig(), commands: [...sig().commands, def.buildEmpty() as Command] }; sig.set(next); this.expanded.set(next.commands.length - 1); this.save(); @@ -184,7 +184,7 @@ export class ControlComponent { updateCommand(i: number, cmd: Record): void { const sig = this.currentSlotSignal(); const cmds = [...sig().commands]; - cmds[i] = cmd as unknown as CommandUnion; + cmds[i] = cmd as unknown as Command; sig.set({ ...sig(), commands: cmds }); this.save(); } diff --git a/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java b/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java new file mode 100644 index 00000000..f1b24afc --- /dev/null +++ b/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java @@ -0,0 +1,42 @@ +package com.getpcpanel.commands; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.CommandBrightness; +import com.getpcpanel.commands.command.CoreCommandModule; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; +import com.getpcpanel.voicemeeter.command.VoiceMeeterCommandModule; + +/** + * Validates that {@link CommandSubtypeRegistrar} turns the {@link CommandModule} contributions into + * working Jackson polymorphic registration — i.e. a command contributed by a feature module (here a + * core one and an integration one) deserializes by its {@code @JsonTypeName} id once the registrar has + * customized the mapper. The {@code @All} injection that feeds {@code modules} at runtime is a standard + * Quarkus mechanism already used elsewhere in the app, so it is exercised here by supplying the real + * modules directly. + */ +@DisplayName("CommandSubtypeRegistrar wiring") +class CommandSubtypeRegistrarTest { + @Test + @DisplayName("module-contributed command types deserialize by their id after customize()") + void registersModuleContributedSubtypes() throws Exception { + var registrar = new CommandSubtypeRegistrar(); + registrar.modules = List.of(new CoreCommandModule(), new VoiceMeeterCommandModule()); + + var mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + registrar.customize(mapper); + + assertInstanceOf(CommandBrightness.class, + mapper.readValue("{\"_type\":\"com.getpcpanel.commands.command.CommandBrightness\"}", Command.class)); + assertInstanceOf(CommandVoiceMeeterAdvanced.class, + mapper.readValue("{\"_type\":\"com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced\"}", Command.class)); + } +} diff --git a/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java b/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java index ea9fe960..b3d28637 100644 --- a/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java +++ b/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java @@ -16,8 +16,14 @@ */ @DisplayName("CommandKeystroke key vs text modes") class CommandKeystrokeTest { + // Command polymorphism is registered per-class (@JsonTypeName) + via the CommandModule SPI at + // runtime, so a bare mapper must be told about the subtype it deserializes. private final ObjectMapper mapper = new ObjectMapper(); + { + mapper.registerSubtypes(CommandKeystroke.class); + } + @Test @DisplayName("legacy JSON without a 'type' field deserializes as a KEY keystroke") void legacyKeystrokeDefaultsToKey() throws Exception { diff --git a/src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java b/src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java index 444e3e4e..219f53ab 100644 --- a/src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java +++ b/src/test/java/com/getpcpanel/commands/command/CommandSubtypeRegistryTest.java @@ -9,95 +9,100 @@ import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashMap; +import java.util.ArrayList; import java.util.HashSet; -import java.util.Map; +import java.util.List; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.getpcpanel.commands.CommandModule; /** - * Guards the {@link Command} polymorphic type registry. - * - *

{@code Command} uses {@code Id.NAME} + {@link JsonSubTypes}: each subtype's {@code name} is a - * stable, location-independent id that equals the class's historical fully-qualified name, which is - * exactly what existing {@code profiles.json} files persist. This lets a command class move into its - * own feature package without changing the persisted {@code _type}. These tests enforce the two - * invariants that keep that safe: + * Guards the decentralized command type registry. There is intentionally no central + * {@code @JsonSubTypes} list: every concrete {@link Command} declares its own stable id with + * {@code @JsonTypeName} in its own package, and every feature module lists its own commands via the + * {@link CommandModule} CDI SPI. These tests enforce the invariants that keep that safe, so a new + * command/plugin that forgets either half fails the build rather than silently breaking at runtime: * *

    - *
  1. Completeness — every concrete {@code Command} subtype on the classpath is registered - * (a missing entry would make Jackson throw at serialization time), and there are no stale - * entries. This is the "you forgot to register your new command" guard.
  2. - *
  3. Migration — every registered persisted id still deserializes to its class, so saved - * profiles keep loading after a command moves package.
  4. + *
  5. Self-identifying — every concrete command carries a unique, non-blank {@code @JsonTypeName}.
  6. + *
  7. Module-covered — the union of all {@link CommandModule#commandTypes()} equals exactly the + * set of concrete commands (none missing, none stale/duplicated).
  8. + *
  9. Resolvable — once registered, every id deserializes back to its class (the save-migration + * guard: a moved class still loads under its frozen id).
  10. *
*/ -@DisplayName("Command @JsonSubTypes registry") +@DisplayName("Decentralized Command type registry") class CommandSubtypeRegistryTest { - // Mirror the Quarkus production mapper, which does not fail on unknown properties — commands - // serialize extra getters (e.g. a dial command's top-level `invert`) that have no creator param. private final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @Test - @DisplayName("every concrete Command subtype is registered exactly once, with a unique id") - void registryIsCompleteAndConsistent() throws Exception { - var registered = registeredSubtypes(); + @DisplayName("every concrete command has a unique @JsonTypeName") + void everyCommandSelfIdentifies() throws Exception { + var seen = new HashSet(); + for (var c : findConcreteCommandSubtypes()) { + var ann = c.getAnnotation(JsonTypeName.class); + assertTrue(ann != null && !ann.value().isBlank(), () -> c.getName() + " is missing a @JsonTypeName id"); + assertTrue(seen.add(ann.value()), () -> "Duplicate @JsonTypeName id: " + ann.value()); + } + } + + @Test + @DisplayName("the CommandModule SPI covers exactly the concrete commands (none missing, none stale)") + void everyCommandIsCoveredByExactlyOneModule() throws Exception { var concrete = findConcreteCommandSubtypes(); + var declared = new ArrayList>(); + for (var module : findCommandModules()) { + declared.addAll(module.commandTypes()); + } + var declaredSet = new HashSet<>(declared); - var missing = concrete.stream().filter(c -> !registered.containsKey(c)).map(Class::getName).sorted().toList(); - assertTrue(missing.isEmpty(), "Concrete Command subtypes missing from Command's @JsonSubTypes (add an @Type line): " + missing); + var missing = concrete.stream().filter(c -> !declaredSet.contains(c)).map(Class::getName).sorted().toList(); + assertTrue(missing.isEmpty(), "Concrete commands not listed in any CommandModule (add them to the feature's module): " + missing); - var stale = registered.keySet().stream().filter(c -> !concrete.contains(c)).map(Class::getName).sorted().toList(); - assertTrue(stale.isEmpty(), "@JsonSubTypes entries that are not concrete Command subtypes (remove them): " + stale); + var stale = declaredSet.stream().filter(c -> !concrete.contains(c)).map(Class::getName).sorted().toList(); + assertTrue(stale.isEmpty(), "CommandModule lists a type that is not a concrete command: " + stale); - var names = new HashSet(); - for (var name : registered.values()) { - assertFalse(name.isBlank(), "Blank @JsonSubTypes name"); - assertTrue(names.add(name), "Duplicate @JsonSubTypes name: " + name); - } + assertEquals(declared.size(), declaredSet.size(), "A command is listed by more than one CommandModule"); } @Test - @DisplayName("every persisted type id deserializes back to its command class") - void persistedTypeIdsResolve() { - registeredSubtypes().forEach((clazz, name) -> { - var json = "{\"_type\":\"" + name + "\"}"; + @DisplayName("every command id deserializes back to its class once registered") + void idsResolveAfterRegistration() throws Exception { + var concrete = findConcreteCommandSubtypes(); + concrete.forEach(mapper::registerSubtypes); + for (var c : concrete) { + var name = c.getAnnotation(JsonTypeName.class).value(); try { - var cmd = mapper.readValue(json, Command.class); - assertInstanceOf(clazz, cmd, () -> "id '" + name + "' resolved to the wrong class"); + assertInstanceOf(c, mapper.readValue("{\"_type\":\"" + name + "\"}", Command.class), + () -> "id '" + name + "' resolved to the wrong class"); } catch (Exception e) { - fail("Persisted type id '" + name + "' (for " + clazz.getName() + ") no longer deserializes: " + e.getMessage()); + fail("id '" + name + "' (for " + c.getName() + ") no longer deserializes: " + e.getMessage()); } - }); - } - - @Test - @DisplayName("a CommandVolumeProcess persists and reloads under its frozen id") - void roundTripFrozenId() throws Exception { - var json = mapper.writeValueAsString(new CommandVolumeProcess(java.util.List.of("foo.exe"), "", false, DialAction.DialCommandParams.DEFAULT)); - assertTrue(json.contains("\"com.getpcpanel.commands.command.CommandVolumeProcess\""), "frozen id not emitted: " + json); - assertInstanceOf(CommandVolumeProcess.class, mapper.readValue(json, Command.class)); + } + assertFalse(concrete.isEmpty(), "no commands discovered — scan is broken"); } - private static Map, String> registeredSubtypes() { - var ann = Command.class.getAnnotation(JsonSubTypes.class); - assertEquals(JsonSubTypes.class, ann.annotationType()); - var result = new HashMap, String>(); - for (var t : ann.value()) { - result.put(t.value(), t.name()); + @SuppressWarnings("unchecked") + private static List findCommandModules() throws Exception { + var result = new ArrayList(); + for (var c : scan(c -> CommandModule.class.isAssignableFrom(c) && !c.isInterface() && !Modifier.isAbstract(c.getModifiers()))) { + result.add((CommandModule) c.getDeclaredConstructor().newInstance()); } return result; } - /** Discovers every concrete {@code com.getpcpanel.**} subclass of {@link Command} from the built classes. */ private static Set> findConcreteCommandSubtypes() throws Exception { + return scan(c -> Command.class.isAssignableFrom(c) && !c.isInterface() && !Modifier.isAbstract(c.getModifiers()) && c != Command.class); + } + + private static Set> scan(java.util.function.Predicate> keep) throws Exception { var root = Path.of(Command.class.getProtectionDomain().getCodeSource().getLocation().toURI()); var loader = CommandSubtypeRegistryTest.class.getClassLoader(); var result = new HashSet>(); @@ -111,7 +116,7 @@ private static Set> findConcreteCommandSubtypes() throws Exception { } catch (Throwable e) { continue; } - if (Command.class.isAssignableFrom(clazz) && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && clazz != Command.class) { + if (keep.test(clazz)) { result.add(clazz); } } From 3cfa48cdc6f6f2f785c0ee2d5f8a082714892db3 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 12:43:29 +0200 Subject: [PATCH 09/49] refactor(commands): split core commands into cohesive feature modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commands/command was still a catch-all mixing unrelated families (volume vs shortcuts vs media...). Split every core family into its own self-contained module via git mv, each registering itself through the CommandModule SPI — so commands/command now holds ONLY the engine (Command, CommandNoOp, CommandConverter, the Dial/Button/DeviceAction SPIs, EngineCommandModule). New core modules (package + commands + a CommandModule bean): volume.command — all CommandVolume* (process/focus/device/default-device…) keyboard.command — CommandKeystroke, CommandMedia program.command — CommandRun, CommandShortcut, CommandEndProgram device.command — CommandBrightness profile.command — CommandProfile analogbands.command — CommandAnalogBands (+ AnalogBand) output.command — CommandHttpRequest (+ CommandValueOutput base) Frozen @JsonTypeName ids keep the persisted _type unchanged, so saves, the TS _type union (verified byte-identical literal set), and the frontend are unaffected by the moves. The classPatterns glob and the CommandModule SPI mean no central file changed. Importers (CommandConverter, NativeImageConfig, etc.) and reachability-metadata repointed; CommandAnalogBandsTest moved next to its subject. Full suite green (only the 3 pre-existing FocusVolume failures); tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../analogbands/AnalogBandColorService.java | 2 +- .../command/AnalogBand.java | 2 +- .../command/AnalogBandsCommandModule.java | 21 ++++++++ .../command/CommandAnalogBands.java | 4 +- .../com/getpcpanel/commands/IconService.java | 16 +++--- .../commands/command/CommandConverter.java | 14 ++++- .../commands/command/CoreCommandModule.java | 40 --------------- .../commands/command/EngineCommandModule.java | 20 ++++++++ .../getpcpanel/cpp/linux/LinuxKeyboard.java | 2 +- .../cpp/linux/LinuxMprisMediaControl.java | 2 +- .../command/CommandBrightness.java | 4 +- .../device/command/DeviceCommandModule.java | 21 ++++++++ .../getpcpanel/graalvm/NativeImageConfig.java | 48 +++++++++--------- .../com/getpcpanel/hid/BrightnessService.java | 2 +- .../command/CommandKeystroke.java | 4 +- .../command/CommandMedia.java | 4 +- .../command/KeyboardCommandModule.java | 22 ++++++++ .../mqtt/command/CommandMqttPublish.java | 2 +- .../mutecolor/DeviceMuteResolver.java | 2 +- .../mutecolor/ProcessMuteResolver.java | 2 +- .../osc/command/CommandOscSend.java | 2 +- .../command/CommandHttpRequest.java | Bin 5355 -> 5404 bytes .../command/CommandValueOutput.java | 5 +- .../output/command/OutputCommandModule.java | 21 ++++++++ .../java/com/getpcpanel/overlay/Overlay.java | 2 +- .../command/CommandProfile.java | 4 +- .../profile/command/ProfileCommandModule.java | 21 ++++++++ .../command/CommandEndProgram.java | 4 +- .../command/CommandRun.java | 4 +- .../command/CommandShortcut.java | 4 +- .../program/command/ProgramCommandModule.java | 23 +++++++++ .../com/getpcpanel/rest/CommandsResource.java | 34 ++++++------- .../com/getpcpanel/util/OsxMediaControl.java | 2 +- .../volume/FocusVolumeOverrideService.java | 2 +- .../volume/LinuxNewSessionVolumeService.java | 2 +- .../volume/VolumeCoordinatorService.java | 2 +- .../command/CommandVolume.java | 3 +- .../CommandVolumeApplicationDeviceToggle.java | 3 +- .../command/CommandVolumeDefaultDevice.java | 3 +- .../CommandVolumeDefaultDeviceAdvanced.java | 3 +- .../CommandVolumeDefaultDeviceToggle.java | 3 +- ...mandVolumeDefaultDeviceToggleAdvanced.java | 3 +- .../command/CommandVolumeDevice.java | 3 +- .../command/CommandVolumeDeviceMute.java | 3 +- .../command/CommandVolumeFocus.java | 3 +- .../command/CommandVolumeFocusMute.java | 3 +- .../command/CommandVolumeProcess.java | 3 +- .../command/CommandVolumeProcessMute.java | 3 +- .../volume/command/VolumeCommandModule.java | 31 +++++++++++ .../native-image/reachability-metadata.json | 18 +++---- .../src/app/models/generated/backend.types.ts | 31 +++++++++-- .../com/getpcpanel/mcp/DebugResolveTools.java | 4 +- .../AnalogBandColorServiceTest.java | 4 +- .../command/CommandAnalogBandsTest.java | 3 +- .../commands/CommandSubtypeRegistrarTest.java | 6 +-- .../command/CommandKeystrokeTest.java | 3 +- .../cpp/linux/LinuxKeyboardKeystrokeTest.java | 2 +- .../linux/LinuxKeyboardNativeConfigTest.java | 2 +- .../getpcpanel/hid/BrightnessServiceTest.java | 2 +- .../hid/DialValueCalculatorTest.java | 2 +- .../com/getpcpanel/hid/DialValueTest.java | 2 +- .../getpcpanel/hid/InputInterpreterTest.java | 2 +- .../profile/BaseLayerServiceTest.java | 2 +- .../FocusVolumeOverrideServiceTest.java | 2 +- 64 files changed, 362 insertions(+), 156 deletions(-) rename src/main/java/com/getpcpanel/{commands => analogbands}/command/AnalogBand.java (96%) create mode 100644 src/main/java/com/getpcpanel/analogbands/command/AnalogBandsCommandModule.java rename src/main/java/com/getpcpanel/{commands => analogbands}/command/CommandAnalogBands.java (97%) delete mode 100644 src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java create mode 100644 src/main/java/com/getpcpanel/commands/command/EngineCommandModule.java rename src/main/java/com/getpcpanel/{commands => device}/command/CommandBrightness.java (90%) create mode 100644 src/main/java/com/getpcpanel/device/command/DeviceCommandModule.java rename src/main/java/com/getpcpanel/{commands => keyboard}/command/CommandKeystroke.java (93%) rename src/main/java/com/getpcpanel/{commands => keyboard}/command/CommandMedia.java (97%) create mode 100644 src/main/java/com/getpcpanel/keyboard/command/KeyboardCommandModule.java rename src/main/java/com/getpcpanel/{commands => output}/command/CommandHttpRequest.java (98%) rename src/main/java/com/getpcpanel/{commands => output}/command/CommandValueOutput.java (92%) create mode 100644 src/main/java/com/getpcpanel/output/command/OutputCommandModule.java rename src/main/java/com/getpcpanel/{commands => profile}/command/CommandProfile.java (87%) create mode 100644 src/main/java/com/getpcpanel/profile/command/ProfileCommandModule.java rename src/main/java/com/getpcpanel/{commands => program}/command/CommandEndProgram.java (88%) rename src/main/java/com/getpcpanel/{commands => program}/command/CommandRun.java (85%) rename src/main/java/com/getpcpanel/{commands => program}/command/CommandShortcut.java (86%) create mode 100644 src/main/java/com/getpcpanel/program/command/ProgramCommandModule.java rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolume.java (81%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeApplicationDeviceToggle.java (97%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeDefaultDevice.java (91%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeDefaultDeviceAdvanced.java (95%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeDefaultDeviceToggle.java (96%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeDefaultDeviceToggleAdvanced.java (97%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeDevice.java (94%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeDeviceMute.java (90%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeFocus.java (91%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeFocusMute.java (89%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeProcess.java (94%) rename src/main/java/com/getpcpanel/{commands => volume}/command/CommandVolumeProcessMute.java (90%) create mode 100644 src/main/java/com/getpcpanel/volume/command/VolumeCommandModule.java rename src/test/java/com/getpcpanel/{commands => analogbands}/command/CommandAnalogBandsTest.java (97%) diff --git a/src/main/java/com/getpcpanel/analogbands/AnalogBandColorService.java b/src/main/java/com/getpcpanel/analogbands/AnalogBandColorService.java index dc680ad0..097a7b46 100644 --- a/src/main/java/com/getpcpanel/analogbands/AnalogBandColorService.java +++ b/src/main/java/com/getpcpanel/analogbands/AnalogBandColorService.java @@ -6,7 +6,7 @@ import org.apache.commons.lang3.StringUtils; import com.getpcpanel.commands.Commands; -import com.getpcpanel.commands.command.CommandAnalogBands; +import com.getpcpanel.analogbands.command.CommandAnalogBands; import com.getpcpanel.device.Device; import com.getpcpanel.hid.DeviceHolder; import com.getpcpanel.profile.BaseLayerService; diff --git a/src/main/java/com/getpcpanel/commands/command/AnalogBand.java b/src/main/java/com/getpcpanel/analogbands/command/AnalogBand.java similarity index 96% rename from src/main/java/com/getpcpanel/commands/command/AnalogBand.java rename to src/main/java/com/getpcpanel/analogbands/command/AnalogBand.java index ffb477ef..cc9058f7 100644 --- a/src/main/java/com/getpcpanel/commands/command/AnalogBand.java +++ b/src/main/java/com/getpcpanel/analogbands/command/AnalogBand.java @@ -1,4 +1,4 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.analogbands.command; import javax.annotation.Nullable; diff --git a/src/main/java/com/getpcpanel/analogbands/command/AnalogBandsCommandModule.java b/src/main/java/com/getpcpanel/analogbands/command/AnalogBandsCommandModule.java new file mode 100644 index 00000000..a17946b6 --- /dev/null +++ b/src/main/java/com/getpcpanel/analogbands/command/AnalogBandsCommandModule.java @@ -0,0 +1,21 @@ +package com.getpcpanel.analogbands.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * AnalogBands feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class AnalogBandsCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandAnalogBands.class); + } +} diff --git a/src/main/java/com/getpcpanel/commands/command/CommandAnalogBands.java b/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java similarity index 97% rename from src/main/java/com/getpcpanel/commands/command/CommandAnalogBands.java rename to src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java index 682c9e0a..1fcf6f8f 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandAnalogBands.java +++ b/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.analogbands.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.DialAction; import java.util.List; import javax.annotation.Nullable; diff --git a/src/main/java/com/getpcpanel/commands/IconService.java b/src/main/java/com/getpcpanel/commands/IconService.java index c2dc8cb5..f9116d8e 100644 --- a/src/main/java/com/getpcpanel/commands/IconService.java +++ b/src/main/java/com/getpcpanel/commands/IconService.java @@ -16,16 +16,16 @@ import org.apache.commons.lang3.SystemUtils; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandBrightness; +import com.getpcpanel.device.command.CommandBrightness; import com.getpcpanel.obs.command.CommandObs; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; -import com.getpcpanel.commands.command.CommandVolumeDefaultDevice; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced; -import com.getpcpanel.commands.command.CommandVolumeDevice; -import com.getpcpanel.commands.command.CommandVolumeFocus; -import com.getpcpanel.commands.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeDefaultDevice; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceAdvanced; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggle; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggleAdvanced; +import com.getpcpanel.volume.command.CommandVolumeDevice; +import com.getpcpanel.volume.command.CommandVolumeFocus; +import com.getpcpanel.volume.command.CommandVolumeProcess; import com.getpcpanel.cpp.ISndCtrl; import com.getpcpanel.iconextract.IIconService; import com.getpcpanel.profile.dto.KnobSetting; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandConverter.java b/src/main/java/com/getpcpanel/commands/command/CommandConverter.java index bcafc14b..357fbcd6 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandConverter.java +++ b/src/main/java/com/getpcpanel/commands/command/CommandConverter.java @@ -1,5 +1,17 @@ package com.getpcpanel.commands.command; +import com.getpcpanel.keyboard.command.CommandKeystroke; +import com.getpcpanel.keyboard.command.CommandMedia; +import com.getpcpanel.profile.command.CommandProfile; +import com.getpcpanel.program.command.CommandEndProgram; +import com.getpcpanel.program.command.CommandShortcut; +import com.getpcpanel.volume.command.CommandVolumeDefaultDevice; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggle; +import com.getpcpanel.volume.command.CommandVolumeDevice; +import com.getpcpanel.volume.command.CommandVolumeDeviceMute; +import com.getpcpanel.volume.command.CommandVolumeFocus; +import com.getpcpanel.volume.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcessMute; import static com.getpcpanel.commands.command.CommandNoOp.NOOP; import java.util.List; @@ -8,7 +20,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; -import com.getpcpanel.commands.command.CommandMedia.VolumeButton; +import com.getpcpanel.keyboard.command.CommandMedia.VolumeButton; import com.getpcpanel.commands.command.DialAction.DialCommandParams; import com.getpcpanel.cpp.MuteType; import com.getpcpanel.obs.command.CommandObsMuteSource; diff --git a/src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java b/src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java deleted file mode 100644 index 9e330018..00000000 --- a/src/main/java/com/getpcpanel/commands/command/CoreCommandModule.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.getpcpanel.commands.command; - -import java.util.List; - -import com.getpcpanel.commands.CommandModule; - -import jakarta.enterprise.context.ApplicationScoped; - -/** - * Core feature module: registers its own command types via the - * {@link com.getpcpanel.commands.CommandModule} SPI. Adding/removing a command touches only this package. - */ -@ApplicationScoped -public class CoreCommandModule implements CommandModule { - @Override - public List> commandTypes() { - return List.of( - CommandAnalogBands.class, - CommandBrightness.class, - CommandEndProgram.class, - CommandHttpRequest.class, - CommandKeystroke.class, - CommandMedia.class, - CommandNoOp.class, - CommandProfile.class, - CommandRun.class, - CommandShortcut.class, - CommandVolumeApplicationDeviceToggle.class, - CommandVolumeDefaultDevice.class, - CommandVolumeDefaultDeviceAdvanced.class, - CommandVolumeDefaultDeviceToggle.class, - CommandVolumeDefaultDeviceToggleAdvanced.class, - CommandVolumeDevice.class, - CommandVolumeDeviceMute.class, - CommandVolumeFocus.class, - CommandVolumeFocusMute.class, - CommandVolumeProcess.class, - CommandVolumeProcessMute.class); - } -} diff --git a/src/main/java/com/getpcpanel/commands/command/EngineCommandModule.java b/src/main/java/com/getpcpanel/commands/command/EngineCommandModule.java new file mode 100644 index 00000000..28b7c56a --- /dev/null +++ b/src/main/java/com/getpcpanel/commands/command/EngineCommandModule.java @@ -0,0 +1,20 @@ +package com.getpcpanel.commands.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Engine feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class EngineCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandNoOp.class); + } +} diff --git a/src/main/java/com/getpcpanel/cpp/linux/LinuxKeyboard.java b/src/main/java/com/getpcpanel/cpp/linux/LinuxKeyboard.java index f727518c..18ad9f1a 100644 --- a/src/main/java/com/getpcpanel/cpp/linux/LinuxKeyboard.java +++ b/src/main/java/com/getpcpanel/cpp/linux/LinuxKeyboard.java @@ -4,7 +4,7 @@ import java.util.HashMap; import java.util.Map; -import com.getpcpanel.commands.command.CommandMedia.VolumeButton; +import com.getpcpanel.keyboard.command.CommandMedia.VolumeButton; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.NativeLong; diff --git a/src/main/java/com/getpcpanel/cpp/linux/LinuxMprisMediaControl.java b/src/main/java/com/getpcpanel/cpp/linux/LinuxMprisMediaControl.java index 30ca9f83..2e2b4f66 100644 --- a/src/main/java/com/getpcpanel/cpp/linux/LinuxMprisMediaControl.java +++ b/src/main/java/com/getpcpanel/cpp/linux/LinuxMprisMediaControl.java @@ -12,7 +12,7 @@ import org.freedesktop.dbus.interfaces.DBus; import org.freedesktop.dbus.interfaces.Properties; -import com.getpcpanel.commands.command.CommandMedia.VolumeButton; +import com.getpcpanel.keyboard.command.CommandMedia.VolumeButton; import lombok.extern.log4j.Log4j2; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandBrightness.java b/src/main/java/com/getpcpanel/device/command/CommandBrightness.java similarity index 90% rename from src/main/java/com/getpcpanel/commands/command/CommandBrightness.java rename to src/main/java/com/getpcpanel/device/command/CommandBrightness.java index 266b2003..e4f090ea 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandBrightness.java +++ b/src/main/java/com/getpcpanel/device/command/CommandBrightness.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.device.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.DialAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/device/command/DeviceCommandModule.java b/src/main/java/com/getpcpanel/device/command/DeviceCommandModule.java new file mode 100644 index 00000000..d9fae6ff --- /dev/null +++ b/src/main/java/com/getpcpanel/device/command/DeviceCommandModule.java @@ -0,0 +1,21 @@ +package com.getpcpanel.device.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Device feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class DeviceCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandBrightness.class); + } +} diff --git a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java index 4d894f32..4889f786 100644 --- a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java +++ b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java @@ -12,15 +12,15 @@ import com.getpcpanel.commands.Commands; import com.getpcpanel.commands.CommandsType; import com.getpcpanel.commands.DeviceSet; -import com.getpcpanel.commands.command.AnalogBand; +import com.getpcpanel.analogbands.command.AnalogBand; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandAnalogBands; -import com.getpcpanel.commands.command.CommandBrightness; -import com.getpcpanel.commands.command.CommandEndProgram; -import com.getpcpanel.commands.command.CommandHttpRequest; -import com.getpcpanel.commands.command.CommandKeystroke; -import com.getpcpanel.commands.command.CommandMedia; -import com.getpcpanel.commands.command.CommandMedia.VolumeButton; +import com.getpcpanel.analogbands.command.CommandAnalogBands; +import com.getpcpanel.device.command.CommandBrightness; +import com.getpcpanel.program.command.CommandEndProgram; +import com.getpcpanel.output.command.CommandHttpRequest; +import com.getpcpanel.keyboard.command.CommandKeystroke; +import com.getpcpanel.keyboard.command.CommandMedia; +import com.getpcpanel.keyboard.command.CommandMedia.VolumeButton; import com.getpcpanel.mqtt.command.CommandMqttPublish; import com.getpcpanel.commands.command.CommandNoOp; import com.getpcpanel.obs.command.CommandObs; @@ -30,27 +30,27 @@ import com.getpcpanel.obs.command.CommandObsSetScene; import com.getpcpanel.obs.command.CommandObsSetSourceVolume; import com.getpcpanel.osc.command.CommandOscSend; -import com.getpcpanel.commands.command.CommandProfile; -import com.getpcpanel.commands.command.CommandRun; -import com.getpcpanel.commands.command.CommandShortcut; -import com.getpcpanel.commands.command.CommandValueOutput; +import com.getpcpanel.profile.command.CommandProfile; +import com.getpcpanel.program.command.CommandRun; +import com.getpcpanel.program.command.CommandShortcut; +import com.getpcpanel.output.command.CommandValueOutput; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; -import com.getpcpanel.commands.command.CommandVolume; -import com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle; -import com.getpcpanel.commands.command.CommandVolumeDefaultDevice; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced; -import com.getpcpanel.commands.command.CommandVolumeDevice; -import com.getpcpanel.commands.command.CommandVolumeDeviceMute; -import com.getpcpanel.commands.command.CommandVolumeFocus; -import com.getpcpanel.commands.command.CommandVolumeFocusMute; -import com.getpcpanel.commands.command.CommandVolumeProcess; -import com.getpcpanel.commands.command.CommandVolumeProcessMute; +import com.getpcpanel.volume.command.CommandVolume; +import com.getpcpanel.volume.command.CommandVolumeApplicationDeviceToggle; +import com.getpcpanel.volume.command.CommandVolumeDefaultDevice; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceAdvanced; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggle; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggleAdvanced; +import com.getpcpanel.volume.command.CommandVolumeDevice; +import com.getpcpanel.volume.command.CommandVolumeDeviceMute; +import com.getpcpanel.volume.command.CommandVolumeFocus; +import com.getpcpanel.volume.command.CommandVolumeFocusMute; +import com.getpcpanel.volume.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcessMute; import com.getpcpanel.commands.command.DialAction.DialCommandParams; import com.getpcpanel.discord.command.CommandDiscord; import com.getpcpanel.discord.command.CommandDiscordJoinVoice; diff --git a/src/main/java/com/getpcpanel/hid/BrightnessService.java b/src/main/java/com/getpcpanel/hid/BrightnessService.java index a577246e..30911d4d 100644 --- a/src/main/java/com/getpcpanel/hid/BrightnessService.java +++ b/src/main/java/com/getpcpanel/hid/BrightnessService.java @@ -8,7 +8,7 @@ import javax.annotation.Nullable; -import com.getpcpanel.commands.command.CommandBrightness; +import com.getpcpanel.device.command.CommandBrightness; import com.getpcpanel.profile.Profile; import com.getpcpanel.profile.SaveService; import com.getpcpanel.profile.dto.KnobSetting; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandKeystroke.java b/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java similarity index 93% rename from src/main/java/com/getpcpanel/commands/command/CommandKeystroke.java rename to src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java index 753c0283..5353995f 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandKeystroke.java +++ b/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.keyboard.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.ButtonAction; import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandMedia.java b/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java similarity index 97% rename from src/main/java/com/getpcpanel/commands/command/CommandMedia.java rename to src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java index aa130ecc..6aaad4bf 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandMedia.java +++ b/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.keyboard.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.ButtonAction; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/com/getpcpanel/keyboard/command/KeyboardCommandModule.java b/src/main/java/com/getpcpanel/keyboard/command/KeyboardCommandModule.java new file mode 100644 index 00000000..5fc0ce30 --- /dev/null +++ b/src/main/java/com/getpcpanel/keyboard/command/KeyboardCommandModule.java @@ -0,0 +1,22 @@ +package com.getpcpanel.keyboard.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Keyboard feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class KeyboardCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandKeystroke.class, + CommandMedia.class); + } +} diff --git a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java index b8239ebd..b400b123 100644 --- a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java +++ b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.mqtt.MqttService; -import com.getpcpanel.commands.command.CommandValueOutput; +import com.getpcpanel.output.command.CommandValueOutput; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.util.ValueInterpolator; diff --git a/src/main/java/com/getpcpanel/mutecolor/DeviceMuteResolver.java b/src/main/java/com/getpcpanel/mutecolor/DeviceMuteResolver.java index 0d13ce6b..80cabd30 100644 --- a/src/main/java/com/getpcpanel/mutecolor/DeviceMuteResolver.java +++ b/src/main/java/com/getpcpanel/mutecolor/DeviceMuteResolver.java @@ -3,7 +3,7 @@ import java.util.Optional; import com.getpcpanel.commands.Commands; -import com.getpcpanel.commands.command.CommandVolumeDevice; +import com.getpcpanel.volume.command.CommandVolumeDevice; import com.getpcpanel.cpp.ISndCtrl; import jakarta.enterprise.context.ApplicationScoped; diff --git a/src/main/java/com/getpcpanel/mutecolor/ProcessMuteResolver.java b/src/main/java/com/getpcpanel/mutecolor/ProcessMuteResolver.java index 4eaf3db2..e1ba5c5a 100644 --- a/src/main/java/com/getpcpanel/mutecolor/ProcessMuteResolver.java +++ b/src/main/java/com/getpcpanel/mutecolor/ProcessMuteResolver.java @@ -6,7 +6,7 @@ import org.apache.commons.lang3.StringUtils; import com.getpcpanel.commands.Commands; -import com.getpcpanel.commands.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcess; import com.getpcpanel.cpp.AudioSession; import com.getpcpanel.cpp.ISndCtrl; diff --git a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java index 7f8d9bcf..18a9241e 100644 --- a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java +++ b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.osc.OSCService; -import com.getpcpanel.commands.command.CommandValueOutput; +import com.getpcpanel.output.command.CommandValueOutput; import com.getpcpanel.util.CdiHelper; import lombok.Getter; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandHttpRequest.java b/src/main/java/com/getpcpanel/output/command/CommandHttpRequest.java similarity index 98% rename from src/main/java/com/getpcpanel/commands/command/CommandHttpRequest.java rename to src/main/java/com/getpcpanel/output/command/CommandHttpRequest.java index 196b56e052ac05a67d1b0476bbd034f9670d60e6..98a0fcb7a3a91a420f4df60536893c7e4f5ea7e3 100644 GIT binary patch delta 47 zcmaE@IY(=PxIlhsNkM6eUUGhJZem`FHP>WECh^JdSp>OVG81zglS?x5^Q<>&9uffn Do@Nl| delta 17 YcmbQE^;&a+_+$lE0VZp%jZue005rY@asU7T diff --git a/src/main/java/com/getpcpanel/commands/command/CommandValueOutput.java b/src/main/java/com/getpcpanel/output/command/CommandValueOutput.java similarity index 92% rename from src/main/java/com/getpcpanel/commands/command/CommandValueOutput.java rename to src/main/java/com/getpcpanel/output/command/CommandValueOutput.java index 61f4a96a..8f2cd28f 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandValueOutput.java +++ b/src/main/java/com/getpcpanel/output/command/CommandValueOutput.java @@ -1,5 +1,8 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.output.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.DialAction; +import com.getpcpanel.commands.command.ButtonAction; import javax.annotation.Nullable; import com.getpcpanel.util.ValueInterpolator; diff --git a/src/main/java/com/getpcpanel/output/command/OutputCommandModule.java b/src/main/java/com/getpcpanel/output/command/OutputCommandModule.java new file mode 100644 index 00000000..f9d2b7fa --- /dev/null +++ b/src/main/java/com/getpcpanel/output/command/OutputCommandModule.java @@ -0,0 +1,21 @@ +package com.getpcpanel.output.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Output feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class OutputCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandHttpRequest.class); + } +} diff --git a/src/main/java/com/getpcpanel/overlay/Overlay.java b/src/main/java/com/getpcpanel/overlay/Overlay.java index 9ec1d574..29c27330 100644 --- a/src/main/java/com/getpcpanel/overlay/Overlay.java +++ b/src/main/java/com/getpcpanel/overlay/Overlay.java @@ -12,7 +12,7 @@ import com.getpcpanel.commands.PCPanelControlEvent; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandVolumeFocus; +import com.getpcpanel.volume.command.CommandVolumeFocus; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.cpp.ISndCtrl; import com.getpcpanel.device.Device; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandProfile.java b/src/main/java/com/getpcpanel/profile/command/CommandProfile.java similarity index 87% rename from src/main/java/com/getpcpanel/commands/command/CommandProfile.java rename to src/main/java/com/getpcpanel/profile/command/CommandProfile.java index a0720311..1e0e99fa 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandProfile.java +++ b/src/main/java/com/getpcpanel/profile/command/CommandProfile.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.profile.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.DeviceAction; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; diff --git a/src/main/java/com/getpcpanel/profile/command/ProfileCommandModule.java b/src/main/java/com/getpcpanel/profile/command/ProfileCommandModule.java new file mode 100644 index 00000000..112bf8f1 --- /dev/null +++ b/src/main/java/com/getpcpanel/profile/command/ProfileCommandModule.java @@ -0,0 +1,21 @@ +package com.getpcpanel.profile.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Profile feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class ProfileCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandProfile.class); + } +} diff --git a/src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java b/src/main/java/com/getpcpanel/program/command/CommandEndProgram.java similarity index 88% rename from src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java rename to src/main/java/com/getpcpanel/program/command/CommandEndProgram.java index 8c22b64a..ba703b56 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandEndProgram.java +++ b/src/main/java/com/getpcpanel/program/command/CommandEndProgram.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.program.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.ButtonAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandRun.java b/src/main/java/com/getpcpanel/program/command/CommandRun.java similarity index 85% rename from src/main/java/com/getpcpanel/commands/command/CommandRun.java rename to src/main/java/com/getpcpanel/program/command/CommandRun.java index 400746b5..0c95c6c5 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandRun.java +++ b/src/main/java/com/getpcpanel/program/command/CommandRun.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.program.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.ButtonAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandShortcut.java b/src/main/java/com/getpcpanel/program/command/CommandShortcut.java similarity index 86% rename from src/main/java/com/getpcpanel/commands/command/CommandShortcut.java rename to src/main/java/com/getpcpanel/program/command/CommandShortcut.java index ecfa4192..f1ddec2e 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandShortcut.java +++ b/src/main/java/com/getpcpanel/program/command/CommandShortcut.java @@ -1,5 +1,7 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.program.command; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.command.ButtonAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/program/command/ProgramCommandModule.java b/src/main/java/com/getpcpanel/program/command/ProgramCommandModule.java new file mode 100644 index 00000000..c3990ee6 --- /dev/null +++ b/src/main/java/com/getpcpanel/program/command/ProgramCommandModule.java @@ -0,0 +1,23 @@ +package com.getpcpanel.program.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Program feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class ProgramCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandRun.class, + CommandShortcut.class, + CommandEndProgram.class); + } +} diff --git a/src/main/java/com/getpcpanel/rest/CommandsResource.java b/src/main/java/com/getpcpanel/rest/CommandsResource.java index cb1bbedc..8fb6cbe1 100644 --- a/src/main/java/com/getpcpanel/rest/CommandsResource.java +++ b/src/main/java/com/getpcpanel/rest/CommandsResource.java @@ -3,30 +3,30 @@ import java.util.Collection; import java.util.List; -import com.getpcpanel.commands.command.CommandBrightness; -import com.getpcpanel.commands.command.CommandEndProgram; -import com.getpcpanel.commands.command.CommandKeystroke; -import com.getpcpanel.commands.command.CommandMedia; +import com.getpcpanel.device.command.CommandBrightness; +import com.getpcpanel.program.command.CommandEndProgram; +import com.getpcpanel.keyboard.command.CommandKeystroke; +import com.getpcpanel.keyboard.command.CommandMedia; import com.getpcpanel.obs.command.CommandObsMuteSource; import com.getpcpanel.obs.command.CommandObsSetScene; import com.getpcpanel.obs.command.CommandObsSetSourceVolume; -import com.getpcpanel.commands.command.CommandRun; -import com.getpcpanel.commands.command.CommandShortcut; +import com.getpcpanel.program.command.CommandRun; +import com.getpcpanel.program.command.CommandShortcut; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; -import com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle; -import com.getpcpanel.commands.command.CommandVolumeDefaultDevice; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle; -import com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced; -import com.getpcpanel.commands.command.CommandVolumeDevice; -import com.getpcpanel.commands.command.CommandVolumeDeviceMute; -import com.getpcpanel.commands.command.CommandVolumeFocus; -import com.getpcpanel.commands.command.CommandVolumeFocusMute; -import com.getpcpanel.commands.command.CommandVolumeProcess; -import com.getpcpanel.commands.command.CommandVolumeProcessMute; +import com.getpcpanel.volume.command.CommandVolumeApplicationDeviceToggle; +import com.getpcpanel.volume.command.CommandVolumeDefaultDevice; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceAdvanced; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggle; +import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggleAdvanced; +import com.getpcpanel.volume.command.CommandVolumeDevice; +import com.getpcpanel.volume.command.CommandVolumeDeviceMute; +import com.getpcpanel.volume.command.CommandVolumeFocus; +import com.getpcpanel.volume.command.CommandVolumeFocusMute; +import com.getpcpanel.volume.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcessMute; import com.getpcpanel.profile.SaveService; import com.getpcpanel.rest.EventBroadcaster.AssignmentChangedEvent.Kinds; import com.getpcpanel.rest.model.dto.CommandType; diff --git a/src/main/java/com/getpcpanel/util/OsxMediaControl.java b/src/main/java/com/getpcpanel/util/OsxMediaControl.java index 00e5b575..dfce49f6 100644 --- a/src/main/java/com/getpcpanel/util/OsxMediaControl.java +++ b/src/main/java/com/getpcpanel/util/OsxMediaControl.java @@ -9,7 +9,7 @@ import org.apache.commons.lang3.StringUtils; -import com.getpcpanel.commands.command.CommandMedia.VolumeButton; +import com.getpcpanel.keyboard.command.CommandMedia.VolumeButton; import com.getpcpanel.platform.MacBuild; import jakarta.enterprise.context.ApplicationScoped; diff --git a/src/main/java/com/getpcpanel/volume/FocusVolumeOverrideService.java b/src/main/java/com/getpcpanel/volume/FocusVolumeOverrideService.java index 77ff3e76..1152a2e6 100644 --- a/src/main/java/com/getpcpanel/volume/FocusVolumeOverrideService.java +++ b/src/main/java/com/getpcpanel/volume/FocusVolumeOverrideService.java @@ -6,7 +6,7 @@ import org.apache.commons.lang3.StringUtils; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcess; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.commands.command.DialAction.DialActionParameters; import com.getpcpanel.cpp.ISndCtrl; diff --git a/src/main/java/com/getpcpanel/volume/LinuxNewSessionVolumeService.java b/src/main/java/com/getpcpanel/volume/LinuxNewSessionVolumeService.java index 2ffc912a..b69b07a6 100644 --- a/src/main/java/com/getpcpanel/volume/LinuxNewSessionVolumeService.java +++ b/src/main/java/com/getpcpanel/volume/LinuxNewSessionVolumeService.java @@ -5,7 +5,7 @@ import org.apache.commons.lang3.StringUtils; -import com.getpcpanel.commands.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcess; import com.getpcpanel.cpp.AudioSessionEvent; import com.getpcpanel.cpp.EventType; import com.getpcpanel.cpp.ISndCtrl; diff --git a/src/main/java/com/getpcpanel/volume/VolumeCoordinatorService.java b/src/main/java/com/getpcpanel/volume/VolumeCoordinatorService.java index 6b9e48fb..ea447207 100644 --- a/src/main/java/com/getpcpanel/volume/VolumeCoordinatorService.java +++ b/src/main/java/com/getpcpanel/volume/VolumeCoordinatorService.java @@ -6,7 +6,7 @@ import org.apache.commons.lang3.StringUtils; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcess; import com.getpcpanel.cpp.ISndCtrl; import com.getpcpanel.profile.Profile; import com.getpcpanel.profile.SaveService; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolume.java b/src/main/java/com/getpcpanel/volume/command/CommandVolume.java similarity index 81% rename from src/main/java/com/getpcpanel/commands/command/CommandVolume.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolume.java index d3e7025f..07171ad5 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolume.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolume.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.Command; import com.fasterxml.jackson.annotation.JsonIgnore; import com.getpcpanel.util.CdiHelper; import jakarta.inject.Inject; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeApplicationDeviceToggle.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeApplicationDeviceToggle.java similarity index 97% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeApplicationDeviceToggle.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeApplicationDeviceToggle.java index b03075bd..494bb343 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeApplicationDeviceToggle.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeApplicationDeviceToggle.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDevice.java similarity index 91% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDevice.java index edc213aa..8c3e656a 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDevice.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDevice.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceAdvanced.java similarity index 95% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceAdvanced.java index 52095f93..530a6e08 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceAdvanced.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceAdvanced.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import com.fasterxml.jackson.annotation.JsonTypeName; import javax.annotation.Nullable; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggle.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggle.java similarity index 96% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggle.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggle.java index 0ef1016b..e75a2354 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggle.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggle.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggleAdvanced.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggleAdvanced.java similarity index 97% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggleAdvanced.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggleAdvanced.java index 0e094a48..f652d05e 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDefaultDeviceToggleAdvanced.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggleAdvanced.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java similarity index 94% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java index ffb1ac7c..87971518 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDevice.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.DialAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDeviceMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java similarity index 90% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeDeviceMute.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java index dbe798e3..ca1d8460 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeDeviceMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocus.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java similarity index 91% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeFocus.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java index 80e30203..0c2f6d63 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocus.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.DialAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java similarity index 89% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java index b5de7136..01e59f4c 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeFocusMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcess.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java similarity index 94% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeProcess.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java index ba78ee50..6dfeaa8d 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcess.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.DialAction; import java.util.HashSet; import java.util.List; diff --git a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcessMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java similarity index 90% rename from src/main/java/com/getpcpanel/commands/command/CommandVolumeProcessMute.java rename to src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java index f0300f2d..b152cfe9 100644 --- a/src/main/java/com/getpcpanel/commands/command/CommandVolumeProcessMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.volume.command; +import com.getpcpanel.commands.command.ButtonAction; import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/getpcpanel/volume/command/VolumeCommandModule.java b/src/main/java/com/getpcpanel/volume/command/VolumeCommandModule.java new file mode 100644 index 00000000..986c75d1 --- /dev/null +++ b/src/main/java/com/getpcpanel/volume/command/VolumeCommandModule.java @@ -0,0 +1,31 @@ +package com.getpcpanel.volume.command; + +import java.util.List; + +import com.getpcpanel.commands.CommandModule; +import com.getpcpanel.commands.command.Command; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * Volume feature module: registers its own command types via the {@link com.getpcpanel.commands.CommandModule} + * SPI. Adding/removing a command touches only this package. + */ +@ApplicationScoped +public class VolumeCommandModule implements CommandModule { + @Override + public List> commandTypes() { + return List.of( + CommandVolumeProcess.class, + CommandVolumeProcessMute.class, + CommandVolumeFocus.class, + CommandVolumeFocusMute.class, + CommandVolumeDevice.class, + CommandVolumeDeviceMute.class, + CommandVolumeDefaultDevice.class, + CommandVolumeDefaultDeviceToggle.class, + CommandVolumeDefaultDeviceAdvanced.class, + CommandVolumeDefaultDeviceToggleAdvanced.class, + CommandVolumeApplicationDeviceToggle.class); + } +} diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json index 2b0ac7d5..7953fccc 100644 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -149,7 +149,7 @@ "type": "com.getpcpanel.commands.command.Command" }, { - "type": "com.getpcpanel.commands.command.CommandBrightness", + "type": "com.getpcpanel.device.command.CommandBrightness", "methods": [ { "name": "", @@ -164,7 +164,7 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandKeystroke", + "type": "com.getpcpanel.keyboard.command.CommandKeystroke", "methods": [ { "name": "", @@ -250,10 +250,10 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandVolume" + "type": "com.getpcpanel.volume.command.CommandVolume" }, { - "type": "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced", + "type": "com.getpcpanel.volume.command.CommandVolumeDefaultDeviceAdvanced", "methods": [ { "name": "getCommunicationPb", @@ -282,7 +282,7 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced$CommandVolumeDefaultDeviceAdvancedBuilder", + "type": "com.getpcpanel.volume.command.CommandVolumeDefaultDeviceAdvanced$CommandVolumeDefaultDeviceAdvancedBuilder", "methods": [ { "name": "", @@ -325,7 +325,7 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandVolumeDeviceMute", + "type": "com.getpcpanel.volume.command.CommandVolumeDeviceMute", "methods": [ { "name": "", @@ -345,7 +345,7 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandVolumeFocus", + "type": "com.getpcpanel.volume.command.CommandVolumeFocus", "methods": [ { "name": "", @@ -360,7 +360,7 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandVolumeProcess", + "type": "com.getpcpanel.volume.command.CommandVolumeProcess", "fields": [ { "name": "unMuteOnVolumeChange" @@ -395,7 +395,7 @@ ] }, { - "type": "com.getpcpanel.commands.command.CommandVolumeProcessMute", + "type": "com.getpcpanel.volume.command.CommandVolumeProcessMute", "methods": [ { "name": "", diff --git a/src/main/webui/src/app/models/generated/backend.types.ts b/src/main/webui/src/app/models/generated/backend.types.ts index 2cf8ab9d..a76afa36 100644 --- a/src/main/webui/src/app/models/generated/backend.types.ts +++ b/src/main/webui/src/app/models/generated/backend.types.ts @@ -15,6 +15,9 @@ export interface AnalogBand { start: number; } +export interface AnalogBandsCommandModule extends CommandModule { +} + export interface AnalogInputSpec { hasButton: boolean; id: string; @@ -39,7 +42,7 @@ export interface ButtonAction { } export interface Command { - _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; } export interface CommandAnalogBands extends Command, DialAction { @@ -237,7 +240,7 @@ export interface CommandType { } export interface CommandValueOutput extends Command, DialAction, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend"; + _type: "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandHttpRequest"; formula?: string; max?: number; min?: number; @@ -400,9 +403,6 @@ export interface ControlAssignmentsUpdateDto { releaseButton?: Commands; } -export interface CoreCommandModule extends CommandModule { -} - export interface DeviceAction { } @@ -410,6 +410,9 @@ export interface DeviceActionParameters { device: string; } +export interface DeviceCommandModule extends CommandModule { +} + export interface DeviceDescriptor { analogInputs: AnalogInputSpec[]; analogOutputs: AnalogOutputSpec[]; @@ -543,6 +546,9 @@ export interface DiscordVoiceChannelDto { name: string; } +export interface EngineCommandModule extends CommandModule { +} + export interface FocusVolumeOverride { includeSource: boolean; sources: string[]; @@ -584,6 +590,9 @@ export interface HomeAssistantSettings { enableDiscovery: boolean; } +export interface KeyboardCommandModule extends CommandModule { +} + export interface KnobSetting { buttonDebounce: number; logarithmic: boolean; @@ -673,6 +682,9 @@ export interface OSCConnectionInfo { port: number; } +export interface OutputCommandModule extends CommandModule { +} + export interface ProcessDto { icon?: string; name: string; @@ -680,6 +692,9 @@ export interface ProcessDto { pid: number; } +export interface ProfileCommandModule extends CommandModule { +} + export interface ProfileDto { isMainProfile: boolean; name: string; @@ -702,6 +717,9 @@ export interface ProfileSnapshotDto { releaseButtonData: { [index: string]: Commands }; } +export interface ProgramCommandModule extends CommandModule { +} + export interface SerialPortDto { description: string; port: string; @@ -790,6 +808,9 @@ export interface SingleSliderLightingConfig { export interface VoiceMeeterCommandModule extends CommandModule { } +export interface VolumeCommandModule extends CommandModule { +} + export interface WaveLinkAppDto { id: string; name: string; diff --git a/src/mcp/java/com/getpcpanel/mcp/DebugResolveTools.java b/src/mcp/java/com/getpcpanel/mcp/DebugResolveTools.java index d3c540e4..fe7be295 100644 --- a/src/mcp/java/com/getpcpanel/mcp/DebugResolveTools.java +++ b/src/mcp/java/com/getpcpanel/mcp/DebugResolveTools.java @@ -11,9 +11,9 @@ import jakarta.inject.Inject; import com.getpcpanel.commands.Commands; -import com.getpcpanel.commands.command.AnalogBand; +import com.getpcpanel.analogbands.command.AnalogBand; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandAnalogBands; +import com.getpcpanel.analogbands.command.CommandAnalogBands; import com.getpcpanel.device.Device; import com.getpcpanel.hid.BrightnessService; import com.getpcpanel.hid.DeviceHolder; diff --git a/src/test/java/com/getpcpanel/analogbands/AnalogBandColorServiceTest.java b/src/test/java/com/getpcpanel/analogbands/AnalogBandColorServiceTest.java index caa8b2bc..a63d103d 100644 --- a/src/test/java/com/getpcpanel/analogbands/AnalogBandColorServiceTest.java +++ b/src/test/java/com/getpcpanel/analogbands/AnalogBandColorServiceTest.java @@ -11,8 +11,8 @@ import com.getpcpanel.commands.Commands; import com.getpcpanel.commands.CommandsType; -import com.getpcpanel.commands.command.AnalogBand; -import com.getpcpanel.commands.command.CommandAnalogBands; +import com.getpcpanel.analogbands.command.AnalogBand; +import com.getpcpanel.analogbands.command.CommandAnalogBands; import com.getpcpanel.profile.dto.LightingConfig; import com.getpcpanel.profile.dto.LightingConfig.LightingMode; diff --git a/src/test/java/com/getpcpanel/commands/command/CommandAnalogBandsTest.java b/src/test/java/com/getpcpanel/analogbands/command/CommandAnalogBandsTest.java similarity index 97% rename from src/test/java/com/getpcpanel/commands/command/CommandAnalogBandsTest.java rename to src/test/java/com/getpcpanel/analogbands/command/CommandAnalogBandsTest.java index d1a10263..eff8d926 100644 --- a/src/test/java/com/getpcpanel/commands/command/CommandAnalogBandsTest.java +++ b/src/test/java/com/getpcpanel/analogbands/command/CommandAnalogBandsTest.java @@ -1,5 +1,6 @@ -package com.getpcpanel.commands.command; +package com.getpcpanel.analogbands.command; +import com.getpcpanel.profile.command.CommandProfile; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java b/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java index f1b24afc..54701cba 100644 --- a/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java +++ b/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java @@ -10,8 +10,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandBrightness; -import com.getpcpanel.commands.command.CoreCommandModule; +import com.getpcpanel.device.command.CommandBrightness; +import com.getpcpanel.device.command.DeviceCommandModule; import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; import com.getpcpanel.voicemeeter.command.VoiceMeeterCommandModule; @@ -29,7 +29,7 @@ class CommandSubtypeRegistrarTest { @DisplayName("module-contributed command types deserialize by their id after customize()") void registersModuleContributedSubtypes() throws Exception { var registrar = new CommandSubtypeRegistrar(); - registrar.modules = List.of(new CoreCommandModule(), new VoiceMeeterCommandModule()); + registrar.modules = List.of(new DeviceCommandModule(), new VoiceMeeterCommandModule()); var mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); registrar.customize(mapper); diff --git a/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java b/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java index b3d28637..3396e19f 100644 --- a/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java +++ b/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java @@ -6,7 +6,8 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.ObjectMapper; -import com.getpcpanel.commands.command.CommandKeystroke.KeystrokeType; +import com.getpcpanel.keyboard.command.CommandKeystroke; +import com.getpcpanel.keyboard.command.CommandKeystroke.KeystrokeType; /** * Functional tests for {@link CommandKeystroke}'s two modes and its JSON contract. The execute() diff --git a/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardKeystrokeTest.java b/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardKeystrokeTest.java index 66c95800..c8994e35 100644 --- a/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardKeystrokeTest.java +++ b/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardKeystrokeTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; -import com.getpcpanel.commands.command.CommandMedia.VolumeButton; +import com.getpcpanel.keyboard.command.CommandMedia.VolumeButton; /** * Functional tests for the Linux keystroke feature's pure string -> X11 keysym mapping (the part of diff --git a/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardNativeConfigTest.java b/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardNativeConfigTest.java index e091c185..bf4b2bfb 100644 --- a/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardNativeConfigTest.java +++ b/src/test/java/com/getpcpanel/cpp/linux/LinuxKeyboardNativeConfigTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; -import com.getpcpanel.commands.command.CommandKeystroke; +import com.getpcpanel.keyboard.command.CommandKeystroke; /** * Generation-only test: drives the real keystroke feature so the GraalVM tracing agent records the diff --git a/src/test/java/com/getpcpanel/hid/BrightnessServiceTest.java b/src/test/java/com/getpcpanel/hid/BrightnessServiceTest.java index d5c70807..99a6e4f8 100644 --- a/src/test/java/com/getpcpanel/hid/BrightnessServiceTest.java +++ b/src/test/java/com/getpcpanel/hid/BrightnessServiceTest.java @@ -9,7 +9,7 @@ import com.getpcpanel.commands.Commands; import com.getpcpanel.commands.CommandsType; -import com.getpcpanel.commands.command.CommandBrightness; +import com.getpcpanel.device.command.CommandBrightness; import com.getpcpanel.device.DeviceType; import com.getpcpanel.profile.Profile; diff --git a/src/test/java/com/getpcpanel/hid/DialValueCalculatorTest.java b/src/test/java/com/getpcpanel/hid/DialValueCalculatorTest.java index b8740b6c..f3015be4 100644 --- a/src/test/java/com/getpcpanel/hid/DialValueCalculatorTest.java +++ b/src/test/java/com/getpcpanel/hid/DialValueCalculatorTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; -import com.getpcpanel.commands.command.CommandBrightness; +import com.getpcpanel.device.command.CommandBrightness; import com.getpcpanel.commands.command.DialAction.DialCommandParams; import com.getpcpanel.profile.dto.KnobSetting; import com.getpcpanel.util.Util; diff --git a/src/test/java/com/getpcpanel/hid/DialValueTest.java b/src/test/java/com/getpcpanel/hid/DialValueTest.java index abec6cc8..721859b8 100644 --- a/src/test/java/com/getpcpanel/hid/DialValueTest.java +++ b/src/test/java/com/getpcpanel/hid/DialValueTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import com.getpcpanel.commands.command.CommandBrightness; +import com.getpcpanel.device.command.CommandBrightness; import com.getpcpanel.commands.command.DialAction.DialCommandParams; import com.getpcpanel.profile.dto.KnobSetting; diff --git a/src/test/java/com/getpcpanel/hid/InputInterpreterTest.java b/src/test/java/com/getpcpanel/hid/InputInterpreterTest.java index 7e5e6da3..27f9697b 100644 --- a/src/test/java/com/getpcpanel/hid/InputInterpreterTest.java +++ b/src/test/java/com/getpcpanel/hid/InputInterpreterTest.java @@ -15,7 +15,7 @@ import com.getpcpanel.commands.Commands; import com.getpcpanel.commands.CommandsType; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandVolumeProcessMute; +import com.getpcpanel.volume.command.CommandVolumeProcessMute; import com.getpcpanel.cpp.MuteType; import com.getpcpanel.device.DeviceType; import com.getpcpanel.profile.BaseLayerService; diff --git a/src/test/java/com/getpcpanel/profile/BaseLayerServiceTest.java b/src/test/java/com/getpcpanel/profile/BaseLayerServiceTest.java index 6597827e..43c28ffe 100644 --- a/src/test/java/com/getpcpanel/profile/BaseLayerServiceTest.java +++ b/src/test/java/com/getpcpanel/profile/BaseLayerServiceTest.java @@ -10,7 +10,7 @@ import com.getpcpanel.commands.Commands; import com.getpcpanel.commands.CommandsType; -import com.getpcpanel.commands.command.CommandProfile; +import com.getpcpanel.profile.command.CommandProfile; import com.getpcpanel.profile.dto.LightingConfig; import com.getpcpanel.profile.dto.LightingConfig.LightingMode; import com.getpcpanel.profile.dto.SingleKnobLightingConfig.SINGLE_KNOB_MODE; diff --git a/src/test/java/com/getpcpanel/volume/FocusVolumeOverrideServiceTest.java b/src/test/java/com/getpcpanel/volume/FocusVolumeOverrideServiceTest.java index 56c49652..1d55f9c1 100644 --- a/src/test/java/com/getpcpanel/volume/FocusVolumeOverrideServiceTest.java +++ b/src/test/java/com/getpcpanel/volume/FocusVolumeOverrideServiceTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test; import com.getpcpanel.commands.command.Command; -import com.getpcpanel.commands.command.CommandVolumeProcess; +import com.getpcpanel.volume.command.CommandVolumeProcess; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.commands.command.DialAction.DialCommandParams; import com.getpcpanel.cpp.AudioDevice; From 3d3cca72f1913a46fb82626bbe2d1ae3032337b2 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 12:45:55 +0200 Subject: [PATCH 10/49] docs: reflect decentralized CommandModule registry + core-command split Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/feature-module-structure.md | 83 ++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/docs/feature-module-structure.md b/docs/feature-module-structure.md index b8c5d176..801ed569 100644 --- a/docs/feature-module-structure.md +++ b/docs/feature-module-structure.md @@ -6,32 +6,44 @@ This document is the source of truth for the end state and the order we get ther ### Implemented so far -- **Stable type-id foundation.** `Command` now uses `@JsonTypeInfo(Id.NAME)` + an explicit - `@JsonSubTypes` registry. Each subtype's `name` is its *historical fully-qualified class name*, - frozen as a location-independent logical id. Saved `profiles.json`, the generated TS `_type` - union, and the frontend catalog are byte-for-byte unchanged, but command classes can now move - package freely. `CommandSubtypeRegistryTest` guards completeness (every concrete command - registered) and migration (every persisted id still deserializes). -- **All integration commands relocated** out of the shared `commands/command` pile into their - feature `*.command` packages via `git mv`: VoiceMeeter, OBS, OSC, MQTT (WaveLink/Discord/Home - Assistant were already there). `commands/command` now holds only the engine + genuinely-core - commands. +- **Fully decentralized command registry — no central list anywhere.** `Command` uses + `@JsonTypeInfo(Id.NAME)` with **no** `@JsonSubTypes`. Each concrete command declares its own stable + id with `@JsonTypeName` *in its own file*, and each feature owns an `@ApplicationScoped` + `CommandModule` bean that lists only its own commands. `CommandSubtypeRegistrar` (an + `ObjectMapperCustomizer`) collects every module via CDI `@All` and registers them with Jackson — + the same `@All`-discovered SPI pattern the codebase already uses for `MuteStateResolver`, + `IIconHandler`, `DeviceProvider`. **Adding a command or a whole new feature/plugin touches nothing + outside its package.** The id is the command's historical FQCN, frozen as a logical name, so saved + `profiles.json`, the generated TS `_type` union, and the frontend catalog are byte-for-byte + unchanged. `typescript-generator` emits the `_type` literals from `@JsonTypeName` (verified). +- **Every command — integration *and* core — now lives in its own feature `*.command` module.** + Integrations: VoiceMeeter, OBS, OSC, MQTT moved in (WaveLink/Discord/Home Assistant already were). + Core split out of the old catch-all into: `volume.command`, `keyboard.command` (keystroke + media), + `program.command` (run/shortcut/end-program), `device.command` (brightness), `profile.command`, + `analogbands.command`, `output.command` (HTTP + the `CommandValueOutput` base). `commands/command` + now holds **only the engine**: `Command`, `CommandNoOp`, `CommandConverter`, the + `Dial/Button/DeviceAction` SPIs, and the `CommandModule` SPI + registrar. - **`VoiceMeeterMuteResolver`** moved from `mutecolor/` into the `voicemeeter` module (still found via - the `@All List` SPI). + `@All List`). - **pom `classPatterns`** collapsed to one glob `com.getpcpanel.**.command.**` — new feature command packages need no build-config edit. -Full backend suite green except 3 pre-existing, environment-specific `FocusVolumeOverrideServiceTest` -failures unrelated to this work; frontend `tsc` clean. +Guards (all green): `CommandSubtypeRegistryTest` enforces every command self-identifies with a unique +`@JsonTypeName`, the `CommandModule` SPI covers exactly the concrete set (none missing/stale/dup), and +every id resolves; `CommandSubtypeRegistrarTest` checks the registrar wiring; +`ReflectionRegistrationCoverageTest` stays green. Full backend suite green except 3 pre-existing, +environment-specific `FocusVolumeOverrideServiceTest` failures unrelated to this work; frontend `tsc` +clean; TS `_type` literal set byte-identical. + +**Adding a command is now entirely package-local:** drop the class in `com.getpcpanel..command` +with `@JsonTypeName("…")`, and add it to that feature's `CommandModule`. Nothing central changes. > **Note on the id scheme vs. the original plan.** The approved design called for *pretty* ids -> (`voicemeeter.advanced`) with the old FQCN as a legacy alias. The implemented foundation instead -> **freezes the current FQCN as the stable logical id** — this is the strictly safer stepping stone: -> it requires *zero* change to saves, the generated TS, or the frontend, while still decoupling the -> persisted id from the class's package (the whole point). Switching to pretty ids later is a clean, -> separable follow-up: add `legacyTypes = { "" }` to each command's future `@CommandMeta`, -> emit the pretty id as the primary `@JsonSubTypes` name, and regenerate the frontend catalog from the -> annotations. That step *does* touch the frontend, so it is best done alongside the generator below. +> (`voicemeeter.advanced`). The implementation **freezes the current FQCN as the stable logical id** — +> the strictly safer stepping stone: zero change to saves, the generated TS, or the frontend, while +> still decoupling the persisted id from the class's package. Switching to pretty ids later just adds +> `legacyTypes` aliases on each command and regenerates the frontend catalog (the one remaining +> central artifact — see the generator phase below). ## Goal @@ -222,20 +234,21 @@ CDI-discovery pattern this refactor leans on. Each phase is independently committable and keeps the build + coverage/parity tests green. -1. **✅ DONE — Type-id infrastructure (no moves).** `Command` switched to `Id.NAME` + `@JsonSubTypes` - with frozen-FQCN names; `CommandSubtypeRegistryTest` guards completeness + migration. Purely - additive — no behaviour change. (Implemented as frozen FQCN ids rather than the custom - `@JsonTypeIdResolver`/`@CommandMeta` form — see the note in *Implemented so far* above.) -2. **✅ DONE — VoiceMeeter commands → `voicemeeter.command`** (git-mv). `VoiceMeeterMuteResolver` also - moved into the module. classPatterns collapsed to the glob. -3. **✅ DONE — OBS commands → `obs.command`** (git-mv). -4. **✅ DONE — OSC + MQTT commands → `osc.command` / `mqtt.command`** (git-mv). -5. **TODO — Annotation-driven catalog + pretty ids.** Add `@CommandMeta` (+ `@FieldMeta` where simple) - to every command, mirroring `command-catalog.ts`. Build a generator that emits the command-level - metadata into a generated TS catalog; the frontend keeps its hand-written composite-field renderers - and live-source wiring. Switch the `@JsonSubTypes` primary names to pretty ids with the old FQCNs as - `legacyTypes` aliases, and regenerate the catalog. (This is the step that touches the frontend, so - verify with `tsc`/`npm run build`.) +1. **✅ DONE — Frozen-id type registry.** `Command` → `Id.NAME` with per-class `@JsonTypeName` ids + (frozen FQCNs), so command classes can move package with no save/TS/frontend impact. +2. **✅ DONE — Integration commands relocated** (git-mv): VoiceMeeter, OBS, OSC, MQTT into their + `*.command` packages; `VoiceMeeterMuteResolver` into the `voicemeeter` module; classPatterns glob. +3. **✅ DONE — Decentralized registry (no central list).** Dropped `@JsonSubTypes` entirely; added the + `CommandModule` CDI SPI + `CommandSubtypeRegistrar` (`@All`-collected `ObjectMapperCustomizer`); each + feature self-registers. Guard tests rewritten for the decentralized invariants. +4. **✅ DONE — Core commands split into feature modules.** `volume`, `keyboard`, `program`, `device`, + `profile`, `analogbands`, `output` — `commands/command` is now engine-only. +5. **TODO — Annotation-driven frontend catalog + pretty ids.** Add `@CommandMeta` (+ `@FieldMeta`) and a + build-time generator that emits the command-level catalog metadata (the frontend keeps its + hand-written composite-field renderers + live-source wiring). The generated `command-catalog` is the + *one remaining central artifact*; generating it eliminates the last hand-maintained registry. Then + optionally switch the `@JsonTypeName` ids to pretty form with the old FQCNs as `legacyTypes` aliases. + (Touches the frontend — verify with `tsc`/`npm run build`.) 6. **TODO — Remaining non-command consolidation.** `VoiceMeeterResource`/`ObsResource`/`OscResource` → `rest/`; migrate the hardcoded `IconService` VoiceMeeter/OBS handlers to the `IIconHandler` SPI (mirror `WaveLinkIconHandler`); per-feature settings records local; delete the @@ -244,7 +257,7 @@ Each phase is independently committable and keeps the build + coverage/parity te `ProxyRegistrationCoverageTest`, `NativeBuildArgsParityTest`; build native if feasible. Git history is preserved throughout by using `git mv` for every relocation. Phases 1–4 are committed -and green (one commit each). +and green. ## Risks & guards From dcc5010a4a192a74c0a12d5d59ac06f883fcb890 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 13:49:27 +0200 Subject: [PATCH 11/49] =?UTF-8?q?feat(commands):=20add=20@CommandMeta=20?= =?UTF-8?q?=E2=80=94=20Java=20source=20of=20truth=20for=20the=20assignable?= =?UTF-8?q?-command=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Annotate the 39 user-assignable commands with @CommandMeta(label, category, kinds, integration, icon) in com.getpcpanel.commands.meta — the picker/registry metadata that is currently hand-maintained in the frontend command-catalog.ts. Values mirror the catalog exactly (verified). Purely additive: nothing consumes the annotation yet; the next commit adds a build-time generator that emits the frontend command registry from these annotations, so a command's metadata lives in Java next to the command, in its own feature package. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../command/CommandAnalogBands.java | 4 ++ .../commands/meta/CommandCategory.java | 6 +++ .../getpcpanel/commands/meta/CommandKind.java | 6 +++ .../getpcpanel/commands/meta/CommandMeta.java | 36 ++++++++++++++++++ .../device/command/CommandBrightness.java | 4 ++ .../command/CommandDiscordJoinVoice.java | 4 ++ .../command/CommandDiscordLeaveVoice.java | 4 ++ .../discord/command/CommandDiscordMute.java | 4 ++ .../command/CommandDiscordScreenShare.java | 4 ++ .../command/CommandDiscordSelfDeafen.java | 4 ++ .../command/CommandDiscordToggleVideo.java | 4 ++ .../discord/command/CommandDiscordVolume.java | 4 ++ .../command/CommandHomeAssistantAction.java | 4 ++ .../command/CommandHomeAssistantValue.java | 4 ++ .../keyboard/command/CommandKeystroke.java | 4 ++ .../keyboard/command/CommandMedia.java | 4 ++ .../mqtt/command/CommandMqttPublish.java | 4 ++ .../obs/command/CommandObsAction.java | 4 ++ .../obs/command/CommandObsMuteSource.java | 4 ++ .../obs/command/CommandObsSetScene.java | 4 ++ .../command/CommandObsSetSourceVolume.java | 4 ++ .../osc/command/CommandOscSend.java | 4 ++ .../output/command/CommandHttpRequest.java | Bin 5404 -> 5689 bytes .../profile/command/CommandProfile.java | 4 ++ .../program/command/CommandEndProgram.java | 4 ++ .../program/command/CommandRun.java | 4 ++ .../program/command/CommandShortcut.java | 4 ++ .../command/CommandVoiceMeeterAdvanced.java | 4 ++ .../CommandVoiceMeeterAdvancedButton.java | 4 ++ .../command/CommandVolumeDefaultDevice.java | 4 ++ .../CommandVolumeDefaultDeviceAdvanced.java | 4 ++ .../CommandVolumeDefaultDeviceToggle.java | 4 ++ .../volume/command/CommandVolumeDevice.java | 4 ++ .../command/CommandVolumeDeviceMute.java | 4 ++ .../volume/command/CommandVolumeFocus.java | 4 ++ .../command/CommandVolumeFocusMute.java | 4 ++ .../volume/command/CommandVolumeProcess.java | 4 ++ .../command/CommandVolumeProcessMute.java | 4 ++ .../CommandWaveLinkAddFocusToChannel.java | 4 ++ .../command/CommandWaveLinkChangeLevel.java | 4 ++ .../command/CommandWaveLinkChangeMute.java | 4 ++ .../command/CommandWaveLinkMainOutput.java | 4 ++ 42 files changed, 200 insertions(+) create mode 100644 src/main/java/com/getpcpanel/commands/meta/CommandCategory.java create mode 100644 src/main/java/com/getpcpanel/commands/meta/CommandKind.java create mode 100644 src/main/java/com/getpcpanel/commands/meta/CommandMeta.java diff --git a/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java b/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java index 1fcf6f8f..627461ef 100644 --- a/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java +++ b/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java @@ -8,6 +8,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.analogbands.AnalogBandColorService; @@ -39,6 +42,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandAnalogBands") +@CommandMeta(label = "Stepped switch (ranges)", category = CommandCategory.system, kinds = {CommandKind.dial}, icon = "sliders") public class CommandAnalogBands extends Command implements DialAction { private static final int MAX_RAW = 255; diff --git a/src/main/java/com/getpcpanel/commands/meta/CommandCategory.java b/src/main/java/com/getpcpanel/commands/meta/CommandCategory.java new file mode 100644 index 00000000..d1be775e --- /dev/null +++ b/src/main/java/com/getpcpanel/commands/meta/CommandCategory.java @@ -0,0 +1,6 @@ +package com.getpcpanel.commands.meta; + +/** Picker grouping for a command. Matches the frontend {@code CommandCategory} union. */ +public enum CommandCategory { + audio, system, integration +} diff --git a/src/main/java/com/getpcpanel/commands/meta/CommandKind.java b/src/main/java/com/getpcpanel/commands/meta/CommandKind.java new file mode 100644 index 00000000..8568242f --- /dev/null +++ b/src/main/java/com/getpcpanel/commands/meta/CommandKind.java @@ -0,0 +1,6 @@ +package com.getpcpanel.commands.meta; + +/** Which control slot a command is valid for. Matches the frontend {@code CommandKind} union. */ +public enum CommandKind { + dial, button +} diff --git a/src/main/java/com/getpcpanel/commands/meta/CommandMeta.java b/src/main/java/com/getpcpanel/commands/meta/CommandMeta.java new file mode 100644 index 00000000..72060a2d --- /dev/null +++ b/src/main/java/com/getpcpanel/commands/meta/CommandMeta.java @@ -0,0 +1,36 @@ +package com.getpcpanel.commands.meta; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares a command as user-assignable and carries the picker/registry metadata that used to be + * hand-maintained in the frontend {@code command-catalog.ts}. A build step generates the frontend + * command registry from these annotations (see {@code docs/feature-module-structure.md}), so the + * authoritative list of which commands exist and how they are labelled/categorised/iconified lives + * in Java, next to each command, in its own feature package. + * + *

Only the registry-level metadata is here; the per-command field editors remain in the + * frontend because they are Angular UI, not data. A command without {@code @CommandMeta} still works + * (it just is not offered in the assignment picker). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface CommandMeta { + /** Human-readable picker label. */ + String label(); + + /** Picker grouping. */ + CommandCategory category(); + + /** Control slots the command can be assigned to (dial / button). */ + CommandKind[] kinds(); + + /** Owning integration id (e.g. {@code "obs"}) for {@code category == integration}; blank for core. */ + String integration() default ""; + + /** Icon name; must be one of the frontend {@code IconName} set. */ + String icon(); +} diff --git a/src/main/java/com/getpcpanel/device/command/CommandBrightness.java b/src/main/java/com/getpcpanel/device/command/CommandBrightness.java index e4f090ea..947a7a49 100644 --- a/src/main/java/com/getpcpanel/device/command/CommandBrightness.java +++ b/src/main/java/com/getpcpanel/device/command/CommandBrightness.java @@ -4,6 +4,9 @@ import com.getpcpanel.commands.command.DialAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.hid.DeviceHolder; @@ -16,6 +19,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandBrightness") +@CommandMeta(label = "Brightness", category = CommandCategory.system, kinds = {CommandKind.dial}, icon = "sun") public class CommandBrightness extends Command implements DialAction { private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java index 9295c5a1..4df25784 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -18,6 +21,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.discord.command.CommandDiscordJoinVoice") +@CommandMeta(label = "Discord — join voice", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "plug") public final class CommandDiscordJoinVoice extends CommandDiscord implements ButtonAction { @Nullable private final String channelId; /** Cosmetic: the channel's name at configure time, for the button label (the id is what we join). */ diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java index aaedf6c0..3759e6d3 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -16,6 +19,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.discord.command.CommandDiscordLeaveVoice") +@CommandMeta(label = "Discord — leave voice", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "log-out") public final class CommandDiscordLeaveVoice extends CommandDiscord implements ButtonAction { @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java index dc1dc067..2078a491 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -23,6 +26,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.discord.command.CommandDiscordMute") +@CommandMeta(label = "Discord — mute", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "mic-off") public final class CommandDiscordMute extends CommandDiscord implements ButtonAction { public static final String SELF = "self"; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java index bfe256f1..aac9d084 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java @@ -10,6 +10,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.discord.DiscordService; @@ -29,6 +32,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.discord.command.CommandDiscordScreenShare") +@CommandMeta(label = "Discord — screen share", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "monitor") public final class CommandDiscordScreenShare extends CommandDiscord implements ButtonAction { public enum Mode { SCREEN, PROCESS, FOCUS } diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java index cb6b2af9..15a84674 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -18,6 +21,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.discord.command.CommandDiscordSelfDeafen") +@CommandMeta(label = "Discord — deafen self", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "volume-x") public final class CommandDiscordSelfDeafen extends CommandDiscord implements ButtonAction { private final MuteType muteType; @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java index 79db504e..78d73df2 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -16,6 +19,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.discord.command.CommandDiscordToggleVideo") +@CommandMeta(label = "Discord — toggle camera", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "film") public final class CommandDiscordToggleVideo extends CommandDiscord implements ButtonAction { @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java index b34d0da9..465bc333 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; @@ -23,6 +26,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.discord.command.CommandDiscordVolume") +@CommandMeta(label = "Discord — volume", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "discord", icon = "volume") public final class CommandDiscordVolume extends CommandDiscord implements DialAction { public static final String MIC = "mic"; public static final String OUTPUT = "output"; diff --git a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java index 927d2978..0842df85 100644 --- a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java +++ b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -21,6 +24,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.homeassistant.command.CommandHomeAssistantAction") +@CommandMeta(label = "Home Assistant — perform action", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "homeassistant", icon = "zap") public class CommandHomeAssistantAction extends CommandHomeAssistant implements ButtonAction { private final String action; @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java index 76e8c7ee..825541ea 100644 --- a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java +++ b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.ValueInterpolator; @@ -24,6 +27,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.homeassistant.command.CommandHomeAssistantValue") +@CommandMeta(label = "Home Assistant — set value", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "homeassistant", icon = "sliders") public class CommandHomeAssistantValue extends CommandHomeAssistant implements DialAction { private final String action; @Nullable private final Double min; diff --git a/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java b/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java index 5353995f..238a53fe 100644 --- a/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java +++ b/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.KeyMacro; @@ -27,6 +30,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandKeystroke") +@CommandMeta(label = "Keystroke", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "keyboard") public class CommandKeystroke extends Command implements ButtonAction { public enum KeystrokeType { /** Single key combination (modifiers + one key). */ diff --git a/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java b/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java index 6aaad4bf..c68dd562 100644 --- a/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java +++ b/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java @@ -10,6 +10,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.util.CdiHelper; import com.getpcpanel.util.OsxMediaControl; @@ -35,6 +38,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandMedia") +@CommandMeta(label = "Media", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "play") public class CommandMedia extends Command implements ButtonAction { private static final int WM_APPCOMMAND = 0x0319; private final VolumeButton button; diff --git a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java index b400b123..3748fa89 100644 --- a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java +++ b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.mqtt.MqttService; import com.getpcpanel.output.command.CommandValueOutput; @@ -23,6 +26,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandMqttPublish") +@CommandMeta(label = "MQTT publish", category = CommandCategory.system, kinds = {CommandKind.dial, CommandKind.button}, icon = "zap") public class CommandMqttPublish extends CommandValueOutput { private final String topic; private final String payload; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java index f6fc0e29..fe16f37a 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.obs.OBS; import com.getpcpanel.commands.command.ButtonAction; @@ -17,6 +20,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandObsAction") +@CommandMeta(label = "OBS — stream / record", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "film") public class CommandObsAction extends CommandObs implements ButtonAction { private final ObsActionType action; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java index 180021fc..77cca77b 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; import com.getpcpanel.obs.OBS; @@ -14,6 +17,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandObsMuteSource") +@CommandMeta(label = "OBS — mute source", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "mic-off") public class CommandObsMuteSource extends CommandObs implements ButtonAction { private final String source; private final MuteType type; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java index e1c27cac..fae47c31 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.util.CdiHelper; @@ -13,6 +16,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandObsSetScene") +@CommandMeta(label = "OBS — switch scene", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "film") public class CommandObsSetScene extends CommandObs implements ButtonAction { private final String scene; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java index f5a259da..beaec0c1 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; import com.getpcpanel.util.CdiHelper; @@ -13,6 +16,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandObsSetSourceVolume") +@CommandMeta(label = "OBS — source volume", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "obs", icon = "sliders") public class CommandObsSetSourceVolume extends CommandObs implements DialAction { private final String sourceName; private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java index 18a9241e..b5e66ccc 100644 --- a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java +++ b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.osc.OSCService; import com.getpcpanel.output.command.CommandValueOutput; @@ -22,6 +25,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandOscSend") +@CommandMeta(label = "OSC send", category = CommandCategory.system, kinds = {CommandKind.dial, CommandKind.button}, icon = "sliders") public class CommandOscSend extends CommandValueOutput { private final String address; diff --git a/src/main/java/com/getpcpanel/output/command/CommandHttpRequest.java b/src/main/java/com/getpcpanel/output/command/CommandHttpRequest.java index 98a0fcb7a3a91a420f4df60536893c7e4f5ea7e3..85d07b951bc9cfcdcd3fe5d47d364216145e5766 100644 GIT binary patch delta 229 zcmbQEwNq!qAI8abOx%+dSUE*6H8Ll^NT7c7cxnJBn6Nqy)*Mt zuuAv>P1yXNF`T*H0cs9NL?b6LDK$sIRzb-lBqTtgD7COOwYWq{MFiL&a5~2 E0hwk`ga7~l delta 21 dcmdm~Ge>K~AI8bY7!@~fVbWyYoXmEO9{^>u2x devices; private int currentIdx; // Used as a fallback for when the current idx cannot be found diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java index 87971518..b066aa98 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java @@ -3,6 +3,9 @@ import com.getpcpanel.commands.command.DialAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; @@ -12,6 +15,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDevice") +@CommandMeta(label = "Device volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume") public class CommandVolumeDevice extends CommandVolume implements DialAction { private final String deviceId; private final boolean unMuteOnVolumeChange; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java index ca1d8460..0cae4b63 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java @@ -3,6 +3,9 @@ import com.getpcpanel.commands.command.ButtonAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; @@ -12,6 +15,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDeviceMute") +@CommandMeta(label = "Device mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x") public class CommandVolumeDeviceMute extends CommandVolume implements ButtonAction { private final String deviceId; private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java index 0c2f6d63..3b0cf6ee 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java @@ -3,6 +3,9 @@ import com.getpcpanel.commands.command.DialAction; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.volume.VolumeCoordinatorService; import com.getpcpanel.util.CdiHelper; @@ -13,6 +16,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandVolumeFocus") +@CommandMeta(label = "Focused-app volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume") public class CommandVolumeFocus extends CommandVolume implements DialAction { private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java index 01e59f4c..06d3c764 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java @@ -5,6 +5,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.getpcpanel.cpp.MuteType; import lombok.Getter; @@ -13,6 +16,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandVolumeFocusMute") +@CommandMeta(label = "Focused-app mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x") public class CommandVolumeFocusMute extends CommandVolume implements ButtonAction { private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java index 6dfeaa8d..d8cac8a0 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.cpp.MuteType; @@ -15,6 +18,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandVolumeProcess") +@CommandMeta(label = "App volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume") public class CommandVolumeProcess extends CommandVolume implements DialAction { private final List processName; private final String device; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java index b152cfe9..9c001b6a 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java @@ -5,6 +5,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.getpcpanel.cpp.MuteType; import lombok.Getter; @@ -13,6 +16,7 @@ @Getter @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.commands.command.CommandVolumeProcessMute") +@CommandMeta(label = "App mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x") public class CommandVolumeProcessMute extends CommandVolume implements ButtonAction { private final Set processName; private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java index 2fa16da1..3ba72051 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -17,6 +20,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel") +@CommandMeta(label = "Wave Link — add focused app", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "plus") public final class CommandWaveLinkAddFocusToChannel extends CommandWaveLink implements ButtonAction { @Nullable private final String id; @Nullable private final String name; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java index 39410a96..ce695624 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.DialAction; @@ -17,6 +20,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel") +@CommandMeta(label = "Wave Link — level", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "wavelink", icon = "sliders") public final class CommandWaveLinkChangeLevel extends CommandWaveLinkChange implements DialAction { @Nullable private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java index 91612a07..7b940f1e 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; import com.getpcpanel.cpp.MuteType; @@ -19,6 +22,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute") +@CommandMeta(label = "Wave Link — mute", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "mic-off") public final class CommandWaveLinkChangeMute extends CommandWaveLinkChange implements ButtonAction { private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java index b931bbe4..76958fa9 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java @@ -6,6 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.meta.CommandCategory; +import com.getpcpanel.commands.meta.CommandKind; +import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; import com.getpcpanel.commands.command.ButtonAction; @@ -17,6 +20,7 @@ @Log4j2 @ToString(callSuper = true) @JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput") +@CommandMeta(label = "Wave Link — main output", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "volume") public final class CommandWaveLinkMainOutput extends CommandWaveLink implements ButtonAction { @Nullable private final String id; @Nullable private final String name; From 705601c951925141db5ed6fdba70e7ec9589d9e8 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 13:57:08 +0200 Subject: [PATCH 12/49] feat(commands): generate the frontend command registry from @CommandMeta The hand-maintained command-level registry in command-catalog.ts (label, category, kinds, integration, icon per command) is now generated from the Java @CommandMeta annotations into command-registry.generated.ts. command-catalog.ts consumes it and keeps only the field editors (buildEmpty + fields[]), which are Angular UI, not a registry. So 'which commands exist and how they're classified' is retrieved from the Java code, per command, in its own feature package. - CommandRegistryGeneratorTest emits the file and guards staleness (regenerate with -Dpcpanel.generate.catalog); it also asserts every @CommandMeta is a concrete command with a @JsonTypeName id. - COMMANDS is assembled by joining the generated metadata with the hand-written field schemas by type id. Values are identical to the previous hand catalog (verified), so frontend behaviour is unchanged; tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../app/features/commands/command-catalog.ts | 91 ++++++++------ .../commands/command-registry.generated.ts | 55 ++++++++ .../meta/CommandRegistryGeneratorTest.java | 117 ++++++++++++++++++ 3 files changed, 223 insertions(+), 40 deletions(-) create mode 100644 src/main/webui/src/app/features/commands/command-registry.generated.ts create mode 100644 src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java diff --git a/src/main/webui/src/app/features/commands/command-catalog.ts b/src/main/webui/src/app/features/commands/command-catalog.ts index 91d5be34..0e480681 100644 --- a/src/main/webui/src/app/features/commands/command-catalog.ts +++ b/src/main/webui/src/app/features/commands/command-catalog.ts @@ -1,4 +1,5 @@ import { IconName } from '../../ui'; +import { GENERATED_COMMANDS } from './command-registry.generated'; /** * Data-driven catalog of every assignable command. One generic editor renders a @@ -56,10 +57,11 @@ const MUTE_OPTS = [ ]; const dialParams = () => ({ invert: false, moveStart: 0, moveEnd: 0 }); -export const COMMANDS: CommandDef[] = [ +interface FieldDef_ { type: string; buildEmpty: () => Record; fields: FieldDef[]; } +const FIELD_DEFS: FieldDef_[] = [ // ── AUDIO ──────────────────────────────────────────────────────────────── { - type: P + 'CommandVolumeProcess', label: 'App volume', category: 'audio', kinds: ['dial'], icon: 'volume', + type: P + 'CommandVolumeProcess', buildEmpty: () => ({ _type: P + 'CommandVolumeProcess', device: '', processName: [], unMuteOnVolumeChange: false, dialParams: dialParams(), invert: false }), fields: [ { kind: 'apps', key: 'processName', label: 'Applications' }, @@ -67,7 +69,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandVolumeProcessMute', label: 'App mute', category: 'audio', kinds: ['button'], icon: 'volume-x', + type: P + 'CommandVolumeProcessMute', buildEmpty: () => ({ _type: P + 'CommandVolumeProcessMute', muteType: 'toggle', processName: [], overlayText: '' }), fields: [ { kind: 'apps', key: 'processName', label: 'Applications' }, @@ -75,17 +77,17 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandVolumeFocus', label: 'Focused-app volume', category: 'audio', kinds: ['dial'], icon: 'volume', + type: P + 'CommandVolumeFocus', buildEmpty: () => ({ _type: P + 'CommandVolumeFocus', dialParams: dialParams(), invert: false }), fields: [], }, { - type: P + 'CommandVolumeFocusMute', label: 'Focused-app mute', category: 'audio', kinds: ['button'], icon: 'volume-x', + type: P + 'CommandVolumeFocusMute', buildEmpty: () => ({ _type: P + 'CommandVolumeFocusMute', muteType: 'toggle', overlayText: '' }), fields: [{ kind: 'mute', key: 'muteType', label: 'Action' }], }, { - type: P + 'CommandVolumeDevice', label: 'Device volume', category: 'audio', kinds: ['dial'], icon: 'volume', + type: P + 'CommandVolumeDevice', buildEmpty: () => ({ _type: P + 'CommandVolumeDevice', deviceId: '', unMuteOnVolumeChange: false, dialParams: dialParams(), invert: false }), fields: [ { kind: 'device', key: 'deviceId', label: 'Audio device', filter: 'all', defaultLabel: 'Default device' }, @@ -93,7 +95,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandVolumeDeviceMute', label: 'Device mute', category: 'audio', kinds: ['button'], icon: 'volume-x', + type: P + 'CommandVolumeDeviceMute', buildEmpty: () => ({ _type: P + 'CommandVolumeDeviceMute', deviceId: '', muteType: 'toggle', overlayText: '' }), fields: [ { kind: 'device', key: 'deviceId', label: 'Audio device', filter: 'all', defaultLabel: 'Default device' }, @@ -101,17 +103,17 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandVolumeDefaultDevice', label: 'Set default device', category: 'audio', kinds: ['button'], icon: 'monitor', + type: P + 'CommandVolumeDefaultDevice', buildEmpty: () => ({ _type: P + 'CommandVolumeDefaultDevice', deviceId: '', overlayText: '' }), fields: [{ kind: 'device', key: 'deviceId', label: 'Default device', filter: 'all' }], }, { - type: P + 'CommandVolumeDefaultDeviceToggle', label: 'Cycle default device', category: 'audio', kinds: ['button'], icon: 'refresh', + type: P + 'CommandVolumeDefaultDeviceToggle', buildEmpty: () => ({ _type: P + 'CommandVolumeDefaultDeviceToggle', currentIdx: 0, devices: [], overlayText: '' }), fields: [{ kind: 'devices-list', key: 'devices', label: 'Devices to cycle' }], }, { - type: P + 'CommandVolumeDefaultDeviceAdvanced', label: 'Advanced default device', category: 'audio', kinds: ['button'], icon: 'monitor', + type: P + 'CommandVolumeDefaultDeviceAdvanced', buildEmpty: () => ({ _type: P + 'CommandVolumeDefaultDeviceAdvanced', communicationPb: '', communicationRec: '', mediaPb: '', mediaRec: '', name: '', overlayText: '' }), fields: [ { kind: 'device', key: 'mediaPb', label: 'Media — playback', filter: 'output' }, @@ -123,32 +125,32 @@ export const COMMANDS: CommandDef[] = [ // ── DEVICE & SYSTEM ──────────────────────────────────────────────────────── { - type: P + 'CommandBrightness', label: 'Brightness', category: 'system', kinds: ['dial'], icon: 'sun', + type: P + 'CommandBrightness', buildEmpty: () => ({ _type: P + 'CommandBrightness', dialParams: dialParams(), invert: false }), fields: [], }, { - type: P + 'CommandProfile', label: 'Switch profile', category: 'system', kinds: ['button'], icon: 'refresh', + type: P + 'CommandProfile', buildEmpty: () => ({ _type: P + 'CommandProfile', profile: '' }), fields: [{ kind: 'select-live', key: 'profile', label: 'Profile', source: 'profiles' }], }, { - type: P + 'CommandAnalogBands', label: 'Stepped switch (ranges)', category: 'system', kinds: ['dial'], icon: 'sliders', + type: P + 'CommandAnalogBands', buildEmpty: () => ({ _type: P + 'CommandAnalogBands', bands: [] }), fields: [{ kind: 'analog-bands' }], }, { - type: P + 'CommandRun', label: 'Run command', category: 'system', kinds: ['button'], icon: 'zap', + type: P + 'CommandRun', buildEmpty: () => ({ _type: P + 'CommandRun', command: '', overlayText: '' }), fields: [{ kind: 'text', key: 'command', label: 'Command / program', placeholder: 'e.g. notepad.exe', mono: true }], }, { - type: P + 'CommandShortcut', label: 'Run shortcut', category: 'system', kinds: ['button'], icon: 'zap', + type: P + 'CommandShortcut', buildEmpty: () => ({ _type: P + 'CommandShortcut', shortcut: '', overlayText: '' }), fields: [{ kind: 'text', key: 'shortcut', label: 'Shortcut path', placeholder: '…/app.lnk', mono: true }], }, { - type: P + 'CommandEndProgram', label: 'End program', category: 'system', kinds: ['button'], icon: 'x', + type: P + 'CommandEndProgram', buildEmpty: () => ({ _type: P + 'CommandEndProgram', name: '', specific: false, overlayText: '' }), fields: [ { kind: 'text', key: 'name', label: 'Process name', placeholder: 'leave blank for focused app' }, @@ -156,12 +158,12 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandKeystroke', label: 'Keystroke', category: 'system', kinds: ['button'], icon: 'keyboard', + type: P + 'CommandKeystroke', buildEmpty: () => ({ _type: P + 'CommandKeystroke', type: 'KEY', keystroke: '', text: '', overlayText: '' }), fields: [{ kind: 'keystroke' }], }, { - type: P + 'CommandMedia', label: 'Media', category: 'system', kinds: ['button'], icon: 'play', + type: P + 'CommandMedia', buildEmpty: () => ({ _type: P + 'CommandMedia', button: 'playPause', spotify: false, overlayText: '' }), fields: [ { @@ -176,7 +178,7 @@ export const COMMANDS: CommandDef[] = [ // Generic outputs: send to anything over HTTP/MQTT/OSC. On a dial the position maps (min/max or // formula) to the number replacing {{ value }}; on a button the value resolves at full scale. { - type: P + 'CommandHttpRequest', label: 'HTTP request', category: 'system', kinds: ['dial', 'button'], icon: 'zap', + type: P + 'CommandHttpRequest', buildEmpty: () => ({ _type: P + 'CommandHttpRequest', url: '', method: 'GET', headers: '', body: '', min: 0, max: 100, formula: '', dialParams: dialParams(), invert: false }), fields: [ { kind: 'text', key: 'url', label: 'URL', placeholder: 'https://host/path?v={{ value }}', mono: true }, @@ -194,7 +196,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandMqttPublish', label: 'MQTT publish', category: 'system', kinds: ['dial', 'button'], icon: 'zap', + type: P + 'CommandMqttPublish', buildEmpty: () => ({ _type: P + 'CommandMqttPublish', topic: '', payload: '', min: 0, max: 100, formula: '', dialParams: dialParams(), invert: false }), fields: [ { kind: 'text', key: 'topic', label: 'Topic', placeholder: 'home/livingroom/light', mono: true }, @@ -205,7 +207,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandOscSend', label: 'OSC send', category: 'system', kinds: ['dial', 'button'], icon: 'sliders', + type: P + 'CommandOscSend', buildEmpty: () => ({ _type: P + 'CommandOscSend', address: '', min: 0, max: 100, formula: '', dialParams: dialParams(), invert: false }), fields: [ { kind: 'text', key: 'address', label: 'OSC address', placeholder: '/track/1/volume', mono: true }, @@ -217,17 +219,17 @@ export const COMMANDS: CommandDef[] = [ // ── INTEGRATIONS ─────────────────────────────────────────────────────────── { - type: P + 'CommandObsSetSourceVolume', label: 'OBS — source volume', category: 'integration', integration: 'obs', kinds: ['dial'], icon: 'sliders', + type: P + 'CommandObsSetSourceVolume', buildEmpty: () => ({ _type: P + 'CommandObsSetSourceVolume', sourceName: '', dialParams: dialParams(), invert: false }), fields: [{ kind: 'select-live', key: 'sourceName', label: 'Source', source: 'obs-sources' }], }, { - type: P + 'CommandObsSetScene', label: 'OBS — switch scene', category: 'integration', integration: 'obs', kinds: ['button'], icon: 'film', + type: P + 'CommandObsSetScene', buildEmpty: () => ({ _type: P + 'CommandObsSetScene', scene: '', overlayText: '' }), fields: [{ kind: 'select-live', key: 'scene', label: 'Scene', source: 'obs-scenes' }], }, { - type: P + 'CommandObsMuteSource', label: 'OBS — mute source', category: 'integration', integration: 'obs', kinds: ['button'], icon: 'mic-off', + type: P + 'CommandObsMuteSource', buildEmpty: () => ({ _type: P + 'CommandObsMuteSource', source: '', type: 'toggle', overlayText: '' }), fields: [ { kind: 'select-live', key: 'source', label: 'Source', source: 'obs-sources' }, @@ -235,7 +237,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandObsAction', label: 'OBS — stream / record', category: 'integration', integration: 'obs', kinds: ['button'], icon: 'film', + type: P + 'CommandObsAction', buildEmpty: () => ({ _type: P + 'CommandObsAction', action: 'TOGGLE_STREAM', overlayText: '' }), fields: [ { @@ -250,7 +252,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandVoiceMeeterAdvanced', label: 'Voicemeeter — parameter', category: 'integration', integration: 'voicemeeter', kinds: ['dial'], icon: 'sliders', + type: P + 'CommandVoiceMeeterAdvanced', buildEmpty: () => ({ _type: P + 'CommandVoiceMeeterAdvanced', ct: 'NEG_12_TO_12', fullParam: '', dialParams: dialParams(), invert: false }), fields: [ { kind: 'select-live', key: 'fullParam', label: 'Parameter', source: 'vm-advanced' }, @@ -264,7 +266,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: P + 'CommandVoiceMeeterAdvancedButton', label: 'Voicemeeter — button', category: 'integration', integration: 'voicemeeter', kinds: ['button'], icon: 'sliders', + type: P + 'CommandVoiceMeeterAdvancedButton', buildEmpty: () => ({ _type: P + 'CommandVoiceMeeterAdvancedButton', bt: 'TOGGLE', fullParam: '', stringValue: '', overlayText: '' }), fields: [ { kind: 'select-live', key: 'fullParam', label: 'Parameter', source: 'vm-advanced' }, @@ -277,7 +279,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: WL + 'CommandWaveLinkChangeLevel', label: 'Wave Link — level', category: 'integration', integration: 'wavelink', kinds: ['dial'], icon: 'sliders', + type: WL + 'CommandWaveLinkChangeLevel', buildEmpty: () => ({ _type: WL + 'CommandWaveLinkChangeLevel', commandType: 'Channel', id1: '', id2: '', dialParams: dialParams(), invert: false }), fields: [ { @@ -290,7 +292,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: WL + 'CommandWaveLinkChangeMute', label: 'Wave Link — mute', category: 'integration', integration: 'wavelink', kinds: ['button'], icon: 'mic-off', + type: WL + 'CommandWaveLinkChangeMute', buildEmpty: () => ({ _type: WL + 'CommandWaveLinkChangeMute', commandType: 'Channel', id1: '', id2: '', muteType: 'toggle', overlayText: '' }), fields: [ { @@ -304,17 +306,17 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: WL + 'CommandWaveLinkMainOutput', label: 'Wave Link — main output', category: 'integration', integration: 'wavelink', kinds: ['button'], icon: 'volume', + type: WL + 'CommandWaveLinkMainOutput', buildEmpty: () => ({ _type: WL + 'CommandWaveLinkMainOutput', id: '', name: '', overlayText: '' }), fields: [{ kind: 'select-live', key: 'id', label: 'Output', source: 'wl-outputs' }], }, { - type: WL + 'CommandWaveLinkAddFocusToChannel', label: 'Wave Link — add focused app', category: 'integration', integration: 'wavelink', kinds: ['button'], icon: 'plus', + type: WL + 'CommandWaveLinkAddFocusToChannel', buildEmpty: () => ({ _type: WL + 'CommandWaveLinkAddFocusToChannel', id: '', name: '', overlayText: '' }), fields: [{ kind: 'select-live', key: 'id', label: 'Channel', source: 'wl-channels' }], }, { - type: DC + 'CommandDiscordMute', label: 'Discord — mute', category: 'integration', integration: 'discord', kinds: ['button'], icon: 'mic-off', + type: DC + 'CommandDiscordMute', buildEmpty: () => ({ _type: DC + 'CommandDiscordMute', target: 'self', muteType: 'toggle', overlayText: '' }), fields: [ { kind: 'select-live', key: 'target', label: 'Target', source: 'discord-mute-targets', searchable: true }, @@ -322,12 +324,12 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: DC + 'CommandDiscordSelfDeafen', label: 'Discord — deafen self', category: 'integration', integration: 'discord', kinds: ['button'], icon: 'volume-x', + type: DC + 'CommandDiscordSelfDeafen', buildEmpty: () => ({ _type: DC + 'CommandDiscordSelfDeafen', muteType: 'toggle', overlayText: '' }), fields: [{ kind: 'mute', key: 'muteType', label: 'Action' }], }, { - type: DC + 'CommandDiscordVolume', label: 'Discord — volume', category: 'integration', integration: 'discord', kinds: ['dial'], icon: 'volume', + type: DC + 'CommandDiscordVolume', buildEmpty: () => ({ _type: DC + 'CommandDiscordVolume', target: 'mic', clearMuteOnChange: false, dialParams: dialParams(), invert: false }), fields: [ { kind: 'select-live', key: 'target', label: 'Target', source: 'discord-volume-targets', searchable: true }, @@ -335,7 +337,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: DC + 'CommandDiscordScreenShare', label: 'Discord — screen share', category: 'integration', integration: 'discord', kinds: ['button'], icon: 'monitor', + type: DC + 'CommandDiscordScreenShare', buildEmpty: () => ({ _type: DC + 'CommandDiscordScreenShare', mode: 'SCREEN', processName: [], overlayText: '' }), fields: [ { @@ -347,22 +349,22 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: DC + 'CommandDiscordToggleVideo', label: 'Discord — toggle camera', category: 'integration', integration: 'discord', kinds: ['button'], icon: 'film', + type: DC + 'CommandDiscordToggleVideo', buildEmpty: () => ({ _type: DC + 'CommandDiscordToggleVideo', overlayText: '' }), fields: [], }, { - type: DC + 'CommandDiscordJoinVoice', label: 'Discord — join voice', category: 'integration', integration: 'discord', kinds: ['button'], icon: 'plug', + type: DC + 'CommandDiscordJoinVoice', buildEmpty: () => ({ _type: DC + 'CommandDiscordJoinVoice', channelId: '', channelName: '', overlayText: '' }), fields: [{ kind: 'select-live', key: 'channelId', label: 'Voice channel', source: 'discord-channels' }], }, { - type: DC + 'CommandDiscordLeaveVoice', label: 'Discord — leave voice', category: 'integration', integration: 'discord', kinds: ['button'], icon: 'log-out', + type: DC + 'CommandDiscordLeaveVoice', buildEmpty: () => ({ _type: DC + 'CommandDiscordLeaveVoice', overlayText: '' }), fields: [], }, { - type: HA + 'CommandHomeAssistantValue', label: 'Home Assistant — set value', category: 'integration', integration: 'homeassistant', kinds: ['dial'], icon: 'sliders', + type: HA + 'CommandHomeAssistantValue', buildEmpty: () => ({ _type: HA + 'CommandHomeAssistantValue', server: '', action: '', min: 0, max: 100, formula: '', dialParams: dialParams(), invert: false }), fields: [ { kind: 'select-live', key: 'server', label: 'Server', source: 'ha-servers' }, @@ -374,7 +376,7 @@ export const COMMANDS: CommandDef[] = [ ], }, { - type: HA + 'CommandHomeAssistantAction', label: 'Home Assistant — perform action', category: 'integration', integration: 'homeassistant', kinds: ['button'], icon: 'zap', + type: HA + 'CommandHomeAssistantAction', buildEmpty: () => ({ _type: HA + 'CommandHomeAssistantAction', server: '', action: '', overlayText: '' }), fields: [ { kind: 'select-live', key: 'server', label: 'Server', source: 'ha-servers' }, @@ -384,6 +386,15 @@ export const COMMANDS: CommandDef[] = [ }, ]; +// COMMANDS is assembled from the Java-generated registry (label/category/kinds/integration/icon) +// joined with the hand-written field editors above, keyed by the command's persisted type id. +const FIELDS_BY_TYPE = new Map(FIELD_DEFS.map(d => [d.type, d] as const)); +export const COMMANDS: CommandDef[] = GENERATED_COMMANDS.map(g => { + const f = FIELDS_BY_TYPE.get(g.type); + if (!f) throw new Error('No field schema for command ' + g.type); + return { ...g, buildEmpty: f.buildEmpty, fields: f.fields }; +}); + export const COMMAND_BY_TYPE = new Map(COMMANDS.map(c => [c.type, c])); export function commandsForKind(kind: CommandKind): CommandDef[] { diff --git a/src/main/webui/src/app/features/commands/command-registry.generated.ts b/src/main/webui/src/app/features/commands/command-registry.generated.ts new file mode 100644 index 00000000..335d63d7 --- /dev/null +++ b/src/main/webui/src/app/features/commands/command-registry.generated.ts @@ -0,0 +1,55 @@ +// GENERATED FILE — do not edit. Source: @CommandMeta on the command classes +// (com.getpcpanel.**.command). Regenerate via CommandRegistryGeneratorTest. +import { IconName } from '../../ui'; +import type { CommandCategory, CommandKind, Integration } from './command-catalog'; + +export interface GeneratedCommand { + type: string; + label: string; + category: CommandCategory; + kinds: CommandKind[]; + integration?: Integration; + icon: IconName; +} + +export const GENERATED_COMMANDS: GeneratedCommand[] = [ + { type: 'com.getpcpanel.commands.command.CommandAnalogBands', label: 'Stepped switch (ranges)', category: 'system', kinds: ['dial'], icon: 'sliders' }, + { type: 'com.getpcpanel.commands.command.CommandBrightness', label: 'Brightness', category: 'system', kinds: ['dial'], icon: 'sun' }, + { type: 'com.getpcpanel.commands.command.CommandEndProgram', label: 'End program', category: 'system', kinds: ['button'], icon: 'x' }, + { type: 'com.getpcpanel.commands.command.CommandHttpRequest', label: 'HTTP request', category: 'system', kinds: ['dial', 'button'], icon: 'zap' }, + { type: 'com.getpcpanel.commands.command.CommandKeystroke', label: 'Keystroke', category: 'system', kinds: ['button'], icon: 'keyboard' }, + { type: 'com.getpcpanel.commands.command.CommandMedia', label: 'Media', category: 'system', kinds: ['button'], icon: 'play' }, + { type: 'com.getpcpanel.commands.command.CommandMqttPublish', label: 'MQTT publish', category: 'system', kinds: ['dial', 'button'], icon: 'zap' }, + { type: 'com.getpcpanel.commands.command.CommandObsAction', label: 'OBS — stream / record', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'film' }, + { type: 'com.getpcpanel.commands.command.CommandObsMuteSource', label: 'OBS — mute source', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'mic-off' }, + { type: 'com.getpcpanel.commands.command.CommandObsSetScene', label: 'OBS — switch scene', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'film' }, + { type: 'com.getpcpanel.commands.command.CommandObsSetSourceVolume', label: 'OBS — source volume', category: 'integration', kinds: ['dial'], integration: 'obs', icon: 'sliders' }, + { type: 'com.getpcpanel.commands.command.CommandOscSend', label: 'OSC send', category: 'system', kinds: ['dial', 'button'], icon: 'sliders' }, + { type: 'com.getpcpanel.commands.command.CommandProfile', label: 'Switch profile', category: 'system', kinds: ['button'], icon: 'refresh' }, + { type: 'com.getpcpanel.commands.command.CommandRun', label: 'Run command', category: 'system', kinds: ['button'], icon: 'zap' }, + { type: 'com.getpcpanel.commands.command.CommandShortcut', label: 'Run shortcut', category: 'system', kinds: ['button'], icon: 'zap' }, + { type: 'com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced', label: 'Voicemeeter — parameter', category: 'integration', kinds: ['dial'], integration: 'voicemeeter', icon: 'sliders' }, + { type: 'com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton', label: 'Voicemeeter — button', category: 'integration', kinds: ['button'], integration: 'voicemeeter', icon: 'sliders' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeDefaultDevice', label: 'Set default device', category: 'audio', kinds: ['button'], icon: 'monitor' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced', label: 'Advanced default device', category: 'audio', kinds: ['button'], icon: 'monitor' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle', label: 'Cycle default device', category: 'audio', kinds: ['button'], icon: 'refresh' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeDevice', label: 'Device volume', category: 'audio', kinds: ['dial'], icon: 'volume' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeDeviceMute', label: 'Device mute', category: 'audio', kinds: ['button'], icon: 'volume-x' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeFocus', label: 'Focused-app volume', category: 'audio', kinds: ['dial'], icon: 'volume' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeFocusMute', label: 'Focused-app mute', category: 'audio', kinds: ['button'], icon: 'volume-x' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeProcess', label: 'App volume', category: 'audio', kinds: ['dial'], icon: 'volume' }, + { type: 'com.getpcpanel.commands.command.CommandVolumeProcessMute', label: 'App mute', category: 'audio', kinds: ['button'], icon: 'volume-x' }, + { type: 'com.getpcpanel.discord.command.CommandDiscordJoinVoice', label: 'Discord — join voice', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'plug' }, + { type: 'com.getpcpanel.discord.command.CommandDiscordLeaveVoice', label: 'Discord — leave voice', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'log-out' }, + { type: 'com.getpcpanel.discord.command.CommandDiscordMute', label: 'Discord — mute', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'mic-off' }, + { type: 'com.getpcpanel.discord.command.CommandDiscordScreenShare', label: 'Discord — screen share', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'monitor' }, + { type: 'com.getpcpanel.discord.command.CommandDiscordSelfDeafen', label: 'Discord — deafen self', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'volume-x' }, + { type: 'com.getpcpanel.discord.command.CommandDiscordToggleVideo', label: 'Discord — toggle camera', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'film' }, + { type: 'com.getpcpanel.discord.command.CommandDiscordVolume', label: 'Discord — volume', category: 'integration', kinds: ['dial'], integration: 'discord', icon: 'volume' }, + { type: 'com.getpcpanel.homeassistant.command.CommandHomeAssistantAction', label: 'Home Assistant — perform action', category: 'integration', kinds: ['button'], integration: 'homeassistant', icon: 'zap' }, + { type: 'com.getpcpanel.homeassistant.command.CommandHomeAssistantValue', label: 'Home Assistant — set value', category: 'integration', kinds: ['dial'], integration: 'homeassistant', icon: 'sliders' }, + { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel', label: 'Wave Link — add focused app', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'plus' }, + { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel', label: 'Wave Link — level', category: 'integration', kinds: ['dial'], integration: 'wavelink', icon: 'sliders' }, + { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute', label: 'Wave Link — mute', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'mic-off' }, + { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput', label: 'Wave Link — main output', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'volume' }, +]; diff --git a/src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java b/src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java new file mode 100644 index 00000000..70dd7d9f --- /dev/null +++ b/src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java @@ -0,0 +1,117 @@ +package com.getpcpanel.commands.meta; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.getpcpanel.commands.command.Command; + +/** + * Generates the frontend command registry from the {@link CommandMeta} annotations and guards that the + * committed {@code command-registry.generated.ts} stays current. + * + *

This is the build-time half of "the command registry is retrieved from Java": the authoritative + * list of assignable commands and their picker metadata (label / category / kinds / integration / icon) + * lives on each command in its own feature package, and the frontend consumes the generated file rather + * than a hand-maintained catalog. Run with {@code -Dpcpanel.generate.catalog} to (re)write the file; + * otherwise the test fails if the committed file is stale, telling you to regenerate. + */ +@DisplayName("Generated frontend command registry") +class CommandRegistryGeneratorTest { + private static final Path OUTPUT = Path.of("src/main/webui/src/app/features/commands/command-registry.generated.ts"); + + @Test + @DisplayName("command-registry.generated.ts is up to date with @CommandMeta") + void generatedRegistryIsCurrent() throws Exception { + var expected = render(collect()); + if (System.getProperty("pcpanel.generate.catalog") != null) { + Files.writeString(OUTPUT, expected); + return; + } + var actual = Files.exists(OUTPUT) ? Files.readString(OUTPUT) : ""; + assertEquals(expected, actual, + "command-registry.generated.ts is stale — regenerate with: ./mvnw test -Dtest=CommandRegistryGeneratorTest -Dpcpanel.generate.catalog"); + } + + /** Each assignable command: its persisted id (@JsonTypeName) + picker metadata (@CommandMeta). */ + private static List collect() throws Exception { + var root = Path.of(Command.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + var loader = CommandRegistryGeneratorTest.class.getClassLoader(); + var result = new ArrayList(); + try (Stream walk = Files.walk(root.resolve("com").resolve("getpcpanel"))) { + for (var classFile : walk.filter(p -> p.toString().endsWith(".class")).toList()) { + var binary = root.relativize(classFile).toString(); + binary = binary.substring(0, binary.length() - ".class".length()).replace(java.io.File.separatorChar, '.'); + Class c; + try { + c = Class.forName(binary, false, loader); + } catch (Throwable e) { + continue; + } + var meta = c.getAnnotation(CommandMeta.class); + if (meta == null) { + continue; + } + assertTrue(Command.class.isAssignableFrom(c) && !Modifier.isAbstract(c.getModifiers()), + () -> c.getName() + " has @CommandMeta but is not a concrete Command"); + var typeName = c.getAnnotation(JsonTypeName.class); + assertTrue(typeName != null && !typeName.value().isBlank(), + () -> c.getName() + " has @CommandMeta but no @JsonTypeName id"); + result.add(new Entry(typeName.value(), meta)); + } + } + result.sort(Comparator.comparing(Entry::type)); + return result; + } + + private static String render(List entries) { + var sb = new StringBuilder(); + sb.append("// GENERATED FILE — do not edit. Source: @CommandMeta on the command classes\n"); + sb.append("// (com.getpcpanel.**.command). Regenerate via CommandRegistryGeneratorTest.\n"); + sb.append("import { IconName } from '../../ui';\n"); + sb.append("import type { CommandCategory, CommandKind, Integration } from './command-catalog';\n\n"); + sb.append("export interface GeneratedCommand {\n"); + sb.append(" type: string;\n label: string;\n category: CommandCategory;\n"); + sb.append(" kinds: CommandKind[];\n integration?: Integration;\n icon: IconName;\n}\n\n"); + sb.append("export const GENERATED_COMMANDS: GeneratedCommand[] = [\n"); + for (var e : entries) { + sb.append(" { type: ").append(q(e.type())); + sb.append(", label: ").append(q(e.meta().label())); + sb.append(", category: ").append(q(e.meta().category().name())); + sb.append(", kinds: ["); + var ks = e.meta().kinds(); + for (var i = 0; i < ks.length; i++) { + sb.append(q(ks[i].name())); + if (i < ks.length - 1) { + sb.append(", "); + } + } + sb.append("]"); + if (!e.meta().integration().isBlank()) { + sb.append(", integration: ").append(q(e.meta().integration())); + } + sb.append(", icon: ").append(q(e.meta().icon())); + sb.append(" },\n"); + } + sb.append("];\n"); + return sb.toString(); + } + + private static String q(String s) { + return "'" + s.replace("\\", "\\\\").replace("'", "\\'") + "'"; + } + + private record Entry(String type, CommandMeta meta) { + } +} From 47b2f3aedae22677714284860393c9f0e267c858 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:03:44 +0200 Subject: [PATCH 13/49] feat(commands): nice command discriminators, backwards-compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the persisted _type of the 39 assignable commands from their frozen FQCN to a readable id (e.g. com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced -> voicemeeter.advanced) via @JsonTypeName, while staying backwards compatible: - @CommandMeta.legacyIds records each command's previous id. CommandSubtypeRegistrar registers the current (nice) id as the subtype and installs a Jackson DeserializationProblemHandler that maps an unknown legacy id back to its command on READ only. So old profiles.json keep loading; re-saving writes the nice id — a transparent one-way conversion. New ids are never ambiguous with old ones for serialization (the handler is deser-only). - The generated registry carries so the frontend joins its hand-written field schemas (keyed by the old id) without churn, and stamps the nice _type in buildEmpty. - CommandSubtypeRegistrarTest covers both directions: nice id loads, legacy FQCN loads, and a re-save converts to the nice id. typescript-generator now emits the nice _type literals; tsc clean; full suite green (only the 3 pre-existing FocusVolume failures). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../command/CommandAnalogBands.java | 4 +- .../commands/CommandSubtypeRegistrar.java | 58 +++++++++-- .../getpcpanel/commands/meta/CommandMeta.java | 9 ++ .../device/command/CommandBrightness.java | 4 +- .../command/CommandDiscordJoinVoice.java | 4 +- .../command/CommandDiscordLeaveVoice.java | 4 +- .../discord/command/CommandDiscordMute.java | 4 +- .../command/CommandDiscordScreenShare.java | 4 +- .../command/CommandDiscordSelfDeafen.java | 4 +- .../command/CommandDiscordToggleVideo.java | 4 +- .../discord/command/CommandDiscordVolume.java | 4 +- .../command/CommandHomeAssistantAction.java | 4 +- .../command/CommandHomeAssistantValue.java | 4 +- .../keyboard/command/CommandKeystroke.java | 4 +- .../keyboard/command/CommandMedia.java | 4 +- .../mqtt/command/CommandMqttPublish.java | 4 +- .../obs/command/CommandObsAction.java | 4 +- .../obs/command/CommandObsMuteSource.java | 4 +- .../obs/command/CommandObsSetScene.java | 4 +- .../command/CommandObsSetSourceVolume.java | 4 +- .../osc/command/CommandOscSend.java | 4 +- .../output/command/CommandHttpRequest.java | Bin 5689 -> 5726 bytes .../profile/command/CommandProfile.java | 4 +- .../program/command/CommandEndProgram.java | 4 +- .../program/command/CommandRun.java | 4 +- .../program/command/CommandShortcut.java | 4 +- .../command/CommandVoiceMeeterAdvanced.java | 4 +- .../CommandVoiceMeeterAdvancedButton.java | 4 +- .../command/CommandVolumeDefaultDevice.java | 4 +- .../CommandVolumeDefaultDeviceAdvanced.java | 4 +- .../CommandVolumeDefaultDeviceToggle.java | 4 +- .../volume/command/CommandVolumeDevice.java | 4 +- .../command/CommandVolumeDeviceMute.java | 4 +- .../volume/command/CommandVolumeFocus.java | 4 +- .../command/CommandVolumeFocusMute.java | 4 +- .../volume/command/CommandVolumeProcess.java | 4 +- .../command/CommandVolumeProcessMute.java | 4 +- .../CommandWaveLinkAddFocusToChannel.java | 4 +- .../command/CommandWaveLinkChangeLevel.java | 4 +- .../command/CommandWaveLinkChangeMute.java | 4 +- .../command/CommandWaveLinkMainOutput.java | 4 +- .../app/features/commands/command-catalog.ts | 6 +- .../commands/command-registry.generated.ts | 80 ++++++++------- .../src/app/models/generated/backend.types.ts | 96 +++++++++--------- .../commands/CommandSubtypeRegistrarTest.java | 45 +++++--- .../command/CommandKeystrokeTest.java | 2 +- .../meta/CommandRegistryGeneratorTest.java | 5 +- 47 files changed, 260 insertions(+), 193 deletions(-) diff --git a/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java b/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java index 627461ef..215acb64 100644 --- a/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java +++ b/src/main/java/com/getpcpanel/analogbands/command/CommandAnalogBands.java @@ -41,8 +41,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandAnalogBands") -@CommandMeta(label = "Stepped switch (ranges)", category = CommandCategory.system, kinds = {CommandKind.dial}, icon = "sliders") +@JsonTypeName("analogbands.ranges") +@CommandMeta(label = "Stepped switch (ranges)", category = CommandCategory.system, kinds = {CommandKind.dial}, icon = "sliders", legacyIds = {"com.getpcpanel.commands.command.CommandAnalogBands"}) public class CommandAnalogBands extends Command implements DialAction { private static final int MAX_RAW = 255; diff --git a/src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java b/src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java index 1d12aecc..9df5fb25 100644 --- a/src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java +++ b/src/main/java/com/getpcpanel/commands/CommandSubtypeRegistrar.java @@ -1,8 +1,15 @@ package com.getpcpanel.commands; +import java.util.HashMap; import java.util.List; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; +import com.getpcpanel.commands.command.Command; +import com.getpcpanel.commands.meta.CommandMeta; import io.quarkus.arc.All; import io.quarkus.jackson.ObjectMapperCustomizer; @@ -12,10 +19,17 @@ /** * Registers every feature module's command classes as Jackson polymorphic subtypes of - * {@link com.getpcpanel.commands.command.Command}. The subtypes are discovered entirely through the - * {@link CommandModule} CDI SPI ({@code @All}), so no central list of command classes exists anywhere — - * a new command/plugin only declares itself in its own package. Jackson reads each class's - * {@code @JsonTypeName} for the stable persisted id. + * {@link Command}. The subtypes are discovered entirely through the {@link CommandModule} CDI SPI + * ({@code @All}), so no central list of command classes exists anywhere — a new command/plugin only + * declares itself in its own package. Jackson reads each class's {@code @JsonTypeName} for the + * (nice, current) persisted id. + * + *

Backwards compatibility. A command's {@code @CommandMeta.legacyIds} list the {@code _type} + * strings older {@code profiles.json} files may contain (their previous fully-qualified class names). + * These are not registered as subtypes (that would risk them being chosen for serialization); + * instead a {@link DeserializationProblemHandler} maps an unknown legacy id back to its command on + * read only. So existing saves keep loading, and re-saving rewrites them with the nice current id — + * a transparent one-way conversion. */ @Log4j2 @Singleton @@ -26,11 +40,35 @@ public class CommandSubtypeRegistrar implements ObjectMapperCustomizer { @Override public void customize(ObjectMapper mapper) { - var registered = modules.stream() - .flatMap(m -> m.commandTypes().stream()) - .distinct() - .peek(mapper::registerSubtypes) - .count(); - log.debug("Registered {} command subtypes from {} module(s)", registered, modules.size()); + var classes = modules.stream().flatMap(m -> m.commandTypes().stream()).distinct().toList(); + var legacy = new HashMap>(); + for (var clazz : classes) { + mapper.registerSubtypes(clazz); // current id via @JsonTypeName + var meta = clazz.getAnnotation(CommandMeta.class); + if (meta != null) { + for (var id : meta.legacyIds()) { + var prev = legacy.put(id, clazz); + if (prev != null && prev != clazz) { + throw new IllegalStateException("Legacy command id '" + id + "' maps to both " + prev + " and " + clazz); + } + } + } + } + if (!legacy.isEmpty()) { + // Resolve a legacy _type id (a command's old FQCN) to its current class on read only. + mapper.addHandler(new DeserializationProblemHandler() { + @Override + public JavaType handleUnknownTypeId(DeserializationContext ctxt, JavaType baseType, String subTypeId, TypeIdResolver idResolver, String failureMsg) { + if (baseType.isTypeOrSubTypeOf(Command.class)) { + var clazz = legacy.get(subTypeId); + if (clazz != null) { + return ctxt.getTypeFactory().constructType(clazz); + } + } + return null; // not a known legacy id — let Jackson report the failure + } + }); + } + log.debug("Registered {} command subtypes ({} legacy aliases) from {} module(s)", classes.size(), legacy.size(), modules.size()); } } diff --git a/src/main/java/com/getpcpanel/commands/meta/CommandMeta.java b/src/main/java/com/getpcpanel/commands/meta/CommandMeta.java index 72060a2d..88dd3f3d 100644 --- a/src/main/java/com/getpcpanel/commands/meta/CommandMeta.java +++ b/src/main/java/com/getpcpanel/commands/meta/CommandMeta.java @@ -33,4 +33,13 @@ /** Icon name; must be one of the frontend {@code IconName} set. */ String icon(); + + /** + * Historical {@code _type} discriminator values older profiles.json may contain (typically the + * command's previous fully-qualified class name). The current id is the {@code @JsonTypeName}; + * these are accepted on read only, mapped back via a DeserializationProblemHandler in + * {@link com.getpcpanel.commands.CommandSubtypeRegistrar}, so old saves keep loading while new + * saves are written with the nice current id. + */ + String[] legacyIds() default {}; } diff --git a/src/main/java/com/getpcpanel/device/command/CommandBrightness.java b/src/main/java/com/getpcpanel/device/command/CommandBrightness.java index 947a7a49..73af3aeb 100644 --- a/src/main/java/com/getpcpanel/device/command/CommandBrightness.java +++ b/src/main/java/com/getpcpanel/device/command/CommandBrightness.java @@ -18,8 +18,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandBrightness") -@CommandMeta(label = "Brightness", category = CommandCategory.system, kinds = {CommandKind.dial}, icon = "sun") +@JsonTypeName("device.brightness") +@CommandMeta(label = "Brightness", category = CommandCategory.system, kinds = {CommandKind.dial}, icon = "sun", legacyIds = {"com.getpcpanel.commands.command.CommandBrightness"}) public class CommandBrightness extends Command implements DialAction { private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java index 4df25784..33c551e0 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordJoinVoice.java @@ -20,8 +20,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordJoinVoice") -@CommandMeta(label = "Discord — join voice", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "plug") +@JsonTypeName("discord.join-voice") +@CommandMeta(label = "Discord — join voice", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "plug", legacyIds = {"com.getpcpanel.discord.command.CommandDiscordJoinVoice"}) public final class CommandDiscordJoinVoice extends CommandDiscord implements ButtonAction { @Nullable private final String channelId; /** Cosmetic: the channel's name at configure time, for the button label (the id is what we join). */ diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java index 3759e6d3..255baac2 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordLeaveVoice.java @@ -18,8 +18,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordLeaveVoice") -@CommandMeta(label = "Discord — leave voice", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "log-out") +@JsonTypeName("discord.leave-voice") +@CommandMeta(label = "Discord — leave voice", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "log-out", legacyIds = {"com.getpcpanel.discord.command.CommandDiscordLeaveVoice"}) public final class CommandDiscordLeaveVoice extends CommandDiscord implements ButtonAction { @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java index 2078a491..741873f9 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordMute.java @@ -25,8 +25,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordMute") -@CommandMeta(label = "Discord — mute", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "mic-off") +@JsonTypeName("discord.mute") +@CommandMeta(label = "Discord — mute", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "mic-off", legacyIds = {"com.getpcpanel.discord.command.CommandDiscordMute"}) public final class CommandDiscordMute extends CommandDiscord implements ButtonAction { public static final String SELF = "self"; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java index aac9d084..364972c9 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordScreenShare.java @@ -31,8 +31,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordScreenShare") -@CommandMeta(label = "Discord — screen share", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "monitor") +@JsonTypeName("discord.screen-share") +@CommandMeta(label = "Discord — screen share", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "monitor", legacyIds = {"com.getpcpanel.discord.command.CommandDiscordScreenShare"}) public final class CommandDiscordScreenShare extends CommandDiscord implements ButtonAction { public enum Mode { SCREEN, PROCESS, FOCUS } diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java index 15a84674..7ec182ea 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordSelfDeafen.java @@ -20,8 +20,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordSelfDeafen") -@CommandMeta(label = "Discord — deafen self", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "volume-x") +@JsonTypeName("discord.self-deafen") +@CommandMeta(label = "Discord — deafen self", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "volume-x", legacyIds = {"com.getpcpanel.discord.command.CommandDiscordSelfDeafen"}) public final class CommandDiscordSelfDeafen extends CommandDiscord implements ButtonAction { private final MuteType muteType; @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java index 78d73df2..c6f2b468 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordToggleVideo.java @@ -18,8 +18,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordToggleVideo") -@CommandMeta(label = "Discord — toggle camera", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "film") +@JsonTypeName("discord.toggle-video") +@CommandMeta(label = "Discord — toggle camera", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "discord", icon = "film", legacyIds = {"com.getpcpanel.discord.command.CommandDiscordToggleVideo"}) public final class CommandDiscordToggleVideo extends CommandDiscord implements ButtonAction { @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java b/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java index 465bc333..62d72a74 100644 --- a/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java +++ b/src/main/java/com/getpcpanel/discord/command/CommandDiscordVolume.java @@ -25,8 +25,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.discord.command.CommandDiscordVolume") -@CommandMeta(label = "Discord — volume", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "discord", icon = "volume") +@JsonTypeName("discord.volume") +@CommandMeta(label = "Discord — volume", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "discord", icon = "volume", legacyIds = {"com.getpcpanel.discord.command.CommandDiscordVolume"}) public final class CommandDiscordVolume extends CommandDiscord implements DialAction { public static final String MIC = "mic"; public static final String OUTPUT = "output"; diff --git a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java index 0842df85..75deb8f2 100644 --- a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java +++ b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantAction.java @@ -23,8 +23,8 @@ */ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.homeassistant.command.CommandHomeAssistantAction") -@CommandMeta(label = "Home Assistant — perform action", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "homeassistant", icon = "zap") +@JsonTypeName("homeassistant.action") +@CommandMeta(label = "Home Assistant — perform action", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "homeassistant", icon = "zap", legacyIds = {"com.getpcpanel.homeassistant.command.CommandHomeAssistantAction"}) public class CommandHomeAssistantAction extends CommandHomeAssistant implements ButtonAction { private final String action; @Nullable private final String overlayText; diff --git a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java index 825541ea..c8193ecb 100644 --- a/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java +++ b/src/main/java/com/getpcpanel/homeassistant/command/CommandHomeAssistantValue.java @@ -26,8 +26,8 @@ */ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.homeassistant.command.CommandHomeAssistantValue") -@CommandMeta(label = "Home Assistant — set value", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "homeassistant", icon = "sliders") +@JsonTypeName("homeassistant.value") +@CommandMeta(label = "Home Assistant — set value", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "homeassistant", icon = "sliders", legacyIds = {"com.getpcpanel.homeassistant.command.CommandHomeAssistantValue"}) public class CommandHomeAssistantValue extends CommandHomeAssistant implements DialAction { private final String action; @Nullable private final Double min; diff --git a/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java b/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java index 238a53fe..d1498d20 100644 --- a/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java +++ b/src/main/java/com/getpcpanel/keyboard/command/CommandKeystroke.java @@ -29,8 +29,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandKeystroke") -@CommandMeta(label = "Keystroke", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "keyboard") +@JsonTypeName("keyboard.keystroke") +@CommandMeta(label = "Keystroke", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "keyboard", legacyIds = {"com.getpcpanel.commands.command.CommandKeystroke"}) public class CommandKeystroke extends Command implements ButtonAction { public enum KeystrokeType { /** Single key combination (modifiers + one key). */ diff --git a/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java b/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java index c68dd562..317c3ac3 100644 --- a/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java +++ b/src/main/java/com/getpcpanel/keyboard/command/CommandMedia.java @@ -37,8 +37,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandMedia") -@CommandMeta(label = "Media", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "play") +@JsonTypeName("keyboard.media") +@CommandMeta(label = "Media", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "play", legacyIds = {"com.getpcpanel.commands.command.CommandMedia"}) public class CommandMedia extends Command implements ButtonAction { private static final int WM_APPCOMMAND = 0x0319; private final VolumeButton button; diff --git a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java index 3748fa89..f9139565 100644 --- a/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java +++ b/src/main/java/com/getpcpanel/mqtt/command/CommandMqttPublish.java @@ -25,8 +25,8 @@ */ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandMqttPublish") -@CommandMeta(label = "MQTT publish", category = CommandCategory.system, kinds = {CommandKind.dial, CommandKind.button}, icon = "zap") +@JsonTypeName("mqtt.publish") +@CommandMeta(label = "MQTT publish", category = CommandCategory.system, kinds = {CommandKind.dial, CommandKind.button}, icon = "zap", legacyIds = {"com.getpcpanel.commands.command.CommandMqttPublish"}) public class CommandMqttPublish extends CommandValueOutput { private final String topic; private final String payload; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java index fe16f37a..ea2a52a5 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsAction.java @@ -19,8 +19,8 @@ */ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandObsAction") -@CommandMeta(label = "OBS — stream / record", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "film") +@JsonTypeName("obs.action") +@CommandMeta(label = "OBS — stream / record", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "film", legacyIds = {"com.getpcpanel.commands.command.CommandObsAction"}) public class CommandObsAction extends CommandObs implements ButtonAction { private final ObsActionType action; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java index 77cca77b..c57555e2 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsMuteSource.java @@ -16,8 +16,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandObsMuteSource") -@CommandMeta(label = "OBS — mute source", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "mic-off") +@JsonTypeName("obs.mute-source") +@CommandMeta(label = "OBS — mute source", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "mic-off", legacyIds = {"com.getpcpanel.commands.command.CommandObsMuteSource"}) public class CommandObsMuteSource extends CommandObs implements ButtonAction { private final String source; private final MuteType type; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java index fae47c31..df1769d1 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetScene.java @@ -15,8 +15,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandObsSetScene") -@CommandMeta(label = "OBS — switch scene", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "film") +@JsonTypeName("obs.set-scene") +@CommandMeta(label = "OBS — switch scene", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "obs", icon = "film", legacyIds = {"com.getpcpanel.commands.command.CommandObsSetScene"}) public class CommandObsSetScene extends CommandObs implements ButtonAction { private final String scene; diff --git a/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java index beaec0c1..83cda5bc 100644 --- a/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java +++ b/src/main/java/com/getpcpanel/obs/command/CommandObsSetSourceVolume.java @@ -15,8 +15,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandObsSetSourceVolume") -@CommandMeta(label = "OBS — source volume", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "obs", icon = "sliders") +@JsonTypeName("obs.set-source-volume") +@CommandMeta(label = "OBS — source volume", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "obs", icon = "sliders", legacyIds = {"com.getpcpanel.commands.command.CommandObsSetSourceVolume"}) public class CommandObsSetSourceVolume extends CommandObs implements DialAction { private final String sourceName; private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java index b5e66ccc..466bcb5e 100644 --- a/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java +++ b/src/main/java/com/getpcpanel/osc/command/CommandOscSend.java @@ -24,8 +24,8 @@ */ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandOscSend") -@CommandMeta(label = "OSC send", category = CommandCategory.system, kinds = {CommandKind.dial, CommandKind.button}, icon = "sliders") +@JsonTypeName("osc.send") +@CommandMeta(label = "OSC send", category = CommandCategory.system, kinds = {CommandKind.dial, CommandKind.button}, icon = "sliders", legacyIds = {"com.getpcpanel.commands.command.CommandOscSend"}) public class CommandOscSend extends CommandValueOutput { private final String address; diff --git a/src/main/java/com/getpcpanel/output/command/CommandHttpRequest.java b/src/main/java/com/getpcpanel/output/command/CommandHttpRequest.java index 85d07b951bc9cfcdcd3fe5d47d364216145e5766..a1888c293471dd22bc3e3b1cc1b1980b28f7fbd4 100644 GIT binary patch delta 56 zcmdm~b5Cc(3Rd3y(vpJG61|L)k^Jr%l_u&dOg_OX M%vifQk?jUQ0BWujZU6uP delta 20 ccmcbovr}io3f9R%Y{rxKu(oY(W4pl*09^$LYXATM diff --git a/src/main/java/com/getpcpanel/profile/command/CommandProfile.java b/src/main/java/com/getpcpanel/profile/command/CommandProfile.java index c5036d53..971d02d5 100644 --- a/src/main/java/com/getpcpanel/profile/command/CommandProfile.java +++ b/src/main/java/com/getpcpanel/profile/command/CommandProfile.java @@ -19,8 +19,8 @@ import lombok.ToString; @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandProfile") -@CommandMeta(label = "Switch profile", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "refresh") +@JsonTypeName("profile.switch") +@CommandMeta(label = "Switch profile", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "refresh", legacyIds = {"com.getpcpanel.commands.command.CommandProfile"}) public class CommandProfile extends Command implements DeviceAction { @Getter private final String profile; diff --git a/src/main/java/com/getpcpanel/program/command/CommandEndProgram.java b/src/main/java/com/getpcpanel/program/command/CommandEndProgram.java index 006aa48e..6bd9c411 100644 --- a/src/main/java/com/getpcpanel/program/command/CommandEndProgram.java +++ b/src/main/java/com/getpcpanel/program/command/CommandEndProgram.java @@ -18,8 +18,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandEndProgram") -@CommandMeta(label = "End program", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "x") +@JsonTypeName("program.end-program") +@CommandMeta(label = "End program", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "x", legacyIds = {"com.getpcpanel.commands.command.CommandEndProgram"}) public class CommandEndProgram extends Command implements ButtonAction { private static final Runtime rt = Runtime.getRuntime(); private final boolean specific; diff --git a/src/main/java/com/getpcpanel/program/command/CommandRun.java b/src/main/java/com/getpcpanel/program/command/CommandRun.java index a74b015a..94ec654e 100644 --- a/src/main/java/com/getpcpanel/program/command/CommandRun.java +++ b/src/main/java/com/getpcpanel/program/command/CommandRun.java @@ -18,8 +18,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandRun") -@CommandMeta(label = "Run command", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "zap") +@JsonTypeName("program.run") +@CommandMeta(label = "Run command", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "zap", legacyIds = {"com.getpcpanel.commands.command.CommandRun"}) public class CommandRun extends Command implements ButtonAction { private final String command; diff --git a/src/main/java/com/getpcpanel/program/command/CommandShortcut.java b/src/main/java/com/getpcpanel/program/command/CommandShortcut.java index 7443f7f9..bed9dafc 100644 --- a/src/main/java/com/getpcpanel/program/command/CommandShortcut.java +++ b/src/main/java/com/getpcpanel/program/command/CommandShortcut.java @@ -18,8 +18,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandShortcut") -@CommandMeta(label = "Run shortcut", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "zap") +@JsonTypeName("program.shortcut") +@CommandMeta(label = "Run shortcut", category = CommandCategory.system, kinds = {CommandKind.button}, icon = "zap", legacyIds = {"com.getpcpanel.commands.command.CommandShortcut"}) public class CommandShortcut extends Command implements ButtonAction { private static final Runtime rt = Runtime.getRuntime(); private final String shortcut; diff --git a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java index 2869f71e..705a7c7c 100644 --- a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvanced.java @@ -15,8 +15,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced") -@CommandMeta(label = "Voicemeeter — parameter", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "voicemeeter", icon = "sliders") +@JsonTypeName("voicemeeter.advanced") +@CommandMeta(label = "Voicemeeter — parameter", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "voicemeeter", icon = "sliders", legacyIds = {"com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced"}) public class CommandVoiceMeeterAdvanced extends CommandVoiceMeeter implements DialAction { private final String fullParam; private final Voicemeeter.DialControlMode ct; diff --git a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java index 49cdc78f..63396616 100644 --- a/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java +++ b/src/main/java/com/getpcpanel/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java @@ -19,8 +19,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton") -@CommandMeta(label = "Voicemeeter — button", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "voicemeeter", icon = "sliders") +@JsonTypeName("voicemeeter.advanced-button") +@CommandMeta(label = "Voicemeeter — button", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "voicemeeter", icon = "sliders", legacyIds = {"com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton"}) public class CommandVoiceMeeterAdvancedButton extends CommandVoiceMeeter implements ButtonAction { private final String fullParam; private final Voicemeeter.ButtonControlMode bt; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDevice.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDevice.java index 2b9291a1..36cccbd8 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDevice.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDevice.java @@ -15,8 +15,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDefaultDevice") -@CommandMeta(label = "Set default device", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "monitor") +@JsonTypeName("volume.default-device") +@CommandMeta(label = "Set default device", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "monitor", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeDefaultDevice"}) public class CommandVolumeDefaultDevice extends CommandVolume implements ButtonAction { private final String deviceId; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceAdvanced.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceAdvanced.java index 43d202b7..ff1191c7 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceAdvanced.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceAdvanced.java @@ -25,8 +25,8 @@ @Jacksonized @AllArgsConstructor @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced") -@CommandMeta(label = "Advanced default device", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "monitor") +@JsonTypeName("volume.default-device-advanced") +@CommandMeta(label = "Advanced default device", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "monitor", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced"}) public class CommandVolumeDefaultDeviceAdvanced extends CommandVolume implements ButtonAction { private final String name; private final String mediaPb; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggle.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggle.java index 9fe0a328..a0ada855 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggle.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDefaultDeviceToggle.java @@ -25,8 +25,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle") -@CommandMeta(label = "Cycle default device", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "refresh") +@JsonTypeName("volume.default-device-toggle") +@CommandMeta(label = "Cycle default device", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "refresh", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle"}) public class CommandVolumeDefaultDeviceToggle extends CommandVolume implements ButtonAction { private final List devices; private int currentIdx; // Used as a fallback for when the current idx cannot be found diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java index b066aa98..8a357d7c 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDevice.java @@ -14,8 +14,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDevice") -@CommandMeta(label = "Device volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume") +@JsonTypeName("volume.device") +@CommandMeta(label = "Device volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeDevice"}) public class CommandVolumeDevice extends CommandVolume implements DialAction { private final String deviceId; private final boolean unMuteOnVolumeChange; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java index 0cae4b63..11499974 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeDeviceMute.java @@ -14,8 +14,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeDeviceMute") -@CommandMeta(label = "Device mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x") +@JsonTypeName("volume.device-mute") +@CommandMeta(label = "Device mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeDeviceMute"}) public class CommandVolumeDeviceMute extends CommandVolume implements ButtonAction { private final String deviceId; private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java index 3b0cf6ee..248396c3 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocus.java @@ -15,8 +15,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeFocus") -@CommandMeta(label = "Focused-app volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume") +@JsonTypeName("volume.focus") +@CommandMeta(label = "Focused-app volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeFocus"}) public class CommandVolumeFocus extends CommandVolume implements DialAction { private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java index 06d3c764..9100980f 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeFocusMute.java @@ -15,8 +15,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeFocusMute") -@CommandMeta(label = "Focused-app mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x") +@JsonTypeName("volume.focus-mute") +@CommandMeta(label = "Focused-app mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeFocusMute"}) public class CommandVolumeFocusMute extends CommandVolume implements ButtonAction { private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java index d8cac8a0..462f9073 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcess.java @@ -17,8 +17,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeProcess") -@CommandMeta(label = "App volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume") +@JsonTypeName("volume.process") +@CommandMeta(label = "App volume", category = CommandCategory.audio, kinds = {CommandKind.dial}, icon = "volume", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeProcess"}) public class CommandVolumeProcess extends CommandVolume implements DialAction { private final List processName; private final String device; diff --git a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java index 9c001b6a..0aeafa82 100644 --- a/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java +++ b/src/main/java/com/getpcpanel/volume/command/CommandVolumeProcessMute.java @@ -15,8 +15,8 @@ @Getter @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.commands.command.CommandVolumeProcessMute") -@CommandMeta(label = "App mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x") +@JsonTypeName("volume.process-mute") +@CommandMeta(label = "App mute", category = CommandCategory.audio, kinds = {CommandKind.button}, icon = "volume-x", legacyIds = {"com.getpcpanel.commands.command.CommandVolumeProcessMute"}) public class CommandVolumeProcessMute extends CommandVolume implements ButtonAction { private final Set processName; private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java index 3ba72051..e444b964 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkAddFocusToChannel.java @@ -19,8 +19,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel") -@CommandMeta(label = "Wave Link — add focused app", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "plus") +@JsonTypeName("wavelink.add-focus-to-channel") +@CommandMeta(label = "Wave Link — add focused app", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "plus", legacyIds = {"com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel"}) public final class CommandWaveLinkAddFocusToChannel extends CommandWaveLink implements ButtonAction { @Nullable private final String id; @Nullable private final String name; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java index ce695624..33dabf04 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeLevel.java @@ -19,8 +19,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel") -@CommandMeta(label = "Wave Link — level", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "wavelink", icon = "sliders") +@JsonTypeName("wavelink.change-level") +@CommandMeta(label = "Wave Link — level", category = CommandCategory.integration, kinds = {CommandKind.dial}, integration = "wavelink", icon = "sliders", legacyIds = {"com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel"}) public final class CommandWaveLinkChangeLevel extends CommandWaveLinkChange implements DialAction { @Nullable private final DialCommandParams dialParams; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java index 7b940f1e..b2f0ea71 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkChangeMute.java @@ -21,8 +21,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute") -@CommandMeta(label = "Wave Link — mute", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "mic-off") +@JsonTypeName("wavelink.change-mute") +@CommandMeta(label = "Wave Link — mute", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "mic-off", legacyIds = {"com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute"}) public final class CommandWaveLinkChangeMute extends CommandWaveLinkChange implements ButtonAction { private final MuteType muteType; diff --git a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java index 76958fa9..5844f84e 100644 --- a/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java +++ b/src/main/java/com/getpcpanel/wavelink/command/CommandWaveLinkMainOutput.java @@ -19,8 +19,8 @@ @Getter @Log4j2 @ToString(callSuper = true) -@JsonTypeName("com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput") -@CommandMeta(label = "Wave Link — main output", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "volume") +@JsonTypeName("wavelink.main-output") +@CommandMeta(label = "Wave Link — main output", category = CommandCategory.integration, kinds = {CommandKind.button}, integration = "wavelink", icon = "volume", legacyIds = {"com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"}) public final class CommandWaveLinkMainOutput extends CommandWaveLink implements ButtonAction { @Nullable private final String id; @Nullable private final String name; diff --git a/src/main/webui/src/app/features/commands/command-catalog.ts b/src/main/webui/src/app/features/commands/command-catalog.ts index 0e480681..4dbdd582 100644 --- a/src/main/webui/src/app/features/commands/command-catalog.ts +++ b/src/main/webui/src/app/features/commands/command-catalog.ts @@ -390,9 +390,11 @@ const FIELD_DEFS: FieldDef_[] = [ // joined with the hand-written field editors above, keyed by the command's persisted type id. const FIELDS_BY_TYPE = new Map(FIELD_DEFS.map(d => [d.type, d] as const)); export const COMMANDS: CommandDef[] = GENERATED_COMMANDS.map(g => { - const f = FIELDS_BY_TYPE.get(g.type); + // field schemas are keyed by the command's historical id; join on `legacy` so renaming the + // persisted id to a nice one needs no change here. buildEmpty stamps the current (nice) _type. + const f = FIELDS_BY_TYPE.get(g.legacy ?? g.type); if (!f) throw new Error('No field schema for command ' + g.type); - return { ...g, buildEmpty: f.buildEmpty, fields: f.fields }; + return { ...g, buildEmpty: () => ({ ...f.buildEmpty(), _type: g.type }), fields: f.fields }; }); export const COMMAND_BY_TYPE = new Map(COMMANDS.map(c => [c.type, c])); diff --git a/src/main/webui/src/app/features/commands/command-registry.generated.ts b/src/main/webui/src/app/features/commands/command-registry.generated.ts index 335d63d7..b4a86664 100644 --- a/src/main/webui/src/app/features/commands/command-registry.generated.ts +++ b/src/main/webui/src/app/features/commands/command-registry.generated.ts @@ -10,46 +10,48 @@ export interface GeneratedCommand { kinds: CommandKind[]; integration?: Integration; icon: IconName; + /** previous _type id(s) for joining hand-written field schemas keyed by the old id */ + legacy?: string; } export const GENERATED_COMMANDS: GeneratedCommand[] = [ - { type: 'com.getpcpanel.commands.command.CommandAnalogBands', label: 'Stepped switch (ranges)', category: 'system', kinds: ['dial'], icon: 'sliders' }, - { type: 'com.getpcpanel.commands.command.CommandBrightness', label: 'Brightness', category: 'system', kinds: ['dial'], icon: 'sun' }, - { type: 'com.getpcpanel.commands.command.CommandEndProgram', label: 'End program', category: 'system', kinds: ['button'], icon: 'x' }, - { type: 'com.getpcpanel.commands.command.CommandHttpRequest', label: 'HTTP request', category: 'system', kinds: ['dial', 'button'], icon: 'zap' }, - { type: 'com.getpcpanel.commands.command.CommandKeystroke', label: 'Keystroke', category: 'system', kinds: ['button'], icon: 'keyboard' }, - { type: 'com.getpcpanel.commands.command.CommandMedia', label: 'Media', category: 'system', kinds: ['button'], icon: 'play' }, - { type: 'com.getpcpanel.commands.command.CommandMqttPublish', label: 'MQTT publish', category: 'system', kinds: ['dial', 'button'], icon: 'zap' }, - { type: 'com.getpcpanel.commands.command.CommandObsAction', label: 'OBS — stream / record', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'film' }, - { type: 'com.getpcpanel.commands.command.CommandObsMuteSource', label: 'OBS — mute source', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'mic-off' }, - { type: 'com.getpcpanel.commands.command.CommandObsSetScene', label: 'OBS — switch scene', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'film' }, - { type: 'com.getpcpanel.commands.command.CommandObsSetSourceVolume', label: 'OBS — source volume', category: 'integration', kinds: ['dial'], integration: 'obs', icon: 'sliders' }, - { type: 'com.getpcpanel.commands.command.CommandOscSend', label: 'OSC send', category: 'system', kinds: ['dial', 'button'], icon: 'sliders' }, - { type: 'com.getpcpanel.commands.command.CommandProfile', label: 'Switch profile', category: 'system', kinds: ['button'], icon: 'refresh' }, - { type: 'com.getpcpanel.commands.command.CommandRun', label: 'Run command', category: 'system', kinds: ['button'], icon: 'zap' }, - { type: 'com.getpcpanel.commands.command.CommandShortcut', label: 'Run shortcut', category: 'system', kinds: ['button'], icon: 'zap' }, - { type: 'com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced', label: 'Voicemeeter — parameter', category: 'integration', kinds: ['dial'], integration: 'voicemeeter', icon: 'sliders' }, - { type: 'com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton', label: 'Voicemeeter — button', category: 'integration', kinds: ['button'], integration: 'voicemeeter', icon: 'sliders' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeDefaultDevice', label: 'Set default device', category: 'audio', kinds: ['button'], icon: 'monitor' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced', label: 'Advanced default device', category: 'audio', kinds: ['button'], icon: 'monitor' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle', label: 'Cycle default device', category: 'audio', kinds: ['button'], icon: 'refresh' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeDevice', label: 'Device volume', category: 'audio', kinds: ['dial'], icon: 'volume' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeDeviceMute', label: 'Device mute', category: 'audio', kinds: ['button'], icon: 'volume-x' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeFocus', label: 'Focused-app volume', category: 'audio', kinds: ['dial'], icon: 'volume' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeFocusMute', label: 'Focused-app mute', category: 'audio', kinds: ['button'], icon: 'volume-x' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeProcess', label: 'App volume', category: 'audio', kinds: ['dial'], icon: 'volume' }, - { type: 'com.getpcpanel.commands.command.CommandVolumeProcessMute', label: 'App mute', category: 'audio', kinds: ['button'], icon: 'volume-x' }, - { type: 'com.getpcpanel.discord.command.CommandDiscordJoinVoice', label: 'Discord — join voice', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'plug' }, - { type: 'com.getpcpanel.discord.command.CommandDiscordLeaveVoice', label: 'Discord — leave voice', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'log-out' }, - { type: 'com.getpcpanel.discord.command.CommandDiscordMute', label: 'Discord — mute', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'mic-off' }, - { type: 'com.getpcpanel.discord.command.CommandDiscordScreenShare', label: 'Discord — screen share', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'monitor' }, - { type: 'com.getpcpanel.discord.command.CommandDiscordSelfDeafen', label: 'Discord — deafen self', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'volume-x' }, - { type: 'com.getpcpanel.discord.command.CommandDiscordToggleVideo', label: 'Discord — toggle camera', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'film' }, - { type: 'com.getpcpanel.discord.command.CommandDiscordVolume', label: 'Discord — volume', category: 'integration', kinds: ['dial'], integration: 'discord', icon: 'volume' }, - { type: 'com.getpcpanel.homeassistant.command.CommandHomeAssistantAction', label: 'Home Assistant — perform action', category: 'integration', kinds: ['button'], integration: 'homeassistant', icon: 'zap' }, - { type: 'com.getpcpanel.homeassistant.command.CommandHomeAssistantValue', label: 'Home Assistant — set value', category: 'integration', kinds: ['dial'], integration: 'homeassistant', icon: 'sliders' }, - { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel', label: 'Wave Link — add focused app', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'plus' }, - { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel', label: 'Wave Link — level', category: 'integration', kinds: ['dial'], integration: 'wavelink', icon: 'sliders' }, - { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute', label: 'Wave Link — mute', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'mic-off' }, - { type: 'com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput', label: 'Wave Link — main output', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'volume' }, + { type: 'analogbands.ranges', label: 'Stepped switch (ranges)', category: 'system', kinds: ['dial'], icon: 'sliders', legacy: 'com.getpcpanel.commands.command.CommandAnalogBands' }, + { type: 'device.brightness', label: 'Brightness', category: 'system', kinds: ['dial'], icon: 'sun', legacy: 'com.getpcpanel.commands.command.CommandBrightness' }, + { type: 'discord.join-voice', label: 'Discord — join voice', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'plug', legacy: 'com.getpcpanel.discord.command.CommandDiscordJoinVoice' }, + { type: 'discord.leave-voice', label: 'Discord — leave voice', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'log-out', legacy: 'com.getpcpanel.discord.command.CommandDiscordLeaveVoice' }, + { type: 'discord.mute', label: 'Discord — mute', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'mic-off', legacy: 'com.getpcpanel.discord.command.CommandDiscordMute' }, + { type: 'discord.screen-share', label: 'Discord — screen share', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'monitor', legacy: 'com.getpcpanel.discord.command.CommandDiscordScreenShare' }, + { type: 'discord.self-deafen', label: 'Discord — deafen self', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'volume-x', legacy: 'com.getpcpanel.discord.command.CommandDiscordSelfDeafen' }, + { type: 'discord.toggle-video', label: 'Discord — toggle camera', category: 'integration', kinds: ['button'], integration: 'discord', icon: 'film', legacy: 'com.getpcpanel.discord.command.CommandDiscordToggleVideo' }, + { type: 'discord.volume', label: 'Discord — volume', category: 'integration', kinds: ['dial'], integration: 'discord', icon: 'volume', legacy: 'com.getpcpanel.discord.command.CommandDiscordVolume' }, + { type: 'homeassistant.action', label: 'Home Assistant — perform action', category: 'integration', kinds: ['button'], integration: 'homeassistant', icon: 'zap', legacy: 'com.getpcpanel.homeassistant.command.CommandHomeAssistantAction' }, + { type: 'homeassistant.value', label: 'Home Assistant — set value', category: 'integration', kinds: ['dial'], integration: 'homeassistant', icon: 'sliders', legacy: 'com.getpcpanel.homeassistant.command.CommandHomeAssistantValue' }, + { type: 'keyboard.keystroke', label: 'Keystroke', category: 'system', kinds: ['button'], icon: 'keyboard', legacy: 'com.getpcpanel.commands.command.CommandKeystroke' }, + { type: 'keyboard.media', label: 'Media', category: 'system', kinds: ['button'], icon: 'play', legacy: 'com.getpcpanel.commands.command.CommandMedia' }, + { type: 'mqtt.publish', label: 'MQTT publish', category: 'system', kinds: ['dial', 'button'], icon: 'zap', legacy: 'com.getpcpanel.commands.command.CommandMqttPublish' }, + { type: 'obs.action', label: 'OBS — stream / record', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'film', legacy: 'com.getpcpanel.commands.command.CommandObsAction' }, + { type: 'obs.mute-source', label: 'OBS — mute source', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'mic-off', legacy: 'com.getpcpanel.commands.command.CommandObsMuteSource' }, + { type: 'obs.set-scene', label: 'OBS — switch scene', category: 'integration', kinds: ['button'], integration: 'obs', icon: 'film', legacy: 'com.getpcpanel.commands.command.CommandObsSetScene' }, + { type: 'obs.set-source-volume', label: 'OBS — source volume', category: 'integration', kinds: ['dial'], integration: 'obs', icon: 'sliders', legacy: 'com.getpcpanel.commands.command.CommandObsSetSourceVolume' }, + { type: 'osc.send', label: 'OSC send', category: 'system', kinds: ['dial', 'button'], icon: 'sliders', legacy: 'com.getpcpanel.commands.command.CommandOscSend' }, + { type: 'output.http-request', label: 'HTTP request', category: 'system', kinds: ['dial', 'button'], icon: 'zap', legacy: 'com.getpcpanel.commands.command.CommandHttpRequest' }, + { type: 'profile.switch', label: 'Switch profile', category: 'system', kinds: ['button'], icon: 'refresh', legacy: 'com.getpcpanel.commands.command.CommandProfile' }, + { type: 'program.end-program', label: 'End program', category: 'system', kinds: ['button'], icon: 'x', legacy: 'com.getpcpanel.commands.command.CommandEndProgram' }, + { type: 'program.run', label: 'Run command', category: 'system', kinds: ['button'], icon: 'zap', legacy: 'com.getpcpanel.commands.command.CommandRun' }, + { type: 'program.shortcut', label: 'Run shortcut', category: 'system', kinds: ['button'], icon: 'zap', legacy: 'com.getpcpanel.commands.command.CommandShortcut' }, + { type: 'voicemeeter.advanced', label: 'Voicemeeter — parameter', category: 'integration', kinds: ['dial'], integration: 'voicemeeter', icon: 'sliders', legacy: 'com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced' }, + { type: 'voicemeeter.advanced-button', label: 'Voicemeeter — button', category: 'integration', kinds: ['button'], integration: 'voicemeeter', icon: 'sliders', legacy: 'com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton' }, + { type: 'volume.default-device', label: 'Set default device', category: 'audio', kinds: ['button'], icon: 'monitor', legacy: 'com.getpcpanel.commands.command.CommandVolumeDefaultDevice' }, + { type: 'volume.default-device-advanced', label: 'Advanced default device', category: 'audio', kinds: ['button'], icon: 'monitor', legacy: 'com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced' }, + { type: 'volume.default-device-toggle', label: 'Cycle default device', category: 'audio', kinds: ['button'], icon: 'refresh', legacy: 'com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle' }, + { type: 'volume.device', label: 'Device volume', category: 'audio', kinds: ['dial'], icon: 'volume', legacy: 'com.getpcpanel.commands.command.CommandVolumeDevice' }, + { type: 'volume.device-mute', label: 'Device mute', category: 'audio', kinds: ['button'], icon: 'volume-x', legacy: 'com.getpcpanel.commands.command.CommandVolumeDeviceMute' }, + { type: 'volume.focus', label: 'Focused-app volume', category: 'audio', kinds: ['dial'], icon: 'volume', legacy: 'com.getpcpanel.commands.command.CommandVolumeFocus' }, + { type: 'volume.focus-mute', label: 'Focused-app mute', category: 'audio', kinds: ['button'], icon: 'volume-x', legacy: 'com.getpcpanel.commands.command.CommandVolumeFocusMute' }, + { type: 'volume.process', label: 'App volume', category: 'audio', kinds: ['dial'], icon: 'volume', legacy: 'com.getpcpanel.commands.command.CommandVolumeProcess' }, + { type: 'volume.process-mute', label: 'App mute', category: 'audio', kinds: ['button'], icon: 'volume-x', legacy: 'com.getpcpanel.commands.command.CommandVolumeProcessMute' }, + { type: 'wavelink.add-focus-to-channel', label: 'Wave Link — add focused app', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'plus', legacy: 'com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel' }, + { type: 'wavelink.change-level', label: 'Wave Link — level', category: 'integration', kinds: ['dial'], integration: 'wavelink', icon: 'sliders', legacy: 'com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel' }, + { type: 'wavelink.change-mute', label: 'Wave Link — mute', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'mic-off', legacy: 'com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute' }, + { type: 'wavelink.main-output', label: 'Wave Link — main output', category: 'integration', kinds: ['button'], integration: 'wavelink', icon: 'volume', legacy: 'com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput' }, ]; diff --git a/src/main/webui/src/app/models/generated/backend.types.ts b/src/main/webui/src/app/models/generated/backend.types.ts index a76afa36..e4ee5473 100644 --- a/src/main/webui/src/app/models/generated/backend.types.ts +++ b/src/main/webui/src/app/models/generated/backend.types.ts @@ -42,49 +42,49 @@ export interface ButtonAction { } export interface Command { - _type: "com.getpcpanel.commands.command.CommandAnalogBands" | "com.getpcpanel.commands.command.CommandNoOp" | "com.getpcpanel.commands.command.CommandBrightness" | "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue" | "com.getpcpanel.commands.command.CommandKeystroke" | "com.getpcpanel.commands.command.CommandMedia" | "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume" | "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandHttpRequest" | "com.getpcpanel.commands.command.CommandProfile" | "com.getpcpanel.commands.command.CommandEndProgram" | "com.getpcpanel.commands.command.CommandRun" | "com.getpcpanel.commands.command.CommandShortcut" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "analogbands.ranges" | "com.getpcpanel.commands.command.CommandNoOp" | "device.brightness" | "discord.join-voice" | "discord.leave-voice" | "discord.mute" | "discord.screen-share" | "discord.self-deafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "discord.toggle-video" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "discord.volume" | "homeassistant.action" | "homeassistant.value" | "keyboard.keystroke" | "keyboard.media" | "obs.action" | "obs.mute-source" | "obs.set-scene" | "obs.set-source-volume" | "mqtt.publish" | "osc.send" | "output.http-request" | "profile.switch" | "program.end-program" | "program.run" | "program.shortcut" | "voicemeeter.advanced" | "voicemeeter.advanced-button" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton" | "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "volume.default-device" | "volume.default-device-advanced" | "volume.default-device-toggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "volume.device" | "volume.device-mute" | "volume.focus" | "volume.focus-mute" | "volume.process" | "volume.process-mute" | "wavelink.add-focus-to-channel" | "wavelink.change-level" | "wavelink.change-mute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "wavelink.main-output"; } export interface CommandAnalogBands extends Command, DialAction { - _type: "com.getpcpanel.commands.command.CommandAnalogBands"; + _type: "analogbands.ranges"; bands?: AnalogBand[]; } export interface CommandBrightness extends Command, DialAction { - _type: "com.getpcpanel.commands.command.CommandBrightness"; + _type: "device.brightness"; } export interface CommandConverter { } export interface CommandDiscord extends Command { - _type: "com.getpcpanel.discord.command.CommandDiscordJoinVoice" | "com.getpcpanel.discord.command.CommandDiscordLeaveVoice" | "com.getpcpanel.discord.command.CommandDiscordMute" | "com.getpcpanel.discord.command.CommandDiscordScreenShare" | "com.getpcpanel.discord.command.CommandDiscordSelfDeafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "com.getpcpanel.discord.command.CommandDiscordToggleVideo" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "com.getpcpanel.discord.command.CommandDiscordVolume"; + _type: "discord.join-voice" | "discord.leave-voice" | "discord.mute" | "discord.screen-share" | "discord.self-deafen" | "com.getpcpanel.discord.command.CommandDiscordSelfInputVolume" | "com.getpcpanel.discord.command.CommandDiscordSelfMute" | "com.getpcpanel.discord.command.CommandDiscordSelfOutputVolume" | "discord.toggle-video" | "com.getpcpanel.discord.command.CommandDiscordUserMute" | "com.getpcpanel.discord.command.CommandDiscordUserVolume" | "discord.volume"; } export interface CommandDiscordJoinVoice extends CommandDiscord, ButtonAction { - _type: "com.getpcpanel.discord.command.CommandDiscordJoinVoice"; + _type: "discord.join-voice"; channelId?: string; channelName?: string; } export interface CommandDiscordLeaveVoice extends CommandDiscord, ButtonAction { - _type: "com.getpcpanel.discord.command.CommandDiscordLeaveVoice"; + _type: "discord.leave-voice"; } export interface CommandDiscordMute extends CommandDiscord, ButtonAction { - _type: "com.getpcpanel.discord.command.CommandDiscordMute"; + _type: "discord.mute"; muteType?: MuteType; target?: string; } export interface CommandDiscordScreenShare extends CommandDiscord, ButtonAction { - _type: "com.getpcpanel.discord.command.CommandDiscordScreenShare"; + _type: "discord.screen-share"; mode?: Mode; processName?: string[]; } export interface CommandDiscordSelfDeafen extends CommandDiscord, ButtonAction { - _type: "com.getpcpanel.discord.command.CommandDiscordSelfDeafen"; + _type: "discord.self-deafen"; muteType?: MuteType; } @@ -104,7 +104,7 @@ export interface CommandDiscordSelfOutputVolume extends CommandDiscord, DialActi } export interface CommandDiscordToggleVideo extends CommandDiscord, ButtonAction { - _type: "com.getpcpanel.discord.command.CommandDiscordToggleVideo"; + _type: "discord.toggle-video"; } export interface CommandDiscordUserMute extends CommandDiscord, ButtonAction { @@ -119,29 +119,29 @@ export interface CommandDiscordUserVolume extends CommandDiscord, DialAction { } export interface CommandDiscordVolume extends CommandDiscord, DialAction { - _type: "com.getpcpanel.discord.command.CommandDiscordVolume"; + _type: "discord.volume"; clearMuteOnChange: boolean; target?: string; } export interface CommandEndProgram extends Command, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandEndProgram"; + _type: "program.end-program"; name: string; specific: boolean; } export interface CommandHomeAssistant extends Command { - _type: "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction" | "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue"; + _type: "homeassistant.action" | "homeassistant.value"; server?: string; } export interface CommandHomeAssistantAction extends CommandHomeAssistant, ButtonAction { - _type: "com.getpcpanel.homeassistant.command.CommandHomeAssistantAction"; + _type: "homeassistant.action"; action: string; } export interface CommandHomeAssistantValue extends CommandHomeAssistant, DialAction { - _type: "com.getpcpanel.homeassistant.command.CommandHomeAssistantValue"; + _type: "homeassistant.value"; action: string; formula?: string; max?: number; @@ -149,7 +149,7 @@ export interface CommandHomeAssistantValue extends CommandHomeAssistant, DialAct } export interface CommandHttpRequest extends CommandValueOutput { - _type: "com.getpcpanel.commands.command.CommandHttpRequest"; + _type: "output.http-request"; body?: string; headers?: string; method: string; @@ -157,14 +157,14 @@ export interface CommandHttpRequest extends CommandValueOutput { } export interface CommandKeystroke extends Command, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandKeystroke"; + _type: "keyboard.keystroke"; keystroke: string; text: string; type: KeystrokeType; } export interface CommandMedia extends Command, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandMedia"; + _type: "keyboard.media"; button: VolumeButton; spotify: boolean; } @@ -173,7 +173,7 @@ export interface CommandModule { } export interface CommandMqttPublish extends CommandValueOutput { - _type: "com.getpcpanel.commands.command.CommandMqttPublish"; + _type: "mqtt.publish"; payload: string; topic: string; } @@ -183,42 +183,42 @@ export interface CommandNoOp extends Command, ButtonAction, DialAction { } export interface CommandObs extends Command { - _type: "com.getpcpanel.commands.command.CommandObsAction" | "com.getpcpanel.commands.command.CommandObsMuteSource" | "com.getpcpanel.commands.command.CommandObsSetScene" | "com.getpcpanel.commands.command.CommandObsSetSourceVolume"; + _type: "obs.action" | "obs.mute-source" | "obs.set-scene" | "obs.set-source-volume"; } export interface CommandObsAction extends CommandObs, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandObsAction"; + _type: "obs.action"; action: ObsActionType; } export interface CommandObsMuteSource extends CommandObs, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandObsMuteSource"; + _type: "obs.mute-source"; source: string; type: MuteType; } export interface CommandObsSetScene extends CommandObs, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandObsSetScene"; + _type: "obs.set-scene"; scene: string; } export interface CommandObsSetSourceVolume extends CommandObs, DialAction { - _type: "com.getpcpanel.commands.command.CommandObsSetSourceVolume"; + _type: "obs.set-source-volume"; sourceName: string; } export interface CommandOscSend extends CommandValueOutput { - _type: "com.getpcpanel.commands.command.CommandOscSend"; + _type: "osc.send"; address: string; } export interface CommandProfile extends Command, DeviceAction { - _type: "com.getpcpanel.commands.command.CommandProfile"; + _type: "profile.switch"; profile?: string; } export interface CommandRun extends Command, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandRun"; + _type: "program.run"; command: string; } @@ -228,7 +228,7 @@ export interface Commands { } export interface CommandShortcut extends Command, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandShortcut"; + _type: "program.shortcut"; shortcut: string; } @@ -240,24 +240,24 @@ export interface CommandType { } export interface CommandValueOutput extends Command, DialAction, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandMqttPublish" | "com.getpcpanel.commands.command.CommandOscSend" | "com.getpcpanel.commands.command.CommandHttpRequest"; + _type: "mqtt.publish" | "osc.send" | "output.http-request"; formula?: string; max?: number; min?: number; } export interface CommandVoiceMeeter extends Command { - _type: "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced" | "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton"; + _type: "voicemeeter.advanced" | "voicemeeter.advanced-button" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasic" | "com.getpcpanel.commands.command.CommandVoiceMeeterBasicButton"; } export interface CommandVoiceMeeterAdvanced extends CommandVoiceMeeter, DialAction { - _type: "com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced"; + _type: "voicemeeter.advanced"; ct: DialControlMode; fullParam: string; } export interface CommandVoiceMeeterAdvancedButton extends CommandVoiceMeeter, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandVoiceMeeterAdvancedButton"; + _type: "voicemeeter.advanced-button"; bt: ButtonControlMode; fullParam: string; stringValue?: string; @@ -278,7 +278,7 @@ export interface CommandVoiceMeeterBasicButton extends CommandVoiceMeeter, Butto } export interface CommandVolume extends Command { - _type: "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDevice" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "com.getpcpanel.commands.command.CommandVolumeDevice" | "com.getpcpanel.commands.command.CommandVolumeDeviceMute" | "com.getpcpanel.commands.command.CommandVolumeFocus" | "com.getpcpanel.commands.command.CommandVolumeFocusMute" | "com.getpcpanel.commands.command.CommandVolumeProcess" | "com.getpcpanel.commands.command.CommandVolumeProcessMute"; + _type: "com.getpcpanel.commands.command.CommandVolumeApplicationDeviceToggle" | "volume.default-device" | "volume.default-device-advanced" | "volume.default-device-toggle" | "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggleAdvanced" | "volume.device" | "volume.device-mute" | "volume.focus" | "volume.focus-mute" | "volume.process" | "volume.process-mute"; } export interface CommandVolumeApplicationDeviceToggle extends CommandVolume, ButtonAction { @@ -290,12 +290,12 @@ export interface CommandVolumeApplicationDeviceToggle extends CommandVolume, But } export interface CommandVolumeDefaultDevice extends CommandVolume, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandVolumeDefaultDevice"; + _type: "volume.default-device"; deviceId: string; } export interface CommandVolumeDefaultDeviceAdvanced extends CommandVolume, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceAdvanced"; + _type: "volume.default-device-advanced"; communicationPb: string; communicationRec: string; mediaPb: string; @@ -307,7 +307,7 @@ export interface CommandVolumeDefaultDeviceAdvancedBuilder { } export interface CommandVolumeDefaultDeviceToggle extends CommandVolume, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandVolumeDefaultDeviceToggle"; + _type: "volume.default-device-toggle"; currentIdx: number; devices: string[]; } @@ -319,29 +319,29 @@ export interface CommandVolumeDefaultDeviceToggleAdvanced extends CommandVolume, } export interface CommandVolumeDevice extends CommandVolume, DialAction { - _type: "com.getpcpanel.commands.command.CommandVolumeDevice"; + _type: "volume.device"; deviceId: string; isUnMuteOnVolumeChange: boolean; unMuteOnVolumeChange: boolean; } export interface CommandVolumeDeviceMute extends CommandVolume, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandVolumeDeviceMute"; + _type: "volume.device-mute"; deviceId: string; muteType: MuteType; } export interface CommandVolumeFocus extends CommandVolume, DialAction { - _type: "com.getpcpanel.commands.command.CommandVolumeFocus"; + _type: "volume.focus"; } export interface CommandVolumeFocusMute extends CommandVolume, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandVolumeFocusMute"; + _type: "volume.focus-mute"; muteType: MuteType; } export interface CommandVolumeProcess extends CommandVolume, DialAction { - _type: "com.getpcpanel.commands.command.CommandVolumeProcess"; + _type: "volume.process"; device: string; isUnMuteOnVolumeChange: boolean; processName: string[]; @@ -349,34 +349,34 @@ export interface CommandVolumeProcess extends CommandVolume, DialAction { } export interface CommandVolumeProcessMute extends CommandVolume, ButtonAction { - _type: "com.getpcpanel.commands.command.CommandVolumeProcessMute"; + _type: "volume.process-mute"; muteType: MuteType; processName: string[]; } export interface CommandWaveLink extends Command { - _type: "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "wavelink.add-focus-to-channel" | "wavelink.change-level" | "wavelink.change-mute" | "com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect" | "wavelink.main-output"; } export interface CommandWaveLinkAddFocusToChannel extends CommandWaveLink, ButtonAction { - _type: "com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel"; + _type: "wavelink.add-focus-to-channel"; id?: string; name?: string; } export interface CommandWaveLinkChange extends CommandWaveLink { - _type: "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel" | "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute"; + _type: "wavelink.change-level" | "wavelink.change-mute"; commandType: WaveLinkCommandTarget; id1?: string; id2?: string; } export interface CommandWaveLinkChangeLevel extends CommandWaveLinkChange, DialAction { - _type: "com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel"; + _type: "wavelink.change-level"; } export interface CommandWaveLinkChangeMute extends CommandWaveLinkChange, ButtonAction { - _type: "com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute"; + _type: "wavelink.change-mute"; muteType: MuteType; } @@ -390,7 +390,7 @@ export interface CommandWaveLinkChannelEffect extends CommandWaveLink, ButtonAct } export interface CommandWaveLinkMainOutput extends CommandWaveLink, ButtonAction { - _type: "com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput"; + _type: "wavelink.main-output"; id?: string; name?: string; } diff --git a/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java b/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java index 54701cba..11003e7d 100644 --- a/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java +++ b/src/test/java/com/getpcpanel/commands/CommandSubtypeRegistrarTest.java @@ -1,6 +1,8 @@ package com.getpcpanel.commands; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; @@ -16,27 +18,38 @@ import com.getpcpanel.voicemeeter.command.VoiceMeeterCommandModule; /** - * Validates that {@link CommandSubtypeRegistrar} turns the {@link CommandModule} contributions into - * working Jackson polymorphic registration — i.e. a command contributed by a feature module (here a - * core one and an integration one) deserializes by its {@code @JsonTypeName} id once the registrar has - * customized the mapper. The {@code @All} injection that feeds {@code modules} at runtime is a standard - * Quarkus mechanism already used elsewhere in the app, so it is exercised here by supplying the real - * modules directly. + * Validates {@link CommandSubtypeRegistrar}: it turns the {@link CommandModule} contributions into + * working Jackson polymorphic registration, and provides backwards-compatible loading of the previous + * {@code _type} ids. The {@code @All} injection that feeds {@code modules} at runtime is a standard + * Quarkus mechanism used elsewhere in the app, so it is exercised here by supplying the real modules. */ -@DisplayName("CommandSubtypeRegistrar wiring") +@DisplayName("CommandSubtypeRegistrar wiring + backwards compatibility") class CommandSubtypeRegistrarTest { - @Test - @DisplayName("module-contributed command types deserialize by their id after customize()") - void registersModuleContributedSubtypes() throws Exception { + private final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + { var registrar = new CommandSubtypeRegistrar(); registrar.modules = List.of(new DeviceCommandModule(), new VoiceMeeterCommandModule()); - - var mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); registrar.customize(mapper); + } + + @Test + @DisplayName("the current (nice) id deserializes") + void niceIdsResolve() throws Exception { + assertInstanceOf(CommandBrightness.class, mapper.readValue("{\"_type\":\"device.brightness\"}", Command.class)); + assertInstanceOf(CommandVoiceMeeterAdvanced.class, mapper.readValue("{\"_type\":\"voicemeeter.advanced\"}", Command.class)); + } + + @Test + @DisplayName("a legacy FQCN id from an old save still loads, and re-saving converts it to the nice id") + void legacyIdsLoadAndConvert() throws Exception { + // an old profiles.json persisted the command's fully-qualified class name as _type + var loaded = mapper.readValue("{\"_type\":\"com.getpcpanel.commands.command.CommandBrightness\"}", Command.class); + assertInstanceOf(CommandBrightness.class, loaded); - assertInstanceOf(CommandBrightness.class, - mapper.readValue("{\"_type\":\"com.getpcpanel.commands.command.CommandBrightness\"}", Command.class)); - assertInstanceOf(CommandVoiceMeeterAdvanced.class, - mapper.readValue("{\"_type\":\"com.getpcpanel.commands.command.CommandVoiceMeeterAdvanced\"}", Command.class)); + // re-saving writes the nice current id — a transparent one-way conversion, never the legacy id + var json = mapper.writeValueAsString(loaded); + assertTrue(json.contains("\"device.brightness\""), () -> "expected the nice id in: " + json); + assertFalse(json.contains("commands.command.CommandBrightness"), () -> "must not re-write the legacy id: " + json); } } diff --git a/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java b/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java index 3396e19f..ec78a62b 100644 --- a/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java +++ b/src/test/java/com/getpcpanel/commands/command/CommandKeystrokeTest.java @@ -28,7 +28,7 @@ class CommandKeystrokeTest { @Test @DisplayName("legacy JSON without a 'type' field deserializes as a KEY keystroke") void legacyKeystrokeDefaultsToKey() throws Exception { - var json = "{\"_type\":\"com.getpcpanel.commands.command.CommandKeystroke\",\"keystroke\":\"ctrl+A\"}"; + var json = "{\"_type\":\"keyboard.keystroke\",\"keystroke\":\"ctrl+A\"}"; var cmd = (CommandKeystroke) mapper.readValue(json, Command.class); assertEquals(KeystrokeType.KEY, cmd.getType()); assertEquals("ctrl+A", cmd.getKeystroke()); diff --git a/src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java b/src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java index 70dd7d9f..814be997 100644 --- a/src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java +++ b/src/test/java/com/getpcpanel/commands/meta/CommandRegistryGeneratorTest.java @@ -83,7 +83,7 @@ private static String render(List entries) { sb.append("import type { CommandCategory, CommandKind, Integration } from './command-catalog';\n\n"); sb.append("export interface GeneratedCommand {\n"); sb.append(" type: string;\n label: string;\n category: CommandCategory;\n"); - sb.append(" kinds: CommandKind[];\n integration?: Integration;\n icon: IconName;\n}\n\n"); + sb.append(" kinds: CommandKind[];\n integration?: Integration;\n icon: IconName;\n /** previous _type id(s) for joining hand-written field schemas keyed by the old id */\n legacy?: string;\n}\n\n"); sb.append("export const GENERATED_COMMANDS: GeneratedCommand[] = [\n"); for (var e : entries) { sb.append(" { type: ").append(q(e.type())); @@ -102,6 +102,9 @@ private static String render(List entries) { sb.append(", integration: ").append(q(e.meta().integration())); } sb.append(", icon: ").append(q(e.meta().icon())); + if (e.meta().legacyIds().length > 0) { + sb.append(", legacy: ").append(q(e.meta().legacyIds()[0])); + } sb.append(" },\n"); } sb.append("];\n"); From a261ae8b2c6f984581ee4ef9137bbca1d945e317 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:04:43 +0200 Subject: [PATCH 14/49] docs: record Java-generated registry + nice backwards-compatible ids Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/feature-module-structure.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/feature-module-structure.md b/docs/feature-module-structure.md index 801ed569..ca07524a 100644 --- a/docs/feature-module-structure.md +++ b/docs/feature-module-structure.md @@ -1,8 +1,9 @@ # Feature-module structure (plugin-style refactor) -Status: **in progress** — the type-id foundation and the command relocations are implemented and -green; the frontend-catalog generator and the remaining non-command consolidation are not yet done. -This document is the source of truth for the end state and the order we get there. +Status: **in progress** — the decentralized command registry, the per-feature module split (core + +integrations), the Java-generated frontend registry, and nice backwards-compatible discriminators are +all implemented and green. Remaining: the non-command consolidation (REST resources, icon SPI, +dead-code/`events.md`). This document is the source of truth for the end state. ### Implemented so far @@ -27,6 +28,18 @@ This document is the source of truth for the end state and the order we get ther `@All List`). - **pom `classPatterns`** collapsed to one glob `com.getpcpanel.**.command.**` — new feature command packages need no build-config edit. +- **Frontend command registry is generated from Java.** Each assignable command carries + `@CommandMeta(label, category, kinds, integration, icon)` in its own file; + `CommandRegistryGeneratorTest` emits `command-registry.generated.ts` from those annotations (and + guards staleness). `command-catalog.ts` consumes it and keeps only the field editors + (`buildEmpty`/`fields[]`, which are Angular UI). So "which commands exist + how they're classified" + is retrieved from Java, per command. +- **Nice, backwards-compatible discriminators.** Each command's persisted `_type` is now a readable id + (`voicemeeter.advanced`) via `@JsonTypeName`. `@CommandMeta.legacyIds` records the previous id (the + old FQCN); `CommandSubtypeRegistrar` installs a Jackson `DeserializationProblemHandler` that maps an + unknown legacy id back to its command on **read only** — old `profiles.json` keep loading and + re-saving rewrites them with the nice id (a transparent one-way conversion). New saves are never + ambiguous with old ids. `CommandSubtypeRegistrarTest` covers both directions. Guards (all green): `CommandSubtypeRegistryTest` enforces every command self-identifies with a unique `@JsonTypeName`, the `CommandModule` SPI covers exactly the concrete set (none missing/stale/dup), and @@ -243,12 +256,13 @@ Each phase is independently committable and keeps the build + coverage/parity te feature self-registers. Guard tests rewritten for the decentralized invariants. 4. **✅ DONE — Core commands split into feature modules.** `volume`, `keyboard`, `program`, `device`, `profile`, `analogbands`, `output` — `commands/command` is now engine-only. -5. **TODO — Annotation-driven frontend catalog + pretty ids.** Add `@CommandMeta` (+ `@FieldMeta`) and a - build-time generator that emits the command-level catalog metadata (the frontend keeps its - hand-written composite-field renderers + live-source wiring). The generated `command-catalog` is the - *one remaining central artifact*; generating it eliminates the last hand-maintained registry. Then - optionally switch the `@JsonTypeName` ids to pretty form with the old FQCNs as `legacyTypes` aliases. - (Touches the frontend — verify with `tsc`/`npm run build`.) +5. **✅ DONE — Annotation-driven frontend registry + nice backwards-compatible ids.** `@CommandMeta` + on each assignable command + `CommandRegistryGeneratorTest` generates `command-registry.generated.ts`; + `command-catalog.ts` consumes it (field editors stay hand-written). `@JsonTypeName` ids switched to + nice form (`voicemeeter.advanced`), with the old FQCN kept as a read-only `@CommandMeta.legacyIds` + alias via a `DeserializationProblemHandler` (old saves load; re-save converts). Field-level + generation (`@FieldMeta` for the composite editors) is a possible future extension but not required — + the editors are genuinely UI, not registry data. 6. **TODO — Remaining non-command consolidation.** `VoiceMeeterResource`/`ObsResource`/`OscResource` → `rest/`; migrate the hardcoded `IconService` VoiceMeeter/OBS handlers to the `IIconHandler` SPI (mirror `WaveLinkIconHandler`); per-feature settings records local; delete the From 7f7e1211a8f8d3b2abd8e2168de392ee4a3a034c Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:17:32 +0200 Subject: [PATCH 15/49] refactor(rest): delete the dead /api/commands/available registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CommandsResource (and its CommandType DTO) was a stale, non-authoritative command list the frontend never called — the frontend command catalog is the real registry, now generated from @CommandMeta. Remove both and prune their reachability-metadata entries. backend.types.ts drops the unused CommandType interface; tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../com/getpcpanel/rest/CommandsResource.java | 98 ------------------- .../rest/model/dto/CommandType.java | 15 --- .../native-image/reachability-metadata.json | 17 +--- .../src/app/models/generated/backend.types.ts | 9 -- 4 files changed, 1 insertion(+), 138 deletions(-) delete mode 100644 src/main/java/com/getpcpanel/rest/CommandsResource.java delete mode 100644 src/main/java/com/getpcpanel/rest/model/dto/CommandType.java diff --git a/src/main/java/com/getpcpanel/rest/CommandsResource.java b/src/main/java/com/getpcpanel/rest/CommandsResource.java deleted file mode 100644 index 8fb6cbe1..00000000 --- a/src/main/java/com/getpcpanel/rest/CommandsResource.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.getpcpanel.rest; - -import java.util.Collection; -import java.util.List; - -import com.getpcpanel.device.command.CommandBrightness; -import com.getpcpanel.program.command.CommandEndProgram; -import com.getpcpanel.keyboard.command.CommandKeystroke; -import com.getpcpanel.keyboard.command.CommandMedia; -import com.getpcpanel.obs.command.CommandObsMuteSource; -import com.getpcpanel.obs.command.CommandObsSetScene; -import com.getpcpanel.obs.command.CommandObsSetSourceVolume; -import com.getpcpanel.program.command.CommandRun; -import com.getpcpanel.program.command.CommandShortcut; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvanced; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterAdvancedButton; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasic; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeterBasicButton; -import com.getpcpanel.volume.command.CommandVolumeApplicationDeviceToggle; -import com.getpcpanel.volume.command.CommandVolumeDefaultDevice; -import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceAdvanced; -import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggle; -import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggleAdvanced; -import com.getpcpanel.volume.command.CommandVolumeDevice; -import com.getpcpanel.volume.command.CommandVolumeDeviceMute; -import com.getpcpanel.volume.command.CommandVolumeFocus; -import com.getpcpanel.volume.command.CommandVolumeFocusMute; -import com.getpcpanel.volume.command.CommandVolumeProcess; -import com.getpcpanel.volume.command.CommandVolumeProcessMute; -import com.getpcpanel.profile.SaveService; -import com.getpcpanel.rest.EventBroadcaster.AssignmentChangedEvent.Kinds; -import com.getpcpanel.rest.model.dto.CommandType; -import com.getpcpanel.rest.model.dto.CommandType.CommandCategory; -import com.getpcpanel.wavelink.command.CommandWaveLinkAddFocusToChannel; -import com.getpcpanel.wavelink.command.CommandWaveLinkChangeLevel; -import com.getpcpanel.wavelink.command.CommandWaveLinkChangeMute; -import com.getpcpanel.wavelink.command.CommandWaveLinkChannelEffect; -import com.getpcpanel.wavelink.command.CommandWaveLinkMainOutput; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import one.util.streamex.StreamEx; - -@Path("/api/commands") -@ApplicationScoped -public class CommandsResource { - @Inject SaveService saveService; - - private static final List commandTypes = List.of( - new CommandType("Brightness", CommandBrightness.class.getName(), CommandCategory.standard, Kinds.dial), - new CommandType("Process volume", CommandVolumeProcess.class.getName(), CommandCategory.standard, Kinds.dial), - new CommandType("Focus volume", CommandVolumeFocus.class.getName(), CommandCategory.standard, Kinds.dial), - new CommandType("Device volume", CommandVolumeDevice.class.getName(), CommandCategory.standard, Kinds.dial), - new CommandType("Obs Source Volume", CommandObsSetSourceVolume.class.getName(), CommandCategory.obs, Kinds.dial), - new CommandType("VoiceMeeter Advanced", CommandVoiceMeeterAdvanced.class.getName(), CommandCategory.voicemeeter, Kinds.dial), - new CommandType("VoiceMeeter Basic", CommandVoiceMeeterBasic.class.getName(), CommandCategory.voicemeeter, Kinds.dial), - new CommandType("WaveLink Change Level", CommandWaveLinkChangeLevel.class.getName(), CommandCategory.wavelink, Kinds.dial), - - new CommandType("End Program", CommandEndProgram.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Keystroke", CommandKeystroke.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Run", CommandRun.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Shortcut", CommandShortcut.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Media", CommandMedia.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Toggle application device", CommandVolumeApplicationDeviceToggle.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Default Device", CommandVolumeDefaultDevice.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Default Device Advanced", CommandVolumeDefaultDeviceAdvanced.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Default Device Toggle", CommandVolumeDefaultDeviceToggle.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Default Device Toggle Advanced", CommandVolumeDefaultDeviceToggleAdvanced.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Device Mute", CommandVolumeDeviceMute.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Focus Mute", CommandVolumeFocusMute.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Process Mute", CommandVolumeProcessMute.class.getName(), CommandCategory.standard, Kinds.button), - new CommandType("Obs Mute Source", CommandObsMuteSource.class.getName(), CommandCategory.obs, Kinds.button), - new CommandType("Obs Set Scene", CommandObsSetScene.class.getName(), CommandCategory.obs, Kinds.button), - new CommandType("VoiceMeeter Advanced", CommandVoiceMeeterAdvancedButton.class.getName(), CommandCategory.voicemeeter, Kinds.button), - new CommandType("VoiceMeeter Basic", CommandVoiceMeeterBasicButton.class.getName(), CommandCategory.voicemeeter, Kinds.button), - new CommandType("WaveLink Add Focus To Channel", CommandWaveLinkAddFocusToChannel.class.getName(), CommandCategory.wavelink, Kinds.button), - new CommandType("WaveLink Change Mute", CommandWaveLinkChangeMute.class.getName(), CommandCategory.wavelink, Kinds.button), - new CommandType("WaveLink Channel Effect", CommandWaveLinkChannelEffect.class.getName(), CommandCategory.wavelink, Kinds.button), - new CommandType("WaveLink Main Output", CommandWaveLinkMainOutput.class.getName(), CommandCategory.wavelink, Kinds.button) - ); - - @GET - @Path("/available") - public Collection listAvailableCommands() { - return StreamEx.of(commandTypes).filter(this::enabled).toList(); - } - - private boolean enabled(CommandType commandType) { - return switch (commandType.category()) { - case standard -> true; - case obs -> saveService.get().isObsEnabled(); - case voicemeeter -> saveService.get().isVoicemeeterEnabled(); - case wavelink -> saveService.get().getWaveLink().enabled(); - }; - } -} diff --git a/src/main/java/com/getpcpanel/rest/model/dto/CommandType.java b/src/main/java/com/getpcpanel/rest/model/dto/CommandType.java deleted file mode 100644 index 4b0217d6..00000000 --- a/src/main/java/com/getpcpanel/rest/model/dto/CommandType.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.getpcpanel.rest.model.dto; - -import com.getpcpanel.rest.EventBroadcaster.AssignmentChangedEvent.Kinds; - -public record CommandType( - String name, - String command, - CommandCategory category, - Kinds kind -) { - - public enum CommandCategory { - standard, voicemeeter, obs, wavelink - } -} diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json index 7953fccc..ffd48374 100644 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -2398,18 +2398,6 @@ } ] }, - { - "type": "com.getpcpanel.rest.CommandsResource" - }, - { - "type": "com.getpcpanel.rest.CommandsResource$quarkusrestinvoker$listAvailableCommands_47312a0429306c144b67bec23b8273a7bf24ceec", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, { "type": "com.getpcpanel.rest.DeviceResource" }, @@ -2777,9 +2765,6 @@ } ] }, - { - "type": "com.getpcpanel.rest.model.dto.CommandType" - }, { "type": "com.getpcpanel.rest.model.dto.ControlAssignmentsUpdateDto" }, @@ -11791,4 +11776,4 @@ "bundle": "sun.awt.resources.awt" } ] -} \ No newline at end of file +} diff --git a/src/main/webui/src/app/models/generated/backend.types.ts b/src/main/webui/src/app/models/generated/backend.types.ts index e4ee5473..77c99b17 100644 --- a/src/main/webui/src/app/models/generated/backend.types.ts +++ b/src/main/webui/src/app/models/generated/backend.types.ts @@ -232,13 +232,6 @@ export interface CommandShortcut extends Command, ButtonAction { shortcut: string; } -export interface CommandType { - category: CommandCategory; - command: string; - kind: Kinds; - name: string; -} - export interface CommandValueOutput extends Command, DialAction, ButtonAction { _type: "mqtt.publish" | "osc.send" | "output.http-request"; formula?: string; @@ -959,8 +952,6 @@ export type ButtonControlMode = "ENABLE" | "DISABLE" | "TOGGLE" | "STRING"; export type ButtonType = "MONO" | "MUTE" | "SOLO" | "MC" | "EQ" | "A1" | "A2" | "A3" | "A4" | "A5" | "B1" | "B2" | "B3" | "SEL" | "MIXA" | "MIXB" | "REPEAT" | "COMPOSITE"; -export type CommandCategory = "standard" | "voicemeeter" | "obs" | "wavelink"; - export type CommandsType = "allAtOnce" | "sequential"; export type ControlType = "STRIP" | "BUS"; From 1fcc3f88cbef9a6a6375ac2ab5eadacf1aa6672c Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:22:38 +0200 Subject: [PATCH 16/49] refactor(rest): move integration resources into rest/ subpackages VoiceMeeterResource -> rest/voicemeeter, ObsResource -> rest/obs, OscResource -> rest/osc, matching the rest/wavelink + rest/discord layout. @Path is unchanged so the URLs (/api/voicemeeter, /api/obs, /api/osc) are identical; reachability-metadata repointed (the quarkusrestinvoker hash is method-derived, so only the package prefix changes). NOTE: rewrites Quarkus native-image REST-invoker metadata, fully verifiable only by a native build (per CLAUDE.md); JVM build + coverage tests are green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../com/getpcpanel/rest/{ => obs}/ObsResource.java | 2 +- .../com/getpcpanel/rest/{ => osc}/OscResource.java | 2 +- .../{ => voicemeeter}/VoiceMeeterResource.java | 2 +- .../native-image/reachability-metadata.json | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) rename src/main/java/com/getpcpanel/rest/{ => obs}/ObsResource.java (95%) rename src/main/java/com/getpcpanel/rest/{ => osc}/OscResource.java (95%) rename src/main/java/com/getpcpanel/rest/{ => voicemeeter}/VoiceMeeterResource.java (96%) diff --git a/src/main/java/com/getpcpanel/rest/ObsResource.java b/src/main/java/com/getpcpanel/rest/obs/ObsResource.java similarity index 95% rename from src/main/java/com/getpcpanel/rest/ObsResource.java rename to src/main/java/com/getpcpanel/rest/obs/ObsResource.java index 17fd7221..c7c7c04b 100644 --- a/src/main/java/com/getpcpanel/rest/ObsResource.java +++ b/src/main/java/com/getpcpanel/rest/obs/ObsResource.java @@ -1,4 +1,4 @@ -package com.getpcpanel.rest; +package com.getpcpanel.rest.obs; import java.util.List; diff --git a/src/main/java/com/getpcpanel/rest/OscResource.java b/src/main/java/com/getpcpanel/rest/osc/OscResource.java similarity index 95% rename from src/main/java/com/getpcpanel/rest/OscResource.java rename to src/main/java/com/getpcpanel/rest/osc/OscResource.java index 0dca117b..90f7df7b 100644 --- a/src/main/java/com/getpcpanel/rest/OscResource.java +++ b/src/main/java/com/getpcpanel/rest/osc/OscResource.java @@ -1,4 +1,4 @@ -package com.getpcpanel.rest; +package com.getpcpanel.rest.osc; import java.util.Map; diff --git a/src/main/java/com/getpcpanel/rest/VoiceMeeterResource.java b/src/main/java/com/getpcpanel/rest/voicemeeter/VoiceMeeterResource.java similarity index 96% rename from src/main/java/com/getpcpanel/rest/VoiceMeeterResource.java rename to src/main/java/com/getpcpanel/rest/voicemeeter/VoiceMeeterResource.java index 5e4792b2..162381bd 100644 --- a/src/main/java/com/getpcpanel/rest/VoiceMeeterResource.java +++ b/src/main/java/com/getpcpanel/rest/voicemeeter/VoiceMeeterResource.java @@ -1,4 +1,4 @@ -package com.getpcpanel.rest; +package com.getpcpanel.rest.voicemeeter; import java.util.List; diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json index ffd48374..26acda84 100644 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -2612,10 +2612,10 @@ ] }, { - "type": "com.getpcpanel.rest.ObsResource" + "type": "com.getpcpanel.rest.obs.ObsResource" }, { - "type": "com.getpcpanel.rest.ObsResource$quarkusrestinvoker$listScenes_d45e7a31126e58273b41c15802da4944d2079682", + "type": "com.getpcpanel.rest.obs.ObsResource$quarkusrestinvoker$listScenes_d45e7a31126e58273b41c15802da4944d2079682", "methods": [ { "name": "", @@ -2624,7 +2624,7 @@ ] }, { - "type": "com.getpcpanel.rest.ObsResource$quarkusrestinvoker$listSources_5f705776229abc7f3484fe8cfadec2344a09af33", + "type": "com.getpcpanel.rest.obs.ObsResource$quarkusrestinvoker$listSources_5f705776229abc7f3484fe8cfadec2344a09af33", "methods": [ { "name": "", @@ -2742,13 +2742,13 @@ ] }, { - "type": "com.getpcpanel.rest.VoiceMeeterResource" + "type": "com.getpcpanel.rest.voicemeeter.VoiceMeeterResource" }, { - "type": "com.getpcpanel.rest.VoiceMeeterResource$VoiceMeeterParam" + "type": "com.getpcpanel.rest.voicemeeter.VoiceMeeterResource$VoiceMeeterParam" }, { - "type": "com.getpcpanel.rest.VoiceMeeterResource$quarkusrestinvoker$getAdvancedParams_789ebbb7df6501274ae825c6a27179c302791436", + "type": "com.getpcpanel.rest.voicemeeter.VoiceMeeterResource$quarkusrestinvoker$getAdvancedParams_789ebbb7df6501274ae825c6a27179c302791436", "methods": [ { "name": "", @@ -2757,7 +2757,7 @@ ] }, { - "type": "com.getpcpanel.rest.VoiceMeeterResource$quarkusrestinvoker$getBasicParams_3fb05518ea0ad168b8b8c6bcdb89e050368544a7", + "type": "com.getpcpanel.rest.voicemeeter.VoiceMeeterResource$quarkusrestinvoker$getBasicParams_3fb05518ea0ad168b8b8c6bcdb89e050368544a7", "methods": [ { "name": "", From bdbb689677b6bb8a97b4978c5b8be87b6a2a9d91 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:22:38 +0200 Subject: [PATCH 17/49] refactor(icon): contribute VoiceMeeter/OBS command icons via the IIconHandler SPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the two hardcoded imageHandlers.put(CommandObs/CommandVoiceMeeter,...) entries in IconService with ObsIconHandler + VoiceMeeterIconHandler @ApplicationScoped beans (mirroring WaveLinkIconHandler), discovered via @All List — so a feature's command icon lives in its package. Behaviour identical (same images); IconService.OBS/VOICEMEETER made public like DEVICE. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../com/getpcpanel/commands/IconService.java | 14 ++-------- .../com/getpcpanel/obs/ObsIconHandler.java | 28 +++++++++++++++++++ .../voicemeeter/VoiceMeeterIconHandler.java | 28 +++++++++++++++++++ 3 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/getpcpanel/obs/ObsIconHandler.java create mode 100644 src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterIconHandler.java diff --git a/src/main/java/com/getpcpanel/commands/IconService.java b/src/main/java/com/getpcpanel/commands/IconService.java index f9116d8e..7bb3a627 100644 --- a/src/main/java/com/getpcpanel/commands/IconService.java +++ b/src/main/java/com/getpcpanel/commands/IconService.java @@ -17,8 +17,6 @@ import com.getpcpanel.commands.command.Command; import com.getpcpanel.device.command.CommandBrightness; -import com.getpcpanel.obs.command.CommandObs; -import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; import com.getpcpanel.volume.command.CommandVolumeDefaultDevice; import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceAdvanced; import com.getpcpanel.volume.command.CommandVolumeDefaultDeviceToggle; @@ -43,8 +41,8 @@ @ApplicationScoped public class IconService { public BufferedImage DEFAULT; - private BufferedImage OBS; - private BufferedImage VOICEMEETER; + public BufferedImage OBS; + public BufferedImage VOICEMEETER; public BufferedImage DEVICE; public BufferedImage SYSTEM_SOUND; @@ -96,8 +94,6 @@ public void init() { // Dials imageHandlers.put(CommandVolumeProcess.class, IconService::getRunningProcessIcon); imageHandlers.put(CommandVolumeFocus.class, IconService::getFocusProcessIcon); - imageHandlers.put(CommandObs.class, IconService::getObsIcon); - imageHandlers.put(CommandVoiceMeeter.class, IconService::getVoiceMeeterIcon); imageHandlers.put(CommandVolumeDevice.class, IconService::getDeviceIcon); imageHandlers.put(CommandBrightness.class, IconService::getBrightnessIcon); @@ -188,13 +184,7 @@ private BufferedImage getDeviceIcon(Command command) { return DEVICE; } - private BufferedImage getVoiceMeeterIcon(CommandVoiceMeeter command) { - return VOICEMEETER; - } - private BufferedImage getObsIcon(CommandObs command) { - return OBS; - } private class SafeMap extends HashMap, BiFunction> { diff --git a/src/main/java/com/getpcpanel/obs/ObsIconHandler.java b/src/main/java/com/getpcpanel/obs/ObsIconHandler.java new file mode 100644 index 00000000..6783d6bb --- /dev/null +++ b/src/main/java/com/getpcpanel/obs/ObsIconHandler.java @@ -0,0 +1,28 @@ +package com.getpcpanel.obs; + +import java.awt.image.BufferedImage; +import java.util.Optional; + +import com.getpcpanel.commands.IIconHandler; +import com.getpcpanel.commands.IconService; +import com.getpcpanel.obs.command.CommandObs; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +/** Supplies the OBS icon for any OBS command, contributed via the {@link IIconHandler} SPI. */ +@ApplicationScoped +public class ObsIconHandler implements IIconHandler { + @Inject + IconService iconService; + + @Override + public Class getCommandClass() { + return CommandObs.class; + } + + @Override + public Optional supplyImage(CommandObs cmd) { + return Optional.ofNullable(iconService.OBS); + } +} diff --git a/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterIconHandler.java b/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterIconHandler.java new file mode 100644 index 00000000..2ff7ac26 --- /dev/null +++ b/src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterIconHandler.java @@ -0,0 +1,28 @@ +package com.getpcpanel.voicemeeter; + +import java.awt.image.BufferedImage; +import java.util.Optional; + +import com.getpcpanel.commands.IIconHandler; +import com.getpcpanel.commands.IconService; +import com.getpcpanel.voicemeeter.command.CommandVoiceMeeter; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +/** Supplies the VoiceMeeter icon for any VoiceMeeter command, contributed via the {@link IIconHandler} SPI. */ +@ApplicationScoped +public class VoiceMeeterIconHandler implements IIconHandler { + @Inject + IconService iconService; + + @Override + public Class getCommandClass() { + return CommandVoiceMeeter.class; + } + + @Override + public Optional supplyImage(CommandVoiceMeeter cmd) { + return Optional.ofNullable(iconService.VOICEMEETER); + } +} From 4472f446e90faff0d902d0bb2ea87c7a00dd9f9d Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:23:26 +0200 Subject: [PATCH 18/49] docs(events): add the missing DiscordChangedEvent row Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/events.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/events.md b/docs/events.md index d7fa9afe..c6c73371 100644 --- a/docs/events.md +++ b/docs/events.md @@ -66,6 +66,7 @@ up to date when you add or remove an event or an observer. | `VoiceMeeterDirtyEvent` | `Voicemeeter` | `VoiceMeeterMuteService` | | `VoiceMeeterMuteEvent` | `VoiceMeeterMuteService` | `VoiceMeeterMuteResolver` (mute-colour) | | `WaveLinkChangedEvent` | `WaveLinkService` (Wave Link state incl. mute changed) | `MuteColorService` | +| `DiscordChangedEvent` | `DiscordService` (Discord voice/mute/deafen state changed) | `MuteColorService` | | `MuteOverridesDirtyEvent` | mute-colour resolvers (e.g. `VoiceMeeterMuteResolver` after caching a mute change) | `MuteColorService` | | `MqttStatusEvent` | `MqttService` | `MqttDeviceService` | From 7218b448d2745647c87ea01920ec4094c7b0ff76 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:24:29 +0200 Subject: [PATCH 19/49] docs: mark feature-module restructure complete Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/feature-module-structure.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/feature-module-structure.md b/docs/feature-module-structure.md index ca07524a..8ff163ed 100644 --- a/docs/feature-module-structure.md +++ b/docs/feature-module-structure.md @@ -1,9 +1,15 @@ # Feature-module structure (plugin-style refactor) -Status: **in progress** — the decentralized command registry, the per-feature module split (core + -integrations), the Java-generated frontend registry, and nice backwards-compatible discriminators are -all implemented and green. Remaining: the non-command consolidation (REST resources, icon SPI, -dead-code/`events.md`). This document is the source of truth for the end state. +Status: **complete** — the decentralized command registry, the per-feature module split (core + +integrations), the Java-generated frontend registry, nice backwards-compatible discriminators, and the +non-command consolidation (REST resources → `rest/`, VM/OBS icons on the `IIconHandler` SPI, +dead `CommandsResource`/`CommandType` removed, `events.md` updated) are all implemented and green +(437 tests pass; `tsc` clean). The branch is rebased on current `origin/main`. This document is the +source of truth for the end state. + +> The only step that cannot be fully verified in a non-native build is the moved REST resources' +> Quarkus native-image invoker metadata (`reachability-metadata.json`) — per the GraalVM notes in +> `CLAUDE.md`, that is verified by the native CI build / running the native binary. ### Implemented so far @@ -263,12 +269,13 @@ Each phase is independently committable and keeps the build + coverage/parity te alias via a `DeserializationProblemHandler` (old saves load; re-save converts). Field-level generation (`@FieldMeta` for the composite editors) is a possible future extension but not required — the editors are genuinely UI, not registry data. -6. **TODO — Remaining non-command consolidation.** `VoiceMeeterResource`/`ObsResource`/`OscResource` - → `rest/`; migrate the hardcoded `IconService` VoiceMeeter/OBS handlers to the - `IIconHandler` SPI (mirror `WaveLinkIconHandler`); per-feature settings records local; delete the - dead `CommandsResource` + `CommandType` DTO (and prune their `reachability-metadata.json` entries); - fix `docs/events.md` (add the missing Discord events). Confirm `ReflectionRegistrationCoverageTest`, - `ProxyRegistrationCoverageTest`, `NativeBuildArgsParityTest`; build native if feasible. +6. **✅ DONE — Remaining non-command consolidation.** `VoiceMeeterResource`/`ObsResource`/`OscResource` + → `rest/`; the hardcoded `IconService` VoiceMeeter/OBS handlers migrated to the + `IIconHandler` SPI (`ObsIconHandler`, `VoiceMeeterIconHandler`); dead `CommandsResource` + + `CommandType` DTO removed (and their `reachability-metadata.json` entries pruned); `docs/events.md` + updated with `DiscordChangedEvent`. `ReflectionRegistrationCoverageTest` green. (Per-feature + settings records are still in `Save`/`SettingsDto` — a possible further tidy, but those are + field-name Jackson, not `Id.CLASS`, so they carry no module-locality or migration risk.) Git history is preserved throughout by using `git mv` for every relocation. Phases 1–4 are committed and green. From 5c385bbd92e69b9c7f378f27c46b16ee73844b34 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 14:33:32 +0200 Subject: [PATCH 20/49] refactor: move integrations under com.getpcpanel.integration.* External-connector integrations are now grouped under a dedicated namespace rather than sitting at the top level next to core/platform packages: obs, voicemeeter, wavelink, discord, homeassistant, mqtt, osc -> com.getpcpanel.integration.{obs,voicemeeter,wavelink,discord,homeassistant,mqtt,osc} Each integration keeps everything it owns (its .command subpackage, service/engine, icon handler, mute resolver, events). Core command families (volume/keyboard/program/ device/profile/analogbands/output) are not integrations and stay put. Persisted ids are unaffected: the nice @JsonTypeName ids and the frozen legacyIds (old FQCN aliases) are location-independent, so saves/TS/frontend don't change (the backend.types.ts diff is a pure reorder of the same _type literal set). Imports rewritten codebase-wide (import-anchored, so legacyIds string literals are preserved); native-image reachability/proxy-config + the VoicemeeterInstance init-at-run-time args (pom + application.properties, parity-locked) repointed. classPatterns glob com.getpcpanel.**.command.** still matches. Full suite green; tsc clean. Native-image REST/JNA wiring is fully verified only by the native CI build (per CLAUDE.md). Co-Authored-By: Claude Opus 4.8 (1M context) --- pom.xml | 2 +- .../commands/command/CommandConverter.java | 24 ++--- .../getpcpanel/graalvm/NativeImageConfig.java | 78 +++++++-------- .../discord/DiscordChangedEvent.java | 2 +- .../discord/DiscordCommandLabels.java | 2 +- .../discord/DiscordOAuth.java | 2 +- .../discord/DiscordService.java | 2 +- .../discord/command/CommandDiscord.java | 4 +- .../command/CommandDiscordJoinVoice.java | 2 +- .../command/CommandDiscordLeaveVoice.java | 2 +- .../discord/command/CommandDiscordMute.java | 4 +- .../command/CommandDiscordScreenShare.java | 4 +- .../command/CommandDiscordSelfDeafen.java | 4 +- .../CommandDiscordSelfInputVolume.java | 2 +- .../command/CommandDiscordSelfMute.java | 4 +- .../CommandDiscordSelfOutputVolume.java | 2 +- .../command/CommandDiscordToggleVideo.java | 2 +- .../command/CommandDiscordUserMute.java | 4 +- .../command/CommandDiscordUserVolume.java | 2 +- .../discord/command/CommandDiscordVolume.java | 2 +- .../discord/command/DiscordCommandModule.java | 2 +- .../homeassistant/HaActionYaml.java | 2 +- .../homeassistant/HomeAssistantClient.java | 4 +- .../homeassistant/HomeAssistantService.java | 6 +- .../command/CommandHomeAssistant.java | 4 +- .../command/CommandHomeAssistantAction.java | 2 +- .../command/CommandHomeAssistantValue.java | 2 +- .../command/HomeAssistantCommandModule.java | 2 +- .../dto/HomeAssistantServer.java | 2 +- .../dto/HomeAssistantServerStatus.java | 2 +- .../rest/HomeAssistantResource.java | 6 +- .../mqtt/MqttDeviceColorService.java | 16 ++-- .../mqtt/MqttDeviceService.java | 22 ++--- .../mqtt/MqttHomeAssistantHelper.java | 12 +-- .../{ => integration}/mqtt/MqttService.java | 2 +- .../mqtt/MqttStatusEvent.java | 2 +- .../mqtt/MqttTopicHelper.java | 2 +- .../mqtt/command/CommandMqttPublish.java | 4 +- .../mqtt/command/MqttCommandModule.java | 2 +- .../getpcpanel/{ => integration}/obs/OBS.java | 2 +- .../obs/OBSConnectEvent.java | 2 +- .../{ => integration}/obs/OBSMuteEvent.java | 2 +- .../obs/ObsConnectedVolumeService.java | 4 +- .../{ => integration}/obs/ObsIconHandler.java | 4 +- .../obs/ObsWebSocketClient.java | 2 +- .../obs/command/CommandObs.java | 2 +- .../obs/command/CommandObsAction.java | 4 +- .../obs/command/CommandObsMuteSource.java | 4 +- .../obs/command/CommandObsSetScene.java | 4 +- .../command/CommandObsSetSourceVolume.java | 4 +- .../obs/command/ObsCommandModule.java | 2 +- .../{ => integration}/osc/OSCService.java | 2 +- .../osc/command/CommandOscSend.java | 4 +- .../osc/command/OscCommandModule.java | 2 +- .../VoiceMeeterConnectedEvent.java | 4 + .../VoiceMeeterConnectedVolumeService.java | 8 +- .../voicemeeter/VoiceMeeterDirtyEvent.java | 4 + .../voicemeeter/VoiceMeeterIconHandler.java | 4 +- .../voicemeeter/VoiceMeeterMuteEvent.java | 7 ++ .../voicemeeter/VoiceMeeterMuteResolver.java | 10 +- .../voicemeeter/VoiceMeeterMuteService.java | 6 +- .../voicemeeter/Voicemeeter.java | 2 +- .../voicemeeter/VoicemeeterAPI.java | 2 +- .../voicemeeter/VoicemeeterException.java | 2 +- .../voicemeeter/VoicemeeterInstance.java | 2 +- .../voicemeeter/VoicemeeterVersion.java | 2 +- .../command/CommandVoiceMeeter.java | 2 +- .../command/CommandVoiceMeeterAdvanced.java | 4 +- .../CommandVoiceMeeterAdvancedButton.java | 4 +- .../command/CommandVoiceMeeterBasic.java | 4 +- .../CommandVoiceMeeterBasicButton.java | 4 +- .../command/VoiceMeeterCommandModule.java | 2 +- .../wavelink/WaveLinkAppCache.java | 2 +- .../wavelink/WaveLinkChangedEvent.java | 2 +- .../wavelink/WaveLinkIconHandler.java | 12 +-- .../wavelink/WaveLinkService.java | 2 +- .../wavelink/command/CommandWaveLink.java | 4 +- .../CommandWaveLinkAddFocusToChannel.java | 2 +- .../command/CommandWaveLinkChange.java | 2 +- .../command/CommandWaveLinkChangeLevel.java | 2 +- .../command/CommandWaveLinkChangeMute.java | 2 +- .../command/CommandWaveLinkChannelEffect.java | 2 +- .../command/CommandWaveLinkMainOutput.java | 2 +- .../command/WaveLinkCommandModule.java | 2 +- .../command/WaveLinkCommandTarget.java | 2 +- .../mutecolor/DiscordMuteResolver.java | 10 +- .../mutecolor/MuteColorService.java | 8 +- .../mutecolor/NamedDeviceMuteResolver.java | 2 +- .../getpcpanel/mutecolor/ObsMuteResolver.java | 6 +- .../mutecolor/WaveLinkMuteResolver.java | 4 +- .../java/com/getpcpanel/profile/Save.java | 2 +- .../rest/FocusVolumeDiagnosticsResource.java | 4 +- .../com/getpcpanel/rest/SettingsResource.java | 2 +- .../rest/discord/DiscordResource.java | 2 +- .../rest/discord/dto/DiscordStatusDto.java | 2 +- .../rest/discord/dto/DiscordUserDto.java | 2 +- .../rest/model/dto/SettingsDto.java | 2 +- .../com/getpcpanel/rest/obs/ObsResource.java | 2 +- .../com/getpcpanel/rest/osc/OscResource.java | 2 +- .../rest/wavelink/WaveLinkResource.java | 2 +- .../wavelink/dto/WaveLinkResponseDto.java | 2 +- .../VoiceMeeterConnectedEvent.java | 4 - .../voicemeeter/VoiceMeeterDirtyEvent.java | 4 - .../voicemeeter/VoiceMeeterMuteEvent.java | 7 -- .../com.getpcpanel/pcpanel/proxy-config.json | 2 +- .../native-image/reachability-metadata.json | 96 +++++++++---------- src/main/resources/application.properties | 8 +- .../src/app/models/generated/backend.types.ts | 2 +- .../com/getpcpanel/mcp/RuntimeInfoTools.java | 8 +- .../com/getpcpanel/mcp/SimulationTools.java | 4 +- .../commands/CommandSubtypeRegistrarTest.java | 4 +- .../homeassistant/HaActionYamlTest.java | 2 +- .../voicemeeter/VoicemeeterApiGuardTest.java | 2 +- .../VoicemeeterControlTypeTest.java | 4 +- .../voicemeeter/VoicemeeterNativeTest.java | 8 +- .../wavelink/WaveLinkAppCacheTest.java | 2 +- .../wavelink/WaveLinkFocusResolutionTest.java | 2 +- .../wavelink/WaveLinkMuteResolverTest.java | 8 +- .../mutecolor/MuteColorServiceTest.java | 4 +- 119 files changed, 313 insertions(+), 313 deletions(-) rename src/main/java/com/getpcpanel/{ => integration}/discord/DiscordChangedEvent.java (78%) rename src/main/java/com/getpcpanel/{ => integration}/discord/DiscordCommandLabels.java (94%) rename src/main/java/com/getpcpanel/{ => integration}/discord/DiscordOAuth.java (98%) rename src/main/java/com/getpcpanel/{ => integration}/discord/DiscordService.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscord.java (89%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordJoinVoice.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordLeaveVoice.java (96%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordMute.java (95%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordScreenShare.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordSelfDeafen.java (94%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordSelfInputVolume.java (96%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordSelfMute.java (92%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordSelfOutputVolume.java (96%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordToggleVideo.java (96%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordUserMute.java (94%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordUserVolume.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/CommandDiscordVolume.java (98%) rename src/main/java/com/getpcpanel/{ => integration}/discord/command/DiscordCommandModule.java (95%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/HaActionYaml.java (98%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/HomeAssistantClient.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/HomeAssistantService.java (96%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/command/CommandHomeAssistant.java (85%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/command/CommandHomeAssistantAction.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/command/CommandHomeAssistantValue.java (98%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/command/HomeAssistantCommandModule.java (91%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/dto/HomeAssistantServer.java (88%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/dto/HomeAssistantServerStatus.java (86%) rename src/main/java/com/getpcpanel/{ => integration}/homeassistant/rest/HomeAssistantResource.java (84%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/MqttDeviceColorService.java (95%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/MqttDeviceService.java (89%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/MqttHomeAssistantHelper.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/MqttService.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/MqttStatusEvent.java (56%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/MqttTopicHelper.java (98%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/command/CommandMqttPublish.java (95%) rename src/main/java/com/getpcpanel/{ => integration}/mqtt/command/MqttCommandModule.java (92%) rename src/main/java/com/getpcpanel/{ => integration}/obs/OBS.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/obs/OBSConnectEvent.java (57%) rename src/main/java/com/getpcpanel/{ => integration}/obs/OBSMuteEvent.java (60%) rename src/main/java/com/getpcpanel/{ => integration}/obs/ObsConnectedVolumeService.java (83%) rename src/main/java/com/getpcpanel/{ => integration}/obs/ObsIconHandler.java (87%) rename src/main/java/com/getpcpanel/{ => integration}/obs/ObsWebSocketClient.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/obs/command/CommandObs.java (73%) rename src/main/java/com/getpcpanel/{ => integration}/obs/command/CommandObsAction.java (96%) rename src/main/java/com/getpcpanel/{ => integration}/obs/command/CommandObsMuteSource.java (94%) rename src/main/java/com/getpcpanel/{ => integration}/obs/command/CommandObsSetScene.java (93%) rename src/main/java/com/getpcpanel/{ => integration}/obs/command/CommandObsSetSourceVolume.java (94%) rename src/main/java/com/getpcpanel/{ => integration}/obs/command/ObsCommandModule.java (93%) rename src/main/java/com/getpcpanel/{ => integration}/osc/OSCService.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/osc/command/CommandOscSend.java (95%) rename src/main/java/com/getpcpanel/{ => integration}/osc/command/OscCommandModule.java (92%) create mode 100644 src/main/java/com/getpcpanel/integration/voicemeeter/VoiceMeeterConnectedEvent.java rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoiceMeeterConnectedVolumeService.java (67%) create mode 100644 src/main/java/com/getpcpanel/integration/voicemeeter/VoiceMeeterDirtyEvent.java rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoiceMeeterIconHandler.java (86%) create mode 100644 src/main/java/com/getpcpanel/integration/voicemeeter/VoiceMeeterMuteEvent.java rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoiceMeeterMuteResolver.java (90%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoiceMeeterMuteService.java (91%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/Voicemeeter.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoicemeeterAPI.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoicemeeterException.java (91%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoicemeeterInstance.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/VoicemeeterVersion.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/command/CommandVoiceMeeter.java (77%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/command/CommandVoiceMeeterAdvanced.java (93%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/command/CommandVoiceMeeterAdvancedButton.java (94%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/command/CommandVoiceMeeterBasic.java (92%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/command/CommandVoiceMeeterBasicButton.java (91%) rename src/main/java/com/getpcpanel/{ => integration}/voicemeeter/command/VoiceMeeterCommandModule.java (93%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/WaveLinkAppCache.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/WaveLinkChangedEvent.java (87%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/WaveLinkIconHandler.java (82%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/WaveLinkService.java (99%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/CommandWaveLink.java (80%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/CommandWaveLinkAddFocusToChannel.java (96%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/CommandWaveLinkChange.java (95%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/CommandWaveLinkChangeLevel.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/CommandWaveLinkChangeMute.java (98%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/CommandWaveLinkChannelEffect.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/CommandWaveLinkMainOutput.java (97%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/WaveLinkCommandModule.java (93%) rename src/main/java/com/getpcpanel/{ => integration}/wavelink/command/WaveLinkCommandTarget.java (57%) delete mode 100644 src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterConnectedEvent.java delete mode 100644 src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterDirtyEvent.java delete mode 100644 src/main/java/com/getpcpanel/voicemeeter/VoiceMeeterMuteEvent.java rename src/test/java/com/getpcpanel/{ => integration}/homeassistant/HaActionYamlTest.java (97%) rename src/test/java/com/getpcpanel/{ => integration}/voicemeeter/VoicemeeterApiGuardTest.java (95%) rename src/test/java/com/getpcpanel/{ => integration}/voicemeeter/VoicemeeterControlTypeTest.java (93%) rename src/test/java/com/getpcpanel/{ => integration}/voicemeeter/VoicemeeterNativeTest.java (95%) rename src/test/java/com/getpcpanel/{ => integration}/wavelink/WaveLinkAppCacheTest.java (98%) rename src/test/java/com/getpcpanel/{ => integration}/wavelink/WaveLinkFocusResolutionTest.java (99%) rename src/test/java/com/getpcpanel/{ => integration}/wavelink/WaveLinkMuteResolverTest.java (92%) diff --git a/pom.xml b/pom.xml index 479b3db8..374ab116 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ --initialize-at-run-time=com.getpcpanel.cpp.linux.LinuxKeyboard$XTest, --initialize-at-run-time=com.getpcpanel.overlay.OverlayRenderer, --trace-object-instantiation=java.security.SecureRandom, - -J-XX:-UseCompressedOops,--initialize-at-run-time=com.sun.jna,--initialize-at-run-time=org.hid4java,--initialize-at-run-time=com.fazecast.jSerialComm,--initialize-at-run-time=com.sun.media.sound,--initialize-at-run-time=javax.sound.midi.MidiSystem,--initialize-at-run-time=com.github.kwhat.jnativehook,--initialize-at-run-time=com.getpcpanel.commands.KeyMacro,--initialize-at-run-time=com.getpcpanel.iconextract.Shell32Extra,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinShell32,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinUser32Ext,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32Desktop,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32PowerNotify,--initialize-at-run-time=com.getpcpanel.sleepdetection.WindowsPowerEventMonitor,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibX11,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibXext,--initialize-at-run-time=com.getpcpanel.cpp.osx.CoreAudioLib,--initialize-at-run-time=com.getpcpanel.cpp.osx.CoreAudioLib$AudioObjectPropertyAddress,--initialize-at-run-time=com.getpcpanel.cpp.osx.CoreAudioLib$AudioObjectPropertyListenerProc,--initialize-at-run-time=com.getpcpanel.cpp.osx.OsxKeyboard$CoreGraphics,--initialize-at-run-time=com.getpcpanel.cpp.osx.OsxKeyboard$CoreFoundation,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$ApplicationServicesLib,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$IOKitLib,--initialize-at-run-time=org.freedesktop.dbus,--initialize-at-run-time=com.getpcpanel.voicemeeter.VoicemeeterInstance,--initialize-at-run-time=com.getpcpanel.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOINFO,--initialize-at-run-time=com.getpcpanel.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOBUFFER,--initialize-at-run-time=com.getpcpanel.voicemeeter.VoicemeeterInstance$tagVBVMR_INTERFACE${native.awt.args}${native.platform.linker.args} + -J-XX:-UseCompressedOops,--initialize-at-run-time=com.sun.jna,--initialize-at-run-time=org.hid4java,--initialize-at-run-time=com.fazecast.jSerialComm,--initialize-at-run-time=com.sun.media.sound,--initialize-at-run-time=javax.sound.midi.MidiSystem,--initialize-at-run-time=com.github.kwhat.jnativehook,--initialize-at-run-time=com.getpcpanel.commands.KeyMacro,--initialize-at-run-time=com.getpcpanel.iconextract.Shell32Extra,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinShell32,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinUser32Ext,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32Desktop,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32PowerNotify,--initialize-at-run-time=com.getpcpanel.sleepdetection.WindowsPowerEventMonitor,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibX11,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibXext,--initialize-at-run-time=com.getpcpanel.cpp.osx.CoreAudioLib,--initialize-at-run-time=com.getpcpanel.cpp.osx.CoreAudioLib$AudioObjectPropertyAddress,--initialize-at-run-time=com.getpcpanel.cpp.osx.CoreAudioLib$AudioObjectPropertyListenerProc,--initialize-at-run-time=com.getpcpanel.cpp.osx.OsxKeyboard$CoreGraphics,--initialize-at-run-time=com.getpcpanel.cpp.osx.OsxKeyboard$CoreFoundation,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$ApplicationServicesLib,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$IOKitLib,--initialize-at-run-time=org.freedesktop.dbus,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOINFO,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOBUFFER,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_INTERFACE${native.awt.args}${native.platform.linker.args} - ,--initialize-at-run-time=java.awt,--initialize-at-run-time=javax.swing,--initialize-at-run-time=sun.awt,--initialize-at-run-time=sun.lwawt,--initialize-at-run-time=sun.java2d,--initialize-at-run-time=sun.font,--initialize-at-run-time=javax.imageio,--initialize-at-run-time=com.sun.imageio,--initialize-at-run-time=com.getpcpanel.overlay.OverlayRenderer + ,--initialize-at-run-time=java.awt,--initialize-at-run-time=javax.swing,--initialize-at-run-time=sun.awt,--initialize-at-run-time=sun.lwawt,--initialize-at-run-time=sun.java2d,--initialize-at-run-time=sun.font,--initialize-at-run-time=javax.imageio,--initialize-at-run-time=com.sun.imageio,--initialize-at-run-time=com.getpcpanel.integration.volume.overlay.OverlayRenderer @@ -574,7 +574,7 @@ - ,--initialize-at-run-time=java.awt,--initialize-at-run-time=javax.swing,--initialize-at-run-time=sun.awt,--initialize-at-run-time=sun.lwawt,--initialize-at-run-time=sun.java2d,--initialize-at-run-time=sun.font,--initialize-at-run-time=javax.imageio,--initialize-at-run-time=com.sun.imageio,--initialize-at-run-time=com.getpcpanel.overlay.OverlayRenderer + ,--initialize-at-run-time=java.awt,--initialize-at-run-time=javax.swing,--initialize-at-run-time=sun.awt,--initialize-at-run-time=sun.lwawt,--initialize-at-run-time=sun.java2d,--initialize-at-run-time=sun.font,--initialize-at-run-time=javax.imageio,--initialize-at-run-time=com.sun.imageio,--initialize-at-run-time=com.getpcpanel.integration.volume.overlay.OverlayRenderer diff --git a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java index 8944ca48..96ddf932 100644 --- a/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java +++ b/src/main/java/com/getpcpanel/graalvm/NativeImageConfig.java @@ -86,8 +86,8 @@ import com.getpcpanel.profile.dto.DiscordAuth; import com.getpcpanel.profile.dto.DiscordSeenUser; import com.getpcpanel.profile.dto.DiscordSettings; -import com.getpcpanel.profile.dto.FocusVolumeOverride; -import com.getpcpanel.profile.dto.FocusVolumeTarget; +import com.getpcpanel.integration.volume.FocusVolumeOverride; +import com.getpcpanel.integration.volume.FocusVolumeTarget; import com.getpcpanel.profile.dto.KnobSetting; import com.getpcpanel.profile.dto.LightingConfig; import com.getpcpanel.profile.dto.LightingConfig.LightingMode; @@ -95,7 +95,7 @@ import com.getpcpanel.profile.dto.MqttSettings.HomeAssistantSettings; import com.getpcpanel.profile.dto.OSCBinding; import com.getpcpanel.profile.dto.OSCConnectionInfo; -import com.getpcpanel.profile.dto.OverlayPosition; +import com.getpcpanel.integration.volume.overlay.OverlayPosition; import com.getpcpanel.profile.dto.SingleKnobLightingConfig; import com.getpcpanel.profile.dto.SingleKnobLightingConfig.SINGLE_KNOB_MODE; import com.getpcpanel.profile.dto.SingleLogoLightingConfig; diff --git a/src/main/java/com/getpcpanel/rest/AudioResource.java b/src/main/java/com/getpcpanel/integration/volume/AudioResource.java similarity index 98% rename from src/main/java/com/getpcpanel/rest/AudioResource.java rename to src/main/java/com/getpcpanel/integration/volume/AudioResource.java index a6b8a517..15d2d69d 100644 --- a/src/main/java/com/getpcpanel/rest/AudioResource.java +++ b/src/main/java/com/getpcpanel/integration/volume/AudioResource.java @@ -1,4 +1,4 @@ -package com.getpcpanel.rest; +package com.getpcpanel.integration.volume; import java.util.Collection; import java.util.LinkedHashMap; diff --git a/src/main/java/com/getpcpanel/rest/FocusVolumeDiagnosticsResource.java b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeDiagnosticsResource.java similarity index 98% rename from src/main/java/com/getpcpanel/rest/FocusVolumeDiagnosticsResource.java rename to src/main/java/com/getpcpanel/integration/volume/FocusVolumeDiagnosticsResource.java index a1da9b0a..32f04407 100644 --- a/src/main/java/com/getpcpanel/rest/FocusVolumeDiagnosticsResource.java +++ b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeDiagnosticsResource.java @@ -1,4 +1,4 @@ -package com.getpcpanel.rest; +package com.getpcpanel.integration.volume; import java.util.LinkedHashMap; import java.util.Map; @@ -14,7 +14,6 @@ import com.getpcpanel.integration.volume.platform.ISndCtrl; import com.getpcpanel.profile.SaveService; -import com.getpcpanel.volume.VolumeCoordinatorService; import com.getpcpanel.integration.wavelink.WaveLinkAppCache; import com.getpcpanel.integration.wavelink.WaveLinkService; diff --git a/src/main/java/com/getpcpanel/profile/dto/FocusVolumeOverride.java b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeOverride.java similarity index 95% rename from src/main/java/com/getpcpanel/profile/dto/FocusVolumeOverride.java rename to src/main/java/com/getpcpanel/integration/volume/FocusVolumeOverride.java index 371fc1fe..b463ee4d 100644 --- a/src/main/java/com/getpcpanel/profile/dto/FocusVolumeOverride.java +++ b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeOverride.java @@ -1,4 +1,4 @@ -package com.getpcpanel.profile.dto; +package com.getpcpanel.integration.volume; import java.util.List; diff --git a/src/main/java/com/getpcpanel/volume/FocusVolumeOverrideService.java b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeOverrideService.java similarity index 97% rename from src/main/java/com/getpcpanel/volume/FocusVolumeOverrideService.java rename to src/main/java/com/getpcpanel/integration/volume/FocusVolumeOverrideService.java index 4243c498..c4ffba07 100644 --- a/src/main/java/com/getpcpanel/volume/FocusVolumeOverrideService.java +++ b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeOverrideService.java @@ -1,4 +1,4 @@ -package com.getpcpanel.volume; +package com.getpcpanel.integration.volume; import java.util.List; @@ -12,8 +12,6 @@ import com.getpcpanel.integration.volume.platform.ISndCtrl; import com.getpcpanel.hid.DialValue; import com.getpcpanel.profile.SaveService; -import com.getpcpanel.profile.dto.FocusVolumeOverride; -import com.getpcpanel.profile.dto.FocusVolumeTarget; import com.getpcpanel.profile.dto.KnobSetting; import io.quarkus.arc.Unremovable; diff --git a/src/main/java/com/getpcpanel/profile/dto/FocusVolumeTarget.java b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeTarget.java similarity index 93% rename from src/main/java/com/getpcpanel/profile/dto/FocusVolumeTarget.java rename to src/main/java/com/getpcpanel/integration/volume/FocusVolumeTarget.java index 1cf606a7..bee08b06 100644 --- a/src/main/java/com/getpcpanel/profile/dto/FocusVolumeTarget.java +++ b/src/main/java/com/getpcpanel/integration/volume/FocusVolumeTarget.java @@ -1,4 +1,4 @@ -package com.getpcpanel.profile.dto; +package com.getpcpanel.integration.volume; import com.getpcpanel.commands.command.Command; diff --git a/src/main/java/com/getpcpanel/volume/IFocusRedirector.java b/src/main/java/com/getpcpanel/integration/volume/IFocusRedirector.java similarity index 95% rename from src/main/java/com/getpcpanel/volume/IFocusRedirector.java rename to src/main/java/com/getpcpanel/integration/volume/IFocusRedirector.java index 1d6e724a..ab773f4e 100644 --- a/src/main/java/com/getpcpanel/volume/IFocusRedirector.java +++ b/src/main/java/com/getpcpanel/integration/volume/IFocusRedirector.java @@ -1,4 +1,4 @@ -package com.getpcpanel.volume; +package com.getpcpanel.integration.volume; public interface IFocusRedirector { boolean handleFocusVolumeRequest(String targetProcess, float volume); diff --git a/src/main/java/com/getpcpanel/volume/LinuxNewSessionVolumeService.java b/src/main/java/com/getpcpanel/integration/volume/LinuxNewSessionVolumeService.java similarity index 98% rename from src/main/java/com/getpcpanel/volume/LinuxNewSessionVolumeService.java rename to src/main/java/com/getpcpanel/integration/volume/LinuxNewSessionVolumeService.java index 82d915a8..c8de678b 100644 --- a/src/main/java/com/getpcpanel/volume/LinuxNewSessionVolumeService.java +++ b/src/main/java/com/getpcpanel/integration/volume/LinuxNewSessionVolumeService.java @@ -1,4 +1,4 @@ -package com.getpcpanel.volume; +package com.getpcpanel.integration.volume; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/getpcpanel/rest/OverlayResource.java b/src/main/java/com/getpcpanel/integration/volume/OverlayResource.java similarity index 93% rename from src/main/java/com/getpcpanel/rest/OverlayResource.java rename to src/main/java/com/getpcpanel/integration/volume/OverlayResource.java index fc50c80a..cd8d02eb 100644 --- a/src/main/java/com/getpcpanel/rest/OverlayResource.java +++ b/src/main/java/com/getpcpanel/integration/volume/OverlayResource.java @@ -1,11 +1,11 @@ -package com.getpcpanel.rest; +package com.getpcpanel.integration.volume; import java.awt.GraphicsEnvironment; import java.util.Arrays; import java.util.List; -import com.getpcpanel.overlay.Overlay; -import com.getpcpanel.overlay.OverlayPreviewRenderer; +import com.getpcpanel.integration.volume.overlay.Overlay; +import com.getpcpanel.integration.volume.overlay.OverlayPreviewRenderer; import com.getpcpanel.profile.Save; import com.getpcpanel.rest.model.dto.SettingsDto; import com.sun.jna.Platform; diff --git a/src/main/java/com/getpcpanel/volume/VolumeCoordinatorService.java b/src/main/java/com/getpcpanel/integration/volume/VolumeCoordinatorService.java similarity index 99% rename from src/main/java/com/getpcpanel/volume/VolumeCoordinatorService.java rename to src/main/java/com/getpcpanel/integration/volume/VolumeCoordinatorService.java index 5072f07f..9deae12f 100644 --- a/src/main/java/com/getpcpanel/volume/VolumeCoordinatorService.java +++ b/src/main/java/com/getpcpanel/integration/volume/VolumeCoordinatorService.java @@ -1,4 +1,4 @@ -package com.getpcpanel.volume; +package com.getpcpanel.integration.volume; import java.io.File; import java.util.Optional; diff --git a/src/main/java/com/getpcpanel/integration/volume/command/CommandVolumeFocus.java b/src/main/java/com/getpcpanel/integration/volume/command/CommandVolumeFocus.java index e2148157..7dd0e172 100644 --- a/src/main/java/com/getpcpanel/integration/volume/command/CommandVolumeFocus.java +++ b/src/main/java/com/getpcpanel/integration/volume/command/CommandVolumeFocus.java @@ -7,7 +7,7 @@ import com.getpcpanel.commands.meta.CommandKind; import com.getpcpanel.commands.meta.CommandMeta; import com.fasterxml.jackson.annotation.JsonProperty; -import com.getpcpanel.volume.VolumeCoordinatorService; +import com.getpcpanel.integration.volume.VolumeCoordinatorService; import com.getpcpanel.util.CdiHelper; import lombok.Getter; diff --git a/src/main/java/com/getpcpanel/overlay/KdeOsdService.java b/src/main/java/com/getpcpanel/integration/volume/overlay/KdeOsdService.java similarity index 95% rename from src/main/java/com/getpcpanel/overlay/KdeOsdService.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/KdeOsdService.java index b3389fc8..4c9718a3 100644 --- a/src/main/java/com/getpcpanel/overlay/KdeOsdService.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/KdeOsdService.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import org.freedesktop.dbus.annotations.DBusInterfaceName; import org.freedesktop.dbus.interfaces.DBusInterface; diff --git a/src/main/java/com/getpcpanel/overlay/LinuxOverlay.java b/src/main/java/com/getpcpanel/integration/volume/overlay/LinuxOverlay.java similarity index 99% rename from src/main/java/com/getpcpanel/overlay/LinuxOverlay.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/LinuxOverlay.java index ac1a19cd..a7dd6997 100644 --- a/src/main/java/com/getpcpanel/overlay/LinuxOverlay.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/LinuxOverlay.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import java.nio.file.Files; import java.nio.file.Path; diff --git a/src/main/java/com/getpcpanel/overlay/NoOpOverlayWindow.java b/src/main/java/com/getpcpanel/integration/volume/overlay/NoOpOverlayWindow.java similarity index 95% rename from src/main/java/com/getpcpanel/overlay/NoOpOverlayWindow.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/NoOpOverlayWindow.java index 989fd360..b1d6e102 100644 --- a/src/main/java/com/getpcpanel/overlay/NoOpOverlayWindow.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/NoOpOverlayWindow.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import com.getpcpanel.profile.Save; diff --git a/src/main/java/com/getpcpanel/overlay/Overlay.java b/src/main/java/com/getpcpanel/integration/volume/overlay/Overlay.java similarity index 98% rename from src/main/java/com/getpcpanel/overlay/Overlay.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/Overlay.java index 20780dc9..e08243c3 100644 --- a/src/main/java/com/getpcpanel/overlay/Overlay.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/Overlay.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import java.awt.Image; import java.io.File; @@ -21,12 +21,11 @@ import com.getpcpanel.profile.SaveService; import com.getpcpanel.profile.SaveService.SaveEvent; import com.getpcpanel.profile.dto.LightingConfig; -import com.getpcpanel.profile.dto.OverlayPosition; import com.getpcpanel.profile.dto.SingleKnobLightingConfig; import com.getpcpanel.profile.dto.SingleKnobLightingConfig.SINGLE_KNOB_MODE; import com.getpcpanel.profile.dto.SingleSliderLightingConfig; import com.getpcpanel.util.coloroverride.OverrideColorService; -import com.getpcpanel.volume.VolumeCoordinatorService; +import com.getpcpanel.integration.volume.VolumeCoordinatorService; import com.sun.jna.Platform; import jakarta.annotation.Nonnull; diff --git a/src/main/java/com/getpcpanel/overlay/OverlayContent.java b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayContent.java similarity index 92% rename from src/main/java/com/getpcpanel/overlay/OverlayContent.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/OverlayContent.java index 9a37f1e1..115c9645 100644 --- a/src/main/java/com/getpcpanel/overlay/OverlayContent.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayContent.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import java.awt.Image; diff --git a/src/main/java/com/getpcpanel/overlay/OverlayDemoTrigger.java b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayDemoTrigger.java similarity index 97% rename from src/main/java/com/getpcpanel/overlay/OverlayDemoTrigger.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/OverlayDemoTrigger.java index be61c954..5ee71c90 100644 --- a/src/main/java/com/getpcpanel/overlay/OverlayDemoTrigger.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayDemoTrigger.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import com.sun.jna.Platform; diff --git a/src/main/java/com/getpcpanel/profile/dto/OverlayPosition.java b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayPosition.java similarity index 84% rename from src/main/java/com/getpcpanel/profile/dto/OverlayPosition.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/OverlayPosition.java index 8170d8b5..5354702e 100644 --- a/src/main/java/com/getpcpanel/profile/dto/OverlayPosition.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayPosition.java @@ -1,4 +1,4 @@ -package com.getpcpanel.profile.dto; +package com.getpcpanel.integration.volume.overlay; /** * Position options for the on-screen overlay. diff --git a/src/main/java/com/getpcpanel/overlay/OverlayPreviewRenderer.java b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayPreviewRenderer.java similarity index 98% rename from src/main/java/com/getpcpanel/overlay/OverlayPreviewRenderer.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/OverlayPreviewRenderer.java index 09af2083..db3e346f 100644 --- a/src/main/java/com/getpcpanel/overlay/OverlayPreviewRenderer.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayPreviewRenderer.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import java.awt.Color; import java.awt.image.BufferedImage; diff --git a/src/main/java/com/getpcpanel/overlay/OverlayRenderer.java b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayRenderer.java similarity index 99% rename from src/main/java/com/getpcpanel/overlay/OverlayRenderer.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/OverlayRenderer.java index b1761f8a..aed0c63a 100644 --- a/src/main/java/com/getpcpanel/overlay/OverlayRenderer.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayRenderer.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import java.awt.Color; import java.awt.Font; diff --git a/src/main/java/com/getpcpanel/overlay/OverlayWindow.java b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayWindow.java similarity index 95% rename from src/main/java/com/getpcpanel/overlay/OverlayWindow.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/OverlayWindow.java index 1a052bf5..f9718d4f 100644 --- a/src/main/java/com/getpcpanel/overlay/OverlayWindow.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/OverlayWindow.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import com.getpcpanel.profile.Save; diff --git a/src/main/java/com/getpcpanel/overlay/ScreenSize.java b/src/main/java/com/getpcpanel/integration/volume/overlay/ScreenSize.java similarity index 87% rename from src/main/java/com/getpcpanel/overlay/ScreenSize.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/ScreenSize.java index fdce8458..66a0b061 100644 --- a/src/main/java/com/getpcpanel/overlay/ScreenSize.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/ScreenSize.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; /** * The size of the primary screen, used to position the overlay. A plain value type so the overlay diff --git a/src/main/java/com/getpcpanel/overlay/Win32VolumeOverlay.java b/src/main/java/com/getpcpanel/integration/volume/overlay/Win32VolumeOverlay.java similarity index 99% rename from src/main/java/com/getpcpanel/overlay/Win32VolumeOverlay.java rename to src/main/java/com/getpcpanel/integration/volume/overlay/Win32VolumeOverlay.java index 3baa5f80..83da0b5e 100644 --- a/src/main/java/com/getpcpanel/overlay/Win32VolumeOverlay.java +++ b/src/main/java/com/getpcpanel/integration/volume/overlay/Win32VolumeOverlay.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; diff --git a/src/main/java/com/getpcpanel/integration/wavelink/WaveLinkService.java b/src/main/java/com/getpcpanel/integration/wavelink/WaveLinkService.java index 0d4fa15e..194db221 100644 --- a/src/main/java/com/getpcpanel/integration/wavelink/WaveLinkService.java +++ b/src/main/java/com/getpcpanel/integration/wavelink/WaveLinkService.java @@ -17,7 +17,7 @@ import com.getpcpanel.profile.SaveService.SaveEvent; import com.getpcpanel.util.Debouncer; import com.getpcpanel.util.ReconnectBackoff; -import com.getpcpanel.volume.IFocusRedirector; +import com.getpcpanel.integration.volume.IFocusRedirector; import jakarta.enterprise.inject.Instance; diff --git a/src/main/java/com/getpcpanel/profile/Save.java b/src/main/java/com/getpcpanel/profile/Save.java index b70de745..121f5adf 100644 --- a/src/main/java/com/getpcpanel/profile/Save.java +++ b/src/main/java/com/getpcpanel/profile/Save.java @@ -14,10 +14,10 @@ import com.getpcpanel.profile.dto.DiscordAuth; import com.getpcpanel.profile.dto.DiscordSeenUser; import com.getpcpanel.profile.dto.DiscordSettings; -import com.getpcpanel.profile.dto.FocusVolumeOverride; +import com.getpcpanel.integration.volume.FocusVolumeOverride; import com.getpcpanel.profile.dto.MqttSettings; import com.getpcpanel.profile.dto.OSCConnectionInfo; -import com.getpcpanel.profile.dto.OverlayPosition; +import com.getpcpanel.integration.volume.overlay.OverlayPosition; import com.getpcpanel.profile.dto.WaveLinkSettings; import lombok.Data; diff --git a/src/main/java/com/getpcpanel/rest/model/dto/SettingsDto.java b/src/main/java/com/getpcpanel/rest/model/dto/SettingsDto.java index 591e92e3..0b236567 100644 --- a/src/main/java/com/getpcpanel/rest/model/dto/SettingsDto.java +++ b/src/main/java/com/getpcpanel/rest/model/dto/SettingsDto.java @@ -6,10 +6,10 @@ import com.getpcpanel.integration.homeassistant.dto.HomeAssistantServer; import com.getpcpanel.profile.Save; -import com.getpcpanel.profile.dto.FocusVolumeOverride; +import com.getpcpanel.integration.volume.FocusVolumeOverride; import com.getpcpanel.profile.dto.MqttSettings; import com.getpcpanel.profile.dto.OSCConnectionInfo; -import com.getpcpanel.profile.dto.OverlayPosition; +import com.getpcpanel.integration.volume.overlay.OverlayPosition; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/proxy-config.json b/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/proxy-config.json index 4a2d7b9b..99cb1b10 100644 --- a/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/proxy-config.json +++ b/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/proxy-config.json @@ -22,7 +22,7 @@ {"interfaces": ["com.getpcpanel.sleepdetection.Login1Manager"]}, {"interfaces": ["com.getpcpanel.sleepdetection.Login1Session"]}, {"interfaces": ["com.getpcpanel.cpp.linux.MprisPlayer"]}, - {"interfaces": ["com.getpcpanel.overlay.KdeOsdService"]}, + {"interfaces": ["com.getpcpanel.integration.volume.overlay.KdeOsdService"]}, {"interfaces": ["org.freedesktop.dbus.interfaces.DBus"]}, {"interfaces": ["org.freedesktop.dbus.interfaces.Properties"]}, diff --git a/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/reflect-config.json b/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/reflect-config.json index 44417600..469db722 100644 --- a/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/reflect-config.json +++ b/src/main/resources/META-INF/native-image/com.getpcpanel/pcpanel/reflect-config.json @@ -47,7 +47,7 @@ "allDeclaredMethods": true }, { - "name": "com.getpcpanel.overlay.KdeOsdService", + "name": "com.getpcpanel.integration.volume.overlay.KdeOsdService", "allDeclaredMethods": true }, { diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json index 4b1ec1d0..2ddc501e 100644 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -1109,10 +1109,10 @@ "type": "com.getpcpanel.integration.osc.OSCService" }, { - "type": "com.getpcpanel.overlay.Overlay" + "type": "com.getpcpanel.integration.volume.overlay.Overlay" }, { - "type": "com.getpcpanel.overlay.VolumeOverlay", + "type": "com.getpcpanel.integration.volume.overlay.VolumeOverlay", "methods": [ { "name": "parseColor", @@ -1124,10 +1124,10 @@ ] }, { - "type": "com.getpcpanel.overlay.VolumeOverlay$OverlayPanel" + "type": "com.getpcpanel.integration.volume.overlay.VolumeOverlay$OverlayPanel" }, { - "type": "com.getpcpanel.overlay.VolumeOverlayNativeTest", + "type": "com.getpcpanel.integration.volume.overlay.VolumeOverlayNativeTest", "methods": [ { "name": "", @@ -1144,12 +1144,12 @@ ] }, { - "type": "com.getpcpanel.overlay.VolumeOverlayNativeTest$HexColors", + "type": "com.getpcpanel.integration.volume.overlay.VolumeOverlayNativeTest$HexColors", "methods": [ { "name": "", "parameterTypes": [ - "com.getpcpanel.overlay.VolumeOverlayNativeTest" + "com.getpcpanel.integration.volume.overlay.VolumeOverlayNativeTest" ] }, { @@ -1183,12 +1183,12 @@ ] }, { - "type": "com.getpcpanel.overlay.VolumeOverlayNativeTest$RgbColors", + "type": "com.getpcpanel.integration.volume.overlay.VolumeOverlayNativeTest$RgbColors", "methods": [ { "name": "", "parameterTypes": [ - "com.getpcpanel.overlay.VolumeOverlayNativeTest" + "com.getpcpanel.integration.volume.overlay.VolumeOverlayNativeTest" ] }, { @@ -1212,12 +1212,12 @@ ] }, { - "type": "com.getpcpanel.overlay.VolumeOverlayNativeTest$SwingClassLoading", + "type": "com.getpcpanel.integration.volume.overlay.VolumeOverlayNativeTest$SwingClassLoading", "methods": [ { "name": "", "parameterTypes": [ - "com.getpcpanel.overlay.VolumeOverlayNativeTest" + "com.getpcpanel.integration.volume.overlay.VolumeOverlayNativeTest" ] }, { @@ -1701,7 +1701,7 @@ { "name": "setOverlayPosition", "parameterTypes": [ - "com.getpcpanel.profile.dto.OverlayPosition" + "com.getpcpanel.integration.volume.overlay.OverlayPosition" ] }, { @@ -2086,7 +2086,7 @@ ] }, { - "type": "com.getpcpanel.profile.dto.OverlayPosition" + "type": "com.getpcpanel.integration.volume.overlay.OverlayPosition" }, { "type": "com.getpcpanel.profile.dto.SingleKnobLightingConfig", @@ -2351,10 +2351,10 @@ ] }, { - "type": "com.getpcpanel.rest.AudioResource" + "type": "com.getpcpanel.integration.volume.AudioResource" }, { - "type": "com.getpcpanel.rest.AudioResource$quarkusrestinvoker$listAudioDevices_550a3b0e13808220fb0b06709e729f8d6f308247", + "type": "com.getpcpanel.integration.volume.AudioResource$quarkusrestinvoker$listAudioDevices_550a3b0e13808220fb0b06709e729f8d6f308247", "methods": [ { "name": "", @@ -2363,7 +2363,7 @@ ] }, { - "type": "com.getpcpanel.rest.AudioResource$quarkusrestinvoker$listAudioSessions_87b1ad592571c0490d5d65ae30900a3fd5c91858", + "type": "com.getpcpanel.integration.volume.AudioResource$quarkusrestinvoker$listAudioSessions_87b1ad592571c0490d5d65ae30900a3fd5c91858", "methods": [ { "name": "", @@ -2372,7 +2372,7 @@ ] }, { - "type": "com.getpcpanel.rest.AudioResource$quarkusrestinvoker$listInputDevices_486c01c1309f7e4a8b1b28fdee3458ff7823b6dc", + "type": "com.getpcpanel.integration.volume.AudioResource$quarkusrestinvoker$listInputDevices_486c01c1309f7e4a8b1b28fdee3458ff7823b6dc", "methods": [ { "name": "", @@ -2381,7 +2381,7 @@ ] }, { - "type": "com.getpcpanel.rest.AudioResource$quarkusrestinvoker$listOutputDevices_07238fa0093e377e9791afc81a93956fc9c90a1f", + "type": "com.getpcpanel.integration.volume.AudioResource$quarkusrestinvoker$listOutputDevices_07238fa0093e377e9791afc81a93956fc9c90a1f", "methods": [ { "name": "", @@ -2390,7 +2390,7 @@ ] }, { - "type": "com.getpcpanel.rest.AudioResource$quarkusrestinvoker$listRunningApplications_111045ded5c591a7480f876665a40f8cc45b501b", + "type": "com.getpcpanel.integration.volume.AudioResource$quarkusrestinvoker$listRunningApplications_111045ded5c591a7480f876665a40f8cc45b501b", "methods": [ { "name": "", @@ -2633,13 +2633,13 @@ ] }, { - "type": "com.getpcpanel.rest.OverlayResource" + "type": "com.getpcpanel.integration.volume.OverlayResource" }, { - "type": "com.getpcpanel.rest.OverlayResource$OverlayDto" + "type": "com.getpcpanel.integration.volume.OverlayResource$OverlayDto" }, { - "type": "com.getpcpanel.rest.OverlayResource$quarkusrestinvoker$showOverlay_53c93d1310f135ffd7c8e51be7eb1f6ba4eac4ba", + "type": "com.getpcpanel.integration.volume.OverlayResource$quarkusrestinvoker$showOverlay_53c93d1310f135ffd7c8e51be7eb1f6ba4eac4ba", "methods": [ { "name": "", @@ -2648,7 +2648,7 @@ ] }, { - "type": "com.getpcpanel.rest.OverlayResource$quarkusrestinvoker$testOverlay_25269a521d728b138ab48fc9e9ade9c8f2dfd2fc", + "type": "com.getpcpanel.integration.volume.OverlayResource$quarkusrestinvoker$testOverlay_25269a521d728b138ab48fc9e9ade9c8f2dfd2fc", "methods": [ { "name": "", @@ -3089,7 +3089,7 @@ { "name": "setOverlayPosition", "parameterTypes": [ - "com.getpcpanel.profile.dto.OverlayPosition" + "com.getpcpanel.integration.volume.overlay.OverlayPosition" ] }, { @@ -3502,7 +3502,7 @@ ] }, { - "type": "com.getpcpanel.volume.VolumeCoordinatorService" + "type": "com.getpcpanel.integration.volume.VolumeCoordinatorService" }, { "type": "com.getpcpanel.integration.wavelink.WaveLinkIconHandler" diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 49ac49e2..cad0b84f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -124,7 +124,7 @@ quarkus.native.additional-build-args=\ --initialize-at-run-time=com.getpcpanel.commands.KeyMacro,\ --initialize-at-run-time=com.getpcpanel.cpp.linux.LinuxKeyboard$X11,\ --initialize-at-run-time=com.getpcpanel.cpp.linux.LinuxKeyboard$XTest,\ - --initialize-at-run-time=com.getpcpanel.overlay.OverlayRenderer,\ + --initialize-at-run-time=com.getpcpanel.integration.volume.overlay.OverlayRenderer,\ --initialize-at-run-time=org.freedesktop.dbus,\ --trace-object-instantiation=java.security.SecureRandom,\ --initialize-at-run-time=com.sun.jna,\ diff --git a/src/mcp/java/com/getpcpanel/mcp/AudioTools.java b/src/mcp/java/com/getpcpanel/mcp/AudioTools.java index d60d4bd5..5b826544 100644 --- a/src/mcp/java/com/getpcpanel/mcp/AudioTools.java +++ b/src/mcp/java/com/getpcpanel/mcp/AudioTools.java @@ -9,7 +9,7 @@ import org.apache.commons.lang3.StringUtils; import com.getpcpanel.integration.volume.platform.ISndCtrl; -import com.getpcpanel.volume.VolumeCoordinatorService; +import com.getpcpanel.integration.volume.VolumeCoordinatorService; import io.quarkus.arc.properties.IfBuildProperty; import io.quarkiverse.mcp.server.Tool; diff --git a/src/test/java/com/getpcpanel/volume/FocusVolumeOverrideServiceTest.java b/src/test/java/com/getpcpanel/integration/volume/FocusVolumeOverrideServiceTest.java similarity index 97% rename from src/test/java/com/getpcpanel/volume/FocusVolumeOverrideServiceTest.java rename to src/test/java/com/getpcpanel/integration/volume/FocusVolumeOverrideServiceTest.java index 67740774..5d1b05cc 100644 --- a/src/test/java/com/getpcpanel/volume/FocusVolumeOverrideServiceTest.java +++ b/src/test/java/com/getpcpanel/integration/volume/FocusVolumeOverrideServiceTest.java @@ -1,4 +1,4 @@ -package com.getpcpanel.volume; +package com.getpcpanel.integration.volume; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -22,8 +22,8 @@ import com.getpcpanel.integration.volume.platform.MuteType; import com.getpcpanel.profile.Save; import com.getpcpanel.profile.SaveService; -import com.getpcpanel.profile.dto.FocusVolumeOverride; -import com.getpcpanel.profile.dto.FocusVolumeTarget; +import com.getpcpanel.integration.volume.FocusVolumeOverride; +import com.getpcpanel.integration.volume.FocusVolumeTarget; /** * The decision logic of the focus-volume override service (issue #49): a focused source app redirects the diff --git a/src/test/java/com/getpcpanel/overlay/OverlayRendererColorTest.java b/src/test/java/com/getpcpanel/integration/volume/overlay/OverlayRendererColorTest.java similarity index 99% rename from src/test/java/com/getpcpanel/overlay/OverlayRendererColorTest.java rename to src/test/java/com/getpcpanel/integration/volume/overlay/OverlayRendererColorTest.java index 9f0be137..8c9829f4 100644 --- a/src/test/java/com/getpcpanel/overlay/OverlayRendererColorTest.java +++ b/src/test/java/com/getpcpanel/integration/volume/overlay/OverlayRendererColorTest.java @@ -1,4 +1,4 @@ -package com.getpcpanel.overlay; +package com.getpcpanel.integration.volume.overlay; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; From 66aab4112e00328eac1b81c0147bb62602a01db6 Mon Sep 17 00:00:00 2001 From: Niels Date: Sun, 28 Jun 2026 15:31:04 +0200 Subject: [PATCH 30/49] =?UTF-8?q?refactor:=20move=20keyboard=20+=20media?= =?UTF-8?q?=20backends=20into=20integration/keyboard/platform=20=E2=80=94?= =?UTF-8?q?=20cpp/=20is=20gone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the dissolution of the cpp/ grab-bag. The per-OS keystroke/media-key backends move under the keyboard feature: - WindowsKeyboard -> integration/keyboard/platform/windows - OsxKeyboard, OsxMediaControl (from util/) -> integration/keyboard/platform/osx - LinuxKeyboard, LinuxMprisMediaControl, MprisPlayer -> integration/keyboard/platform/linux They are consumed by KeyMacro (engine) and CommandMedia (keyboard integration); the Linux MPRIS + macOS media controls are the media-key backends peer to the keyboards, so they sit beside them per platform. With this the com.getpcpanel.cpp package — and its fluent-accessor lombok.config, which only the audio data classes needed — is removed entirely. Native-image config repointed in lockstep: LinuxKeyboard$X11/$XTest --initialize-at- run-time in BOTH pom.xml and application.properties + reachability-metadata; OsxKeyboard$CoreGraphics/$CoreFoundation in pom/app.properties + proxy-config; MprisPlayer in reflect-config + proxy-config. The keyboard *NativeConfigTest + *KeystrokeTest suites moved with their subjects. Verified: JVM build green; parity/coverage guards + all keyboard keystroke/native- config tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- pom.xml | 6 +++--- src/main/java/com/getpcpanel/commands/KeyMacro.java | 6 +++--- src/main/java/com/getpcpanel/cpp/lombok.config | 1 - .../integration/keyboard/command/CommandMedia.java | 4 ++-- .../keyboard/platform}/linux/LinuxKeyboard.java | 2 +- .../keyboard/platform}/linux/LinuxMprisMediaControl.java | 2 +- .../keyboard/platform}/linux/MprisPlayer.java | 2 +- .../keyboard/platform}/osx/OsxKeyboard.java | 2 +- .../keyboard/platform/osx}/OsxMediaControl.java | 3 ++- .../keyboard/platform}/windows/WindowsKeyboard.java | 2 +- .../native-image/com.getpcpanel/pcpanel/proxy-config.json | 6 +++--- .../com.getpcpanel/pcpanel/reflect-config.json | 2 +- .../META-INF/native-image/reachability-metadata.json | 8 ++++---- src/main/resources/application.properties | 8 ++++---- .../getpcpanel/graalvm/ProxyRegistrationCoverageTest.java | 2 +- .../platform}/linux/LinuxKeyboardKeystrokeTest.java | 2 +- .../platform}/linux/LinuxKeyboardNativeConfigTest.java | 2 +- .../keyboard/platform}/osx/OsxKeyboardKeystrokeTest.java | 2 +- .../platform}/windows/WindowsKeyboardKeystrokeTest.java | 2 +- 19 files changed, 32 insertions(+), 32 deletions(-) delete mode 100644 src/main/java/com/getpcpanel/cpp/lombok.config rename src/main/java/com/getpcpanel/{cpp => integration/keyboard/platform}/linux/LinuxKeyboard.java (99%) rename src/main/java/com/getpcpanel/{cpp => integration/keyboard/platform}/linux/LinuxMprisMediaControl.java (98%) rename src/main/java/com/getpcpanel/{cpp => integration/keyboard/platform}/linux/MprisPlayer.java (92%) rename src/main/java/com/getpcpanel/{cpp => integration/keyboard/platform}/osx/OsxKeyboard.java (99%) rename src/main/java/com/getpcpanel/{util => integration/keyboard/platform/osx}/OsxMediaControl.java (96%) rename src/main/java/com/getpcpanel/{cpp => integration/keyboard/platform}/windows/WindowsKeyboard.java (99%) rename src/test/java/com/getpcpanel/{cpp => integration/keyboard/platform}/linux/LinuxKeyboardKeystrokeTest.java (98%) rename src/test/java/com/getpcpanel/{cpp => integration/keyboard/platform}/linux/LinuxKeyboardNativeConfigTest.java (97%) rename src/test/java/com/getpcpanel/{cpp => integration/keyboard/platform}/osx/OsxKeyboardKeystrokeTest.java (97%) rename src/test/java/com/getpcpanel/{cpp => integration/keyboard/platform}/windows/WindowsKeyboardKeystrokeTest.java (97%) diff --git a/pom.xml b/pom.xml index 5a7ffe84..13f46f3b 100644 --- a/pom.xml +++ b/pom.xml @@ -57,11 +57,11 @@ -Os, -R:MaxHeapSize=67108864, --initialize-at-run-time=com.getpcpanel.util.Kernel32Console, - --initialize-at-run-time=com.getpcpanel.cpp.linux.LinuxKeyboard$X11, - --initialize-at-run-time=com.getpcpanel.cpp.linux.LinuxKeyboard$XTest, + --initialize-at-run-time=com.getpcpanel.integration.keyboard.platform.linux.LinuxKeyboard$X11, + --initialize-at-run-time=com.getpcpanel.integration.keyboard.platform.linux.LinuxKeyboard$XTest, --initialize-at-run-time=com.getpcpanel.integration.volume.overlay.OverlayRenderer, --trace-object-instantiation=java.security.SecureRandom, - -J-XX:-UseCompressedOops,--initialize-at-run-time=com.sun.jna,--initialize-at-run-time=org.hid4java,--initialize-at-run-time=com.fazecast.jSerialComm,--initialize-at-run-time=com.sun.media.sound,--initialize-at-run-time=javax.sound.midi.MidiSystem,--initialize-at-run-time=com.github.kwhat.jnativehook,--initialize-at-run-time=com.getpcpanel.commands.KeyMacro,--initialize-at-run-time=com.getpcpanel.iconextract.Shell32Extra,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinShell32,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinUser32Ext,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32Desktop,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32PowerNotify,--initialize-at-run-time=com.getpcpanel.sleepdetection.WindowsPowerEventMonitor,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibX11,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibXext,--initialize-at-run-time=com.getpcpanel.integration.volume.platform.osx.CoreAudioLib,--initialize-at-run-time=com.getpcpanel.integration.volume.platform.osx.CoreAudioLib$AudioObjectPropertyAddress,--initialize-at-run-time=com.getpcpanel.integration.volume.platform.osx.CoreAudioLib$AudioObjectPropertyListenerProc,--initialize-at-run-time=com.getpcpanel.cpp.osx.OsxKeyboard$CoreGraphics,--initialize-at-run-time=com.getpcpanel.cpp.osx.OsxKeyboard$CoreFoundation,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$ApplicationServicesLib,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$IOKitLib,--initialize-at-run-time=org.freedesktop.dbus,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOINFO,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOBUFFER,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_INTERFACE${native.awt.args}${native.platform.linker.args} + -J-XX:-UseCompressedOops,--initialize-at-run-time=com.sun.jna,--initialize-at-run-time=org.hid4java,--initialize-at-run-time=com.fazecast.jSerialComm,--initialize-at-run-time=com.sun.media.sound,--initialize-at-run-time=javax.sound.midi.MidiSystem,--initialize-at-run-time=com.github.kwhat.jnativehook,--initialize-at-run-time=com.getpcpanel.commands.KeyMacro,--initialize-at-run-time=com.getpcpanel.iconextract.Shell32Extra,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinShell32,--initialize-at-run-time=com.getpcpanel.util.tray.win.WinUser32Ext,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32Desktop,--initialize-at-run-time=com.getpcpanel.sleepdetection.Win32PowerNotify,--initialize-at-run-time=com.getpcpanel.sleepdetection.WindowsPowerEventMonitor,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibX11,--initialize-at-run-time=com.getpcpanel.sleepdetection.LinuxX11$LibXext,--initialize-at-run-time=com.getpcpanel.integration.volume.platform.osx.CoreAudioLib,--initialize-at-run-time=com.getpcpanel.integration.volume.platform.osx.CoreAudioLib$AudioObjectPropertyAddress,--initialize-at-run-time=com.getpcpanel.integration.volume.platform.osx.CoreAudioLib$AudioObjectPropertyListenerProc,--initialize-at-run-time=com.getpcpanel.integration.keyboard.platform.osx.OsxKeyboard$CoreGraphics,--initialize-at-run-time=com.getpcpanel.integration.keyboard.platform.osx.OsxKeyboard$CoreFoundation,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$ApplicationServicesLib,--initialize-at-run-time=com.getpcpanel.util.OsxPermissionHelper$IOKitLib,--initialize-at-run-time=org.freedesktop.dbus,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOINFO,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_AUDIOBUFFER,--initialize-at-run-time=com.getpcpanel.integration.voicemeeter.VoicemeeterInstance$tagVBVMR_INTERFACE${native.awt.args}${native.platform.linker.args}