diff --git a/artifacts/FavIcon.svgz b/artifacts/FavIcon.svgz new file mode 100644 index 0000000..700e69e Binary files /dev/null and b/artifacts/FavIcon.svgz differ diff --git a/docs/_config.yml b/docs/_config.yml index 5bd324b..caf2865 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -36,6 +36,7 @@ header_pages: theme: minima plugins: - jekyll-feed + - jekyll-seo-tag include: - .well-known diff --git a/src/main.cpp b/src/main.cpp index 0665dcc..3e38c95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -313,6 +313,7 @@ int main(int argc, char *argv[]) sigset_t sigSet; int s; sigemptyset(&sigSet); + sigaddset(&sigSet, SIGINT); sigaddset(&sigSet, SIGTERM); sigaddset(&sigSet, SIGUSR1); diff --git a/vite/index.html b/vite/index.html index 66a5daf..3a6c884 100644 --- a/vite/index.html +++ b/vite/index.html @@ -100,23 +100,23 @@ // Run the function when the window loads window.addEventListener('load', removeHashOnLoad); - - function getFocusedElementBounds() { - let focusedElement = document.activeElement; - if (focusedElement && focusedElement !== document.body) { - let rect = focusedElement.getBoundingClientRect(); - return { - top: rect.top, - left: rect.left, - right: rect.right, - bottom: rect.bottom, - windowWidth: window.innerWidth, - windowHeight: window.innerHeight, - }; - } - return null; - } + // used by Android client to perform focus scrolling on keyboard open. + // function getFocusedElementBounds() { + // let focusedElement = document.activeElement; + // if (focusedElement && focusedElement !== document.body) { + // let rect = focusedElement.getBoundingClientRect(); + // return { + // top: rect.top, + // left: rect.left, + // right: rect.right, + // bottom: rect.bottom, + // windowWidth: window.innerWidth, + // windowHeight: window.innerHeight, + // }; + // } + // return null; + // } diff --git a/vite/src/pipedal/AndroidHost.tsx b/vite/src/pipedal/AndroidHost.tsx index 5fe7aa4..18556ac 100644 --- a/vite/src/pipedal/AndroidHost.tsx +++ b/vite/src/pipedal/AndroidHost.tsx @@ -32,6 +32,11 @@ export interface AndroidHostInterface { setThemePreference(theme: number): void; getThemePreference(): number; isDarkTheme?: ()=> boolean; + setServerVersion(serverVersion: string) : void; + setKeepScreenOn(keepScreenOn: boolean): void; + getKeepScreenOn(): boolean; + setScreenOrientation(orientation: number): void; + getScreenOrientation(): number; }; export class FakeAndroidHost implements AndroidHostInterface @@ -40,7 +45,7 @@ export class FakeAndroidHost implements AndroidHostInterface return true; } getHostVersion(): string { - return "Fake Android 1.0"; + return "Fake Host: v1.1.26"; } chooseNewDevice(): void { @@ -64,6 +69,21 @@ export class FakeAndroidHost implements AndroidHostInterface getThemePreference(): number { return this.theme; } + setServerVersion(_serverVersion: string): void { + // No-op for fake host + } + + private keepScreenOn = false; + setKeepScreenOn(keepScreenOn: boolean): void {this.keepScreenOn = keepScreenOn; }; + getKeepScreenOn(): boolean { return this.keepScreenOn; }; + + private screenOrientation = 0; + setScreenOrientation(_orientation: number): void { + this.screenOrientation = _orientation; + } + getScreenOrientation(): number { + return this.screenOrientation; + } } export function isAndroidHosted(): boolean { return ((window as any).AndroidHost as AndroidHostInterface) !== undefined; diff --git a/vite/src/pipedal/AppThemed.tsx b/vite/src/pipedal/AppThemed.tsx index f7e1d1e..42d7347 100644 --- a/vite/src/pipedal/AppThemed.tsx +++ b/vite/src/pipedal/AppThemed.tsx @@ -1000,7 +1000,7 @@ export - + diff --git a/vite/src/pipedal/FilePropertyDialog.tsx b/vite/src/pipedal/FilePropertyDialog.tsx index bca518f..bd789e4 100644 --- a/vite/src/pipedal/FilePropertyDialog.tsx +++ b/vite/src/pipedal/FilePropertyDialog.tsx @@ -23,8 +23,9 @@ import { createStyles } from './WithStyles'; // import Tone3000Dialog from './Tone3000Dialog'; import Tone3000HelpDialog from './Tone3000HelpDialog'; +import GuitarMLHelpDialog from './GuitarMlHelpDialog'; import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; -import Link from '@mui/material/Link'; +import LinkEx from './LinkEx'; import DraggableButtonBase from './DraggableButtonBase'; import CloseIcon from '@mui/icons-material/Close'; import { Theme } from '@mui/material/styles'; @@ -69,6 +70,7 @@ import { getAlbumArtUri, getTrackTitle } from './AudioFileMetadata'; const ToobNamModelFileUrl = "http://two-play.com/plugins/toob-nam#modelFile"; +const ToobMlModelFileUrl = "http://two-play.com/plugins/toob-ml#modelFile"; const AUTOSCROLL_TICK_DELAY = 30; const AUTOSCROLL_THRESHOLD = 48; @@ -168,7 +170,8 @@ export interface FilePropertyDialogState { multiSelect: boolean, selectedFiles: string[], //openTone3000Dialog: boolean, - openTone3000Help: boolean + openTone3000Help: boolean, + openGuitarMlHelp: boolean }; @@ -241,7 +244,8 @@ export default withStyles( multiSelect: false, selectedFiles: [], //openTone3000Dialog: false, - openTone3000Help: false + openTone3000Help: false, + openGuitarMlHelp: false }; this.requestScroll = true; } @@ -1057,6 +1061,15 @@ export default withStyles( // } + handleGuitarMlHelp(e: React.MouseEvent) { + e.stopPropagation(); + e.preventDefault(); + + this.setState({ openGuitarMlHelp: true }); + + } + + handleTone3000Help(e: React.MouseEvent) { e.stopPropagation(); e.preventDefault(); @@ -1068,6 +1081,7 @@ export default withStyles( render() { const isTracksDirectory = this.isTracksDirectory(); const isToobNamModelFile = this.props.fileProperty.patchProperty === ToobNamModelFileUrl; + const isToobMLModelFile = this.props.fileProperty.patchProperty === ToobMlModelFileUrl; const classes = withStyles.getClasses(this.props); let columnWidth = this.state.columnWidth; @@ -1487,26 +1501,46 @@ export default withStyles( {(!this.state.reordering && !this.state.multiSelect) && ( <> - + +
+ {isToobNamModelFile && ( +
+ + Download model files from TONE3000 + + { this.handleTone3000Help(e); }} aria-label="help" edge="end" color="inherit" style={{ opacity: 0.6, marginLeft: 8 }} + > + + +
+ + )} + {isToobMLModelFile && ( +
+ + Download model files from GuitarML + + { this.handleGuitarMlHelp(e); }} aria-label="help" edge="end" color="inherit" style={{ opacity: 0.6, marginLeft: 8 }} + > + + +
+ + )} + + {this.state.windowWidth > 500 ? (
- {isToobNamModelFile && ( -
- - Download model files from TONE3000 - - { this.handleTone3000Help(e); }} aria-label="help" edge="end" color="inherit" style={{ opacity: 0.6, marginLeft: 8 }} - > - - -
- - )}
)} +
)} @@ -1702,6 +1737,12 @@ export default withStyles( onClose={() => this.setState({ openTone3000Help: false })} /> )} + {this.state.openGuitarMlHelp && ( + this.setState({ openGuitarMlHelp: false })} + /> + )} ); } diff --git a/vite/src/pipedal/GuitarMlHelpDialog.tsx b/vite/src/pipedal/GuitarMlHelpDialog.tsx new file mode 100644 index 0000000..957575a --- /dev/null +++ b/vite/src/pipedal/GuitarMlHelpDialog.tsx @@ -0,0 +1,76 @@ +import DialogEx from "./DialogEx"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import Toolbar from "@mui/material/Toolbar"; +import IconButtonEx from "./IconButtonEx"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import Typography from "@mui/material/Typography"; +import Link from "@mui/material/Link"; + + + +function GuitarMlHelpDialog(props: { + open: boolean; + onClose: () => void; +}) { + const { open, onClose } = props; + return ( + { onClose(); }} + onClose={() => { onClose(); }} + open={open}> + + + + { onClose(); }} + > + + + + + TONE3000 Help + + + + + +
+
+
+ + The GuitarML Tone Library Project provides + a substantial collection of high-quality neural amp models for use in plugins that use the ML + model file format. + + + To use GuitarML models with TooB ML, you must first download the collection of models from the Guitar ML website + by clicking on the link. This should download a file named "Proteus_Tone_Packs.zip" into your browser's Downloads directory. + You must then upload the downloaded zip file, found in your browser's Download directory to the + Pipedal server using the Upload button in PiPedal's file browser dialog. + PiPedal automatically extracts bundles of model files from zip archives, so manual extraction is unnecessary. + + + Please consider sponsoring the GuitarML project in order to support the ongoing development of the Tone Library. + +
+
+
+ + + + + ); +} +export default GuitarMlHelpDialog; \ No newline at end of file diff --git a/vite/src/pipedal/JackStatusView.tsx b/vite/src/pipedal/JackStatusView.tsx index b47d96a..e8530a9 100644 --- a/vite/src/pipedal/JackStatusView.tsx +++ b/vite/src/pipedal/JackStatusView.tsx @@ -70,9 +70,9 @@ export default class JackStatusView extends React.Component {JackHostStatus.getDisplayView("",this.state.jackStatus) }
diff --git a/vite/src/pipedal/LinkEx.tsx b/vite/src/pipedal/LinkEx.tsx new file mode 100644 index 0000000..6b82c4d --- /dev/null +++ b/vite/src/pipedal/LinkEx.tsx @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025 Robin E. R. Davies + * All rights reserved. + + * 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 Link, {LinkProps} from '@mui/material/Link'; +import {PiPedalModel,PiPedalModelFactory} from './PiPedalModel'; + +export interface LinkExProps extends LinkProps { + +} +function LinkEx(props: LinkExProps) { + const { href, ...rest } = props; + + function handleClick(event: React.MouseEvent) { + const model:PiPedalModel = PiPedalModelFactory.getInstance(); + + // Prevent default behavior if needed + if (model.isAndroidHosted()) { + model.androidHost?.launchExternalUrl(href as string); + } else { + window.open(href as string, '_blank', 'noopener,noreferrer'); + } + event.preventDefault(); + event.stopPropagation(); + } + return ( + )=>{ handleClick(e); }} component="span" + {...rest} /> + + ) +} + +export default LinkEx; \ No newline at end of file diff --git a/vite/src/pipedal/PerformanceView.tsx b/vite/src/pipedal/PerformanceView.tsx index db346c1..c05142b 100644 --- a/vite/src/pipedal/PerformanceView.tsx +++ b/vite/src/pipedal/PerformanceView.tsx @@ -79,6 +79,7 @@ interface PerformanceViewState { presets: PresetIndex; banks: BankIndex; showSnapshotEditor: boolean; + showStatusMonitor: boolean; snapshotEditorIndex: number; presetModified: boolean; } @@ -100,15 +101,24 @@ export const PerformanceView = banks: this.model.banks.get(), wrapSelects: false, showSnapshotEditor: false, + showStatusMonitor: this.model.showStatusMonitor.get(), snapshotEditorIndex: 0, presetModified: this.model.presetChanged.get() }; this.onPresetsChanged = this.onPresetsChanged.bind(this); this.onBanksChanged = this.onBanksChanged.bind(this); this.onPresetChangedChanged = this.onPresetChangedChanged.bind(this); + this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this); } + + showStatusMonitorHandler() { + this.setState({ + showStatusMonitor: this.model.showStatusMonitor.get() + }); + } + getTag() { return "performView"} isOpen() { return this.props.open } onDialogStackClose() { @@ -156,6 +166,8 @@ export const PerformanceView = this.model.presets.addOnChangedHandler(this.onPresetsChanged); this.model.banks.addOnChangedHandler(this.onBanksChanged); this.model.presetChanged.addOnChangedHandler(this.onPresetChangedChanged) + this.model.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler); + this.setState({ presets: this.model.presets.get(), banks: this.model.banks.get(), @@ -173,6 +185,8 @@ export const PerformanceView = this.model.presetChanged.removeOnChangedHandler(this.onPresetChangedChanged) this.model.presets.removeOnChangedHandler(this.onPresetsChanged); this.model.banks.removeOnChangedHandler(this.onBanksChanged); + this.model.banks.removeOnChangedHandler(this.showStatusMonitorHandler); + this.mounted = false; this.updateHooks(); @@ -393,10 +407,10 @@ export const PerformanceView = { return this.handleOnEdit(index); }} />
- - {!this.state.showSnapshotEditor && ( - ) - } + + {!this.state.showSnapshotEditor && this.state.showStatusMonitor && ( + + )}
{this.state.showSnapshotEditor && ( = 0) { + this.hostName = hostVersion.substring(0, pos).trim(); + remainder = hostVersion.substring(pos + 1).trim(); + } else { + this.hostName = "Unknown Host"; + remainder = "v0.0.0"; + } + + const args = remainder.split(","); // make make provisions for more. + if (args.length >= 1) + { + let versionString = args[0].trim(); + if (versionString.startsWith('v')) { + versionString = versionString.substring(1).trim(); + } + let releaseTypePos = versionString.indexOf("-"); + if (releaseTypePos >= 0) { + this.releaseType = versionString.substring(releaseTypePos + 1).trim(); + versionString = versionString.substring(0, releaseTypePos).trim(); + } + else { + this.releaseType = "Release"; + } + + const parts = args[0].split("."); + if (parts.length >= 3) { + this.majorVersion = parseInt(parts[0]); + this.minorVersion = parseInt(parts[1]); + this.buildNumber = parseInt(parts[2]); + } + } + } + lessThan(major: number, minor: number, build: number): boolean { + if (this.majorVersion < major) return true; + if (this.majorVersion > major) return false; + if (this.minorVersion < minor) return true; + if (this.minorVersion > minor) return false; + return this.buildNumber < build; + } + versionString: string = ""; + hostName: string = ""; + releaseType: string = ""; + majorVersion: number = 0; + minorVersion: number = 0; + buildNumber: number = 0; +} export type PedalboardItemEnabledChangeCallback = (instanceId: number, isEnabled: boolean) => void; interface PedalboardItemEnabledChangeItem { handle: number; @@ -440,6 +494,12 @@ export class PiPedalModel //implements PiPedalModel hasTone3000Auth: ObservableProperty = new ObservableProperty(false); + canKeepScreenOn: boolean = false; + keepScreenOn: ObservableProperty = new ObservableProperty(false); + + canSetScreenOrientation: boolean = false; + screenOrientation: ObservableProperty = new ObservableProperty(ScreenOrientation.SystemDefault); + hasWifiDevice: ObservableProperty = new ObservableProperty(false); onSnapshotModified: ObservableEvent = new ObservableEvent(); @@ -504,6 +564,7 @@ export class PiPedalModel //implements PiPedalModel } androidHost?: AndroidHostInterface; + hostVersion?: HostVersion; constructor() { this.androidHost = (window as any).AndroidHost as AndroidHostInterface; @@ -1295,6 +1356,17 @@ export class PiPedalModel //implements PiPedalModel .then((succeeded) => { if (succeeded) { this.state.set(State.Ready); + if (this.androidHost) { + this.hostVersion = new HostVersion(this.androidHost.getHostVersion()); + if (!this.hostVersion.lessThan(1,1,16)) + { + this.androidHost.setServerVersion(this.serverVersion?.serverVersion??""); + this.canKeepScreenOn = true; + this.keepScreenOn.set(this.androidHost.getKeepScreenOn()) + this.canSetScreenOrientation = true; + this.screenOrientation.set(this.androidHost.getScreenOrientation()) + } + } } }) .catch((error) => { @@ -3412,6 +3484,27 @@ export class PiPedalModel //implements PiPedalModel return false; } + setKeepScreenOn(keepScreenOn: boolean): void { + if (this.canKeepScreenOn) { + this.keepScreenOn.set(keepScreenOn); + if (this.androidHost) { + this.androidHost.setKeepScreenOn(keepScreenOn); + } + } + } + getKeepScreenOn(): boolean { + return this.keepScreenOn.get(); + } + + getScreenOrientation(): ScreenOrientation { + return this.screenOrientation.get(); + } + setScreenOrientation(value: ScreenOrientation) { + if (this.canSetScreenOrientation) { + this.screenOrientation.set(value); + this.androidHost?.setScreenOrientation(value as number); + } + } }; let instance: PiPedalModel | undefined = undefined; diff --git a/vite/src/pipedal/ScreenOrientation.tsx b/vite/src/pipedal/ScreenOrientation.tsx new file mode 100644 index 0000000..05f3465 --- /dev/null +++ b/vite/src/pipedal/ScreenOrientation.tsx @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Robin E. R. Davies + * All rights reserved. + + * 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. + */ + +enum ScreenOrientation { + // values must match Pipedal Remote ScreenOrientation.java + SystemDefault = 0, + Landscape = 1, + Portrait = 2 +} + +export default ScreenOrientation; \ No newline at end of file diff --git a/vite/src/pipedal/SelectScreenOrientationDialog.tsx b/vite/src/pipedal/SelectScreenOrientationDialog.tsx new file mode 100644 index 0000000..6a002bf --- /dev/null +++ b/vite/src/pipedal/SelectScreenOrientationDialog.tsx @@ -0,0 +1,74 @@ +// 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 List from '@mui/material/List'; +import ListItemText from '@mui/material/ListItemText'; +import DialogEx from './DialogEx'; + +import { ListItemButton } from '@mui/material'; +import ScreenOrientation from './ScreenOrientation'; + + + +export interface SelectScreenOrientationDialogProps { + open: boolean; + screenOrientation: ScreenOrientation; + onClose: (screenOrientation: ScreenOrientation | null) => void; +} + + +function SelectScreenOrientationDialog(props: SelectScreenOrientationDialogProps) { + //const classes = useStyles(); + const { onClose, screenOrientation, open } = props; + + const handleCancel = () => { + onClose(null); + } + const handleOk = (value: ScreenOrientation) => { + onClose(value); + }; + + const handleListItemClick = (value: ScreenOrientation) => { + handleOk(value); + }; + + return ( + { }} + > + + + handleListItemClick(ScreenOrientation.SystemDefault)} key={"0"} + selected={screenOrientation === ScreenOrientation.SystemDefault} > + + + handleListItemClick(ScreenOrientation.Portrait)} key={"1"} + selected={screenOrientation === ScreenOrientation.Portrait} > + + + handleListItemClick(ScreenOrientation.Landscape)} key={"2"} + selected={screenOrientation === ScreenOrientation.Landscape} > + + + + + ); +} + +export default SelectScreenOrientationDialog; diff --git a/vite/src/pipedal/SettingsDialog.tsx b/vite/src/pipedal/SettingsDialog.tsx index aa51806..cef8440 100644 --- a/vite/src/pipedal/SettingsDialog.tsx +++ b/vite/src/pipedal/SettingsDialog.tsx @@ -46,10 +46,10 @@ import DialogEx from './DialogEx' import GovernorSettings from './GovernorSettings'; import SystemMidiBindingsDialog from './SystemMidiBindingsDialog'; import SelectThemeDialog from './SelectThemeDialog'; -import {AlsaSequencerConfiguration} from './AlsaSequencer'; +import { AlsaSequencerConfiguration } from './AlsaSequencer'; import Slide, { SlideProps } from '@mui/material/Slide'; -import {createStyles} from './WithStyles'; +import { createStyles } from './WithStyles'; import { Theme } from '@mui/material/styles'; import { withStyles } from "tss-react/mui"; @@ -57,6 +57,8 @@ import WithStyles from './WithStyles'; import { canScaleWindow, getWindowScaleOptions, getWindowScaleText, setWindowScale, getWindowScale } from './WindowScale'; import OptionsDialog from './OptionsDialog'; import { css } from '@emotion/react'; +import ScreenOrientation from './ScreenOrientation'; +import SelectScreenOrientationDialog from './SelectScreenOrientationDialog'; interface SettingsDialogProps extends WithStyles { @@ -74,6 +76,8 @@ interface SettingsDialogState { jackSettings: JackChannelSelection; jackServerSettings: JackServerSettings; alsaSequencerConfiguration: AlsaSequencerConfiguration; + keepScreenOn: boolean; + screenOrientation: ScreenOrientation; jackStatus?: JackHostStatus; governorSettings: GovernorSettings; @@ -88,6 +92,7 @@ interface SettingsDialogState { showGovernorSettingsDialog: boolean; showInputSelectDialog: boolean; showOutputSelectDialog: boolean; + showScreenOrientationDialog: boolean; showMidiSelectDialog: boolean; showThemeSelectDialog: boolean; showJackServerSettingsDialog: boolean; @@ -191,13 +196,15 @@ const SettingsDialog = withStyles( wifiDirectConfigSettings: this.model.wifiDirectConfigSettings.get(), governorSettings: this.model.governorSettings.get(), continueDisabled: true, - + keepScreenOn: this.model.keepScreenOn.get(), + screenOrientation: this.model.getScreenOrientation(), showWindowScaleDialog: false, showWifiConfigDialog: false, showWifiDirectConfigDialog: false, showGovernorSettingsDialog: false, showInputSelectDialog: false, showOutputSelectDialog: false, + showScreenOrientationDialog: false, showMidiSelectDialog: false, showThemeSelectDialog: false, showJackServerSettingsDialog: false, @@ -219,11 +226,21 @@ const SettingsDialog = withStyles( this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this); this.handleShowStatusMonitorChanged = this.handleShowStatusMonitorChanged.bind(this); this.handleHasWifiChanged = this.handleHasWifiChanged.bind(this); + this.handleKeepScreenOnChanged = this.handleKeepScreenOnChanged.bind(this); + this.handleScreenOrientationChanged = this.handleScreenOrientationChanged.bind(this); } + handleScreenOrientationChanged(newValue: ScreenOrientation) { + this.setState({ + screenOrientation: newValue + }); + } + handleKeepScreenOnChanged(newValue: boolean) { + this.setState({ keepScreenOn: newValue }); + } handleHasWifiChanged(newValue: boolean) { - this.setState({hasWifiDevice: newValue}); + this.setState({ hasWifiDevice: newValue }); } handleShowStatusMonitorChanged(): void { @@ -337,6 +354,8 @@ const SettingsDialog = withStyles( if (active !== this.active) { this.active = active; if (active) { + this.model.keepScreenOn.addOnChangedHandler(this.handleKeepScreenOnChanged); + this.model.screenOrientation.addOnChangedHandler(this.handleScreenOrientationChanged); this.model.hasWifiDevice.addOnChangedHandler(this.handleHasWifiChanged); this.model.state.addOnChangedHandler(this.handleConnectionStateChanged); this.model.showStatusMonitor.addOnChangedHandler(this.handleShowStatusMonitorChanged); @@ -368,7 +387,7 @@ const SettingsDialog = withStyles( this.handleJackServerSettingsChanged(); this.handleWifiConfigSettingsChanged(); this.handleWifiDirectConfigSettingsChanged(); - this.setState({hasWifiDevice: this.model.hasWifiDevice.get()}); + this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() }); this.timerHandle = setInterval(() => this.tick(), 1000); } else { @@ -377,6 +396,8 @@ const SettingsDialog = withStyles( } this.model.state.removeOnChangedHandler(this.handleConnectionStateChanged); this.model.showStatusMonitor.removeOnChangedHandler(this.handleShowStatusMonitorChanged); + this.model.keepScreenOn.removeOnChangedHandler(this.handleKeepScreenOnChanged); + this.model.screenOrientation.removeOnChangedHandler(this.handleScreenOrientationChanged); this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged); this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged); @@ -441,6 +462,28 @@ const SettingsDialog = withStyles( showOutputSelectDialog: false }); } + handleScreenOrientation() { + this.setState({ + showScreenOrientationDialog: true, + }); + } + getOrientationText() { + switch (this.state.screenOrientation) { + case ScreenOrientation.Portrait: + return "Portrait"; + case ScreenOrientation.Landscape: + return "Landscape"; + default: + return "System default"; + } + } + handleScreenOrientationDialogResult(orientation: ScreenOrientation | null): void { + if (orientation !== null) { + this.setState({ screenOrientation: orientation }); + this.model.setScreenOrientation(orientation); + } + this.setState({ showScreenOrientationDialog: false }); + } handleOutputSelection() { this.setState({ @@ -548,11 +591,13 @@ const SettingsDialog = withStyles( } render() { const classes = withStyles.getClasses(this.props); - + let isConfigValid = this.state.jackConfiguration.isValid; let selectedChannels: string[] = this.state.showInputSelectDialog ? this.state.jackSettings.inputAudioPorts : this.state.jackSettings.outputAudioPorts; let disableShutdown = this.state.shuttingDown || this.state.restarting; + let canKeepScreenOn = this.model.canKeepScreenOn; + return ( { this.props.onClose() }} TransitionComponent={Transition} @@ -730,6 +775,51 @@ const SettingsDialog = withStyles( + {canKeepScreenOn && + ( + { + this.model.setKeepScreenOn(!this.state.keepScreenOn) + }} > + +
+
+
+ + Keep screen on. +
+ +
+ { this.model.setKeepScreenOn(e.target.checked); } + } + /> +
+ +
+
+
+ ) + } + {this.model.canSetScreenOrientation && ( + this.handleScreenOrientation()} + > + +
+ Display Orientation + { + this.getOrientationText() + } +
+
+ ) + } {(canScaleWindow()) && ( @@ -908,6 +998,21 @@ const SettingsDialog = withStyles( ) } + { + this.state.showScreenOrientationDialog && + ( + { + this.handleScreenOrientationDialogResult(screenOrientation); + }} + screenOrientation={this.state.screenOrientation} + + /> + ) + + } + { (this.state.showGovernorSettingsDialog) && (