From 64b6251c1a220db9ed721a4cff37c0cd001210fe Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 21 Dec 2022 16:00:59 -0600 Subject: [PATCH] terminal: add audio/visual feedback when no user program Characters typed into the terminal are only sent to the hub when a user program is running. This provides audio and visual feedback to indicate to the user that keystrokes have no effect when the user program is not running. --- CHANGELOG.md | 3 +++ src/terminal/Terminal.tsx | 43 +++++++++++++++++++++++++++++++++++++- src/terminal/sagas.ts | 2 ++ src/terminal/terminal.scss | 19 +++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/terminal/terminal.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 9200913d..23df65dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ## [Unreleased] +### Added +- Added audio and visual feedback when typing into terminal while user program is not running. + ## [2.0.1] - 2022-12-21 ### Fixed diff --git a/src/terminal/Terminal.tsx b/src/terminal/Terminal.tsx index 138b63b3..88c5d579 100644 --- a/src/terminal/Terminal.tsx +++ b/src/terminal/Terminal.tsx @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import 'xterm/css/xterm.css'; +import './terminal.scss'; import { Menu, MenuDivider, MenuItem, ResizeSensor } from '@blueprintjs/core'; import { ContextMenu2, ContextMenu2ContentProps } from '@blueprintjs/popover2'; import React, { useContext, useEffect, useMemo, useRef } from 'react'; @@ -13,7 +15,12 @@ import { TerminalContext } from './TerminalContext'; import { receiveData } from './actions'; import { useI18n } from './i18n'; -import 'xterm/css/xterm.css'; +// Source: https://freesound.org/people/altemark/sounds/45759/ +// This sound is released under the Creative Commons Attribution 3.0 Unported +// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been +// made, apart from the conversion to base64. +const BELL_SOUND = + 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; function handleKeyEvent(event: KeyboardEvent): boolean { if ( @@ -186,12 +193,46 @@ const Terminal: React.FC = (_props) => { return () => removeEventListener('pb-terminal-focus', listener); }, [xterm]); + // audio and visual notification of bell + + const bellRef = useRef(null); + const bellOverlayRef = useRef(null); + const bellTimeoutRef = useRef>(); + + useEffect(() => { + const audioElement = bellRef.current; + const overlayElement = bellOverlayRef.current; + + if (process.env.NODE_ENV === 'test' || !audioElement || !overlayElement) { + return; + } + + const subscription = xterm.onBell(() => { + if (bellTimeoutRef.current) { + clearTimeout(bellTimeoutRef.current); + } + + audioElement.play(); + overlayElement.classList.add('pb-bell'); + + bellTimeoutRef.current = setTimeout(() => { + overlayElement.classList.remove('pb-bell'); + }, 150); + }); + + return () => subscription.dispose(); + }, [xterm, bellRef, bellOverlayRef, bellTimeoutRef]); + return ( xterm.focus() }} > + +
fitAddon.fit()}>
diff --git a/src/terminal/sagas.ts b/src/terminal/sagas.ts index ae0ee9ef..199809a4 100644 --- a/src/terminal/sagas.ts +++ b/src/terminal/sagas.ts @@ -79,6 +79,8 @@ function* receiveTerminalData(): Generator { // it should be fine as long a the logic for the state doesn't change. if (!isUserProgramRunning) { // if no user program is running, input goes to /dev/null + // print the BEL character (^G) to notify the user that the input was ignored + yield* put(sendData('\x07')); continue; } diff --git a/src/terminal/terminal.scss b/src/terminal/terminal.scss new file mode 100644 index 00000000..88c1151b --- /dev/null +++ b/src/terminal/terminal.scss @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022 The Pybricks Authors + +@use '@blueprintjs/core/lib/scss/variables' as bp; + +.pb-terminal-bell-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: bp.$pt-z-index-content - 1; + pointer-events: none; + + &.pb-bell { + background-color: adjust-color(bp.$pt-intent-danger, $alpha: -0.5); + transition: linear 100ms; + } +}