From d06784958d73159b5e4abafe5114804662114ed7 Mon Sep 17 00:00:00 2001 From: Ryan Schanzenbacher Date: Thu, 9 Jul 2026 22:15:53 -0400 Subject: move ubersicht modules in tree --- .../simple-bar-source/lib/components/data/mpd.jsx | 228 +++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100755 users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx (limited to 'users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/mpd.jsx') 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 @@ +import * as Uebersicht from "uebersicht"; +import * as DataWidget from "./data-widget.jsx"; +import * as DataWidgetLoader from "./data-widget-loader.jsx"; +import * as Icons from "../icons/icons.jsx"; +import useWidgetRefresh from "../../hooks/use-widget-refresh"; +import useServerSocket from "../../hooks/use-server-socket"; +import { useSimpleBarContext } from "../simple-bar-context.jsx"; +import * as Utils from "../../utils"; + +export { mpdStyles as styles } from "../../styles/components/data/mpd"; + +const { React } = Uebersicht; + +const DEFAULT_REFRESH_FREQUENCY = 10000; + +/** + * MPD Widget component + * @returns {JSX.Element|null} The MPD widget + */ +export const Widget = React.memo(() => { + const ref = React.useRef(); + const { displayIndex, settings } = useSimpleBarContext(); + const { widgets, mpdWidgetOptions } = settings; + const { mpdWidget } = widgets; + const { + refreshFrequency, + showSpecter, + mpdBinaryPath, + mpdHost, + mpdPort, + mpdFormatString, + showOnDisplay, + showIcon, + } = mpdWidgetOptions; + + // Determine the refresh frequency for the widget + const refresh = React.useMemo( + () => + Utils.getRefreshFrequency(refreshFrequency, DEFAULT_REFRESH_FREQUENCY), + [refreshFrequency], + ); + + // Determine if the widget should be visible on the current display + const visible = + Utils.isVisibleOnDisplay(displayIndex, showOnDisplay) && mpdWidget; + + const [state, setState] = React.useState(); + const [loading, setLoading] = React.useState(visible); + const [isMpdActive, setIsMpdActive] = React.useState(false); + const { volume: _volume } = state || {}; + const defaultVolume = _volume && parseInt(_volume); + const [volume, setVolume] = React.useState(defaultVolume); + const [dragging, setDragging] = React.useState(false); + + /** + * Reset the widget state + */ + const resetWidget = () => { + setState(undefined); + setLoading(false); + setIsMpdActive(false); + }; + + /** + * Fetch MPD data + */ + const getMpd = React.useCallback(async () => { + if (!visible) return; + + try { + const mpdProcess = await Utils.cachedRun( + `pgrep -x mpd > /dev/null && echo "true" || echo "false"`, + refresh, + ); + + if (Utils.cleanupOutput(mpdProcess) === "false") { + setLoading(false); + setIsMpdActive(false); + return; + } + + const [playerState, trackInfo, volumeState] = await Promise.all([ + Utils.cachedRun( + `${mpdBinaryPath} --host ${mpdHost} --port ${mpdPort} | head -n 2 | tail -n 1 | awk '{print substr($1,2,length($1)-2)}' 2>/dev/null || echo "stopped"`, + refresh, + ), + Utils.cachedRun( + `${mpdBinaryPath} --host ${mpdHost} --port ${mpdPort} --format "${mpdFormatString}" | head -n 1`, + refresh, + ), + Utils.cachedRun( + `${mpdBinaryPath} --host ${mpdHost} --port ${mpdPort} volume | sed -e 's/volume:[ ]*//g' -e 's/%//g'`, + refresh, + ), + ]); + if (Utils.cleanupOutput(trackInfo) === "") { + setLoading(false); + setIsMpdActive(false); + return; + } + setState({ + playerState: Utils.cleanupOutput(playerState), + trackInfo: Utils.cleanupOutput(trackInfo), + volume: Utils.cleanupOutput(volumeState), + }); + setIsMpdActive(true); + setLoading(false); + } catch { + setLoading(false); + setIsMpdActive(false); + } + }, [visible, mpdBinaryPath, mpdHost, mpdPort, mpdFormatString, refresh]); + + // Update the volume when dragging ends + React.useEffect(() => { + if (!dragging) setSound(mpdBinaryPath, mpdHost, mpdPort, volume); + }, [dragging, mpdBinaryPath, mpdHost, mpdPort, volume]); + + // Set the volume state when dragging ends + React.useEffect(() => { + if (!dragging) setVolume(volume); + }, [dragging, volume]); + + useServerSocket("mpd", visible, getMpd, resetWidget, setLoading); + useWidgetRefresh(visible, getMpd, refresh); + + if (loading) return ; + if (!state || !isMpdActive) return null; + const { playerState, trackInfo } = state; + + if (!trackInfo.length) return null; + + const isPlaying = playerState === "playing"; + const Icon = isPlaying ? Icons.Playing : Icons.Paused; + + /** + * Handle click event to toggle play/pause + * @param {React.MouseEvent} e - The click event + */ + const onClick = async (e) => { + Utils.clickEffect(e); + await togglePlay(mpdBinaryPath, mpdHost, mpdPort); + await getMpd(); + }; + + /** + * Handle volume change event + * @param {React.ChangeEvent} e - The change event + */ + const onChange = (e) => { + const value = parseInt(e.target.value); + setVolume(value); + }; + + const onMouseDown = () => setDragging(true); + + const onMouseUp = () => setDragging(false); + + const stopPropagation = (e) => e.stopPropagation(); + + const onMouseEnter = () => + Utils.startSliding(ref.current, ".mpd__inner", ".mpd__info"); + const onMouseLeave = () => Utils.stopSliding(ref.current, ".mpd__slider"); + + const classes = Utils.classNames("sound", "mpd", { + "mpd--playing": isPlaying, + "mpd--dragging": dragging, + }); + + return ( + +
+
+
{trackInfo}
+
+
+ +
+
+
+ ); +}); + +Widget.displayName = "Mpd"; + +/** + * Toggle play/pause state of MPD + * @param {string} path - The path to the MPD binary + * @param {string} host - The MPD host + * @param {string} port - The MPD port + * @returns {Promise} + */ +async function togglePlay(path, host, port) { + return Uebersicht.run(`${path} --host ${host} --port ${port} toggle`); +} + +/** + * Set the volume of MPD + * @param {string} path - The path to the MPD binary + * @param {string} host - The MPD host + * @param {string} port - The MPD port + * @param {number} volume - The volume level to set + */ +function setSound(path, host, port, volume) { + if (!volume) return; + Uebersicht.run(`${path} --host ${host} --port ${port} volume ${volume}`); +} -- cgit v1.3