Configurable CPU Governor (fixed)

modified:   .vscode/launch.json
	modified:   react/CMakeLists.txt
	modified:   react/public/var/config.json
	new file:   react/src/GovernorSettings.tsx
	modified:   react/src/JackHostStatus.tsx
	new file:   react/src/ListSelectDialog.tsx
	modified:   react/src/PiPedalModel.tsx
	modified:   react/src/SettingsDialog.tsx
	modified:   react/tmp.txt
	modified:   src/BeastServer.cpp
	modified:   src/BeastServer.hpp
	modified:   src/CMakeLists.txt
	modified:   src/ConfigMain.cpp
	new file:   src/CpuGovernor.cpp
	new file:   src/CpuGovernor.hpp
	new file:   src/GovernorSettings.cpp
	new file:   src/GovernorSettings.hpp
	new file:   src/Ipv6Helpers.cpp
	new file:   src/Ipv6Helpers.hpp
	modified:   src/JackHost.cpp
	modified:   src/PiPedalModel.cpp
This commit is contained in:
Robin Davies
2022-03-09 21:35:01 -05:00
parent 5d42ec0753
commit 8ad2de8c72
31 changed files with 1642 additions and 377 deletions
+3 -1
View File
@@ -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}",
+75 -76
View File
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"socket_server_port": 8080,
"socket_server_address": "*",
"socket_server_address": "[fe80::df30:41b9:1ede:c23c]",
"max_upload_size": 1048576,
+35
View File
@@ -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";
}
+2 -2
View File
@@ -93,10 +93,10 @@ export default class JackHostStatus {
{
(status.cpuFreqMax === status.cpuFreqMin)?
(
<span> {fmtCpuFreq(status.cpuFreqMax)} {status.governor} </span>
<span> {status.governor} {fmtCpuFreq(status.cpuFreqMax)} </span>
)
:(
<span> {fmtCpuFreq(status.cpuFreqMax)}-{fmtCpuFreq(status.cpuFreqMax)} {status.governor} </span>
<span> {status.governor} {fmtCpuFreq(status.cpuFreqMax)}-{fmtCpuFreq(status.cpuFreqMax)} </span>
)
}
+116
View File
@@ -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<ListSelectDialogProps, ListSelectDialogState> {
refText: React.RefObject<HTMLInputElement>;
constructor(props: ListSelectDialogProps) {
super(props);
this.state = {
fullScreen: false
};
this.refText = React.createRef<HTMLInputElement>();
}
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 (
<Dialog onClose={()=>this.props.onClose()} open={this.props.open}>
<List sx={{pt: 0}}>
{
this.props.items.map(
(value: string, index: number) => {
return (
<ListItem key={index} >
<ListItemButton role={undefined} onClick={()=> this.props.onOk(value)} dense>
<ListItemIcon>
<Radio
edge="start"
checked={value === this.props.selectedItem}
disableRipple
/>
</ListItemIcon>
<ListItemText primary={value} />
</ListItemButton>
</ListItem>
);
}
)
}
</List>
<DialogActions>
<Button onClick={()=> this.props.onClose()} color="primary">
Cancel
</Button>
</DialogActions>
</Dialog>
);
}
}
+64 -29
View File
@@ -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<BankIndex>;
jackServerSettings: ObservableProperty<JackServerSettings>;
wifiConfigSettings: ObservableProperty<WifiConfigSettings>;
governorSettings: ObservableProperty<GovernorSettings>;
presets: ObservableProperty<PresetIndex>;
@@ -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<void>;
@@ -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<number>;
uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise<number>;
uploadBank(file: File, uploadAfter: number): Promise<number>;
setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise<void>;
setGovernorSettings(governor: string): Promise<void>;
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
@@ -447,6 +450,7 @@ class PiPedalModelImpl implements PiPedalModel {
= new ObservableProperty<JackServerSettings>(new JackServerSettings());
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings());
presets: ObservableProperty<PresetIndex> = new ObservableProperty<PresetIndex>
@@ -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<any>("getGovernorSettings");
})
.then(data => {
this.governorSettings.set(new GovernorSettings().deserialize(data));
return this.getWebSocket().request<any>("getJackServerSettings");
})
.then(data => {
@@ -792,6 +808,11 @@ class PiPedalModelImpl implements PiPedalModel {
.then(data => {
this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data));
return this.getWebSocket().request<any>("getGovernorSettings");
})
.then(data => {
this.governorSettings.set(new GovernorSettings().deserialize(data));
return this.getWebSocket().request<any>("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<number> {
saveCurrentPluginPresetAs(pluginInstanceId: number, newName: string): Promise<number> {
// 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<void>
{
updatePluginPresets(pluginUri: string, presets: PluginUiPresets): Promise<void> {
this.presetCache[pluginUri] = presets;
this.firePluginPresetsChanged(pluginUri);
return nullCast(this.webSocket).request<void>("updatePluginPresets", presets);
}
duplicatePluginPreset(pluginUri: string, instanceId: number): Promise<number>
{
return nullCast(this.webSocket).request<number>("copyPluginPreset", {
pluginUri: pluginUri, instanceId: instanceId });
duplicatePluginPreset(pluginUri: string, instanceId: number): Promise<number> {
return nullCast(this.webSocket).request<number>("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<number> {
uploadPreset(uploadPage: string, file: File, uploadAfter: number): Promise<number> {
let result = new Promise<number>((resolve, reject) => {
try {
if (file.size > this.maxUploadSize) {
@@ -1775,6 +1790,27 @@ class PiPedalModelImpl implements PiPedalModel {
});
return result;
}
setGovernorSettings(governor: string): Promise<void> {
let newSettings = this.governorSettings.get().clone();
newSettings.governor = governor;
this.governorSettings.set(newSettings);
return new Promise<void>((resolve, reject) => {
let ws = this.webSocket;
if (!ws) return;
ws.request<void>(
"setGovernorSettings",
governor
)
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
});
}
setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise<void> {
let result = new Promise<void>((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;
+77 -4
View File
@@ -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 })(
>
<div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
AUDIO
STATUS
</Typography>
{(!isConfigValid) ?
(
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
<Typography display="block" variant="caption" color="textSecondary">Status: <span style={{ color: "#F00" }}>Not configured.</span></Typography>
<Typography display="block" variant="caption" color="textSecondary">CPU: </Typography>
<Typography display="block" variant="caption" color="textSecondary">Governor: </Typography>
</div>
) :
(
<div style={{ paddingLeft: 48, position: "relative", top: -12 }}>
{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)}
</div>
)
}
@@ -442,6 +474,9 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
)
}
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">
AUDIO
</Typography>
<ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()}
>
<SelectHoverBackground selected={false} showHover={true} />
@@ -512,6 +547,18 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
</div>
</ButtonBase>
<ButtonBase className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
onClick={() => this.handleShowGovernorSettingsDialogDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
CPU Governor</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.governorSettings.governor }
</Typography>
</div>
</ButtonBase>
<ButtonBase className={classes.setting} disabled={!isConfigValid || disableShutdown}
onClick={() => this.handleRestart()} >
<SelectHoverBackground selected={false} showHover={true} />
@@ -556,6 +603,32 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
)
}
{
(this.state.showGovernorSettingsDialog) &&
(
<ListSelectDialog
width={220}
open={this.state.showGovernorSettingsDialog}
items={this.state.governorSettings.governors}
selectedItem={this.state.governorSettings.governor}
title="Governor"
onClose={()=> this.setState({showGovernorSettingsDialog: false})}
onOk={(selectedValue) => {
this.model.setGovernorSettings(selectedValue)
.then(()=>{
})
.catch(error => {
this.model.showAlert(error);
});
this.setState({showGovernorSettingsDialog: false})
}}
>
</ListSelectDialog>
)
}
{
(this.state.showMidiSelectDialog) &&
(
+74 -76
View File
@@ -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
+57 -6
View File
@@ -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<std::string, std::string> 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.
+34 -1
View File
@@ -3,7 +3,7 @@
#include <string.h>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ip/network_v4.hpp>
#include <boost/asio/ip/network_v6.hpp>
#include <cstring>
@@ -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 {
+5 -1
View File
@@ -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)
+6 -5
View File
@@ -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 <country_code> <ssid> <wep_password> <channel>" << endl
<< " Enable the Wi-Fi access point. <country_code> 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. <country_code> 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
+141
View File
@@ -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 <fstream>
#include <filesystem>
#include "PiPedalException.hpp"
#include <unistd.h>
using namespace pipedal;
#ifdef __WIN32__
// not a feature on Win32.
void pipedal::SetCpuGovernor(const std::string &governor) { }
std::vector<std::string> pipedal::GetAvailableGovernors() { return std::vector<std::string>(); }
#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<std::string> pipedal::GetAvailableGovernors() {
return std::vector<std::string> {
"performance",
"ondemand",
"powersave"
};
}
#endif
void SetCpuGovernor(const std::string &governor);
std::vector<std::string> GetAvailableGovernors();
+31
View File
@@ -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 <string>
#include <vector>
namespace pipedal {
std::string GetCpuGovernor();
void SetCpuGovernor(const std::string &governor);
std::vector<std::string> GetAvailableGovernors();
};
+27
View File
@@ -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()
+36
View File
@@ -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 <string>
#include <vector>
namespace pipedal {
class GovernorSettings {
public:
std::string governor_;
std::vector<std::string> governors_;
DECLARE_JSON_MAP(GovernorSettings);
};
}
+360
View File
@@ -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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "ss.hpp"
#include <sys/types.h>
#include <ifaddrs.h>
#include <memory.h>
#include <exception>
#include <netdb.h>
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;
}
+31
View File
@@ -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);
}
+2 -9
View File
@@ -33,6 +33,7 @@ using namespace pipedal;
#include <thread>
#include <semaphore.h>
#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();
}
+59 -8
View File
@@ -26,6 +26,7 @@
#include "PiPedalConfiguration.hpp"
#include "ShutdownClient.hpp"
#include "SplitEffect.hpp"
#include "CpuGovernor.hpp"
#ifndef NO_MLOCK
#include <sys/mman.h>
@@ -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<std::recursive_mutex> guard(mutex);
GovernorSettings result;
result.governor_ = storage.GetGovernorSettings();
result.governors_ = pipedal::GetAvailableGovernors();
return result;
}
}
void PiPedalModel::SetGovernorSettings(const std::string& governor)
{
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> guard(mutex);
+5
View File
@@ -20,6 +20,7 @@
#pragma once
#include <mutex>
#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();
+20 -1
View File
@@ -28,6 +28,7 @@
#include "JackConfiguration.hpp"
#include <future>
#include <atomic>
#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)
{
+52 -46
View File
@@ -27,6 +27,7 @@
#include <sstream>
#include <sys/types.h>
#include <ifaddrs.h>
#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.");
}
}
+4 -1
View File
@@ -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
+212 -103
View File
@@ -23,6 +23,7 @@
#include "pch.h"
#include "CommandLineParser.hpp"
#include "CpuGovernor.hpp"
#include <iostream>
#include <cstdint>
#include <boost/asio.hpp>
@@ -40,7 +41,10 @@
#include "SetWifiConfig.hpp"
#include "SysExec.hpp"
#include <filesystem>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
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<std::mutex> lock(mutex);
if (!pThread)
{
this->governor = governor;
cancelled = false;
wake = false;
pThread = std::make_unique<std::thread>([this]()
{ this->ServiceProc(); });
} else {
this->governor = governor;
wake = true;
lock.unlock();
cv.notify_one();
}
}
void Stop()
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::thread> 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<std::string> tokenize(std::string value)
std::vector<std::string> 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<std::string> 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<Reader>
@@ -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<std::string> 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<std::string> 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;
}
+45
View File
@@ -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)
+6
View File
@@ -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 &currentPreset);
bool RestoreCurrentPreset(CurrentPreset*pResult);
+35 -5
View File
@@ -27,12 +27,13 @@
#include <boost/system/error_code.hpp>
#include <filesystem>
#include "PiPedalConfiguration.hpp"
#include "Shutdown.hpp"
#include "ShutdownClient.hpp"
#include "CommandLineParser.hpp"
#include "Lv2SystemdLogger.hpp"
#include <sys/stat.h>
#include <boost/asio.hpp>
#include "HtmlHelper.hpp"
#include "Ipv6Helpers.hpp"
#include <signal.h>
#include <semaphore.h>
@@ -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());
+1 -2
View File
@@ -53,5 +53,4 @@ template <typename T> 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"
+26
View File
@@ -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 <sstream>
#include <iostream>
// usage: SS("xyz" << 123 << 45.6) returning a std::string rvalue.
#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str())