summaryrefslogtreecommitdiff
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.jsx156
1 files changed, 156 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..3b39594
--- /dev/null
+++ b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/sound.jsx
@@ -0,0 +1,156 @@
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 500,
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 volume state when the fetched volume changes.
75 React.useEffect(() => {
76 setVolume((currentVolume) => {
77 if (_volume && currentVolume !== parseInt(_volume, 10)) {
78 return parseInt(_volume, 10);
79 }
80 return currentVolume;
81 });
82 }, [_volume]);
83
84 if (loading) return <DataWidgetLoader.Widget className="sound" />;
85 if (!state || volume === undefined) return null;
86
87 const { muted } = state;
88 if (_volume === "missing value" || muted === "missing value") return null;
89
90 let Icon = Icons.VolumeHigh;
91 if (volume < 50) Icon = Icons.VolumeLow;
92 if (volume < 20) Icon = Icons.NoVolume;
93 if (muted === "true" || !volume) Icon = Icons.VolumeMuted;
94
95 /**
96 * Handle volume change event.
97 * @param {React.ChangeEvent<HTMLInputElement>} e - The change event.
98 */
99 const onChange = (e) => {
100 const value = parseInt(e.target.value, 10);
101 setVolume(value);
102 };
103
104 const onInteractionEnd = (e) => {
105 setDragging(false);
106 const finalVolume = parseInt(e.target.value, 10);
107 setSound(finalVolume);
108 };
109
110 // const onMouseDown = () => setDragging(true);
111 // const onMouseUp = () => setDragging(false);
112
113 const formattedVolume = `${volume.toString().padStart(2, "0")}%`;
114
115 const classes = Utils.classNames("sound", {
116 "sound--dragging": dragging,
117 });
118
119 return (
120 <DataWidget.Widget classes={classes} disableSlider>
121 <div className="sound__display">
122 {showIcon && (
123 <SuspenseIcon>
124 <Icon />
125 </SuspenseIcon>
126 )}
127 <span className="sound__value">{formattedVolume}</span>
128 </div>
129 <div className="sound__slider-container">
130 <input
131 type="range"
132 min="0"
133 max="100"
134 step="1"
135 value={volume}
136 className="sound__slider"
137 onMouseDown={() => setDragging(true)}
138 onMouseUp={onInteractionEnd}
139 onKeyUp={onInteractionEnd}
140 onChange={onChange}
141 />
142 </div>
143 </DataWidget.Widget>
144 );
145});
146
147Widget.displayName = "Sound";
148
149/**
150 * Set the system volume.
151 * @param {number} volume - The volume to set.
152 */
153function setSound(volume) {
154 if (volume === undefined) return;
155 Uebersicht.run(`osascript -e 'set volume output volume ${volume}'`);
156}