From d06784958d73159b5e4abafe5114804662114ed7 Mon Sep 17 00:00:00 2001
From: Ryan Schanzenbacher <ryan@rschanz.org>
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 <DataWidgetLoader.Widget className="mpd" />;
+  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<HTMLInputElement>} 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 (
+    <DataWidget.Widget
+      classes={classes}
+      Icon={showIcon ? Icon : null}
+      onClick={onClick}
+      showSpecter={showSpecter && isPlaying}
+      disableSlider
+    >
+      <div
+        ref={ref}
+        className="mpd__outer"
+        onMouseEnter={onMouseEnter}
+        onMouseLeave={onMouseLeave}
+      >
+        <div className="mpd__inner">
+          <div className="mpd__info">{trackInfo}</div>
+        </div>
+        <div className="mpd__slider-container" onClick={stopPropagation}>
+          <input
+            type="range"
+            min="0"
+            max="100"
+            step="1"
+            value={volume}
+            className="mpd__slider"
+            onMouseDown={onMouseDown}
+            onMouseUp={onMouseUp}
+            onChange={onChange}
+          />
+        </div>
+      </div>
+    </DataWidget.Widget>
+  );
+});
+
+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<void>}
+ */
+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

