aboutsummaryrefslogtreecommitdiff
path: root/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/sound.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/sound.jsx')
-rwxr-xr-xusers/ryan/modules/simple-bar/simple-bar-source/lib/components/data/sound.jsx154
1 files changed, 154 insertions, 0 deletions
diff --git a/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/sound.jsx b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/sound.jsx
new file mode 100755
index 0000000..7301426
--- /dev/null
+++ b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/sound.jsx
@@ -0,0 +1,154 @@
1import * as Uebersicht from "uebersicht";
2import * as DataWidget from "./data-widget.jsx";
3import * as DataWidgetLoader from "./data-widget-loader.jsx";
4import * as Icons from "../icons/icons.jsx";
5import { SuspenseIcon } from "../icons/icon.jsx";
6import useWidgetRefresh from "../../hooks/use-widget-refresh";
7import useServerSocket from "../../hooks/use-server-socket";
8import { useSimpleBarContext } from "../simple-bar-context.jsx";
9import * as Utils from "../../utils";
10
11export { soundStyles as styles } from "../../styles/components/data/sound";
12
13const { React } = Uebersicht;
14
15const DEFAULT_REFRESH_FREQUENCY = 20000;
16
17/**
18 * Sound widget component.
19 * @returns {JSX.Element|null} The sound widget.
20 */
21export const Widget = React.memo(() => {
22 const { displayIndex, settings } = useSimpleBarContext();
23 const { widgets, soundWidgetOptions } = settings;
24 const { soundWidget } = widgets;
25 const { refreshFrequency, showOnDisplay, showIcon } = soundWidgetOptions;
26
27 // Determine the refresh frequency for the widget.
28 const refresh = React.useMemo(
29 () =>
30 Utils.getRefreshFrequency(refreshFrequency, DEFAULT_REFRESH_FREQUENCY),
31 [refreshFrequency],
32 );
33
34 // Determine if the widget should be visible on the current display.
35 const visible =
36 Utils.isVisibleOnDisplay(displayIndex, showOnDisplay) && soundWidget;
37
38 const [state, setState] = React.useState();
39 const [loading, setLoading] = React.useState(visible);
40 const { volume: _volume } = state || {};
41 const [volume, setVolume] = React.useState(_volume && parseInt(_volume, 10));
42 const [dragging, setDragging] = React.useState(false);
43
44 /**
45 * Reset the widget state.
46 */
47 const resetWidget = () => {
48 setState(undefined);
49 setLoading(false);
50 };
51
52 /**
53 * Fetch the current sound settings.
54 */
55 const getSound = React.useCallback(async () => {
56 if (!visible) return;
57 const output = await Utils.cachedRun(
58 `osascript -e 'set v to get volume settings' -e 'output volume of v & output muted of v'`,
59 refresh,
60 );
61 const parts = Utils.cleanupOutput(output).split(",");
62 setState({
63 volume: parts[0],
64 muted: parts[1],
65 });
66 setLoading(false);
67 }, [visible, refresh]);
68
69 // Use server socket to listen for sound updates.
70 useServerSocket("sound", visible, getSound, resetWidget, setLoading);
71 // Refresh the widget at the specified interval.
72 useWidgetRefresh(visible, getSound, refresh);
73
74 // Update the sound settings when dragging ends.
75 React.useEffect(() => {
76 if (!dragging) setSound(volume);
77 }, [dragging, volume]);
78
79 // Update the volume state when the fetched volume changes.
80 React.useEffect(() => {
81 setVolume((currentVolume) => {
82 if (_volume && currentVolume !== parseInt(_volume, 10)) {
83 return parseInt(_volume, 10);
84 }
85 return currentVolume;
86 });
87 }, [_volume]);
88
89 if (loading) return <DataWidgetLoader.Widget className="sound" />;
90 if (!state || volume === undefined) return null;
91
92 const { muted } = state;
93 if (_volume === "missing value" || muted === "missing value") return null;
94
95 let Icon = Icons.VolumeHigh;
96 if (volume < 50) Icon = Icons.VolumeLow;
97 if (volume < 20) Icon = Icons.NoVolume;
98 if (muted === "true" || !volume) Icon = Icons.VolumeMuted;
99
100 /**
101 * Handle volume change event.
102 * @param {React.ChangeEvent<HTMLInputElement>} e - The change event.
103 */
104 const onChange = (e) => {
105 const value = parseInt(e.target.value, 10);
106 setVolume(value);
107 };
108
109 const onMouseDown = () => setDragging(true);
110 const onMouseUp = () => setDragging(false);
111
112 const formattedVolume = `${volume.toString().padStart(2, "0")}%`;
113
114 const classes = Utils.classNames("sound", {
115 "sound--dragging": dragging,
116 });
117
118 return (
119 <DataWidget.Widget classes={classes} disableSlider>
120 <div className="sound__display">
121 {showIcon && (
122 <SuspenseIcon>
123 <Icon />
124 </SuspenseIcon>
125 )}
126 <span className="sound__value">{formattedVolume}</span>
127 </div>
128 <div className="sound__slider-container">
129 <input
130 type="range"
131 min="0"
132 max="100"
133 step="1"
134 value={volume}
135 className="sound__slider"
136 onMouseDown={onMouseDown}
137 onMouseUp={onMouseUp}
138 onChange={onChange}
139 />
140 </div>
141 </DataWidget.Widget>
142 );
143});
144
145Widget.displayName = "Sound";
146
147/**
148 * Set the system volume.
149 * @param {number} volume - The volume to set.
150 */
151function setSound(volume) {
152 if (volume === undefined) return;
153 Uebersicht.run(`osascript -e 'set volume output volume ${volume}'`);
154}