aboutsummaryrefslogtreecommitdiff
path: root/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx')
-rwxr-xr-xusers/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx228
1 files changed, 228 insertions, 0 deletions
diff --git a/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx
new file mode 100755
index 0000000..53449b1
--- /dev/null
+++ b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx
@@ -0,0 +1,228 @@
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 useWidgetRefresh from "../../hooks/use-widget-refresh";
6import useServerSocket from "../../hooks/use-server-socket";
7import { useSimpleBarContext } from "../simple-bar-context.jsx";
8import * as Utils from "../../utils";
9
10export { mpdStyles as styles } from "../../styles/components/data/mpd";
11
12const { React } = Uebersicht;
13
14const DEFAULT_REFRESH_FREQUENCY = 10000;
15
16/**
17 * MPD Widget component
18 * @returns {JSX.Element|null} The MPD widget
19 */
20export const Widget = React.memo(() => {
21 const ref = React.useRef();
22 const { displayIndex, settings } = useSimpleBarContext();
23 const { widgets, mpdWidgetOptions } = settings;
24 const { mpdWidget } = widgets;
25 const {
26 refreshFrequency,
27 showSpecter,
28 mpdBinaryPath,
29 mpdHost,
30 mpdPort,
31 mpdFormatString,
32 showOnDisplay,
33 showIcon,
34 } = mpdWidgetOptions;
35
36 // Determine the refresh frequency for the widget
37 const refresh = React.useMemo(
38 () =>
39 Utils.getRefreshFrequency(refreshFrequency, DEFAULT_REFRESH_FREQUENCY),
40 [refreshFrequency],
41 );
42
43 // Determine if the widget should be visible on the current display
44 const visible =
45 Utils.isVisibleOnDisplay(displayIndex, showOnDisplay) && mpdWidget;
46
47 const [state, setState] = React.useState();
48 const [loading, setLoading] = React.useState(visible);
49 const [isMpdActive, setIsMpdActive] = React.useState(false);
50 const { volume: _volume } = state || {};
51 const defaultVolume = _volume && parseInt(_volume);
52 const [volume, setVolume] = React.useState(defaultVolume);
53 const [dragging, setDragging] = React.useState(false);
54
55 /**
56 * Reset the widget state
57 */
58 const resetWidget = () => {
59 setState(undefined);
60 setLoading(false);
61 setIsMpdActive(false);
62 };
63
64 /**
65 * Fetch MPD data
66 */
67 const getMpd = React.useCallback(async () => {
68 if (!visible) return;
69
70 try {
71 const mpdProcess = await Utils.cachedRun(
72 `pgrep -x mpd > /dev/null && echo "true" || echo "false"`,
73 refresh,
74 );
75
76 if (Utils.cleanupOutput(mpdProcess) === "false") {
77 setLoading(false);
78 setIsMpdActive(false);
79 return;
80 }
81
82 const [playerState, trackInfo, volumeState] = await Promise.all([
83 Utils.cachedRun(
84 `${mpdBinaryPath} --host ${mpdHost} --port ${mpdPort} | head -n 2 | tail -n 1 | awk '{print substr($1,2,length($1)-2)}' 2>/dev/null || echo "stopped"`,
85 refresh,
86 ),
87 Utils.cachedRun(
88 `${mpdBinaryPath} --host ${mpdHost} --port ${mpdPort} --format "${mpdFormatString}" | head -n 1`,
89 refresh,
90 ),
91 Utils.cachedRun(
92 `${mpdBinaryPath} --host ${mpdHost} --port ${mpdPort} volume | sed -e 's/volume:[ ]*//g' -e 's/%//g'`,
93 refresh,
94 ),
95 ]);
96 if (Utils.cleanupOutput(trackInfo) === "") {
97 setLoading(false);
98 setIsMpdActive(false);
99 return;
100 }
101 setState({
102 playerState: Utils.cleanupOutput(playerState),
103 trackInfo: Utils.cleanupOutput(trackInfo),
104 volume: Utils.cleanupOutput(volumeState),
105 });
106 setIsMpdActive(true);
107 setLoading(false);
108 } catch {
109 setLoading(false);
110 setIsMpdActive(false);
111 }
112 }, [visible, mpdBinaryPath, mpdHost, mpdPort, mpdFormatString, refresh]);
113
114 // Update the volume when dragging ends
115 React.useEffect(() => {
116 if (!dragging) setSound(mpdBinaryPath, mpdHost, mpdPort, volume);
117 }, [dragging, mpdBinaryPath, mpdHost, mpdPort, volume]);
118
119 // Set the volume state when dragging ends
120 React.useEffect(() => {
121 if (!dragging) setVolume(volume);
122 }, [dragging, volume]);
123
124 useServerSocket("mpd", visible, getMpd, resetWidget, setLoading);
125 useWidgetRefresh(visible, getMpd, refresh);
126
127 if (loading) return <DataWidgetLoader.Widget className="mpd" />;
128 if (!state || !isMpdActive) return null;
129 const { playerState, trackInfo } = state;
130
131 if (!trackInfo.length) return null;
132
133 const isPlaying = playerState === "playing";
134 const Icon = isPlaying ? Icons.Playing : Icons.Paused;
135
136 /**
137 * Handle click event to toggle play/pause
138 * @param {React.MouseEvent} e - The click event
139 */
140 const onClick = async (e) => {
141 Utils.clickEffect(e);
142 await togglePlay(mpdBinaryPath, mpdHost, mpdPort);
143 await getMpd();
144 };
145
146 /**
147 * Handle volume change event
148 * @param {React.ChangeEvent<HTMLInputElement>} e - The change event
149 */
150 const onChange = (e) => {
151 const value = parseInt(e.target.value);
152 setVolume(value);
153 };
154
155 const onMouseDown = () => setDragging(true);
156
157 const onMouseUp = () => setDragging(false);
158
159 const stopPropagation = (e) => e.stopPropagation();
160
161 const onMouseEnter = () =>
162 Utils.startSliding(ref.current, ".mpd__inner", ".mpd__info");
163 const onMouseLeave = () => Utils.stopSliding(ref.current, ".mpd__slider");
164
165 const classes = Utils.classNames("sound", "mpd", {
166 "mpd--playing": isPlaying,
167 "mpd--dragging": dragging,
168 });
169
170 return (
171 <DataWidget.Widget
172 classes={classes}
173 Icon={showIcon ? Icon : null}
174 onClick={onClick}
175 showSpecter={showSpecter && isPlaying}
176 disableSlider
177 >
178 <div
179 ref={ref}
180 className="mpd__outer"
181 onMouseEnter={onMouseEnter}
182 onMouseLeave={onMouseLeave}
183 >
184 <div className="mpd__inner">
185 <div className="mpd__info">{trackInfo}</div>
186 </div>
187 <div className="mpd__slider-container" onClick={stopPropagation}>
188 <input
189 type="range"
190 min="0"
191 max="100"
192 step="1"
193 value={volume}
194 className="mpd__slider"
195 onMouseDown={onMouseDown}
196 onMouseUp={onMouseUp}
197 onChange={onChange}
198 />
199 </div>
200 </div>
201 </DataWidget.Widget>
202 );
203});
204
205Widget.displayName = "Mpd";
206
207/**
208 * Toggle play/pause state of MPD
209 * @param {string} path - The path to the MPD binary
210 * @param {string} host - The MPD host
211 * @param {string} port - The MPD port
212 * @returns {Promise<void>}
213 */
214async function togglePlay(path, host, port) {
215 return Uebersicht.run(`${path} --host ${host} --port ${port} toggle`);
216}
217
218/**
219 * Set the volume of MPD
220 * @param {string} path - The path to the MPD binary
221 * @param {string} host - The MPD host
222 * @param {string} port - The MPD port
223 * @param {number} volume - The volume level to set
224 */
225function setSound(path, host, port, volume) {
226 if (!volume) return;
227 Uebersicht.run(`${path} --host ${host} --port ${port} volume ${volume}`);
228}