aboutsummaryrefslogtreecommitdiff
path: root/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/music.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/music.jsx')
-rwxr-xr-xusers/ryan/modules/simple-bar/simple-bar-source/lib/components/data/music.jsx161
1 files changed, 161 insertions, 0 deletions
diff --git a/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/music.jsx b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/music.jsx
new file mode 100755
index 0000000..f0fabd6
--- /dev/null
+++ b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/music.jsx
@@ -0,0 +1,161 @@
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 { musicStyles as styles } from "../../styles/components/data/music";
11
12const { React } = Uebersicht;
13
14const DEFAULT_REFRESH_FREQUENCY = 10000;
15
16/**
17 * Music widget component.
18 * @returns {JSX.Element|null} The music widget.
19 */
20export const Widget = React.memo(() => {
21 const { displayIndex, settings } = useSimpleBarContext();
22 const { widgets, musicWidgetOptions } = settings;
23 const { musicWidget } = widgets;
24 const { refreshFrequency, showSpecter, showOnDisplay, showIcon } =
25 musicWidgetOptions;
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) && musicWidget;
37
38 const [state, setState] = React.useState();
39 const [loading, setLoading] = React.useState(visible);
40 const [isMusicActive, setIsMusicActive] = React.useState(false);
41
42 /**
43 * Resets the widget state.
44 */
45 const resetWidget = () => {
46 setState(undefined);
47 setLoading(false);
48 setIsMusicActive(false);
49 };
50
51 /**
52 * Fetches the current music information.
53 */
54 const getMusic = React.useCallback(async () => {
55 if (!visible) return;
56 const osVersion = await Utils.cachedRun(`sw_vers -productVersion`, refresh);
57 const processName =
58 Utils.cleanupOutput(osVersion) === "10.15" ? "iTunes" : "Music";
59 const isRunning = await Utils.cachedRun(
60 `pgrep -xq "${processName}" && echo "true" || echo "false"`,
61 refresh,
62 );
63 if (Utils.cleanupOutput(isRunning) === "false") {
64 setLoading(false);
65 setIsMusicActive(false);
66 return;
67 }
68 const output = await Utils.cachedRun(
69 `osascript -e 'tell application "${processName}"' -e 'set output to (player state as string) & "|" & (name of current track as string) & "|" & (artist of current track as string)' -e 'end tell' 2>/dev/null || echo "stopped|unknown track|unknown artist"`,
70 refresh,
71 );
72 const [playerState, trackName, artistName] =
73 Utils.cleanupOutput(output).split("|");
74 setState({
75 playerState,
76 trackName,
77 artistName,
78 processName: Utils.cleanupOutput(processName),
79 });
80 setIsMusicActive(true);
81 setLoading(false);
82 }, [visible, refresh]);
83
84 // Use server socket to listen for music events
85 useServerSocket("music", visible, getMusic, resetWidget, setLoading);
86 // Refresh the widget at the specified interval
87 useWidgetRefresh(visible, getMusic, refresh);
88
89 if (loading) return <DataWidgetLoader.Widget className="music" />;
90 if (!state || !isMusicActive) return null;
91 const { processName, playerState, trackName, artistName } = state;
92
93 if (!trackName.length) return null;
94
95 const isPlaying = playerState === "playing";
96 const Icon = isPlaying ? Icons.Playing : Icons.Paused;
97
98 /**
99 * Handles click event to toggle play/pause.
100 * @param {React.MouseEvent} e - The click event.
101 */
102 const onClick = (e) => {
103 Utils.clickEffect(e);
104 togglePlay(!isPlaying, processName);
105 getMusic();
106 };
107
108 /**
109 * Handles right-click event to skip to the next track.
110 * @param {React.MouseEvent} e - The right-click event.
111 */
112 const onRightClick = (e) => {
113 Utils.clickEffect(e);
114 Uebersicht.run(
115 `osascript -e 'tell application "${processName}" to Next Track'`,
116 );
117 getMusic();
118 };
119
120 /**
121 * Handles middle-click event to open the music application.
122 * @param {React.MouseEvent} e - The middle-click event.
123 */
124 const onMiddleClick = (e) => {
125 Utils.clickEffect(e);
126 Uebersicht.run(`open -a '${processName}'`);
127 getMusic();
128 };
129
130 const classes = Utils.classNames("music", {
131 "music--playing": isPlaying,
132 });
133
134 return (
135 <DataWidget.Widget
136 classes={classes}
137 Icon={showIcon ? Icon : null}
138 onClick={onClick}
139 onRightClick={onRightClick}
140 onMiddleClick={onMiddleClick}
141 showSpecter={showSpecter && isPlaying}
142 >
143 {trackName} - {artistName}
144 </DataWidget.Widget>
145 );
146});
147
148Widget.displayName = "Music";
149
150/**
151 * Toggles play/pause state of the music application.
152 * @param {boolean} isPaused - Whether the music is paused.
153 * @param {string} processName - The name of the music application process.
154 */
155function togglePlay(isPaused, processName) {
156 if (isPaused) {
157 Uebersicht.run(`osascript -e 'tell application "${processName}" to play'`);
158 } else {
159 Uebersicht.run(`osascript -e 'tell application "${processName}" to pause'`);
160 }
161}