diff --git a/.vscode/launch.json b/.vscode/launch.json index 80d59ea..c1a7e43 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -47,7 +47,9 @@ "/etc/pipedal/config", "/etc/pipedal/react", "-port", - "0.0.0.0:8080" + "0.0.0.0:8080", + "-shutdownPort", + "3147" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/react/CMakeLists.txt b/react/CMakeLists.txt index 2569431..34a816d 100644 --- a/react/CMakeLists.txt +++ b/react/CMakeLists.txt @@ -21,89 +21,88 @@ add_custom_command( WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react DEPENDS - src/NumericInput.tsx - src/RenameDialog.tsx - src/ToobInputStageView.tsx - src/ToobCabSimView.tsx - src/SearchFilter.tsx - src/Draggable.tsx - src/SelectHoverBackground.tsx - src/JackStatusView.tsx - src/SplitControlView.tsx - src/MidiBindingsDialog.tsx - src/WifiChannel.tsx - src/PluginControlView.tsx - src/TemporaryDrawer.tsx - src/logo.svg - src/AlsaDeviceInfo.tsx - src/JackServerSettingsDialog.tsx - src/ZoomedUiControl.tsx - src/PedalBoardView.tsx - src/MainPage.tsx - src/App.tsx - src/AppThemed.tsx - src/PresetDialog.tsx - src/PluginPreset.tsx - src/react-app-env.d.ts - src/ToobSpectrumResponseView.tsx - src/SettingsDialog.tsx - src/SelectChannelsDialog.tsx - src/StringBuilder.tsx - src/ToobPowerStage2View.tsx src/SearchControl.tsx - src/XxxSnippet.tsx - src/Units.tsx - src/Jack.tsx - src/PluginControl.tsx - src/ResizeResponsiveComponent.tsx - src/Lv2Plugin.tsx - src/index.tsx - src/NoChangePassword.tsx - src/VuMeter.tsx - src/ControlViewFactory.tsx - src/JackServerSettings.tsx - src/MidiBinding.tsx - src/UploadDialog.tsx - src/PiPedalSocket.tsx - src/PedalBoard.tsx - src/JackHostStatus.tsx - src/ZoomedDial.tsx - src/Rect.tsx - src/SvgPathBuilder.tsx - src/IControlViewFactory.tsx - src/PiPedalModel.tsx - src/PluginInfoDialog.tsx - src/reportWebVitals.ts - src/PiPedalError.tsx - src/BankDialog.tsx - src/PluginIcon.tsx - src/WifiConfigDialog.tsx - src/PresetSelector.tsx - src/LoadPluginDialog.tsx - src/ToobFrequencyResponseView.tsx + src/SplitControlView.tsx src/WifiConfigSettings.tsx - src/setupTests.ts - src/AboutDialog.tsx - src/index.css - src/Banks.tsx - src/DialogEx.tsx - src/ToobWaveShapeView.tsx src/FullScreenIME.tsx - src/AppThemed.css + src/JackHostStatus.tsx + src/SearchFilter.tsx + src/MainPage.tsx + src/PiPedalModel.tsx src/PluginPresetSelector.tsx - src/Utility.tsx - src/SelectMidiChannelsDialog.tsx - src/App.test.tsx - src/MidiBindingView.tsx - src/SplitUiControls.tsx - src/PluginClass.tsx - src/ObservableProperty.tsx src/Pedal.tsx - src/GxTunerControl.tsx - src/DraggableGrid.tsx + src/ToobMLView.tsx + src/SettingsDialog.tsx + src/PiPedalSocket.tsx + src/ToobFrequencyResponseView.tsx + src/ControlViewFactory.tsx + src/Utility.tsx src/ToobToneStackView.tsx + src/SelectHoverBackground.tsx + src/MidiBindingsDialog.tsx + src/TemporaryDrawer.tsx + src/GxTunerView.tsx + src/ToobInputStageView.tsx + src/DraggableGrid.tsx + src/JackStatusView.tsx + src/PluginControl.tsx + src/XxxSnippet.tsx + src/PluginPreset.tsx + src/Jack.tsx + src/AlsaDeviceInfo.tsx + src/MidiBinding.tsx + src/PiPedalError.tsx + src/IControlViewFactory.tsx + src/Lv2Plugin.tsx + src/PedalBoard.tsx + src/PedalBoardView.tsx + src/PresetDialog.tsx + src/AppThemed.tsx + src/ZoomedDial.tsx + src/RenameDialog.tsx + src/SplitUiControls.tsx + src/GovernorSettings.tsx + src/AboutDialog.tsx + src/ToobSpectrumResponseView.tsx + src/PluginControlView.tsx + src/NoChangePassword.tsx src/ToobSpectrumAnalyzerView.tsx - + src/MidiBindingView.tsx + src/GxTunerControl.tsx + src/Banks.tsx + src/PluginPresetsDialog.tsx + src/BankDialog.tsx + src/UploadDialog.tsx + src/ToobPowerStage2View.tsx + src/index.tsx + src/Units.tsx + src/VuMeter.tsx + src/App.test.tsx + src/DialogEx.tsx + src/StringBuilder.tsx + src/SelectChannelsDialog.tsx + src/Draggable.tsx + src/ZoomedUiControl.tsx + src/Rect.tsx + src/PluginClass.tsx + src/PluginIcon.tsx + src/App.tsx + src/ResizeResponsiveComponent.tsx + src/SvgPathBuilder.tsx + src/NumericInput.tsx + src/JackServerSettings.tsx + src/ListSelectDialog.tsx + src/JackServerSettingsDialog.tsx + src/ObservableProperty.tsx + src/SelectMidiChannelsDialog.tsx + src/WifiChannel.tsx + src/LoadPluginDialog.tsx + src/WifiConfigDialog.tsx + src/PluginInfoDialog.tsx + src/ToobWaveShapeView.tsx + src/ToobCabSimView.tsx + src/PresetSelector.tsx + public/css/roboto.css public/fonts/Roboto-BlackItalic.woff2 public/fonts/Roboto-Black.woff2 diff --git a/react/public/var/config.json b/react/public/var/config.json index 93f81dd..f4d9e48 100644 --- a/react/public/var/config.json +++ b/react/public/var/config.json @@ -1,6 +1,6 @@ { "socket_server_port": 8080, - "socket_server_address": "*", + "socket_server_address": "[fe80::df30:41b9:1ede:c23c]", "max_upload_size": 1048576, diff --git a/react/src/GovernorSettings.tsx b/react/src/GovernorSettings.tsx new file mode 100644 index 0000000..6190491 --- /dev/null +++ b/react/src/GovernorSettings.tsx @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +export default class GovernorSettings { + deserialize(input: any) : GovernorSettings{ + this.governors = input.governors; + this.governor = input.governor; + return this; + } + clone(): GovernorSettings + { + return new GovernorSettings().deserialize(this); + } + governors: string[] = ["ondemand","performance"]; + governor: string = "performance"; + +} \ No newline at end of file diff --git a/react/src/JackHostStatus.tsx b/react/src/JackHostStatus.tsx index 70c3184..2efb61d 100644 --- a/react/src/JackHostStatus.tsx +++ b/react/src/JackHostStatus.tsx @@ -93,10 +93,10 @@ export default class JackHostStatus { { (status.cpuFreqMax === status.cpuFreqMin)? ( - {fmtCpuFreq(status.cpuFreqMax)} {status.governor} + {status.governor} {fmtCpuFreq(status.cpuFreqMax)} ) :( - {fmtCpuFreq(status.cpuFreqMax)}-{fmtCpuFreq(status.cpuFreqMax)} {status.governor} + {status.governor} {fmtCpuFreq(status.cpuFreqMax)}-{fmtCpuFreq(status.cpuFreqMax)} ) } diff --git a/react/src/ListSelectDialog.tsx b/react/src/ListSelectDialog.tsx new file mode 100644 index 0000000..05898cb --- /dev/null +++ b/react/src/ListSelectDialog.tsx @@ -0,0 +1,116 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import React from 'react'; +import Button from '@mui/material/Button'; + + +import Radio from '@mui/material/Radio'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; + +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import ResizeResponsiveComponent from './ResizeResponsiveComponent'; + + +export interface ListSelectDialogProps { + open: boolean, + title: string, + width: number, + selectedItem: string, + items: string[], + onOk: (selectedItem: string) => void, + onClose: () => void +}; + +export interface ListSelectDialogState { + fullScreen: boolean; +}; + +export default class ListSelectDialog extends ResizeResponsiveComponent { + + refText: React.RefObject; + + constructor(props: ListSelectDialogProps) { + super(props); + this.state = { + fullScreen: false + }; + this.refText = React.createRef(); + } + mounted: boolean = false; + + + + onWindowSizeChanged(width: number, height: number): void { + this.setState({ fullScreen: height < 200 }) + } + + + componentDidMount() { + super.componentDidMount(); + this.mounted = true; + } + componentWillUnmount() { + super.componentWillUnmount(); + this.mounted = false; + } + + componentDidUpdate() { + } + + render() { + + return ( + this.props.onClose()} open={this.props.open}> + + { + this.props.items.map( + (value: string, index: number) => { + return ( + + this.props.onOk(value)} dense> + + + + + + + ); + } + ) + } + + + + + + ); + } +} \ No newline at end of file diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 01487f2..9fc9da2 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -31,8 +31,9 @@ import { BankIndex } from './Banks'; import JackHostStatus from './JackHostStatus'; import JackServerSettings from './JackServerSettings'; import MidiBinding from './MidiBinding'; -import {PluginUiPresets} from './PluginPreset'; +import { PluginUiPresets } from './PluginPreset'; import WifiConfigSettings from './WifiConfigSettings'; +import GovernorSettings from './GovernorSettings'; import WifiChannel from './WifiChannel'; import AlsaDeviceInfo from './AlsaDeviceInfo'; @@ -316,6 +317,7 @@ export interface PiPedalModel { banks: ObservableProperty; jackServerSettings: ObservableProperty; wifiConfigSettings: ObservableProperty; + governorSettings: ObservableProperty; presets: ObservableProperty; @@ -350,7 +352,7 @@ export interface PiPedalModel { saveCurrentPreset(): void; saveCurrentPresetAs(newName: string): void; - saveCurrentPluginPresetAs(pluginInstanceId: number,newName: string): void; + saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): void; loadPreset(instanceId: number): void; updatePresets(presets: PresetIndex): Promise; @@ -397,7 +399,7 @@ export interface PiPedalModel { addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle removeControlValueChangeListener(handle: ControlValueChangedHandle): void; - addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler) : PluginPresetsChangedHandle; + addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler): PluginPresetsChangedHandle; removePluginPresetsChangedListener(handle: PluginPresetsChangedHandle): void; listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle; @@ -405,12 +407,13 @@ export interface PiPedalModel { - download(targetType: string, isntanceId: number|string): void; + download(targetType: string, isntanceId: number | string): void; - uploadPreset(uploadPage: string,file: File, uploadAfter: number): Promise; + uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise; uploadBank(file: File, uploadAfter: number): Promise; setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise; + setGovernorSettings(governor: string): Promise; getWifiChannels(countryIso3661: string): Promise; @@ -447,6 +450,7 @@ class PiPedalModelImpl implements PiPedalModel { = new ObservableProperty(new JackServerSettings()); wifiConfigSettings: ObservableProperty = new ObservableProperty(new WifiConfigSettings()); + governorSettings: ObservableProperty = new ObservableProperty(new GovernorSettings()); presets: ObservableProperty = new ObservableProperty @@ -575,6 +579,12 @@ class PiPedalModelImpl implements PiPedalModel { } else if (message === "onWifiConfigSettingsChanged") { let wifiConfigSettings = new WifiConfigSettings().deserialize(body); this.wifiConfigSettings.set(wifiConfigSettings); + } + else if (message === "onGovernorSettingsChanged") { + let governor = body as string; + let newSettings = this.governorSettings.get().clone(); + newSettings.governor = governor; + this.governorSettings.set(newSettings); } else if (message === "onBanksChanged") { let banks = new BankIndex().deserialize(body); this.banks.set(banks); @@ -660,6 +670,12 @@ class PiPedalModelImpl implements PiPedalModel { .then(data => { this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data)); + return this.getWebSocket().request("getGovernorSettings"); + }) + .then(data => { + this.governorSettings.set(new GovernorSettings().deserialize(data)); + + return this.getWebSocket().request("getJackServerSettings"); }) .then(data => { @@ -792,6 +808,11 @@ class PiPedalModelImpl implements PiPedalModel { .then(data => { this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data)); + return this.getWebSocket().request("getGovernorSettings"); + }) + .then(data => { + this.governorSettings.set(new GovernorSettings().deserialize(data)); + return this.getWebSocket().request("getJackServerSettings"); }) @@ -994,7 +1015,7 @@ class PiPedalModelImpl implements PiPedalModel { _pluginPresetsChangedHandles: PluginPresetsChangedHandle[] = []; - addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler) : PluginPresetsChangedHandle { + addPluginPresetsChangedListener(onPluginPresetsChanged: PluginPresetsChangedHandler): PluginPresetsChangedHandle { let handle = ++this.nextListenHandle; let t: PluginPresetsChangedHandle = { _id: handle, @@ -1003,28 +1024,23 @@ class PiPedalModelImpl implements PiPedalModel { this._pluginPresetsChangedHandles.push(t); return t; } - removePluginPresetsChangedListener(handle: PluginPresetsChangedHandle): void - { + removePluginPresetsChangedListener(handle: PluginPresetsChangedHandle): void { let pos = -1; - for (let i = 0; i < this._pluginPresetsChangedHandles.length; ++i) - { - if (this._pluginPresetsChangedHandles[i]._id === handle._id) - { + for (let i = 0; i < this._pluginPresetsChangedHandles.length; ++i) { + if (this._pluginPresetsChangedHandles[i]._id === handle._id) { pos = i; break; } } - if (pos !== -1) - { - this._pluginPresetsChangedHandles.splice(pos,1); + if (pos !== -1) { + this._pluginPresetsChangedHandles.splice(pos, 1); } } firePluginPresetsChanged(pluginUri: string): void { - for (let i = 0; i < this._pluginPresetsChangedHandles.length; ++i) - { + for (let i = 0; i < this._pluginPresetsChangedHandles.length; ++i) { this._pluginPresetsChangedHandles[i]._handler(pluginUri); } - + } @@ -1298,7 +1314,7 @@ class PiPedalModelImpl implements PiPedalModel { return data; }); } - saveCurrentPluginPresetAs(pluginInstanceId: number,newName: string): Promise { + saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise { // default behaviour is to save after the currently selected preset. let request: any = { instanceId: pluginInstanceId, @@ -1567,22 +1583,21 @@ class PiPedalModelImpl implements PiPedalModel { this.webSocket?.send("loadPluginPreset", { pluginInstanceId: pluginInstanceId, presetInstanceId: presetInstanceId }); } - handlePluginPresetsChanged(pluginUri: string):void { + handlePluginPresetsChanged(pluginUri: string): void { this.uncachePluginPreset(pluginUri); this.firePluginPresetsChanged(pluginUri); } - updatePluginPresets(pluginUri: string, presets: PluginUiPresets): Promise - { + updatePluginPresets(pluginUri: string, presets: PluginUiPresets): Promise { this.presetCache[pluginUri] = presets; this.firePluginPresetsChanged(pluginUri); return nullCast(this.webSocket).request("updatePluginPresets", presets); } - duplicatePluginPreset(pluginUri: string, instanceId: number): Promise - { - return nullCast(this.webSocket).request("copyPluginPreset", { - pluginUri: pluginUri, instanceId: instanceId }); + duplicatePluginPreset(pluginUri: string, instanceId: number): Promise { + return nullCast(this.webSocket).request("copyPluginPreset", { + pluginUri: pluginUri, instanceId: instanceId + }); } @@ -1692,7 +1707,7 @@ class PiPedalModelImpl implements PiPedalModel { window.open(url, "_blank"); } - uploadPreset(uploadPage: string,file: File, uploadAfter: number): Promise { + uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise { let result = new Promise((resolve, reject) => { try { if (file.size > this.maxUploadSize) { @@ -1775,6 +1790,27 @@ class PiPedalModelImpl implements PiPedalModel { }); return result; } + setGovernorSettings(governor: string): Promise { + let newSettings = this.governorSettings.get().clone(); + newSettings.governor = governor; + this.governorSettings.set(newSettings); + + return new Promise((resolve, reject) => { + + let ws = this.webSocket; + if (!ws) return; + ws.request( + "setGovernorSettings", + governor + ) + .then(() => { + resolve(); + }) + .catch((err) => { + reject(err); + }); + }); + } setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise { let result = new Promise((resolve, reject) => { let oldSettings = this.wifiConfigSettings.get(); @@ -1866,8 +1902,7 @@ class PiPedalModelImpl implements PiPedalModel { preloadImages(imageList: string): void { let imageNames = imageList.split(';'); - for (let i = 0; i < imageNames.length; ++i) - { + for (let i = 0; i < imageNames.length; ++i) { let imageName = imageNames[i]; let img = new Image(); img.src = "/img/" + imageName; diff --git a/react/src/SettingsDialog.tsx b/react/src/SettingsDialog.tsx index 28a9fd1..961161c 100644 --- a/react/src/SettingsDialog.tsx +++ b/react/src/SettingsDialog.tsx @@ -18,6 +18,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import React, { SyntheticEvent, Component } from 'react'; +import ListSelectDialog from './ListSelectDialog'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel'; @@ -36,6 +37,7 @@ import JackHostStatus from './JackHostStatus'; import WifiConfigSettings from './WifiConfigSettings'; import WifiConfigDialog from './WifiConfigDialog'; import DialogEx from './DialogEx' +import GovernorSettings from './GovernorSettings'; import Slide, {SlideProps} from '@mui/material/Slide'; import { createStyles, Theme } from '@mui/material/styles'; @@ -55,10 +57,12 @@ interface SettingsDialogState { jackSettings: JackChannelSelection; jackServerSettings: JackServerSettings; jackStatus?: JackHostStatus; + governorSettings: GovernorSettings; wifiConfigSettings: WifiConfigSettings; showWifiConfigDialog: boolean; + showGovernorSettingsDialog: boolean; showInputSelectDialog: boolean; showOutputSelectDialog: boolean; showMidiSelectDialog: boolean; @@ -146,7 +150,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( jackStatus: undefined, jackSettings: this.model.jackSettings.get(), wifiConfigSettings: this.model.wifiConfigSettings.get(), + governorSettings: this.model.governorSettings.get(), showWifiConfigDialog: false, + showGovernorSettingsDialog: false, showInputSelectDialog: false, showOutputSelectDialog: false, showMidiSelectDialog: false, @@ -160,6 +166,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this); this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this); this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this); + this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this); this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this); } @@ -182,6 +189,15 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.model.showAlert(err); }); } + handleApplyGovernorSettings(governor: string): void { + this.model.setGovernorSettings(governor) + .then(() => { + + }) + .catch((err) => { + this.model.showAlert(err); + }); + } handleWifiConfigSettingsChanged(): void { this.setState( @@ -190,6 +206,14 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( } ) } + handleGovernorSettingsChanged(): void { + this.setState( + { + governorSettings: this.model.governorSettings.get() + } + ) + } + handleJackSettingsChanged(): void { this.setState({ jackSettings: this.model.jackSettings.get() @@ -233,6 +257,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged); this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged); + this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged); this.model.getJackStatus() .then((jackStatus) => this.setState( @@ -261,6 +286,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged); this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged); this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged); + this.model.wifiConfigSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged); } } @@ -314,6 +340,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( let newSelection = this.state.jackSettings.clone(); newSelection.inputMidiPorts = channels; this.model.setJackSettings(newSelection); + } this.setState({ showMidiSelectDialog: false, @@ -358,6 +385,11 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( showWifiConfigDialog: true }); } + handleShowGovernorSettingsDialogDialog() { + this.setState({ + showGovernorSettingsDialog: true + }); + } handleRestart() { this.setState({ restarting: true }); this.model.restart() @@ -417,19 +449,19 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( >
- AUDIO + STATUS {(!isConfigValid) ? (
Status: Not configured. - CPU: + Governor:
) : (
- {JackHostStatus.getDisplayView("Status:\u00A0", this.state.jackStatus)} - {JackHostStatus.getCpuInfo("CPU:\u00A0", this.state.jackStatus)} + {JackHostStatus.getDisplayView("", this.state.jackStatus)} + { JackHostStatus.getCpuInfo("Governor:\u00A0", this.state.jackStatus)}
) } @@ -442,6 +474,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( ) } + + AUDIO + this.handleJackServerSettings()} > @@ -512,6 +547,18 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
+ this.handleShowGovernorSettingsDialogDialog()} > + +
+ + CPU Governor + + {this.state.governorSettings.governor } + + +
+
this.handleRestart()} > @@ -556,6 +603,32 @@ const SettingsDialog = withStyles(styles, { withTheme: true })( ) } + { + (this.state.showGovernorSettingsDialog) && + ( + this.setState({showGovernorSettingsDialog: false})} + onOk={(selectedValue) => { + this.model.setGovernorSettings(selectedValue) + .then(()=>{ + + }) + .catch(error => { + this.model.showAlert(error); + }); + + this.setState({showGovernorSettingsDialog: false}) + }} + > + + ) + + } { (this.state.showMidiSelectDialog) && ( diff --git a/react/tmp.txt b/react/tmp.txt index f04cd76..247c309 100644 --- a/react/tmp.txt +++ b/react/tmp.txt @@ -1,83 +1,81 @@ -src -src/NumericInput.tsx -src/RenameDialog.tsx -src/ToobInputStageView.tsx -src/ToobCabSimView.tsx -src/SearchFilter.tsx -src/Draggable.tsx -src/SelectHoverBackground.tsx -src/JackStatusView.tsx -src/SplitControlView.tsx -src/MidiBindingsDialog.tsx -src/WifiChannel.tsx -src/PluginControlView.tsx -src/TemporaryDrawer.tsx -src/LoadPluginDialogBackup.tsx -src/logo.svg -src/AlsaDeviceInfo.tsx -src/JackServerSettingsDialog.tsx -src/ZoomedUiControl.tsx -src/PedalBoardView.tsx -src/MainPage.tsx -src/App.tsx -src/PresetDialog.tsx -src/PluginPreset.tsx -src/react-app-env.d.ts -src/ToobSpectrumResponseView.tsx -src/SettingsDialog.tsx -src/SelectChannelsDialog.tsx -src/StringBuilder.tsx -src/ToobPowerStage2View.tsx src/SearchControl.tsx -src/XxxSnippet.tsx -src/Units.tsx -src/Jack.tsx -src/PluginControl.tsx -src/ResizeResponsiveComponent.tsx -src/Lv2Plugin.tsx -src/index.tsx -src/NoChangePassword.tsx -src/VuMeter.tsx -src/ControlViewFactory.tsx -src/JackServerSettings.tsx -src/MidiBinding.tsx -src/UploadDialog.tsx -src/PiPedalSocket.tsx -src/PedalBoard.tsx -src/JackHostStatus.tsx -src/ZoomedDial.tsx -src/Rect.tsx -src/SvgPathBuilder.tsx -src/IControlViewFactory.tsx -src/PiPedalModel.tsx -src/PluginInfoDialog.tsx -src/reportWebVitals.ts -src/PiPedalError.tsx -src/BankDialog.tsx -src/PluginIcon.tsx -src/WifiConfigDialog.tsx -src/PresetSelector.tsx -src/LoadPluginDialog.tsx -src/ToobFrequencyResponseView.tsx +src/SplitControlView.tsx src/WifiConfigSettings.tsx -src/setupTests.ts -src/AboutDialog.tsx -src/index.css -src/Banks.tsx -src/DialogEx.tsx -src/ToobWaveShapeView.tsx src/FullScreenIME.tsx -src/App.css +src/JackHostStatus.tsx +src/SearchFilter.tsx +src/MainPage.tsx +src/PiPedalModel.tsx src/PluginPresetSelector.tsx -src/Utility.tsx -src/SelectMidiChannelsDialog.tsx -src/App.test.tsx -src/MidiBindingView.tsx -src/SplitUiControls.tsx -src/PluginClass.tsx -src/ObservableProperty.tsx src/Pedal.tsx -src/GxTunerControl.tsx -src/DraggableGrid.tsx +src/ToobMLView.tsx +src/SettingsDialog.tsx +src/PiPedalSocket.tsx +src/ToobFrequencyResponseView.tsx +src/ControlViewFactory.tsx +src/Utility.tsx src/ToobToneStackView.tsx +src/SelectHoverBackground.tsx +src/MidiBindingsDialog.tsx +src/TemporaryDrawer.tsx +src/GxTunerView.tsx +src/ToobInputStageView.tsx +src/DraggableGrid.tsx +src/JackStatusView.tsx +src/PluginControl.tsx +src/XxxSnippet.tsx +src/PluginPreset.tsx +src/Jack.tsx +src/AlsaDeviceInfo.tsx +src/MidiBinding.tsx +src/PiPedalError.tsx +src/IControlViewFactory.tsx +src/Lv2Plugin.tsx +src/PedalBoard.tsx +src/PedalBoardView.tsx +src/PresetDialog.tsx +src/AppThemed.tsx +src/ZoomedDial.tsx +src/RenameDialog.tsx +src/SplitUiControls.tsx +src/GovernorSettings.tsx +src/AboutDialog.tsx +src/ToobSpectrumResponseView.tsx +src/PluginControlView.tsx +src/NoChangePassword.tsx src/ToobSpectrumAnalyzerView.tsx +src/MidiBindingView.tsx +src/GxTunerControl.tsx +src/Banks.tsx +src/PluginPresetsDialog.tsx +src/BankDialog.tsx +src/UploadDialog.tsx +src/ToobPowerStage2View.tsx +src/index.tsx +src/Units.tsx +src/VuMeter.tsx +src/App.test.tsx +src/DialogEx.tsx +src/StringBuilder.tsx +src/SelectChannelsDialog.tsx +src/Draggable.tsx +src/ZoomedUiControl.tsx +src/Rect.tsx +src/PluginClass.tsx +src/PluginIcon.tsx +src/App.tsx +src/ResizeResponsiveComponent.tsx +src/SvgPathBuilder.tsx +src/NumericInput.tsx +src/JackServerSettings.tsx +src/ListSelectDialog.tsx +src/JackServerSettingsDialog.tsx +src/ObservableProperty.tsx +src/SelectMidiChannelsDialog.tsx +src/WifiChannel.tsx +src/LoadPluginDialog.tsx +src/WifiConfigDialog.tsx +src/PluginInfoDialog.tsx +src/ToobWaveShapeView.tsx +src/ToobCabSimView.tsx +src/PresetSelector.tsx diff --git a/src/BeastServer.cpp b/src/BeastServer.cpp index a720a9b..1c3daa8 100644 --- a/src/BeastServer.cpp +++ b/src/BeastServer.cpp @@ -49,6 +49,40 @@ pipedal::last_modified(const std::filesystem::path &path) } } +static std::string getHostName() +{ + char buff[512]; + if (gethostname(buff,sizeof(buff)) == 0) + { + buff[511] = '\0'; + return buff; + } + return ""; +} + +static std::string getIpv4Address(const std::string interface) +{ + int fd = -1; + struct ifreq ifr; + memset(&ifr,0,sizeof(ifr)); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + + /* I want to get an IPv4 IP address */ + ifr.ifr_addr.sa_family = AF_INET; + + /* I want an IP address attached to "eth0" */ + strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ-1); + + int result = ioctl(fd, SIOCGIFADDR, &ifr); + if (result == -1) return ""; + + + close(fd); + char *name = inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); + if (name == nullptr) return ""; + return name; +} static std::map extensionsToMimeType = { {".htm", "text/html; charset=UTF-8"}, {".html", "text/html; charset=UTF-8"}, @@ -192,7 +226,8 @@ namespace pipedal webSocket(webSocket) { uri requestUri(webSocket->get_uri()->str().c_str()); - fromAddress = requestUri.authority(); + fromAddress = SS(webSocket->get_socket().remote_endpoint()); + auto pFactory = pServer->GetSocketFactory(requestUri); if (!pFactory) @@ -364,11 +399,14 @@ namespace pipedal con->set_status(websocketpp::http::status_code::ok); return; } + for (auto requestHandler : this->request_handlers) { if (requestHandler->wants(req.method(), requestUri)) { + std::string fromAddress = SS(con->get_remote_endpoint()); + try { if (req.method() == HttpVerb::head) @@ -377,7 +415,7 @@ namespace pipedal res.set(HttpField::date, HtmlHelper::timeToHttpDate(time(nullptr))); res.set(HttpField::access_control_allow_origin, origin); - requestHandler->head_response(requestUri, req, res, ec); + requestHandler->head_response(fromAddress,requestUri, req, res, ec); res.keepAlive(req.keepAlive()); if (ec == std::errc::no_such_file_or_directory) { @@ -401,7 +439,7 @@ namespace pipedal res.set(HttpField::cache_control, "max-age: 31104000"); // cache for a year. - requestHandler->get_response(requestUri, req, res, ec); + requestHandler->get_response(fromAddress,requestUri, req, res, ec); res.keepAlive(req.keepAlive()); if (ec == std::errc::no_such_file_or_directory) @@ -425,7 +463,7 @@ namespace pipedal res.set(HttpField::date, HtmlHelper::timeToHttpDate(time(nullptr))); res.set(HttpField::access_control_allow_origin, origin); - requestHandler->post_response(requestUri, req, res, ec); + requestHandler->post_response(fromAddress,requestUri, req, res, ec); if (ec == std::errc::no_such_file_or_directory) { @@ -547,15 +585,28 @@ namespace pipedal m_endpoint.set_close_handler(bind(&BeastServerImpl::on_close, this, _1)); m_endpoint.set_http_handler(bind(&BeastServerImpl::on_http, this, _1)); + std::string hostName = getHostName(); + if (hostName.length() != 0) { std::stringstream ss; - ss << "Listening on " << this->address << ":" << this->port; + ss << "Listening on " << hostName << ".local:" << this->port; Lv2Log::info(ss.str()); } + std::string ipv4Address = getIpv4Address("eth0"); + if (ipv4Address.length() != 0) + { + Lv2Log::info(SS("Listening on " << ipv4Address << ":" << this->port)); + } + std::string wifiAddress = getIpv4Address("wlan0"); + if (wifiAddress.length() != 0) + { + Lv2Log::info(SS("Listening on Wi-Fi address " << wifiAddress << ":" << this->port)); + } std::stringstream ss; ss << port; - m_endpoint.listen(this->address, ss.str()); + // m_endpoint.listen(this->address, ss.str()); + m_endpoint.listen(tcp::v6(),(uint16_t)port); m_endpoint.start_accept(); // Start IOC service threads. diff --git a/src/BeastServer.hpp b/src/BeastServer.hpp index 11f3945..3f3602b 100644 --- a/src/BeastServer.hpp +++ b/src/BeastServer.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include @@ -164,12 +164,34 @@ public: HttpResponse &res, std::error_code &ec) = 0; + virtual void head_response( + const std::string&fromAddress, + const uri&request_uri, + const HttpRequest &req, + HttpResponse &res, + std::error_code &ec) { + head_response(request_uri,req,res,ec); + } + + + virtual void get_response( const uri&request_uri, const HttpRequest &req, HttpResponse &res, std::error_code &ec) = 0; + virtual void get_response( + const std::string&fromAddress, + const uri&request_uri, + const HttpRequest &req, + HttpResponse &res, + std::error_code &ec) + { + get_response(request_uri,req,res,ec); + } + + virtual void post_response( const uri&request_uri, const HttpRequest &req, @@ -178,6 +200,17 @@ public: { get_response(request_uri,req,res,ec); } + virtual void post_response( + const std::string&fromAddress, + const uri&request_uri, + const HttpRequest &req, + HttpResponse &res, + std::error_code &ec) + { + post_response( + request_uri,req,res,ec); + } + }; class BeastServer { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 41d394d..4921510 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -102,7 +102,10 @@ add_custom_command( set (PIPEDAL_SOURCES - PluginPreset.cpp PluginPreset.hpp + Ipv6Helpers.cpp Ipv6Helpers.hpp + PluginPreset.cpp PluginPreset.hpp + CpuGovernor.cpp CpuGovernor.hpp + GovernorSettings.cpp GovernorSettings.hpp SysExec.cpp SysExec.hpp BeastServer.cpp BeastServer.hpp HtmlHelper.cpp HtmlHelper.hpp pch.h Uri.cpp Uri.hpp WifiConfigSettings.hpp WifiConfigSettings.cpp @@ -233,6 +236,7 @@ add_executable(pipedalshutdownd ShutdownMain.cpp CommandLineParser.hpp SetWifiConfig.hpp SetWifiConfig.cpp SystemConfigFile.hpp SystemConfigFile.cpp SysExec.hpp SysExec.cpp + CpuGovernor.cpp CpuGovernor.hpp asan_options.cpp ) target_link_libraries(pipedalshutdownd PRIVATE pthread atomic stdc++fs systemd) diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index 67c98e5..ae413e2 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -650,7 +650,7 @@ int main(int argc, char **argv) << " (only valid with the -install option). Usually determined " << endl << " automatically." << endl << endl - << " --uninstall Remove installed services and service accounts." << endl + << " --uninstall Remove installed services." << endl << endl << " --enable Start the pipedal service at boot time." << endl << endl @@ -668,10 +668,11 @@ int main(int argc, char **argv) << " web server will listen on 0.0.0.0 (any)." << endl << endl << " --enable_ap " << endl - << " Enable the Wi-Fi access point. is the 2-letter " << endl - << " ISO-3166 country code for the country in which you are currently" << endl - << " located. The country code determines which channels may be legally" << endl - << " used in (and which features need to be enabled) in order to use a " << endl + << " Enable the Wi-Fi access point. is the " << endl + << " 2-letter ISO-3166 country code for the country in " << endl + << " which you are currently located. The country code " << endl + << " determines which channels may be legally used in (and " << endl + << " which features need to be enabled) in order to use a " << endl << " Wi-Fi channel in your legislative regime." << endl << endl << " --disable_ap Disabled the Wi-Fi access point." << endl diff --git a/src/CpuGovernor.cpp b/src/CpuGovernor.cpp new file mode 100644 index 0000000..d74db5d --- /dev/null +++ b/src/CpuGovernor.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "CpuGovernor.hpp" +#include "ss.hpp" +#include +#include +#include "PiPedalException.hpp" +#include + +using namespace pipedal; + +#ifdef __WIN32__ + +// not a feature on Win32. +void pipedal::SetCpuGovernor(const std::string &governor) { } +std::vector pipedal::GetAvailableGovernors() { return std::vector(); } + +#else + +static const int SYSFS_RETRIES = 3; + + +std::string pipedal::GetCpuGovernor() +{ + std::string result; + try { + std::ifstream f("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); + f >> result; + } catch (const std::exception &) + { + + } + return result; + +} + +static bool writeAndVerify(std::filesystem::path&sysfsPath, const std::string &value) +{ + // return if the value has already been set. + { + std::ifstream f; + f.open(sysfsPath); + if (f.is_open()) + { + std::string line; + std::getline(f,line); + if (!f.fail() && value == line) + { + return true; + } + } + } + for (int i = 0; i < SYSFS_RETRIES; ++i) + { + { + // write the value. + // write the value. + std::ofstream f; + f.open(sysfsPath); + if (f.is_open()) + { + f << value; + } else { + std::cout << SS("Can't open " << sysfsPath << " for writing.") << std::endl; + sleep(1); + continue; + } + } + // verify that we wrote it + { + std::ifstream f; + f.open(sysfsPath); + if (f.is_open()) + { + std::string line; + std::getline(f,line); + if (value == line) + { + return true; + } + std::cout << "Read value: " << line << "Expected value: " << value << std::endl; + } + std::cout << "Failed to update " << sysfsPath << std::endl; + } + sleep(1); + } + return false; +} + +void pipedal::SetCpuGovernor(const std::string &governor) { + // using sysfs + int nCpu = 0; + + while (true) + { + std::filesystem::path base = SS("/sys/devices/system/cpu/cpu" << nCpu); + if (!std::filesystem::is_directory(base)) + break; + + std::filesystem::path sysFsPath = SS("/sys/devices/system/cpu/cpu" << nCpu << "/cpufreq/scaling_governor"); + + if (!writeAndVerify(sysFsPath,governor)) + { + throw PiPedalException(SS("Write to " << sysFsPath << " failed.")); + break; + } + ++nCpu; + } +} +std::vector pipedal::GetAvailableGovernors() { + return std::vector { + "performance", + "ondemand", + "powersave" + }; +} + + +#endif + + + void SetCpuGovernor(const std::string &governor); + + std::vector GetAvailableGovernors(); diff --git a/src/CpuGovernor.hpp b/src/CpuGovernor.hpp new file mode 100644 index 0000000..6e282bf --- /dev/null +++ b/src/CpuGovernor.hpp @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#pragma once +#include +#include + +namespace pipedal { + std::string GetCpuGovernor(); + + void SetCpuGovernor(const std::string &governor); + + std::vector GetAvailableGovernors(); +}; \ No newline at end of file diff --git a/src/GovernorSettings.cpp b/src/GovernorSettings.cpp new file mode 100644 index 0000000..981772b --- /dev/null +++ b/src/GovernorSettings.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "GovernorSettings.hpp" + +using namespace pipedal; + +JSON_MAP_BEGIN(GovernorSettings) +JSON_MAP_REFERENCE(GovernorSettings, governor) +JSON_MAP_REFERENCE(GovernorSettings, governors) +JSON_MAP_END() diff --git a/src/GovernorSettings.hpp b/src/GovernorSettings.hpp new file mode 100644 index 0000000..f3f5126 --- /dev/null +++ b/src/GovernorSettings.hpp @@ -0,0 +1,36 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#pragma once + +#include "json.hpp" +#include +#include + +namespace pipedal { + + class GovernorSettings { + public: + std::string governor_; + std::vector governors_; + + DECLARE_JSON_MAP(GovernorSettings); + }; + +} \ No newline at end of file diff --git a/src/Ipv6Helpers.cpp b/src/Ipv6Helpers.cpp new file mode 100644 index 0000000..f347426 --- /dev/null +++ b/src/Ipv6Helpers.cpp @@ -0,0 +1,360 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "Ipv6Helpers.hpp" +#include +#include +#include +#include "ss.hpp" +#include +#include +#include +#include +#include + +using namespace pipedal; + +std::string pipedal::GetLinkLocalAddress(const std::string fromAddress); + +static bool IsIpv4MappedAddress(const struct in6_addr &inetAddr6) +{ + return IN6_IS_ADDR_V4MAPPED(&inetAddr6) != 0; +} +static uint32_t GetIpv4MappedAddress(const struct in6_addr &inetAddr6) +{ + if (!IsIpv4MappedAddress(inetAddr6)) + { + return 0; + } + uint8_t *p = (uint8_t *)&inetAddr6; + return htonl(*(uint32_t *)(p + 12)); +} +static bool ipv6NetmaskCompare(const struct in6_addr &left, const struct in6_addr &right, const struct in6_addr &mask) +{ + uint8_t *pLeft = (uint8_t *)&left; + uint8_t *pRight = (uint8_t *)&right; + uint8_t *pMask = (uint8_t *)&mask; + for (int i = 0; i < 16; ++i) + { + if ((pLeft[i] & pMask[i]) != (pRight[i] & pMask[i])) + { + return false; + } + } + return true; +} + +static bool IsIpv4OnLocalSubnet(uint32_t ipv4Addres) +{ + bool result = false; + struct ifaddrs *ifap = nullptr; + if (getifaddrs(&ifap) != 0) + return false; + + for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next) + { + if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr) + { // TODO: Add support for AF_INET6 + uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr); + uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr); + + if ((netmask & ifAddr) == (netmask & ipv4Addres)) + { + result = true; + break; + } + } + } + freeifaddrs(ifap); + return result; +} + +static std::string StripPortNumber(const std::string &fromAddress) +{ + std::string address = fromAddress; + + if (address.size() == 0) + return fromAddress; + + char lastChar = address[address.size() - 1]; + size_t pos = address.find_last_of(':'); + + // if ipv6, make sure we found an actual port address. + size_t posBracket = address.find_last_of(']'); + if (posBracket != std::string::npos && pos != std::string::npos) + { + if (posBracket > pos) + { + pos = std::string::npos; + } + } + if (pos != std::string::npos) + { + address = address.substr(0, pos); + } + return address; +} + +bool pipedal::IsOnLocalSubnet(const std::string &fromAddress) +{ + std::string address = StripPortNumber(fromAddress); + if (address.length() == 0) + return false; + + bool result = false; + if (address[0] != '[') + { + // ipv4 + struct in_addr inetAddr; + memset(&inetAddr, 0, sizeof(inetAddr)); + + if (inet_pton(AF_INET, address.c_str(), &inetAddr) == 1) + { + uint32_t remoteAddress = htonl(inetAddr.s_addr); + + result = IsIpv4OnLocalSubnet(remoteAddress); + } + } + else + { + // ipv6 + if (address[0] != '[' || address[address.length() - 1] != ']') + return false; + address = address.substr(1, address.length() - 2); + // strip scope if neccessary. + auto pos = address.find_last_of('%'); + if (pos != std::string::npos) + { + address = address.substr(0, pos); + } + + struct in6_addr inetAddr6; + memset(&inetAddr6, 0, sizeof(inetAddr6)); + if (inet_pton(AF_INET6, address.c_str(), &inetAddr6) == 1) + { + int32_t remoteAddress = -1; + // cases: + // [::FFFF:ipv4 address] + // [FE80:: ] link local. + // [FEC0:: ] site local. + // others? + if (IsIpv4MappedAddress(inetAddr6)) + { + uint32_t remoteAddress = GetIpv4MappedAddress(inetAddr6); + return IsIpv4OnLocalSubnet(remoteAddress); + } + if (IN6_IS_ADDR_LINKLOCAL(&inetAddr6)) + { + return true; + } + if (IN6_IS_ADDR_SITELOCAL(&inetAddr6)) + { + return true; + } + return false; +#if JUNK + // if the address is global, we have to reject it. + // no point in going further. + struct ifaddrs *ifap = nullptr; + if (getifaddrs(&ifap) != 0) + return false; + + const int SITE_LOCAL_SCOPE = 5; + + for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next) + { + if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr) + { + + struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr); + struct sockaddr_in6 *pNetMask = (struct sockaddr_in6 *)(p->ifa_netmask); + if (pAddr->sin6_scope_id <= SITE_LOCAL_SCOPE) + { + if (ipv6NetmaskCompare(inetAddr6, pAddr->sin6_addr, pNetMask->sin6_addr)) + { + result = true; + break; + } + } + } + } + freeifaddrs(ifap); +#endif + } + } + return result; +} + +static std::string GetInterfaceForIp4Address(uint32_t ipv4Address) +{ + struct ifaddrs *ifap = nullptr; + if (getifaddrs(&ifap) != 0) + return ""; + std::string result; + for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next) + { + if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr) + { // TODO: Add support for AF_INET6 + uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr); + uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr); + + if ((netmask & ifAddr) == (netmask & ipv4Address)) + { + result = p->ifa_name; + break; + } + } + } + freeifaddrs(ifap); + return result; +} + +static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6) +{ + struct ifaddrs *ifap = nullptr; + if (getifaddrs(&ifap) != 0) + return ""; + std::string result; + for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next) + { + if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr) + { // TODO: Add support for AF_INET6 + struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr); + struct sockaddr_in6 *pNetMask = (struct sockaddr_in6 *)(p->ifa_netmask); + if (ipv6NetmaskCompare(inetAddr6, pAddr->sin6_addr, pNetMask->sin6_addr)) + { + result = p->ifa_name; + break; + } + } + } + freeifaddrs(ifap); + return result; +} + +static std::string GetLinkLocalAddressForInterface(const std::string &name) +{ + struct ifaddrs *ifap = nullptr; + if (getifaddrs(&ifap) != 0) + return ""; + std::string result; + for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next) + { + if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr) + { // TODO: Add support for AF_INET6 + struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr); + if (IN6_IS_ADDR_LINKLOCAL(&(pAddr->sin6_addr))) + { + if (name.length() == 0 || name == p->ifa_name) + { + const int BUFSIZE = 128; + char host[BUFSIZE]; + if (getnameinfo(p->ifa_addr, sizeof(struct sockaddr_in6), host, BUFSIZE, NULL, 0, NI_NUMERICHOST) == 0) + { + host[BUFSIZE - 1] = '\0'; + + // trim the interface spec if present + for (char *p = host; *p != 0; ++p) + { + if (*p == '%') + { + *p = '\0'; + break; + } + } + result = SS('[' << host << ']'); + break; + } + } + } + } + } + freeifaddrs(ifap); + return result; +} + +std::string pipedal::GetLinkLocalAddress(const std::string fromAddress) +{ + std::string address = StripPortNumber(fromAddress); + + std::string result; + if (address[0] != '[') + { + // ipv4 + struct in_addr inetAddr; + memset(&inetAddr, 0, sizeof(inetAddr)); + + if (inet_pton(AF_INET, address.c_str(), &inetAddr) == 1) + { + uint32_t remoteAddress = htonl(inetAddr.s_addr); + std::string interfaceName = GetInterfaceForIp4Address(remoteAddress); + result = GetLinkLocalAddressForInterface(interfaceName); + } + } + else + { + // ipv6 + if (address[0] != '[' || address[address.length() - 1] != ']') + throw std::invalid_argument("Bad address."); + address = address.substr(1, address.length() - 2); + // strip scope if neccessary. + auto pos = address.find_last_of('%'); + if (pos != std::string::npos) + { + address = address.substr(0, pos); + } + + struct in6_addr inetAddr6; + memset(&inetAddr6, 0, sizeof(inetAddr6)); + if (inet_pton(AF_INET6, address.c_str(), &inetAddr6) == 1) + { + int32_t remoteAddress = -1; + // cases: + // [::FFFF:ipv4 address] + // [FE80:: ] link local. + // [FEC0:: ] site local. + // others? + if (IN6_IS_ADDR_LINKLOCAL(&inetAddr6)) + { + return address; + } + if (IN6_IS_ADDR_SITELOCAL(&inetAddr6)) + { + return address; + } + + std::string interfaceName; + if (IsIpv4MappedAddress(inetAddr6)) + { + uint32_t remoteAddress = GetIpv4MappedAddress(inetAddr6); + interfaceName = GetInterfaceForIp4Address(remoteAddress); + } + else + { + interfaceName = GetInterfaceForIp6Address(inetAddr6); + } + result = GetLinkLocalAddressForInterface(interfaceName); + } + } + if (result == "") + { + result = GetLinkLocalAddressForInterface(""); + } + return result; +} diff --git a/src/Ipv6Helpers.hpp b/src/Ipv6Helpers.hpp new file mode 100644 index 0000000..db8f3ac --- /dev/null +++ b/src/Ipv6Helpers.hpp @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#pragma once + + +namespace pipedal { + + + std::string GetLinkLocalAddress(const std::string fromAddress); + + bool IsOnLocalSubnet(const std::string&fromAddress); + + +} \ No newline at end of file diff --git a/src/JackHost.cpp b/src/JackHost.cpp index 79430a8..8be6efb 100644 --- a/src/JackHost.cpp +++ b/src/JackHost.cpp @@ -33,6 +33,7 @@ using namespace pipedal; #include #include #include "VuUpdate.hpp" +#include "CpuGovernor.hpp" #include "RingBuffer.hpp" #include "RingBufferReader.hpp" @@ -99,15 +100,7 @@ static void GetCpuFrequency(uint64_t*freqMin,uint64_t*freqMax) } static std::string GetGovernor() { - std::string result; - try { - std::ifstream f("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); - f >> result; - } catch (const std::exception &) - { - - } - return result; + return pipedal::GetCpuGovernor(); } diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 6e2659d..f91649f 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -26,6 +26,7 @@ #include "PiPedalConfiguration.hpp" #include "ShutdownClient.hpp" #include "SplitEffect.hpp" +#include "CpuGovernor.hpp" #ifndef NO_MLOCK #include @@ -76,14 +77,31 @@ void PiPedalModel::Close() PiPedalModel::~PiPedalModel() { - CurrentPreset currentPreset; - currentPreset.modified_ = this->hasPresetChanged; - currentPreset.preset_ = this->pedalBoard; - storage.SaveCurrentPreset(currentPreset); - - if (jackHost) + try { + ShutdownClient::UnmonitorGovernor(); + } catch (...) // noexcept here! { - jackHost->Close(); + + } + + try { + CurrentPreset currentPreset; + currentPreset.modified_ = this->hasPresetChanged; + currentPreset.preset_ = this->pedalBoard; + storage.SaveCurrentPreset(currentPreset); + } catch (...) + { + + } + + try { + if (jackHost) + { + jackHost->Close(); + } + } catch (...) + { + } } @@ -134,7 +152,7 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration) this->webRoot = configuration.GetWebRoot(); this->jackServerSettings.ReadJackConfiguration(); - + ShutdownClient::MonitorGovernor(storage.GetGovernorSettings()); // lv2Host.Load(configuration.GetLv2Path().c_str()); @@ -594,6 +612,39 @@ bool PiPedalModel::RenamePreset(int64_t clientId, int64_t instanceId, const std: } } +GovernorSettings PiPedalModel::GetGovernorSettings() +{ + { + std::lock_guard guard(mutex); + GovernorSettings result; + result.governor_ = storage.GetGovernorSettings(); + result.governors_ = pipedal::GetAvailableGovernors(); + return result; + } +} +void PiPedalModel::SetGovernorSettings(const std::string& governor) +{ + std::lock_guard guard(mutex); + ShutdownClient::SetGovernorSettings(governor); + + this->storage.SetGovernorSettings(governor); + + IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; + { + for (size_t i = 0; i < subscribers.size(); ++i) + { + t[i] = this->subscribers[i]; + } + size_t n = this->subscribers.size(); + for (size_t i = 0; i < n; ++i) + { + t[i]->OnGovernorSettingsChanged(governor); + } + delete[] t; + } + +} + void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings) { std::lock_guard guard(mutex); diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 2cd2ee5..79aa3ba 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -20,6 +20,7 @@ #pragma once #include #include "Lv2Host.hpp" +#include "GovernorSettings.hpp" #include "PedalBoard.hpp" #include "Storage.hpp" #include "Banks.hpp" @@ -57,6 +58,7 @@ public: virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0; virtual void OnNotifyAtomOutput(int64_t clientModel,uint64_t instanceId,const std::string&atomType, const std::string&atomJson) = 0; virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) = 0; + virtual void OnGovernorSettingsChanged(const std::string &governor) = 0; virtual void Close() = 0; }; @@ -193,6 +195,9 @@ public: void SetWifiConfigSettings(const WifiConfigSettings&wifiConfigSettings); WifiConfigSettings GetWifiConfigSettings(); + + void SetGovernorSettings(const std::string& governor); + GovernorSettings GetGovernorSettings(); diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index 34012e8..2624d5a 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -28,6 +28,7 @@ #include "JackConfiguration.hpp" #include #include +#include "Ipv6Helpers.hpp" #include "ShutdownClient.hpp" #include "WifiConfigSettings.hpp" @@ -804,6 +805,17 @@ public: this->Reply(replyTo,"setJackserverSettings"); } + else if (message == "setGovernorSettings") { + std::string governor; + pReader->read(&governor); + std::string fromAddress = this->getFromAddress(); + if (!IsOnLocalSubnet(fromAddress)) + { + throw PiPedalException("Permission denied. Not on local subnet."); + } + this->model.SetGovernorSettings(governor); + this->Reply(replyTo,"setGovernorSettings"); + } else if (message == "setWifiConfigSettings") { WifiConfigSettings wifiConfigSettings; pReader->read(&wifiConfigSettings); @@ -812,7 +824,7 @@ public: throw PiPedalException("Can't change server settings when running interactively."); } std::string fromAddress = this->getFromAddress(); - if (!ShutdownClient::IsOnLocalSubnet(fromAddress)) + if (!IsOnLocalSubnet(fromAddress)) { throw PiPedalException("Permission denied. Not on local subnet."); } @@ -824,6 +836,10 @@ public: else if (message == "getWifiConfigSettings") { this->Reply(replyTo, "getWifiConfigSettings", model.GetWifiConfigSettings()); + } + else if (message == "getGovernorSettings") { + this->Reply(replyTo, "getGovernorSettings", model.GetGovernorSettings()); + } else if (message == "getJackServerSettings") { @@ -1461,6 +1477,9 @@ public: virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) { Send("onWifiConfigSettingsChanged",wifiConfigSettings); } + virtual void OnGovernorSettingsChanged(const std::string& governor) { + Send("onGovernorSettingsChanged",governor); + } virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) { diff --git a/src/ShutdownClient.cpp b/src/ShutdownClient.cpp index be6cd96..5de4b36 100644 --- a/src/ShutdownClient.cpp +++ b/src/ShutdownClient.cpp @@ -27,6 +27,7 @@ #include #include #include +#include "Ipv6Helpers.hpp" using namespace pipedal; @@ -165,57 +166,12 @@ bool ShutdownClient::SetJackServerConfiguration(const JackServerSettings & jackS return WriteMessage(s.str().c_str()); } -bool ShutdownClient::IsOnLocalSubnet(const std::string&fromAddress) -{ - std::string address = fromAddress; - if (address.size() == 0) return false; - char lastChar = address[address.size()-1]; - if (address[0] != '[' || lastChar != ']') // i.e. not an ipv6 address; - { - size_t pos = address.find_last_of(':'); - if (pos != std::string::npos) - { - address = address.substr(0,pos); - } - } - if (address[0] == '[') return false; - - char buf[512]; - - struct in_addr inetAddr; - - memset(&inetAddr,0,sizeof(inetAddr)); - - if (inet_pton(AF_INET,address.c_str(),&inetAddr) == 0) { - return false; - } - uint32_t remoteAddress = htonl(inetAddr.s_addr); - - struct ifaddrs *ifap = nullptr; - if (getifaddrs(&ifap) != 0) return false; - - bool result = false; - for (ifaddrs*p = ifap; p != nullptr; p = p->ifa_next) { - if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr) { // TODO: Add support for AF_INET6 - uint32_t netmask = htonl(((sockaddr_in*)(p->ifa_netmask))->sin_addr.s_addr); - uint32_t ifAddr = htonl(((sockaddr_in*)(p->ifa_addr))->sin_addr.s_addr); - - if ((netmask & ifAddr) == (netmask & remoteAddress)) - { - result = true; - break; - } - } - } - freeifaddrs(ifap); - return result; -} void ShutdownClient::SetWifiConfig(const WifiConfigSettings & settings) { if (!CanUseShutdownClient()) { - throw PiPedalException("Can't use ShutdownClient when running interactively."); + throw PiPedalException("Can't perform this operation when debugging."); } std::stringstream cmd; cmd << "WifiConfigSettings "; @@ -227,3 +183,53 @@ void ShutdownClient::SetWifiConfig(const WifiConfigSettings & settings) throw PiPedalException("Operation failed."); } } + +void ShutdownClient::SetGovernorSettings(const std::string & settings) +{ + if (!CanUseShutdownClient()) + { + throw PiPedalException("Can't use ShutdownClient when running interactively."); + } + std::stringstream cmd; + cmd << "GovernorSettings "; + json_writer writer(cmd,true); + writer.write(settings); + cmd << '\n'; + bool result = WriteMessage(cmd.str().c_str()); + if (!result) { // unexpected. Should throw exception on failure. + throw PiPedalException("Operation failed."); + } +} + +void ShutdownClient::MonitorGovernor(const std::string &governor) +{ + if (!CanUseShutdownClient()) + { + return; + } + std::stringstream cmd; + cmd << "MonitorGovernor "; + json_writer writer(cmd,true); + writer.write(governor); + cmd << '\n'; + bool result = WriteMessage(cmd.str().c_str()); + if (!result) { // unexpected. Should throw exception on failure. + throw PiPedalException("Operation failed."); + } + +} +void ShutdownClient::UnmonitorGovernor() +{ + if (!CanUseShutdownClient()) + { + return; + } + std::stringstream cmd; + cmd << "UnmonitorGovernor"; + cmd << '\n'; + bool result = WriteMessage(cmd.str().c_str()); + if (!result) { // unexpected. Should throw exception on failure. + throw PiPedalException("Operation failed."); + } + +} diff --git a/src/ShutdownClient.hpp b/src/ShutdownClient.hpp index 18f752a..d28dd2e 100644 --- a/src/ShutdownClient.hpp +++ b/src/ShutdownClient.hpp @@ -25,14 +25,17 @@ namespace pipedal { + class ShutdownClient { static bool WriteMessage(const char*message); public: static bool CanUseShutdownClient(); static bool RequestShutdown(bool restart); static bool SetJackServerConfiguration(const JackServerSettings & jackServerSettings); - static bool IsOnLocalSubnet(const std::string&fromAddress); static void SetWifiConfig(const WifiConfigSettings & settings); + static void SetGovernorSettings(const std::string & governor); + static void MonitorGovernor(const std::string &governor); + static void UnmonitorGovernor(); }; } // namespace \ No newline at end of file diff --git a/src/ShutdownMain.cpp b/src/ShutdownMain.cpp index 558ebca..4f52dce 100644 --- a/src/ShutdownMain.cpp +++ b/src/ShutdownMain.cpp @@ -23,6 +23,7 @@ #include "pch.h" #include "CommandLineParser.hpp" +#include "CpuGovernor.hpp" #include #include #include @@ -40,7 +41,10 @@ #include "SetWifiConfig.hpp" #include "SysExec.hpp" #include - +#include +#include +#include +#include using namespace std; using namespace pipedal; @@ -50,15 +54,122 @@ using ip::tcp; const size_t MAX_LENGTH = 128; - - -static bool startsWith(const std::string&s, const char*text) +class GovernorMonitorThread { - if (s.length() < strlen(text)) return false; - const char*sp = s.c_str(); +public: + ~GovernorMonitorThread() + { + Stop(); + } + void Start(std::string governor) + { + std::unique_lock lock(mutex); + if (!pThread) + { + this->governor = governor; + cancelled = false; + wake = false; + pThread = std::make_unique([this]() + { this->ServiceProc(); }); + } else { + this->governor = governor; + wake = true; + + lock.unlock(); + cv.notify_one(); + } + } + void Stop() + { + std::unique_lock lock(mutex); + if (pThread) + { + wake = true; + cancelled = true; + lock.unlock(); + + cv.notify_one(); + pThread->join(); + pThread.reset(); + } + } + void SetGovernor(const std::string &governor) + { + { + std::unique_lock lock(mutex); + wake = true; + this->governor = governor; + } + cv.notify_one(); + } + +private: + bool wake = false; + bool cancelled = false; + std::string savedGovernor; + void ServiceProc() + { + savedGovernor = pipedal::GetCpuGovernor(); + pipedal::SetCpuGovernor(this->governor); + while (true) + { + bool cancelled; + std::string governor; + { + std::unique_lock lock(mutex); + auto timeToWaitFor = std::chrono::system_clock::now() + 5000ms; + cv.wait_until( + lock, + timeToWaitFor, + [this]() + { return wake; }); + wake = false; + governor = this->governor; + cancelled = this->cancelled; + } + if (cancelled) { + break; + } + std::string activeGovernor = pipedal::GetCpuGovernor(); + if (activeGovernor != governor) + { + // somebody set it so they must have wanted it. + // save the value so that we can restore it when done. + savedGovernor = activeGovernor; + + // but insist on using ours!! + pipedal::SetCpuGovernor(governor); + } + } + pipedal::SetCpuGovernor(savedGovernor); + } + std::unique_ptr pThread; + std::string governor; + std::mutex mutex; + bool canceled; + std::condition_variable cv; +}; + +GovernorMonitorThread governorMonitorThread; + +void StopGovernorMonitorThread() +{ + governorMonitorThread.Stop(); +} +void StartGovernorMonitorThread(const std::string&governor) +{ + governorMonitorThread.Start(governor); +} + +static bool startsWith(const std::string &s, const char *text) +{ + if (s.length() < strlen(text)) + return false; + const char *sp = s.c_str(); while (*text) { - if (*text++ != *sp++) return false; + if (*text++ != *sp++) + return false; } return true; } @@ -67,7 +178,7 @@ static std::vector tokenize(std::string value) std::vector result; std::stringstream s(value); std::string item; - while (std::getline(s,item,' ')) + while (std::getline(s, item, ' ')) { if (item.length() != 0) result.push_back(item); @@ -75,26 +186,17 @@ static std::vector tokenize(std::string value) return result; } -static void CaptureAccessPoint(const std::string gatewayAddress) -{ - -} -static void ReleaseAccessPoint(const std::string gatewayAddress) -{ -} - -void delayedRestartProc() +static void delayedRestartProc() { sleep(1); // give a chance for websocket messages to propagate. Lv2Log::error("Delayed Restart"); std::string pipedalConfigPath = std::filesystem::path(getSelfExePath()).parent_path() / "pipedalconfig"; std::stringstream s; - s << pipedalConfigPath.c_str() << " --restart --excludeShutdownService"; - if (::system(s.str().c_str())!= EXIT_SUCCESS) + s << pipedalConfigPath.c_str() << " --restart --excludeShutdownService"; + if (::system(s.str().c_str()) != EXIT_SUCCESS) { Lv2Log::error("Delayed Restart failed."); - } } @@ -110,8 +212,6 @@ bool setJackConfiguration(JackServerSettings serverSettings) std::thread delayedRestartThread(delayedRestartProc); delayedRestartThread.detach(); return true; - - } class Reader : public std::enable_shared_from_this @@ -162,7 +262,7 @@ private: return; } socket.async_write_some( - boost::asio::buffer(response.c_str()+ writePosition, response.length()-writePosition), + boost::asio::buffer(response.c_str() + writePosition, response.length() - writePosition), boost::bind(&Reader::HandleWrite, shared_from_this(), boost::asio::placeholders::error, @@ -185,7 +285,7 @@ private: if (c == '\n') { std::string command = message.str(); - cout << "Received command: " << command << endl; + cout << command << endl; HandleCommand(command); socket.close(); return; @@ -202,59 +302,88 @@ private: socket.close(); } } - void HandleCommand(const std::string &s) + void HandleCommand(const std::string &text) { int result = -1; - try { - if (startsWith(s,"WifiConfigSettings ")) + std::string command; + std::string args; + auto pos = text.find_first_of(' '); + if (pos == std::string::npos) + { + command = text; + } + else + { + command = text.substr(0, pos); + args = text.substr(pos + 1); + } + try + { + if (command == "UnmonitorGovernor") { - std::string json = s.substr(strlen("WifiConfigSettings ")); - std::stringstream ss(json); + StopGovernorMonitorThread(); + result = 0; + } + else if (command == "MonitorGovernor") + { + std::stringstream ss(args); + std::string governor; + try + { + json_reader reader(ss); + reader.read(&governor); + } + catch (const std::exception &e) + { + throw PiPedalArgumentException("Invalid arguments."); + } + StartGovernorMonitorThread(governor); + result = 0; + } + else if (command == "GovernorSettings") + { + std::stringstream ss(args); + std::string governor; + try + { + json_reader reader(ss); + reader.read(&governor); + } + catch (const std::exception &e) + { + throw PiPedalArgumentException("Invalid arguments."); + } + governorMonitorThread.SetGovernor(governor); + result = 0; + } + else if (command == "WifiConfigSettings ") + { + std::stringstream ss(args); WifiConfigSettings settings; - try { + try + { json_reader reader(ss); reader.read(&settings); - } catch (const std::exception &e) + } + catch (const std::exception &e) { throw PiPedalArgumentException("Invalid arguments."); } pipedal::SetWifiConfig(settings); result = 0; - - } else if (startsWith(s,"release_ap ")) - { - std::vector argv = tokenize(s); - if (argv.size() == 2) { - ReleaseAccessPoint(argv[1]); - result = 0; - } else { - throw PiPedalArgumentException("Invalid arguments."); - } - - } else if (startsWith(s,"capture_ap ")) - { - std::vector argv = tokenize(s); - if (argv.size() == 2) { - CaptureAccessPoint(argv[1]); - result = 0; - } else { - throw PiPedalArgumentException("Invalid arguments."); - } - - - } else if (s == "shutdown") + } + else if (command == "shutdown") { result = sysExec("/usr/sbin/shutdown -P now"); } - else if (s == "restart") + else if (command == "restart") { result = sysExec("/usr/sbin/shutdown -r now"); - } else if (startsWith(s,"setJackConfiguration ")) + } + else if (command == "setJackConfiguration") { - auto remainder = s.substr(strlen("setJackConfiguration ")); - - std::stringstream input(remainder); + std::stringstream input(args); JackServerSettings serverSettings; json_reader reader(input); @@ -263,12 +392,18 @@ private: if (input.fail()) { result = -1; - } else { - result = setJackConfiguration(serverSettings) ? 0: -1; } - + else + { + result = setJackConfiguration(serverSettings) ? 0 : -1; + } } - } catch (const std::exception &e) + else + { + throw std::invalid_argument(SS("Unknown command:" << command)); + } + } + catch (const std::exception &e) { std::stringstream t; t << "-2 " << e.what() << "\n"; @@ -284,7 +419,6 @@ private: this->writePosition = 0; WriteSome(); } - }; class Server @@ -314,16 +448,16 @@ private: } public: - //constructor for accepting connection from client + // constructor for accepting connection from client Server(boost::asio::io_service &io_service, const tcp::endpoint &endpoint) : acceptor_(io_service, endpoint), - io_service(io_service) + io_service(io_service) { StartAccept(); } - ~Server() { - + ~Server() + { } }; @@ -332,29 +466,26 @@ void runServer(uint16_t port) asio::ip::tcp::endpoint endpoint(ip::make_address_v4("127.0.0.1"), port); asio::io_service ios; - try { - Server server(ios,endpoint); + try + { + Server server(ios, endpoint); ios.run(); cout << "Processing terminated." << endl; - } catch (const std::exception & e) + } + catch (const std::exception &e) { cout << "Error: " << e.what() << endl; } } - int main(int argc, char **argv) { Lv2Log::set_logger(MakeLv2SystemdLogger()); CommandLineParser parser; uint16_t port = 0; - std::string captureAccessPoint; - std::string releaseAccessPoint; parser.AddOption("-port", &port); - parser.AddOption("-captureAP",&captureAccessPoint); - parser.AddOption("-releaseAP",&releaseAccessPoint); try { @@ -369,34 +500,12 @@ int main(int argc, char **argv) cout << "Error: " << e.what() << endl; return EXIT_FAILURE; } - if (captureAccessPoint.length() != 0) + if (port == 0) { - try{ - CaptureAccessPoint(captureAccessPoint); - - } catch (const std::exception &e) - { - std::cerr << "Error: " << e.what() << std::endl; - return EXIT_FAILURE; - } - std::cout << "Access point captured." << std::endl; - } else if (releaseAccessPoint.length() != 0) - { - try{ - ReleaseAccessPoint(releaseAccessPoint); - } catch (const std::exception &e) - { - std::cerr << "Error: " << e.what() << std::endl; - return EXIT_FAILURE; - } - std::cout << "Access point released." << std::endl; - - } else { - if (port == 0) { - std::cerr << "Error: port not specified." << std::endl; - return EXIT_FAILURE; - } - runServer(port); + std::cerr << "Error: port not specified." << std::endl; + return EXIT_FAILURE; } + runServer(port); + governorMonitorThread.Stop(); return EXIT_SUCCESS; } diff --git a/src/Storage.cpp b/src/Storage.cpp index 7edac3b..69a038c 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -32,6 +32,7 @@ const char *BANK_EXTENSION = ".bank"; const char *BANKS_FILENAME = "index.banks"; #define WIFI_CONFIG_SETTINGS_FILENAME "wifiConfigSettings.json"; +#define GOVERNOR_SETTINGS_FILENAME "governorSettings.json"; Storage::Storage() { @@ -182,6 +183,7 @@ void Storage::Initialize() { } LoadWifiConfigSettings(); + LoadGovernorSettings(); } @@ -802,6 +804,25 @@ int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter) return lastBank; } +void Storage::SetGovernorSettings(const std::string & governor) +{ + std::filesystem::path path = this->dataRoot / GOVERNOR_SETTINGS_FILENAME; + { + std::ofstream f(path); + if (!f.is_open()) + { + throw PiPedalException("Unable to write to " + ((std::string)path)); + } + json_writer writer(f); + writer.write(governor); + this->governorSettings = governor; + } +} +std::string Storage::GetGovernorSettings() const +{ + return this->governorSettings; +} + void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings) { @@ -839,7 +860,30 @@ void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSetting } this->wifiConfigSettings = copyToStore; } +void Storage::LoadGovernorSettings() +{ + std::filesystem::path path = this->dataRoot / GOVERNOR_SETTINGS_FILENAME; + try { + if (std::filesystem::is_regular_file(path)) + { + std::ifstream f(path); + if (!f.is_open()) + { + throw PiPedalException("Unable to write to " + ((std::string)path)); + } + json_reader reader(f); + std::string governorSettings; + reader.read(&governorSettings); + this->governorSettings = governorSettings; + } + } catch (const std::exception&) + { + this->governorSettings = "performance"; + } + this->wifiConfigSettings.valid_ = true; + +} void Storage::LoadWifiConfigSettings() { std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME; @@ -1137,6 +1181,7 @@ uint64_t Storage::CopyPluginPreset(const std::string&pluginUri,uint64_t presetId } + JSON_MAP_BEGIN(CurrentPreset) JSON_MAP_REFERENCE(CurrentPreset,modified) JSON_MAP_REFERENCE(CurrentPreset,preset) diff --git a/src/Storage.hpp b/src/Storage.hpp index e086b09..cb41567 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -72,6 +72,7 @@ private: bool isJackChannelSelectionValid = false; JackChannelSelection jackChannelSelection; WifiConfigSettings wifiConfigSettings; + std::string governorSettings = "performance"; public: Storage(); void Initialize(); @@ -85,6 +86,7 @@ public: void LoadWifiConfigSettings(); + void LoadGovernorSettings(); void LoadBank(int64_t instanceId); const PedalBoard& GetCurrentPreset(); void SaveCurrentPreset(const PedalBoard&pedalBoard); @@ -116,6 +118,10 @@ public: void SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings); WifiConfigSettings GetWifiConfigSettings(); + void SetGovernorSettings(const std::string & governor); + std::string GetGovernorSettings() const; + + void SaveCurrentPreset(const CurrentPreset ¤tPreset); bool RestoreCurrentPreset(CurrentPreset*pResult); diff --git a/src/main.cpp b/src/main.cpp index c612683..3a6a664 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,12 +27,13 @@ #include #include #include "PiPedalConfiguration.hpp" -#include "Shutdown.hpp" +#include "ShutdownClient.hpp" #include "CommandLineParser.hpp" #include "Lv2SystemdLogger.hpp" #include #include #include "HtmlHelper.hpp" +#include "Ipv6Helpers.hpp" #include #include @@ -406,19 +407,26 @@ public: class InterceptConfig : public RequestHandler { private: - std::string response; uint64_t maxUploadSize; + int portNumber; public: InterceptConfig(int portNumber, uint64_t maxUploadSize) : RequestHandler("/var/config.json"), - maxUploadSize(maxUploadSize) + maxUploadSize(maxUploadSize), + portNumber(portNumber) { + } + std::string GetConfig(const std::string&fromAddress) + { + std::string linkLocalAddress = GetLinkLocalAddress(fromAddress); + std::stringstream s; s << "{ \"socket_server_port\": " << portNumber - << ", \"socket_server_address\": \"*\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << " }"; - response = s.str(); + << ", \"socket_server_address\": \"" << linkLocalAddress << "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << " }"; + + return s.str(); } virtual ~InterceptConfig() {} @@ -428,6 +436,17 @@ public: HttpResponse &res, std::error_code &ec) { + // intercepted. See the other overload. + } + + virtual void head_response( + const std::string &fromAddress, + const uri&request_uri, + const HttpRequest &req, + HttpResponse &res, + std::error_code &ec) + { + std::string response = GetConfig(fromAddress); res.set(HttpField::content_type, "application/json"); res.set(HttpField::cache_control, "no-cache"); res.setContentLength(response.length()); @@ -439,7 +458,18 @@ public: const HttpRequest &req, HttpResponse &res, std::error_code &ec) + { + // intercepted. see the other overload. + } + + virtual void get_response( + const std::string&fromAddress, + const uri &request_uri, + const HttpRequest &req, + HttpResponse &res, + std::error_code &ec) { + std::string response = GetConfig(fromAddress); res.set(HttpField::content_type, "application/json"); res.set(HttpField::cache_control, "no-cache"); res.setContentLength(response.length()); diff --git a/src/pch.h b/src/pch.h index 19facaa..d7e2e6b 100644 --- a/src/pch.h +++ b/src/pch.h @@ -53,5 +53,4 @@ template class TypeDisplay; #include "lv2/parameters.lv2/parameters.h" #include "lv2/units.lv2/units.h" */ - -#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str()) +#include "ss.hpp" diff --git a/src/ss.hpp b/src/ss.hpp new file mode 100644 index 0000000..6b34c10 --- /dev/null +++ b/src/ss.hpp @@ -0,0 +1,26 @@ +// Copyright (c) 2022 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#pragma once + +#include +#include + +// usage: SS("xyz" << 123 << 45.6) returning a std::string rvalue. +#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str())