From 23ced2797d07f1c7f004a426daeb9718ecb80be8 Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Tue, 22 Oct 2024 13:54:57 +0200 Subject: [PATCH 1/9] Initial unicom-guard implementation --- package-lock.json | 2 +- src/main/config.ts | 22 +------- src/renderer/src/components/navbar.tsx | 2 +- .../src/components/radio/radio-container.tsx | 5 ++ .../src/components/radio/unicom-guard.tsx | 21 ++++++++ src/renderer/src/style/UnicomGuard.scss | 52 +++++++++++++++++++ src/renderer/src/style/app.scss | 9 ++-- src/shared/config.type.ts | 20 +++++++ tsconfig.node.json | 2 +- tsconfig.web.json | 1 + 10 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 src/renderer/src/components/radio/unicom-guard.tsx create mode 100644 src/renderer/src/style/UnicomGuard.scss create mode 100644 src/shared/config.type.ts diff --git a/package-lock.json b/package-lock.json index 6d310fb7..9bf5c3f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-uy2CO4u+6luDl7WGbPx6+TBHhPf+0GQ7ozs/+oY/XEuHP250Mv34wINKjBRT2pQoAFIABbGSXEZFDuDKbWWyUg==", + "integrity": "sha512-HI4Ewp077EQpg0ognTYOXioMF8Cs6+xc7rLrfaSn5p6LzyQJ5cP3Q8lPUCQoPFSNXHLfOrofcJjLs9saCF3LoA==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", diff --git a/src/main/config.ts b/src/main/config.ts index 6eb2d41d..c5684d3a 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -1,7 +1,6 @@ import { dialog } from 'electron'; import Store from 'electron-store'; - -export type AlwaysOnTopMode = 'never' | 'always' | 'inMiniMode'; +import { AlwaysOnTopMode, Configuration } from '../shared/config.type'; // Used to check for older settings that need upgrading. This should get // increased any time the Configuration object has a breaking change. @@ -23,25 +22,6 @@ export const defaultConfiguration = { alwaysOnTop: 'never' as AlwaysOnTopMode }; -export interface Configuration { - version?: number; - - audioApi: number; - audioInputDeviceId: string; - headsetOutputDeviceId: string; - speakerOutputDeviceId: string; - - cid: string; - password: string; - callsign: string; - - hardwareType: number; - radioGain: number; - - // Boolean is the prior type for this property, AlwaysOnTopMode is the updated type. - alwaysOnTop: boolean | AlwaysOnTopMode; -} - class ConfigManager { private static _instance: ConfigManager | null = null; private _currentConfiguration: Configuration | null = null; diff --git a/src/renderer/src/components/navbar.tsx b/src/renderer/src/components/navbar.tsx index bb301e26..0f0fb5af 100644 --- a/src/renderer/src/components/navbar.tsx +++ b/src/renderer/src/components/navbar.tsx @@ -1,6 +1,5 @@ import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; -import { Configuration } from '../../../../src/main/config'; import { checkIfCallsignIsRelief, getCleanCallsign } from '../helpers/CallsignHelper'; import useErrorStore from '../store/errorStore'; import useSessionStore from '../store/sessionStore'; @@ -9,6 +8,7 @@ import '../style/navbar.scss'; import Clock from './clock'; import MiniModeToggleButton from './MiniModeToggleButton'; import SettingsModal from './settings-modal/settings-modal'; +import { Configuration } from 'src/shared/config.type'; const Navbar: React.FC = () => { const [showModal, setShowModal] = useState(false); diff --git a/src/renderer/src/components/radio/radio-container.tsx b/src/renderer/src/components/radio/radio-container.tsx index 29b61617..ca18327a 100644 --- a/src/renderer/src/components/radio/radio-container.tsx +++ b/src/renderer/src/components/radio/radio-container.tsx @@ -1,12 +1,17 @@ import React from 'react'; import Radio from './radio'; import useRadioState from '../../store/radioStore'; +import UnicomGuardBar from './unicom-guard'; const RadioContainer: React.FC = () => { const radios = useRadioState((state) => state.radios); return ( <>
+
+ +
+
{radios.map((radio) => ( diff --git a/src/renderer/src/components/radio/unicom-guard.tsx b/src/renderer/src/components/radio/unicom-guard.tsx new file mode 100644 index 00000000..c554a7c9 --- /dev/null +++ b/src/renderer/src/components/radio/unicom-guard.tsx @@ -0,0 +1,21 @@ +import '../../style/UnicomGuard.scss'; + +const UnicomGuardBar = () => { + return ( +
+ + UNICOM (122.800) + + + + + + GUARD (121.500) + + + +
+ ); +}; + +export default UnicomGuardBar; diff --git a/src/renderer/src/style/UnicomGuard.scss b/src/renderer/src/style/UnicomGuard.scss new file mode 100644 index 00000000..bc662178 --- /dev/null +++ b/src/renderer/src/style/UnicomGuard.scss @@ -0,0 +1,52 @@ +@import 'variables'; + +.unicon-overall-container { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.unicom-bar-container { + height: 28px; + line-height: 25px; + width: fit-content; + margin: 10px; + background-color: lighten($disabled, 10%); + border-radius: $card-border-radius; + border-color: lighten($disabled, 20%); + outline: lighten($disabled, 25%) solid 2px; + padding-left: 10px; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; // Added to center items horizontally + @media screen and (max-width: 465px) { + display: none; + } +} + +.unicom-line-item { + margin-right: 10px; +} + +.unicom-text { + display: inline-block; + vertical-align: middle; +} + +.sm-button { + height: 19px !important; + width: 30px !important; + min-height: 0 !important; + padding-left: 3px !important; + padding-right: 3px !important; + padding-top: 2px !important; + padding-bottom: 2px !important; + margin: 0 !important; + line-height: 2px !important; + font-size: 14px !important; + outline: darken($info, 15%) solid 1px !important; + margin-left: 5px !important; +} diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 7cdb6084..953df16a 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -233,9 +233,7 @@ button:disabled { float: left; height: 100%; width: 65%; - padding-top: 0; - padding-bottom: 0; - padding-right: 0; + padding: 0; } // When changing the max-width property make sure to update the miniModeWidthBreakpoint constant in @@ -385,8 +383,7 @@ select:disabled { font-size: $mini-font-size; } -.radio-list -{ +.radio-list { max-height: 80vh; overflow-y: auto; } @@ -421,4 +418,4 @@ select:disabled { .exit-mini-mode-container.hidden { opacity: 0; -} \ No newline at end of file +} diff --git a/src/shared/config.type.ts b/src/shared/config.type.ts new file mode 100644 index 00000000..0cc87cce --- /dev/null +++ b/src/shared/config.type.ts @@ -0,0 +1,20 @@ +export type AlwaysOnTopMode = 'never' | 'always' | 'inMiniMode'; + +export interface Configuration { + version?: number; + + audioApi: number; + audioInputDeviceId: string; + headsetOutputDeviceId: string; + speakerOutputDeviceId: string; + + cid: string; + password: string; + callsign: string; + + hardwareType: number; + radioGain: number; + + // Boolean is the prior type for this property, AlwaysOnTopMode is the updated type. + alwaysOnTop: boolean | AlwaysOnTopMode; +} diff --git a/tsconfig.node.json b/tsconfig.node.json index eabf67bd..c3ad7893 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -1,6 +1,6 @@ { "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", - "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"], + "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/shared/**/*"], "compilerOptions": { "sourceMap": true, "composite": true, diff --git a/tsconfig.web.json b/tsconfig.web.json index 464fb277..eb373d9a 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -5,6 +5,7 @@ "src/renderer/src/**/*", "src/preload/*.d.ts", "src/renderer/src/**/*.tsx", + "src/shared/**/*" ], "compilerOptions": { "sourceMap": true, From a9fb540a691eecec2f395dacda1f909b9e06bb0c Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Tue, 22 Oct 2024 18:04:03 +0200 Subject: [PATCH 2/9] Version bump & logic for unicom and guard --- backend/include/Helpers.hpp | 16 +- backend/include/RadioHelper.hpp | 7 +- backend/include/Shared.hpp | 13 +- backend/src/main.cpp | 44 ++- backend/types/index.d.ts | 1 + package-lock.json | 6 +- package.json | 2 +- src/main/index.ts | 7 +- src/preload/bindings.ts | 4 +- src/renderer/src/components/bootstrap.tsx | 10 + .../src/components/radio/radio-container.tsx | 15 +- .../src/components/radio/unicom-guard.tsx | 258 +++++++++++++++++- .../settings-modal/settings-modal.tsx | 2 +- src/renderer/src/store/radioStore.ts | 9 +- src/renderer/src/style/UnicomGuard.scss | 17 ++ src/shared/common.ts | 2 + 16 files changed, 363 insertions(+), 50 deletions(-) create mode 100644 src/shared/common.ts diff --git a/backend/include/Helpers.hpp b/backend/include/Helpers.hpp index a581edd4..b90c0e45 100644 --- a/backend/include/Helpers.hpp +++ b/backend/include/Helpers.hpp @@ -20,7 +20,7 @@ class Helpers { * @param frequency The frequency value to be cleaned up. * @return The cleaned up frequency value. */ - inline static int CleanUpFrequency(int frequency) + static int CleanUpFrequency(int frequency) { // We don't clean up an unset frequency if (std::abs(frequency) == OBS_FREQUENCY) { @@ -37,7 +37,7 @@ class Helpers { * @return A string representation of the frequency in a human-readable format (e.g. "122.800" * for 122800000 Hz) */ - inline static std::string ConvertHzToHumanString(unsigned int frequencyHz) + static std::string ConvertHzToHumanString(unsigned int frequencyHz) { std::string temp = std::to_string(frequencyHz / 1000); return temp.substr(0, 3) + "." + temp.substr(3, 7); @@ -52,8 +52,7 @@ class Helpers { * opposite of currentValue if the property is "toggle", or the currentValue if * incoming value is undefined or invalid. */ - inline static bool ConvertBoolOrToggleToBool( - const nlohmann::json& incomingValue, bool currentValue) + static bool ConvertBoolOrToggleToBool(const nlohmann::json& incomingValue, bool currentValue) { if (incomingValue.is_null()) { TRACK_LOG_INFO("ConvertBoolOrToggleToBool: Incoming value wasn't specified, returning " @@ -94,7 +93,7 @@ class NapiHelpers { NapiHelpers::callbackAvailable = true; } - inline static void callElectron( + static void callElectron( const std::string& eventName, const std::string& data = "", const std::string& data2 = "") { if (!NapiHelpers::callbackAvailable || NapiHelpers::callbackRef == nullptr) { @@ -111,10 +110,7 @@ class NapiHelpers { }); } - inline static void sendErrorToElectron(const std::string& message) - { - callElectron("error", message); - } + static void sendErrorToElectron(const std::string& message) { callElectron("error", message); } inline static std::mutex _callElectronMutex; -}; \ No newline at end of file +}; diff --git a/backend/include/RadioHelper.hpp b/backend/include/RadioHelper.hpp index 5828fc5d..5d861212 100644 --- a/backend/include/RadioHelper.hpp +++ b/backend/include/RadioHelper.hpp @@ -2,6 +2,8 @@ #include "Shared.hpp" #include "sdk.hpp" +#include "Helpers.hpp" + class RadioState { public: @@ -22,8 +24,7 @@ class RadioHelper { * @return true The state was set successfully * @return false The state was not set successfully */ - inline static bool SetRadioState( - const std::shared_ptr& mApiServer, const RadioState& newState) + static bool SetRadioState(const std::shared_ptr& mApiServer, const RadioState& newState) { if (!mClient->IsVoiceConnected()) { TRACK_LOG_TRACE("Voice is not connected, not setting radio state"); @@ -76,4 +77,4 @@ class RadioHelper { return true; } }; -; \ No newline at end of file +; diff --git a/backend/include/Shared.hpp b/backend/include/Shared.hpp index fe14b508..9e48b141 100644 --- a/backend/include/Shared.hpp +++ b/backend/include/Shared.hpp @@ -23,11 +23,12 @@ #define VERSION_CHECK_ENDPOINT "/pierr3/TrackAudio/main/MANDATORY_VERSION" #define OBS_FREQUENCY 199998000 // 199.998 -#define UNICOM_FREQUENCY = 122800000 // 122.800 +#define UNICOM_FREQUENCY 122800000 // 122.800 +#define GUARD_FREQUENCY 121500000 // 121.500 #define API_SERVER_PORT 49080 -constexpr semver::version VERSION = semver::version { 1, 3, 0, semver::prerelease::beta, 2 }; +constexpr semver::version VERSION = semver::version { 1, 3, 0, semver::prerelease::beta, 3 }; // NOLINTNEXTLINE const std::string CLIENT_NAME = std::string("TrackAudio-") + VERSION.to_string(); @@ -35,7 +36,7 @@ const std::string CLIENT_NAME = std::string("TrackAudio-") + VERSION.to_string() static std::unique_ptr mClient = nullptr; struct FileSystem { - inline static std::filesystem::path GetStateFolderPath() + static std::filesystem::path GetStateFolderPath() { return std::filesystem::path(sago::getStateDir()) / "trackaudio"; } @@ -71,7 +72,7 @@ struct UserSettings { // NOLINTNEXTLINE inline static CSimpleIniA ini; - inline static void load() + static void load() { try { _load(); @@ -80,7 +81,7 @@ struct UserSettings { } } - inline static void save() + static void save() { std::string settingsFilePath = (FileSystem::GetStateFolderPath() / "settings.ini").string(); @@ -103,7 +104,7 @@ struct UserSettings { } protected: - inline static void _load() + static void _load() { std::string settingsFilePath = (FileSystem::GetStateFolderPath() / "settings.ini").string(); ini.SetUnicode(); diff --git a/backend/src/main.cpp b/backend/src/main.cpp index 664c8abd..a4f822f1 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -2,8 +2,6 @@ #include "afv-native/atcClientWrapper.h" #include "afv-native/event.h" #include "afv-native/hardwareType.h" -#include "spdlog/sinks/rotating_file_sink.h" -#include "spdlog/spdlog.h" #include #include #include @@ -38,7 +36,7 @@ struct MainThreadShared { inline static std::unique_ptr inputHandler = nullptr; }; - +namespace { Napi::Array GetAudioApis(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); @@ -149,12 +147,15 @@ Napi::Boolean AddFrequency(const Napi::CallbackInfo& info) auto hasBeenAddded = mClient->AddFrequency(frequency, callsign); if (!hasBeenAddded) { + if (frequency == UNICOM_FREQUENCY || frequency == GUARD_FREQUENCY) { + return Napi::Boolean::New(info.Env(), true); + } NapiHelpers::sendErrorToElectron("Could not add frequency: it already exists"); TRACK_LOG_WARNING("Could not add frequency, it already exists: {} {}", frequency, callsign); return Napi::Boolean::New(info.Env(), false); } - RadioState newState; + RadioState newState {}; newState.frequency = frequency; newState.rx = false; @@ -169,7 +170,7 @@ Napi::Boolean AddFrequency(const Napi::CallbackInfo& info) void RemoveFrequency(const Napi::CallbackInfo& info) { - RadioState newState; + RadioState newState {}; newState.frequency = info[0].As().Int32Value(); newState.rx = false; @@ -188,7 +189,7 @@ void Reset(const Napi::CallbackInfo& /*info*/) { mClient->reset(); } Napi::Boolean SetFrequencyState(const Napi::CallbackInfo& info) { - RadioState newState; + RadioState newState {}; newState.frequency = info[0].As().Int32Value(); newState.rx = info[1].As().Value(); @@ -295,7 +296,22 @@ void SetRadioGain(const Napi::CallbackInfo& info) float gain = info[0].As().FloatValue(); UserSession::currentRadioGain = gain; - mClient->SetRadioGainAll(gain); + auto states = mClient->getRadioState(); + for (const auto& state : states) { + if (state.second.Frequency == UNICOM_FREQUENCY + || state.second.Frequency == GUARD_FREQUENCY) { + continue; + } + mClient->SetRadioGain(state.first, gain); + } +} + +void SetFrequencyRadioGain(const Napi::CallbackInfo& info) +{ + int frequency = info[0].As().Int32Value(); + float gain = info[1].As().FloatValue(); + + mClient->SetRadioGain(frequency, gain); } Napi::String Version(const Napi::CallbackInfo& info) @@ -399,7 +415,7 @@ void RequestPttKeyName(const Napi::CallbackInfo& info) } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,readability-function-cognitive-complexity) -static void HandleAfvEvents(afv_native::ClientEventType eventType, void* data, void* data2) +void HandleAfvEvents(afv_native::ClientEventType eventType, void* data, void* data2) { if (!NapiHelpers::callbackAvailable) { return; @@ -427,6 +443,8 @@ static void HandleAfvEvents(afv_native::ClientEventType eventType, void* data, v for (const auto& state : states) { if (state.second.stationName == station) { mClient->UseTransceiversFromStation(station, static_cast(state.first)); + mClient->UseTransceiversFromStation(station, UNICOM_FREQUENCY); + mClient->UseTransceiversFromStation(station, GUARD_FREQUENCY); break; } } @@ -458,7 +476,7 @@ static void HandleAfvEvents(afv_native::ClientEventType eventType, void* data, v } NapiHelpers::callElectron("StationDataReceived", callsign, std::to_string(frequency)); - MainThreadShared::mApiServer->publishStationAdded(callsign, frequency); + MainThreadShared::mApiServer->publishStationAdded(callsign, static_cast(frequency)); } if (eventType == afv_native::ClientEventType::VccsReceived) { @@ -480,7 +498,8 @@ static void HandleAfvEvents(afv_native::ClientEventType eventType, void* data, v } NapiHelpers::callElectron("StationDataReceived", callsign, std::to_string(frequency)); - MainThreadShared::mApiServer->publishStationAdded(callsign, frequency); + MainThreadShared::mApiServer->publishStationAdded( + callsign, static_cast(frequency)); } } @@ -762,6 +781,9 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) exports.Set(Napi::String::New(env, "SetPtt"), Napi::Function::New(env, SetPtt)); + exports.Set(Napi::String::New(env, "SetFrequencyRadioGain"), + Napi::Function::New(env, SetFrequencyRadioGain)); + exports.Set(Napi::String::New(env, "SetRadioGain"), Napi::Function::New(env, SetRadioGain)); exports.Set( @@ -793,5 +815,5 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) return exports; } - NODE_API_MODULE(addon, Init) +} diff --git a/backend/types/index.d.ts b/backend/types/index.d.ts index 6628ba95..f18f4040 100644 --- a/backend/types/index.d.ts +++ b/backend/types/index.d.ts @@ -76,6 +76,7 @@ declare namespace TrackAudioAfv { export function SetCid(cid: string): void; export function SetRadioGain(gain: number): void; + export function SetFrequencyRadioGain(frequency: number, gain: number): void; export function SetPtt(activate: boolean): void; export function SetHardwareType(type: number): void; diff --git a/package-lock.json b/package-lock.json index 9bf5c3f1..0945af65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trackaudio", - "version": "1.3.0-beta.2", + "version": "1.3.0-beta.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trackaudio", - "version": "1.3.0-beta.2", + "version": "1.3.0-beta.3", "hasInstallScript": true, "dependencies": { "@electron-toolkit/preload": "^3.0.1", @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-HI4Ewp077EQpg0ognTYOXioMF8Cs6+xc7rLrfaSn5p6LzyQJ5cP3Q8lPUCQoPFSNXHLfOrofcJjLs9saCF3LoA==", + "integrity": "sha512-ULoB+61PxokiB301NEWlaKWSyMcKB6QUyTluLiIcsyLCtk2sSBh3NKYC0VgQ64Lx4kEvcTcy2k5OehnLdQIang==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", diff --git a/package.json b/package.json index c2b9487f..f6dce30c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "trackaudio", "productName": "TrackAudio", - "version": "1.3.0-beta.2", + "version": "1.3.0-beta.3", "description": "A next generation Audio-For-VATSIM ATC Client for macOS, Linux and Windows", "main": "./out/main/index.js", "author": "github.com/pierr3", diff --git a/src/main/index.ts b/src/main/index.ts index 5fc3cc92..12a119b6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -4,7 +4,8 @@ import Store from 'electron-store'; import { join } from 'path'; import { AfvEventTypes, TrackAudioAfv } from 'trackaudio-afv'; import icon from '../../resources/AppIcon/icon.png?asset'; -import configManager, { AlwaysOnTopMode } from './config'; +import configManager from './config'; +import { AlwaysOnTopMode } from '../shared/config.type'; type WindowMode = 'mini' | 'maxi'; @@ -432,6 +433,10 @@ ipcMain.handle('set-radio-gain', (_, radioGain: number) => { TrackAudioAfv.SetRadioGain(radioGain); }); +ipcMain.handle('set-frequency-radio-gain', (_, frequency: number, radioGain: number) => { + TrackAudioAfv.SetFrequencyRadioGain(frequency, radioGain); +}); + ipcMain.handle('set-hardware-type', (_, hardwareType: number) => { configManager.updateConfig({ hardwareType }); TrackAudioAfv.SetHardwareType(hardwareType); diff --git a/src/preload/bindings.ts b/src/preload/bindings.ts index 838c9a28..2ea343e0 100644 --- a/src/preload/bindings.ts +++ b/src/preload/bindings.ts @@ -1,5 +1,5 @@ import { ipcRenderer, IpcRendererEvent } from 'electron'; -import { AlwaysOnTopMode } from '../main/config'; +import { AlwaysOnTopMode } from '../shared/config.type'; export const api = { /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -65,6 +65,8 @@ export const api = { SetupPtt: (pttIndex: number) => ipcRenderer.invoke('setup-ptt', pttIndex), + SetFrequencyRadioGain: (frequency: number, gain: number) => + ipcRenderer.invoke('set-frequency-radio-gain', frequency, gain), SetRadioGain: (gain: number) => ipcRenderer.invoke('set-radio-gain', gain), SetHardwareType: (type: number) => ipcRenderer.invoke('set-hardware-type', type), diff --git a/src/renderer/src/components/bootstrap.tsx b/src/renderer/src/components/bootstrap.tsx index 8b4416c7..330ab1a2 100644 --- a/src/renderer/src/components/bootstrap.tsx +++ b/src/renderer/src/components/bootstrap.tsx @@ -4,6 +4,7 @@ import useErrorStore from '../store/errorStore'; import useSessionStore from '../store/sessionStore'; import useUtilStore from '../store/utilStore'; import { StationStateUpdate } from '../interfaces/StationStateUpdate'; +import { GuardFrequency, UnicomFrequency } from '../../../shared/common'; const Bootsrap: React.FC = () => { useEffect(() => { @@ -104,6 +105,15 @@ const Bootsrap: React.FC = () => { if (useSessionStore.getState().isAtc) { void window.api.GetStation(useSessionStore.getState().stationCallsign); } + + void window.api.addFrequency(UnicomFrequency, 'UNICOM'); + void window.api.addFrequency(GuardFrequency, 'GUARD'); + useRadioState + .getState() + .addRadio(UnicomFrequency, 'UNICOM', useSessionStore.getState().getStationCallsign()); + useRadioState + .getState() + .addRadio(GuardFrequency, 'GUARD', useSessionStore.getState().getStationCallsign()); }); window.api.on('VoiceDisconnected', () => { diff --git a/src/renderer/src/components/radio/radio-container.tsx b/src/renderer/src/components/radio/radio-container.tsx index ca18327a..fdc22cf8 100644 --- a/src/renderer/src/components/radio/radio-container.tsx +++ b/src/renderer/src/components/radio/radio-container.tsx @@ -1,20 +1,27 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import Radio from './radio'; import useRadioState from '../../store/radioStore'; import UnicomGuardBar from './unicom-guard'; const RadioContainer: React.FC = () => { const radios = useRadioState((state) => state.radios); + + const filteredRadios = useMemo(() => { + return radios.filter( + (radio) => radio.frequency !== 0 && radio.frequency !== 122.8e6 && radio.frequency !== 121.5e6 + ); + }, [radios]); + return ( <>
-
+
-
- {radios.map((radio) => ( +
+ {filteredRadios.map((radio) => ( ))}
diff --git a/src/renderer/src/components/radio/unicom-guard.tsx b/src/renderer/src/components/radio/unicom-guard.tsx index c554a7c9..b5494e99 100644 --- a/src/renderer/src/components/radio/unicom-guard.tsx +++ b/src/renderer/src/components/radio/unicom-guard.tsx @@ -1,18 +1,264 @@ +import useRadioState, { RadioType } from '@renderer/store/radioStore'; import '../../style/UnicomGuard.scss'; +import { useEffect, useMemo, useState } from 'react'; +import clsx from 'clsx'; +import useSessionStore from '@renderer/store/sessionStore'; +import useErrorStore from '@renderer/store/errorStore'; +import { GuardFrequency, UnicomFrequency } from '../../../../shared/common'; const UnicomGuardBar = () => { + const [radios, setRadioState] = useRadioState((state) => [state.radios, state.setRadioState]); + const [isNetworkConnected, isAtc] = useSessionStore((state) => [state.isNetworkConnected, state.isAtc]); + + const [localRadioGain, setLocalRadioGain] = useState(50); + + const postError = useErrorStore((state) => state.postError); + + const unicom = useMemo(() => { + return radios.find((radio) => radio.frequency === UnicomFrequency); + }, [radios]); + + const guard = useMemo(() => { + return radios.find((radio) => radio.frequency === GuardFrequency); + }, [radios]); + + const clickRx = (radio: RadioType | undefined) => { + if (!radio) return; + const newState = !radio.rx; + + window.api + .setFrequencyState( + radio.frequency, + newState, + newState ? radio.tx : false, + false, + radio.onSpeaker, + false + ) + .then((ret) => { + if (!ret) { + postError('Invalid action on invalid radio: RX.'); + return; + } + setRadioState(radio.frequency, { + rx: newState, + tx: !newState ? false : radio.tx, + xc: !false, + crossCoupleAcross: false, + onSpeaker: radio.onSpeaker + }); + }) + .catch((err: unknown) => { + console.error(err); + }); + }; + + const clickTx = (radio: RadioType | undefined) => { + if (!radio) return; + const newState = !radio.tx; + + window.api + .setFrequencyState( + radio.frequency, + newState ? true : radio.rx, // If tx is true, rx must be true + newState, + false, + radio.onSpeaker, + false + ) + .then((ret) => { + if (!ret) { + postError('Invalid action on invalid radio: TX.'); + return; + } + setRadioState(radio.frequency, { + rx: !radio.rx && newState ? true : radio.rx, + tx: newState, + xc: false, + crossCoupleAcross: false, + onSpeaker: radio.onSpeaker + }); + }) + .catch((err: unknown) => { + console.error(err); + }); + }; + + const clickSpK = (radio: RadioType | undefined) => { + if (!radio) return; + const newState = !radio.onSpeaker; + window.api + .setFrequencyState( + radio.frequency, + radio.rx, + radio.tx, + radio.xc, + newState, + radio.crossCoupleAcross + ) + .then((ret) => { + if (!ret) { + postError('Invalid action on invalid radio: OnSPK.'); + return; + } + setRadioState(radio.frequency, { + rx: radio.rx, + tx: radio.tx, + xc: radio.xc, + crossCoupleAcross: radio.crossCoupleAcross, + onSpeaker: newState + }); + }) + .catch((err: unknown) => { + console.error(err); + }); + }; + + useEffect(() => { + const storedGain = window.localStorage.getItem('unicomRadioGain'); + const gainToSet = storedGain?.length ? parseInt(storedGain) : 50; + setLocalRadioGain(storedGain?.length ? parseInt(storedGain) : 50); + console.log('Setting unicom radio gain to', gainToSet); + void window.api.SetFrequencyRadioGain(UnicomFrequency, gainToSet / 100); + void window.api.SetFrequencyRadioGain(GuardFrequency, gainToSet / 100); + }, []); + + const updateRadioGainValue = (newGain: number) => { + if (!unicom || !guard) return; + window.api + .SetFrequencyRadioGain(unicom.frequency, newGain / 100) + .then(() => { + void window.api.SetFrequencyRadioGain(guard.frequency, newGain / 100); + setLocalRadioGain(newGain); + console.log('Setting unicom radio gain to', newGain); + }) + .catch((err: unknown) => { + console.error(err); + }); + + window.localStorage.setItem('unicomRadioGain', newGain.toString()); + }; + + const handleRadioGainChange = (event: React.ChangeEvent) => { + updateRadioGainValue(event.target.valueAsNumber); + }; + + const handleRadioGainMouseWheel = (event: React.WheelEvent) => { + const newValue = Math.min(Math.max(localRadioGain + (event.deltaY > 0 ? -1 : 1), 0), 100); + + updateRadioGainValue(newValue); + }; + return (
- UNICOM (122.800) - - + + UNICOM + + + + + + - GUARD (121.500) - - + + GUARD + + + + + + + + +
); diff --git a/src/renderer/src/components/settings-modal/settings-modal.tsx b/src/renderer/src/components/settings-modal/settings-modal.tsx index e339e75e..db4bcfbe 100644 --- a/src/renderer/src/components/settings-modal/settings-modal.tsx +++ b/src/renderer/src/components/settings-modal/settings-modal.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { AudioApi, AudioDevice } from 'trackaudio-afv'; import { useDebouncedCallback } from 'use-debounce'; -import { AlwaysOnTopMode, Configuration } from '../../../../../src/main/config'; +import { AlwaysOnTopMode, Configuration } from '../../../../shared/config.type'; import useRadioState from '../../store/radioStore'; import useUtilStore from '../../store/utilStore'; import AudioApis from './audio-apis'; diff --git a/src/renderer/src/store/radioStore.ts b/src/renderer/src/store/radioStore.ts index 3da1f1ab..c1bfef98 100644 --- a/src/renderer/src/store/radioStore.ts +++ b/src/renderer/src/store/radioStore.ts @@ -2,6 +2,7 @@ import { create } from 'zustand'; import useSessionStore from './sessionStore'; import { radioCompare } from '../helpers/RadioHelper'; import { getCallsignParts } from '../helpers/CallsignHelper'; +import { GuardFrequency, UnicomFrequency } from '../../../shared/common'; export interface RadioType { frequency: number; @@ -78,9 +79,11 @@ const useRadioState = create((set) => ({ pttIsOn: false, addRadio: (frequency, callsign, stationCallsign) => { if (RadioHelper.doesRadioExist(useRadioState.getState().radios, frequency)) { - postMessage( - 'Frequency already exists in local client, but maybe not in AFV, delete it and try again' - ); + if (frequency !== UnicomFrequency && frequency !== GuardFrequency) { + postMessage( + 'Frequency already exists in local client, but maybe not in AFV, delete it and try again' + ); + } return; } diff --git a/src/renderer/src/style/UnicomGuard.scss b/src/renderer/src/style/UnicomGuard.scss index bc662178..7a76aa2d 100644 --- a/src/renderer/src/style/UnicomGuard.scss +++ b/src/renderer/src/style/UnicomGuard.scss @@ -22,6 +22,10 @@ display: flex; align-items: center; justify-content: center; // Added to center items horizontally + +} + +.hide-unicom-container { @media screen and (max-width: 465px) { display: none; } @@ -49,4 +53,17 @@ font-size: 14px !important; outline: darken($info, 15%) solid 1px !important; margin-left: 5px !important; + + &.btn-success { + outline: darken($success, 15%) solid 1px !important; + } + + &.btn-warning { + outline: darken($warning, 15%) solid 1px !important; + } +} + +.unicom-volume-bar { + margin-right: 10px; + width: 50px !important; } diff --git a/src/shared/common.ts b/src/shared/common.ts new file mode 100644 index 00000000..444ace10 --- /dev/null +++ b/src/shared/common.ts @@ -0,0 +1,2 @@ +export const UnicomFrequency = 122800000; +export const GuardFrequency = 121500000; From 60679ccd0f4b8f6ff596715d745d58864df42446 Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Tue, 22 Oct 2024 22:25:02 +0200 Subject: [PATCH 3/9] Force link relevant transceivers for unicom and guard on change --- backend/extern/afv-native | 2 +- backend/src/main.cpp | 37 +++++++++++++++++++++++++++++++++++-- package-lock.json | 2 +- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/backend/extern/afv-native b/backend/extern/afv-native index 904f896c..0d190b80 160000 --- a/backend/extern/afv-native +++ b/backend/extern/afv-native @@ -1 +1 @@ -Subproject commit 904f896c672109a9e30cdc3f1df4043000cfd385 +Subproject commit 0d190b8058e13455052e0a07f04c80549461f107 diff --git a/backend/src/main.cpp b/backend/src/main.cpp index a4f822f1..96cbc824 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -1,4 +1,5 @@ #include "LogFactory.h" +#include "afv-native/afv/dto/StationTransceiver.h" #include "afv-native/atcClientWrapper.h" #include "afv-native/event.h" #include "afv-native/hardwareType.h" @@ -120,6 +121,38 @@ void Disconnect(const Napi::CallbackInfo& /*info*/) sdk::types::Event::kDisconnectFrequencyStateUpdate, {}, {}); } +void SetGuardAndUnicomTransceivers() +{ + auto transceivers = mClient->GetAllTransceivers(); + auto state = mClient->getRadioState(); + + std::vector activeStations; + std::vector transceiversToLink; + + for (const auto& [frequency, radioState] : state) { + if (mClient->GetRxActive(frequency) && radioState.stationName != "") { + activeStations.push_back(radioState.stationName); + } + } + + for (const auto& [stationName, antennas] : transceivers) { + // NOLINTNEXTLINE (annoying boost suggestion) + if (std::find(activeStations.begin(), activeStations.end(), stationName) + != activeStations.end()) { + for (const auto& ant : antennas) { + transceiversToLink.push_back(ant); + } + } + } + + if (transceiversToLink.empty()) { + return; // Don't override default transceivers if we got nothing to link + } + + mClient->SetManualTransceivers(UNICOM_FREQUENCY, transceiversToLink); + mClient->SetManualTransceivers(GUARD_FREQUENCY, transceiversToLink); +} + void SetAudioSettings(const Napi::CallbackInfo& info) { if (mClient->IsVoiceConnected()) { @@ -200,6 +233,7 @@ Napi::Boolean SetFrequencyState(const Napi::CallbackInfo& info) newState.xca = info[5].As().Value(); // Not used auto result = RadioHelper::SetRadioState(MainThreadShared::mApiServer, newState); + SetGuardAndUnicomTransceivers(); return Napi::Boolean::New(info.Env(), result); } @@ -443,8 +477,7 @@ void HandleAfvEvents(afv_native::ClientEventType eventType, void* data, void* da for (const auto& state : states) { if (state.second.stationName == station) { mClient->UseTransceiversFromStation(station, static_cast(state.first)); - mClient->UseTransceiversFromStation(station, UNICOM_FREQUENCY); - mClient->UseTransceiversFromStation(station, GUARD_FREQUENCY); + SetGuardAndUnicomTransceivers(); break; } } diff --git a/package-lock.json b/package-lock.json index 0945af65..aabf7072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-ULoB+61PxokiB301NEWlaKWSyMcKB6QUyTluLiIcsyLCtk2sSBh3NKYC0VgQ64Lx4kEvcTcy2k5OehnLdQIang==", + "integrity": "sha512-iKvaakzdHK14Ouw4pcKCTmSWRTrVUGfQ4XPnNNaJPHNKXsOXGY8d4Rnt1tuF2DDFnkU5spd+XV2zFv8wTiFL1Q==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", From 282fde33c656e7b2c11c7b621ba34a7c86b62a59 Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Tue, 22 Oct 2024 23:38:13 +0200 Subject: [PATCH 4/9] Fix transceiver linking for unicom/guard --- backend/extern/afv-native | 2 +- backend/src/main.cpp | 37 +++++-------------------------------- package-lock.json | 2 +- 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/backend/extern/afv-native b/backend/extern/afv-native index 0d190b80..29f8a582 160000 --- a/backend/extern/afv-native +++ b/backend/extern/afv-native @@ -1 +1 @@ -Subproject commit 0d190b8058e13455052e0a07f04c80549461f107 +Subproject commit 29f8a582293ddfd253911f70fcd8a86b5e9ae72c diff --git a/backend/src/main.cpp b/backend/src/main.cpp index 96cbc824..12a0b0e7 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -1,5 +1,4 @@ #include "LogFactory.h" -#include "afv-native/afv/dto/StationTransceiver.h" #include "afv-native/atcClientWrapper.h" #include "afv-native/event.h" #include "afv-native/hardwareType.h" @@ -121,36 +120,9 @@ void Disconnect(const Napi::CallbackInfo& /*info*/) sdk::types::Event::kDisconnectFrequencyStateUpdate, {}, {}); } -void SetGuardAndUnicomTransceivers() -{ - auto transceivers = mClient->GetAllTransceivers(); - auto state = mClient->getRadioState(); - - std::vector activeStations; - std::vector transceiversToLink; - - for (const auto& [frequency, radioState] : state) { - if (mClient->GetRxActive(frequency) && radioState.stationName != "") { - activeStations.push_back(radioState.stationName); - } - } - - for (const auto& [stationName, antennas] : transceivers) { - // NOLINTNEXTLINE (annoying boost suggestion) - if (std::find(activeStations.begin(), activeStations.end(), stationName) - != activeStations.end()) { - for (const auto& ant : antennas) { - transceiversToLink.push_back(ant); - } - } - } - - if (transceiversToLink.empty()) { - return; // Don't override default transceivers if we got nothing to link - } - - mClient->SetManualTransceivers(UNICOM_FREQUENCY, transceiversToLink); - mClient->SetManualTransceivers(GUARD_FREQUENCY, transceiversToLink); +void SetGuardAndUnicomTransceivers() { + mClient->UseAllActiveTransceivers(UNICOM_FREQUENCY); + mClient->UseAllActiveTransceivers(GUARD_FREQUENCY); } void SetAudioSettings(const Napi::CallbackInfo& info) @@ -232,8 +204,9 @@ Napi::Boolean SetFrequencyState(const Napi::CallbackInfo& info) newState.headset = !info[4].As().Value(); newState.xca = info[5].As().Value(); // Not used - auto result = RadioHelper::SetRadioState(MainThreadShared::mApiServer, newState); SetGuardAndUnicomTransceivers(); + + auto result = RadioHelper::SetRadioState(MainThreadShared::mApiServer, newState); return Napi::Boolean::New(info.Env(), result); } diff --git a/package-lock.json b/package-lock.json index aabf7072..20db1a3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-iKvaakzdHK14Ouw4pcKCTmSWRTrVUGfQ4XPnNNaJPHNKXsOXGY8d4Rnt1tuF2DDFnkU5spd+XV2zFv8wTiFL1Q==", + "integrity": "sha512-S3g2LXjv4XikGCxUfjAe6ibzcOsWsmhb6cXrBoBb40PXzxit0iB8ZovCEhTRNyrWbbycOY9VKmizkoS+3FVv7w==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", From 95212ae0cb72316a83daf76c49c3b84c470e6227 Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Tue, 22 Oct 2024 23:58:20 +0200 Subject: [PATCH 5/9] Force scrollbar styling --- package-lock.json | 2 +- src/renderer/src/style/app.scss | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 20db1a3c..da491fea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-S3g2LXjv4XikGCxUfjAe6ibzcOsWsmhb6cXrBoBb40PXzxit0iB8ZovCEhTRNyrWbbycOY9VKmizkoS+3FVv7w==", + "integrity": "sha512-dzSBsLe76pCfq2o//W1JCaC/wOVpjXlDFYTvB4R+4L7ASVA523vNOEWH4Gw3JICZdq/hsQgvQHU2KqW/kJti4Q==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", diff --git a/src/renderer/src/style/app.scss b/src/renderer/src/style/app.scss index 953df16a..52775911 100644 --- a/src/renderer/src/style/app.scss +++ b/src/renderer/src/style/app.scss @@ -419,3 +419,20 @@ select:disabled { .exit-mini-mode-container.hidden { opacity: 0; } + +::-webkit-scrollbar { + width: 5px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgb(50, 50, 58); /* Color of the scrollbar thumb */ + border-radius: 2px; /* Rounded corners of the scrollbar thumb */ +} + +::-webkit-scrollbar-thumb:hover { + background: lighten(rgb(50, 50, 58), 10%) /* Color of the scrollbar thumb on hover */ +} From 06cf5732df2f7c2356c7bda860e5042fc14c99dd Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Fri, 25 Oct 2024 15:52:45 +0200 Subject: [PATCH 6/9] Fix missing station callsign for unicom --- backend/extern/afv-native | 2 +- package-lock.json | 2 +- src/renderer/src/components/bootstrap.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/extern/afv-native b/backend/extern/afv-native index 29f8a582..2993fc1b 160000 --- a/backend/extern/afv-native +++ b/backend/extern/afv-native @@ -1 +1 @@ -Subproject commit 29f8a582293ddfd253911f70fcd8a86b5e9ae72c +Subproject commit 2993fc1bb00dfbf4093236da2242e7a02dac545c diff --git a/package-lock.json b/package-lock.json index da491fea..684d28ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-dzSBsLe76pCfq2o//W1JCaC/wOVpjXlDFYTvB4R+4L7ASVA523vNOEWH4Gw3JICZdq/hsQgvQHU2KqW/kJti4Q==", + "integrity": "sha512-BwnUSTSS0r0Lqr7uCVzua1ul9aIcx9iPYBI1WSuZEJAPLJw62cHkMu6kIbzYQ37p9B5/xwrEgN4tVdmXyXldDg==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", diff --git a/src/renderer/src/components/bootstrap.tsx b/src/renderer/src/components/bootstrap.tsx index 330ab1a2..34eb70ad 100644 --- a/src/renderer/src/components/bootstrap.tsx +++ b/src/renderer/src/components/bootstrap.tsx @@ -110,10 +110,10 @@ const Bootsrap: React.FC = () => { void window.api.addFrequency(GuardFrequency, 'GUARD'); useRadioState .getState() - .addRadio(UnicomFrequency, 'UNICOM', useSessionStore.getState().getStationCallsign()); + .addRadio(UnicomFrequency, 'UNICOM', 'UNICOM'); useRadioState .getState() - .addRadio(GuardFrequency, 'GUARD', useSessionStore.getState().getStationCallsign()); + .addRadio(GuardFrequency, 'GUARD', 'GUARD'); }); window.api.on('VoiceDisconnected', () => { From 42403c760c121e5f673fe06eea65f40191c411c4 Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Sat, 26 Oct 2024 20:39:51 +0200 Subject: [PATCH 7/9] Change unicom/guard handling --- backend/extern/afv-native | 2 +- backend/src/main.cpp | 32 ++++++--- fast-afv-update.sh | 4 ++ package-lock.json | 2 +- src/renderer/src/components/bootstrap.tsx | 10 --- .../src/components/radio/unicom-guard.tsx | 70 +++++++++++++++---- 6 files changed, 84 insertions(+), 36 deletions(-) create mode 100755 fast-afv-update.sh diff --git a/backend/extern/afv-native b/backend/extern/afv-native index 2993fc1b..c34bb1c0 160000 --- a/backend/extern/afv-native +++ b/backend/extern/afv-native @@ -1 +1 @@ -Subproject commit 2993fc1bb00dfbf4093236da2242e7a02dac545c +Subproject commit c34bb1c05fd6ba77353dba500a33e627a5ced28e diff --git a/backend/src/main.cpp b/backend/src/main.cpp index 12a0b0e7..ebb6b2d5 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -1,4 +1,5 @@ #include "LogFactory.h" +#include "afv-native/afv/dto/StationTransceiver.h" #include "afv-native/atcClientWrapper.h" #include "afv-native/event.h" #include "afv-native/hardwareType.h" @@ -120,9 +121,26 @@ void Disconnect(const Napi::CallbackInfo& /*info*/) sdk::types::Event::kDisconnectFrequencyStateUpdate, {}, {}); } -void SetGuardAndUnicomTransceivers() { - mClient->UseAllActiveTransceivers(UNICOM_FREQUENCY); - mClient->UseAllActiveTransceivers(GUARD_FREQUENCY); +void SetGuardAndUnicomTransceivers() +{ + const auto transceivers = mClient->GetTransceivers(); + const auto states = mClient->getRadioState(); + + std::vector guardAndUnicomTransceivers; + for (const auto& [frequency, state] : states) { + if (frequency == UNICOM_FREQUENCY || frequency == GUARD_FREQUENCY || !state.rx) { + continue; + } + + if (transceivers.find(state.stationName) != transceivers.end()) { + for (const auto& transceiver : transceivers.at(state.stationName)) { + guardAndUnicomTransceivers.push_back(transceiver); + } + } + } + + mClient->SetManualTransceivers(UNICOM_FREQUENCY, guardAndUnicomTransceivers); + mClient->SetManualTransceivers(GUARD_FREQUENCY, guardAndUnicomTransceivers); } void SetAudioSettings(const Napi::CallbackInfo& info) @@ -152,9 +170,6 @@ Napi::Boolean AddFrequency(const Napi::CallbackInfo& info) auto hasBeenAddded = mClient->AddFrequency(frequency, callsign); if (!hasBeenAddded) { - if (frequency == UNICOM_FREQUENCY || frequency == GUARD_FREQUENCY) { - return Napi::Boolean::New(info.Env(), true); - } NapiHelpers::sendErrorToElectron("Could not add frequency: it already exists"); TRACK_LOG_WARNING("Could not add frequency, it already exists: {} {}", frequency, callsign); return Napi::Boolean::New(info.Env(), false); @@ -204,7 +219,7 @@ Napi::Boolean SetFrequencyState(const Napi::CallbackInfo& info) newState.headset = !info[4].As().Value(); newState.xca = info[5].As().Value(); // Not used - SetGuardAndUnicomTransceivers(); + // SetGuardAndUnicomTransceivers(); auto result = RadioHelper::SetRadioState(MainThreadShared::mApiServer, newState); return Napi::Boolean::New(info.Env(), result); @@ -450,11 +465,10 @@ void HandleAfvEvents(afv_native::ClientEventType eventType, void* data, void* da for (const auto& state : states) { if (state.second.stationName == station) { mClient->UseTransceiversFromStation(station, static_cast(state.first)); - SetGuardAndUnicomTransceivers(); break; } } - + SetGuardAndUnicomTransceivers(); NapiHelpers::callElectron( "StationTransceiversUpdated", station, std::to_string(transceiverCount)); } diff --git a/fast-afv-update.sh b/fast-afv-update.sh new file mode 100755 index 00000000..2d86ecf0 --- /dev/null +++ b/fast-afv-update.sh @@ -0,0 +1,4 @@ +cd backend/extern/afv-native +git pull origin $1 +cd ../../../ +npm run build:backend-fast diff --git a/package-lock.json b/package-lock.json index 684d28ad..3a86a7c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-BwnUSTSS0r0Lqr7uCVzua1ul9aIcx9iPYBI1WSuZEJAPLJw62cHkMu6kIbzYQ37p9B5/xwrEgN4tVdmXyXldDg==", + "integrity": "sha512-49sKaQgGK4FHbb+M8ncn187e1rwqBAc3SfZHtUo7pSLg2DJvzJddQTWXXMEeSASXLP1Shz9GpUBKEPY16iV4PQ==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", diff --git a/src/renderer/src/components/bootstrap.tsx b/src/renderer/src/components/bootstrap.tsx index 34eb70ad..8b4416c7 100644 --- a/src/renderer/src/components/bootstrap.tsx +++ b/src/renderer/src/components/bootstrap.tsx @@ -4,7 +4,6 @@ import useErrorStore from '../store/errorStore'; import useSessionStore from '../store/sessionStore'; import useUtilStore from '../store/utilStore'; import { StationStateUpdate } from '../interfaces/StationStateUpdate'; -import { GuardFrequency, UnicomFrequency } from '../../../shared/common'; const Bootsrap: React.FC = () => { useEffect(() => { @@ -105,15 +104,6 @@ const Bootsrap: React.FC = () => { if (useSessionStore.getState().isAtc) { void window.api.GetStation(useSessionStore.getState().stationCallsign); } - - void window.api.addFrequency(UnicomFrequency, 'UNICOM'); - void window.api.addFrequency(GuardFrequency, 'GUARD'); - useRadioState - .getState() - .addRadio(UnicomFrequency, 'UNICOM', 'UNICOM'); - useRadioState - .getState() - .addRadio(GuardFrequency, 'GUARD', 'GUARD'); }); window.api.on('VoiceDisconnected', () => { diff --git a/src/renderer/src/components/radio/unicom-guard.tsx b/src/renderer/src/components/radio/unicom-guard.tsx index b5494e99..ab37d5a9 100644 --- a/src/renderer/src/components/radio/unicom-guard.tsx +++ b/src/renderer/src/components/radio/unicom-guard.tsx @@ -7,8 +7,16 @@ import useErrorStore from '@renderer/store/errorStore'; import { GuardFrequency, UnicomFrequency } from '../../../../shared/common'; const UnicomGuardBar = () => { - const [radios, setRadioState] = useRadioState((state) => [state.radios, state.setRadioState]); - const [isNetworkConnected, isAtc] = useSessionStore((state) => [state.isNetworkConnected, state.isAtc]); + const [radios, setRadioState, addRadio, removeRadio] = useRadioState((state) => [ + state.radios, + state.setRadioState, + state.addRadio, + state.removeRadio + ]); + const [isConnected, isAtc] = useSessionStore((state) => [ + state.isConnected, + state.isAtc + ]); const [localRadioGain, setLocalRadioGain] = useState(50); @@ -16,11 +24,11 @@ const UnicomGuardBar = () => { const unicom = useMemo(() => { return radios.find((radio) => radio.frequency === UnicomFrequency); - }, [radios]); + }, [radios, isConnected]); const guard = useMemo(() => { return radios.find((radio) => radio.frequency === GuardFrequency); - }, [radios]); + }, [radios, isConnected]); const clickRx = (radio: RadioType | undefined) => { if (!radio) return; @@ -114,13 +122,46 @@ const UnicomGuardBar = () => { }); }; + useEffect(() => { + if (!isConnected) { + void window.api.removeFrequency(UnicomFrequency).then((ret) => { + if (!ret) { + return; + } + removeRadio(UnicomFrequency); + }); + void window.api.removeFrequency(GuardFrequency).then((ret) => { + if (!ret) { + return; + } + removeRadio(GuardFrequency); + }); + } else { + void window.api.addFrequency(UnicomFrequency, 'UNICOM').then((ret) => { + if (!ret) { + console.error('Failed to add UNICOM frequency'); + return; + } + console.log('Adding unicom frequency'); + addRadio(UnicomFrequency, 'UNICOM', 'UNICOM'); + void window.api.SetFrequencyRadioGain(UnicomFrequency, localRadioGain / 100); + }); + void window.api.addFrequency(GuardFrequency, 'GUARD').then((ret) => { + if (!ret) { + console.error('Failed to add GUARD frequency'); + return; + } + console.log('Adding guard frequency'); + addRadio(GuardFrequency, 'GUARD', 'GUARD'); + void window.api.SetFrequencyRadioGain(GuardFrequency, localRadioGain / 100); + }); + } + }, [isConnected]); + useEffect(() => { const storedGain = window.localStorage.getItem('unicomRadioGain'); const gainToSet = storedGain?.length ? parseInt(storedGain) : 50; - setLocalRadioGain(storedGain?.length ? parseInt(storedGain) : 50); - console.log('Setting unicom radio gain to', gainToSet); - void window.api.SetFrequencyRadioGain(UnicomFrequency, gainToSet / 100); - void window.api.SetFrequencyRadioGain(GuardFrequency, gainToSet / 100); + setLocalRadioGain(gainToSet); }, []); const updateRadioGainValue = (newGain: number) => { @@ -130,7 +171,6 @@ const UnicomGuardBar = () => { .then(() => { void window.api.SetFrequencyRadioGain(guard.frequency, newGain / 100); setLocalRadioGain(newGain); - console.log('Setting unicom radio gain to', newGain); }) .catch((err: unknown) => { console.error(err); @@ -162,7 +202,7 @@ const UnicomGuardBar = () => { unicom?.rx && unicom.currentlyRx && 'btn-warning', unicom?.rx && !unicom.currentlyRx && 'btn-success' )} - disabled={!isNetworkConnected || !unicom} + disabled={!isConnected || !unicom} onClick={() => { clickRx(unicom); }} @@ -176,7 +216,7 @@ const UnicomGuardBar = () => { unicom?.tx && unicom.currentlyTx && 'btn-warning', unicom?.tx && !unicom.currentlyTx && 'btn-success' )} - disabled={!isNetworkConnected || !unicom || !isAtc} + disabled={!isConnected || !unicom || !isAtc} onClick={() => { clickTx(unicom); }} @@ -190,7 +230,7 @@ const UnicomGuardBar = () => { !unicom?.onSpeaker && 'btn-info', unicom?.onSpeaker && 'btn-success' )} - disabled={!isNetworkConnected || !unicom} + disabled={!isConnected || !unicom} onClick={() => { clickSpK(unicom); }} @@ -211,7 +251,7 @@ const UnicomGuardBar = () => { guard?.rx && guard.currentlyRx && 'btn-warning', guard?.rx && !guard.currentlyRx && 'btn-success' )} - disabled={!isNetworkConnected || !guard} + disabled={!isConnected || !guard} onClick={() => { clickRx(guard); }} @@ -225,7 +265,7 @@ const UnicomGuardBar = () => { guard?.tx && guard.currentlyTx && 'btn-warning', guard?.tx && !guard.currentlyTx && 'btn-success' )} - disabled={!isNetworkConnected || !guard || !isAtc} + disabled={!isConnected || !guard || !isAtc} onClick={() => { clickTx(guard); }} @@ -239,7 +279,7 @@ const UnicomGuardBar = () => { !guard?.onSpeaker && 'btn-info', guard?.onSpeaker && 'btn-success' )} - disabled={!isNetworkConnected || !guard} + disabled={!isConnected || !guard} onClick={() => { clickSpK(guard); }} From 9e6df8782e5b1526c701fca738caee68c52e44fa Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Sat, 26 Oct 2024 21:25:31 +0200 Subject: [PATCH 8/9] Fix missing callsign in kStationStateUpdate websocket message --- backend/include/RadioHelper.hpp | 15 ++++++++++----- backend/src/main.cpp | 2 +- backend/src/sdk.cpp | 2 +- package-lock.json | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/backend/include/RadioHelper.hpp b/backend/include/RadioHelper.hpp index 5d861212..7410ff0d 100644 --- a/backend/include/RadioHelper.hpp +++ b/backend/include/RadioHelper.hpp @@ -1,9 +1,9 @@ #pragma once +#include "Helpers.hpp" #include "Shared.hpp" #include "sdk.hpp" -#include "Helpers.hpp" - +#include class RadioState { public: @@ -24,7 +24,8 @@ class RadioHelper { * @return true The state was set successfully * @return false The state was not set successfully */ - static bool SetRadioState(const std::shared_ptr& mApiServer, const RadioState& newState) + static bool SetRadioState(const std::shared_ptr& mApiServer, const RadioState& newState, + const std::string& stationCallsign = "") { if (!mClient->IsVoiceConnected()) { TRACK_LOG_TRACE("Voice is not connected, not setting radio state"); @@ -57,6 +58,7 @@ class RadioHelper { mClient->SetOnHeadset(newState.frequency, newState.headset); + if (!oldRxValue && newState.rx) { // When turning on RX, we refresh the transceivers auto states = mClient->getRadioState(); @@ -67,10 +69,13 @@ class RadioHelper { } // Included for legacy reasons in case some older client depends on this message. - mApiServer->handleAFVEventForWebsocket(sdk::types::Event::kFrequencyStateUpdate, {}, {}); + mApiServer->handleAFVEventForWebsocket( + sdk::types::Event::kFrequencyStateUpdate, std::nullopt, std::nullopt); // New event that only notifies of the change to this specific station. - auto stateJson = mApiServer->buildStationStateJson(std::nullopt, newState.frequency); + std::optional optionalCallsign + = stationCallsign.empty() ? std::nullopt : std::optional(stationCallsign); + auto stateJson = mApiServer->buildStationStateJson(optionalCallsign, newState.frequency); mApiServer->publishStationState(stateJson); NapiHelpers::callElectron("station-state-update", stateJson.dump()); diff --git a/backend/src/main.cpp b/backend/src/main.cpp index ebb6b2d5..3f424d74 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -184,7 +184,7 @@ Napi::Boolean AddFrequency(const Napi::CallbackInfo& info) newState.headset = true; newState.xca = false; - auto result = RadioHelper::SetRadioState(MainThreadShared::mApiServer, newState); + auto result = RadioHelper::SetRadioState(MainThreadShared::mApiServer, newState, callsign); return Napi::Boolean::New(info.Env(), result); } diff --git a/backend/src/sdk.cpp b/backend/src/sdk.cpp index dc28f633..1ddd6aa8 100644 --- a/backend/src/sdk.cpp +++ b/backend/src/sdk.cpp @@ -57,7 +57,6 @@ void SDK::handleVoiceConnectedEventForWebsocket(bool isVoiceConnected) jsonMessage["value"]["connected"] = isVoiceConnected; this->broadcastOnWebsocket(jsonMessage.dump()); - return; } // NOLINTNEXTLINE @@ -330,6 +329,7 @@ void SDK::handleGetStationStates() // Collect the states for all the radios auto allRadios = mClient->getRadioState(); + stationStates.reserve(allRadios.size()); for (const auto& [frequency, state] : allRadios) { stationStates.push_back(this->buildStationStateJson(state.stationName, frequency)); } diff --git a/package-lock.json b/package-lock.json index 3a86a7c3..e04ecfd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-49sKaQgGK4FHbb+M8ncn187e1rwqBAc3SfZHtUo7pSLg2DJvzJddQTWXXMEeSASXLP1Shz9GpUBKEPY16iV4PQ==", + "integrity": "sha512-t9hCFLaLN7zRt9Q2FTvMoZrVIDyVAGOdAy7RjwgOD4SdzmUeZ2W0ttwREzcyEsXkZZEDa1RcP4E2rbthpV2f0w==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0", From 3cbf83591856788f9459a23c71e8e999f68eadc3 Mon Sep 17 00:00:00 2001 From: Pierre Ferran Date: Sat, 26 Oct 2024 21:59:02 +0200 Subject: [PATCH 9/9] Fix deadlock on exit --- backend/include/Helpers.hpp | 4 +++- backend/src/main.cpp | 6 ++++-- package-lock.json | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/include/Helpers.hpp b/backend/include/Helpers.hpp index b90c0e45..5a142024 100644 --- a/backend/include/Helpers.hpp +++ b/backend/include/Helpers.hpp @@ -96,7 +96,8 @@ class NapiHelpers { static void callElectron( const std::string& eventName, const std::string& data = "", const std::string& data2 = "") { - if (!NapiHelpers::callbackAvailable || NapiHelpers::callbackRef == nullptr) { + if (!NapiHelpers::callbackAvailable || NapiHelpers::callbackRef == nullptr + || NapiHelpers::_requestExit.load()) { return; } @@ -113,4 +114,5 @@ class NapiHelpers { static void sendErrorToElectron(const std::string& message) { callElectron("error", message); } inline static std::mutex _callElectronMutex; + inline static std::atomic _requestExit = false; }; diff --git a/backend/src/main.cpp b/backend/src/main.cpp index 3f424d74..540ac484 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -739,16 +739,18 @@ Napi::Object Bootstrap(const Napi::CallbackInfo& info) Napi::Boolean Exit(const Napi::CallbackInfo& info) { TRACK_LOG_INFO("Awaiting to exit TrackAudio..."); - std::lock_guard HelperLock(NapiHelpers::_callElectronMutex); - TRACK_LOG_INFO("Exiting TrackAudio...") + NapiHelpers::_requestExit.store(true); if (mClient->IsVoiceConnected()) { + TRACK_LOG_INFO("Forcing disconnect..."); mClient->Disconnect(); } + MainThreadShared::mApiServer.reset(); MainThreadShared::mRemoteDataHandler.reset(); MainThreadShared::inputHandler.reset(); mClient.reset(); + TRACK_LOG_INFO("Exiting TrackAudio...") LogFactory::destroyLoggers(); return Napi::Boolean::New(info.Env(), true); diff --git a/package-lock.json b/package-lock.json index e04ecfd2..4a729d26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8218,7 +8218,7 @@ "node_modules/trackaudio-afv": { "version": "1.0.0", "resolved": "file:backend/trackaudio-afv-1.0.0.tgz", - "integrity": "sha512-t9hCFLaLN7zRt9Q2FTvMoZrVIDyVAGOdAy7RjwgOD4SdzmUeZ2W0ttwREzcyEsXkZZEDa1RcP4E2rbthpV2f0w==", + "integrity": "sha512-v8yOyEKXGA1HLz5JqE0UN0DjqS0wIulCMT8cwkIf5g1J0pPT7wTy1jkZ2KRLm7EXHatxVxdtxLTBW7xsPBkF/Q==", "license": "GPL-3.0-only", "dependencies": { "bindings": "^1.5.0",