Blame


1 d9c21e6f 2023-07-24 spnw #import <Cocoa/Cocoa.h>
2 d9c21e6f 2023-07-24 spnw #include <MacTypes.h>
3 d9c21e6f 2023-07-24 spnw #include <assert.h>
4 d9c21e6f 2023-07-24 spnw #include <mpd/client.h>
5 d9c21e6f 2023-07-24 spnw #include <objc/NSObjCRuntime.h>
6 d9c21e6f 2023-07-24 spnw #include <stdbool.h>
7 d9c21e6f 2023-07-24 spnw #include <stdio.h>
8 d9c21e6f 2023-07-24 spnw #include <stdlib.h>
9 d9c21e6f 2023-07-24 spnw #include <string.h>
10 d9c21e6f 2023-07-24 spnw
11 2fcdff49 2024-04-06 spnw #define VERSION "0.1.1"
12 d9c21e6f 2023-07-24 spnw #define TITLE_MAX_LENGTH 96
13 d9c21e6f 2023-07-24 spnw #define SLEEP_INTERVAL 0.2
14 d9c21e6f 2023-07-24 spnw
15 d9c21e6f 2023-07-24 spnw static NSString *utf8String(const char *s) {
16 d9c21e6f 2023-07-24 spnw return [NSString stringWithCString:s encoding:NSUTF8StringEncoding];
17 d9c21e6f 2023-07-24 spnw }
18 d9c21e6f 2023-07-24 spnw
19 d9c21e6f 2023-07-24 spnw static NSString *formatTime(unsigned int t) {
20 d9c21e6f 2023-07-24 spnw unsigned int hours = (t / 3600), minutes = (t % 3600 / 60),
21 d9c21e6f 2023-07-24 spnw seconds = (t % 60);
22 d9c21e6f 2023-07-24 spnw
23 d9c21e6f 2023-07-24 spnw if (hours)
24 d9c21e6f 2023-07-24 spnw return [NSString stringWithFormat:@"%u:%02u:%02u", hours, minutes, seconds];
25 d9c21e6f 2023-07-24 spnw else
26 d9c21e6f 2023-07-24 spnw return [NSString stringWithFormat:@"%u:%02u", minutes, seconds];
27 d9c21e6f 2023-07-24 spnw }
28 d9c21e6f 2023-07-24 spnw
29 d9c21e6f 2023-07-24 spnw @interface MPDController : NSObject
30 d9c21e6f 2023-07-24 spnw @end
31 d9c21e6f 2023-07-24 spnw
32 d9c21e6f 2023-07-24 spnw @implementation MPDController {
33 d9c21e6f 2023-07-24 spnw struct mpd_connection *connection;
34 d9c21e6f 2023-07-24 spnw BOOL songMenuNeedsUpdate;
35 d9c21e6f 2023-07-24 spnw
36 d9c21e6f 2023-07-24 spnw NSMenu *controlMenu;
37 d9c21e6f 2023-07-24 spnw NSMenuItem *timeItem, *timeSeparator, *playPauseItem, *stopItem, *nextItem,
38 d9c21e6f 2023-07-24 spnw *previousItem, *singleItem, *clearItem, *updateDatabaseItem,
39 d9c21e6f 2023-07-24 spnw *addToQueueItem;
40 d9c21e6f 2023-07-24 spnw NSImage *playImage, *pauseImage, *stopImage, *nextImage, *previousImage,
41 d9c21e6f 2023-07-24 spnw *singleImage, *clearImage;
42 d9c21e6f 2023-07-24 spnw NSButton *menuButton;
43 d9c21e6f 2023-07-24 spnw
44 d9c21e6f 2023-07-24 spnw NSMenu *songMenu;
45 d9c21e6f 2023-07-24 spnw NSMapTable *songMap;
46 d9c21e6f 2023-07-24 spnw }
47 d9c21e6f 2023-07-24 spnw - (void)connect {
48 d9c21e6f 2023-07-24 spnw assert(connection == NULL);
49 d9c21e6f 2023-07-24 spnw
50 d9c21e6f 2023-07-24 spnw connection = mpd_connection_new(NULL, 0, 0);
51 d9c21e6f 2023-07-24 spnw if (!connection) {
52 d9c21e6f 2023-07-24 spnw NSLog(@"Failed to create MPD connection");
53 d9c21e6f 2023-07-24 spnw exit(1);
54 d9c21e6f 2023-07-24 spnw }
55 d9c21e6f 2023-07-24 spnw
56 d9c21e6f 2023-07-24 spnw songMenuNeedsUpdate = YES;
57 d9c21e6f 2023-07-24 spnw }
58 d9c21e6f 2023-07-24 spnw - (void)disconnect {
59 d9c21e6f 2023-07-24 spnw assert(connection != NULL);
60 d9c21e6f 2023-07-24 spnw mpd_connection_free(connection);
61 d9c21e6f 2023-07-24 spnw connection = NULL;
62 d9c21e6f 2023-07-24 spnw [songMap removeAllObjects];
63 d9c21e6f 2023-07-24 spnw [songMenu removeAllItems];
64 d9c21e6f 2023-07-24 spnw }
65 d9c21e6f 2023-07-24 spnw - (void)disableAllItems {
66 d9c21e6f 2023-07-24 spnw [playPauseItem setEnabled:NO];
67 d9c21e6f 2023-07-24 spnw [stopItem setEnabled:NO];
68 d9c21e6f 2023-07-24 spnw [nextItem setEnabled:NO];
69 d9c21e6f 2023-07-24 spnw [previousItem setEnabled:NO];
70 d9c21e6f 2023-07-24 spnw [singleItem setEnabled:NO];
71 d9c21e6f 2023-07-24 spnw [updateDatabaseItem setEnabled:NO];
72 d9c21e6f 2023-07-24 spnw [addToQueueItem setEnabled:NO];
73 d9c21e6f 2023-07-24 spnw }
74 d9c21e6f 2023-07-24 spnw - (void)updateLoop {
75 d9c21e6f 2023-07-24 spnw for (;;) {
76 d9c21e6f 2023-07-24 spnw [NSThread sleepForTimeInterval:SLEEP_INTERVAL];
77 d9c21e6f 2023-07-24 spnw if (!connection) {
78 d9c21e6f 2023-07-24 spnw [self disableAllItems];
79 d9c21e6f 2023-07-24 spnw [self showError:@"Failed to get status (is MPD running?)"];
80 d9c21e6f 2023-07-24 spnw [self connect];
81 d9c21e6f 2023-07-24 spnw }
82 d9c21e6f 2023-07-24 spnw if (!mpd_send_idle(connection)) {
83 d9c21e6f 2023-07-24 spnw [self disconnect];
84 d9c21e6f 2023-07-24 spnw continue;
85 d9c21e6f 2023-07-24 spnw }
86 d9c21e6f 2023-07-24 spnw enum mpd_idle mask = mpd_run_noidle(connection);
87 d9c21e6f 2023-07-24 spnw enum mpd_error err;
88 d9c21e6f 2023-07-24 spnw if (mask == 0 &&
89 d9c21e6f 2023-07-24 spnw (err = mpd_connection_get_error(connection)) != MPD_ERROR_SUCCESS) {
90 d9c21e6f 2023-07-24 spnw NSLog(@"mpd_run_idle error code %d: %s", err,
91 d9c21e6f 2023-07-24 spnw mpd_connection_get_error_message(connection));
92 d9c21e6f 2023-07-24 spnw [self disconnect];
93 d9c21e6f 2023-07-24 spnw continue;
94 d9c21e6f 2023-07-24 spnw }
95 d9c21e6f 2023-07-24 spnw
96 d9c21e6f 2023-07-24 spnw if ((mask & MPD_IDLE_DATABASE) || songMenuNeedsUpdate) {
97 d9c21e6f 2023-07-24 spnw [self performSelectorOnMainThread:@selector(updateSongMenu)
98 d9c21e6f 2023-07-24 spnw withObject:nil
99 d9c21e6f 2023-07-24 spnw waitUntilDone:YES];
100 d9c21e6f 2023-07-24 spnw songMenuNeedsUpdate = NO;
101 d9c21e6f 2023-07-24 spnw }
102 d9c21e6f 2023-07-24 spnw
103 d9c21e6f 2023-07-24 spnw [self performSelectorOnMainThread:@selector(updateControlMenu)
104 d9c21e6f 2023-07-24 spnw withObject:nil
105 d9c21e6f 2023-07-24 spnw waitUntilDone:YES];
106 d9c21e6f 2023-07-24 spnw
107 d9c21e6f 2023-07-24 spnw [self performSelectorOnMainThread:@selector(updateStatus)
108 d9c21e6f 2023-07-24 spnw withObject:nil
109 d9c21e6f 2023-07-24 spnw waitUntilDone:YES];
110 d9c21e6f 2023-07-24 spnw }
111 d9c21e6f 2023-07-24 spnw }
112 d9c21e6f 2023-07-24 spnw - (void)updateControlMenu {
113 d9c21e6f 2023-07-24 spnw if (!connection)
114 d9c21e6f 2023-07-24 spnw return;
115 d9c21e6f 2023-07-24 spnw
116 d9c21e6f 2023-07-24 spnw struct mpd_status *status = NULL;
117 d9c21e6f 2023-07-24 spnw struct mpd_song *song = NULL;
118 d9c21e6f 2023-07-24 spnw NSString *errorMsg = nil;
119 d9c21e6f 2023-07-24 spnw
120 d9c21e6f 2023-07-24 spnw NSMutableString *output = [NSMutableString new];
121 d9c21e6f 2023-07-24 spnw
122 d9c21e6f 2023-07-24 spnw status = mpd_run_status(connection);
123 d9c21e6f 2023-07-24 spnw if (!status) {
124 d9c21e6f 2023-07-24 spnw NSLog(@"%s", mpd_connection_get_error_message(connection));
125 d9c21e6f 2023-07-24 spnw
126 d9c21e6f 2023-07-24 spnw [self disconnect];
127 d9c21e6f 2023-07-24 spnw goto cleanup;
128 d9c21e6f 2023-07-24 spnw }
129 d9c21e6f 2023-07-24 spnw
130 d9c21e6f 2023-07-24 spnw enum mpd_state state = mpd_status_get_state(status);
131 d9c21e6f 2023-07-24 spnw enum mpd_single_state single = mpd_status_get_single_state(status);
132 d9c21e6f 2023-07-24 spnw bool active = (state == MPD_STATE_PLAY || state == MPD_STATE_PAUSE);
133 d9c21e6f 2023-07-24 spnw if (active) {
134 d9c21e6f 2023-07-24 spnw song = mpd_run_current_song(connection);
135 d9c21e6f 2023-07-24 spnw if (!song) {
136 d9c21e6f 2023-07-24 spnw errorMsg = @"Failed to retrieve current song";
137 d9c21e6f 2023-07-24 spnw goto cleanup;
138 d9c21e6f 2023-07-24 spnw }
139 d9c21e6f 2023-07-24 spnw
140 d9c21e6f 2023-07-24 spnw if (mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) {
141 d9c21e6f 2023-07-24 spnw errorMsg = utf8String(mpd_connection_get_error_message(connection));
142 d9c21e6f 2023-07-24 spnw goto cleanup;
143 d9c21e6f 2023-07-24 spnw }
144 d9c21e6f 2023-07-24 spnw
145 d9c21e6f 2023-07-24 spnw if (state == MPD_STATE_PAUSE)
146 d9c21e6f 2023-07-24 spnw [menuButton setImage:pauseImage];
147 d9c21e6f 2023-07-24 spnw else if (state == MPD_STATE_PLAY &&
148 d9c21e6f 2023-07-24 spnw (single == MPD_SINGLE_ON || single == MPD_SINGLE_ONESHOT))
149 d9c21e6f 2023-07-24 spnw [menuButton setImage:singleImage];
150 d9c21e6f 2023-07-24 spnw else
151 d9c21e6f 2023-07-24 spnw [menuButton setImage:nil];
152 d9c21e6f 2023-07-24 spnw
153 d9c21e6f 2023-07-24 spnw const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
154 d9c21e6f 2023-07-24 spnw const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
155 d9c21e6f 2023-07-24 spnw
156 d9c21e6f 2023-07-24 spnw if (artist)
157 d9c21e6f 2023-07-24 spnw [output appendString:utf8String(artist)];
158 d9c21e6f 2023-07-24 spnw if (artist && title)
159 d9c21e6f 2023-07-24 spnw [output appendString:@" - "];
160 d9c21e6f 2023-07-24 spnw if (title)
161 d9c21e6f 2023-07-24 spnw [output appendString:utf8String(title)];
162 d9c21e6f 2023-07-24 spnw if (!(artist || title))
163 d9c21e6f 2023-07-24 spnw [output appendString:utf8String(mpd_song_get_uri(song))];
164 d9c21e6f 2023-07-24 spnw } else {
165 d9c21e6f 2023-07-24 spnw [output setString:@"No song playing"];
166 d9c21e6f 2023-07-24 spnw [menuButton setImage:nil];
167 d9c21e6f 2023-07-24 spnw }
168 d9c21e6f 2023-07-24 spnw
169 d9c21e6f 2023-07-24 spnw int song_pos = mpd_status_get_song_pos(status);
170 d9c21e6f 2023-07-24 spnw unsigned int queue_length = mpd_status_get_queue_length(status);
171 d9c21e6f 2023-07-24 spnw if (song_pos < 0)
172 d9c21e6f 2023-07-24 spnw [output appendFormat:@" (%u)", queue_length];
173 d9c21e6f 2023-07-24 spnw else
174 d9c21e6f 2023-07-24 spnw [output appendFormat:@" (%u/%u)", song_pos + 1, queue_length];
175 d9c21e6f 2023-07-24 spnw
176 d9c21e6f 2023-07-24 spnw if ([output length] > TITLE_MAX_LENGTH) {
177 d9c21e6f 2023-07-24 spnw int leftCount = (TITLE_MAX_LENGTH - 3) / 2;
178 d9c21e6f 2023-07-24 spnw int rightCount = TITLE_MAX_LENGTH - leftCount - 3;
179 d9c21e6f 2023-07-24 spnw [menuButton setTitle:[@[
180 d9c21e6f 2023-07-24 spnw [output substringToIndex:leftCount],
181 d9c21e6f 2023-07-24 spnw [output substringFromIndex:[output length] - rightCount]
182 d9c21e6f 2023-07-24 spnw ] componentsJoinedByString:@"..."]];
183 d9c21e6f 2023-07-24 spnw } else
184 d9c21e6f 2023-07-24 spnw [menuButton setTitle:output];
185 d9c21e6f 2023-07-24 spnw
186 d9c21e6f 2023-07-24 spnw if (state == MPD_STATE_PLAY) {
187 d9c21e6f 2023-07-24 spnw [playPauseItem setTitle:@"Pause"];
188 d9c21e6f 2023-07-24 spnw [playPauseItem setImage:pauseImage];
189 d9c21e6f 2023-07-24 spnw [playPauseItem setAction:@selector(pause)];
190 d9c21e6f 2023-07-24 spnw [playPauseItem setEnabled:YES];
191 d9c21e6f 2023-07-24 spnw } else {
192 d9c21e6f 2023-07-24 spnw [playPauseItem setTitle:@"Play"];
193 d9c21e6f 2023-07-24 spnw [playPauseItem setImage:playImage];
194 d9c21e6f 2023-07-24 spnw [playPauseItem setAction:@selector(play)];
195 d9c21e6f 2023-07-24 spnw [playPauseItem setEnabled:(queue_length > 0)];
196 d9c21e6f 2023-07-24 spnw }
197 d9c21e6f 2023-07-24 spnw [stopItem setEnabled:active];
198 d9c21e6f 2023-07-24 spnw [nextItem setEnabled:(active && (song_pos < (queue_length - 1)))];
199 d9c21e6f 2023-07-24 spnw [previousItem setEnabled:(active && (song_pos > 0))];
200 d9c21e6f 2023-07-24 spnw
201 d9c21e6f 2023-07-24 spnw if (queue_length == 0 && single == MPD_SINGLE_ONESHOT) {
202 d9c21e6f 2023-07-24 spnw [self single_off];
203 d9c21e6f 2023-07-24 spnw single = MPD_SINGLE_OFF;
204 d9c21e6f 2023-07-24 spnw }
205 d9c21e6f 2023-07-24 spnw
206 d9c21e6f 2023-07-24 spnw if (single == MPD_SINGLE_OFF) {
207 d9c21e6f 2023-07-24 spnw [singleItem setTitle:@"Pause After This Track"];
208 d9c21e6f 2023-07-24 spnw [singleItem setAction:@selector(single_oneshot)];
209 d9c21e6f 2023-07-24 spnw } else {
210 d9c21e6f 2023-07-24 spnw [singleItem setTitle:@"Keep Playing After This Track"];
211 d9c21e6f 2023-07-24 spnw [singleItem setAction:@selector(single_off)];
212 d9c21e6f 2023-07-24 spnw }
213 d9c21e6f 2023-07-24 spnw
214 d9c21e6f 2023-07-24 spnw [singleItem setEnabled:(active && (song_pos < queue_length))];
215 d9c21e6f 2023-07-24 spnw [clearItem setEnabled:(queue_length > 0)];
216 2fcdff49 2024-04-06 spnw [updateDatabaseItem setEnabled:YES];
217 d9c21e6f 2023-07-24 spnw
218 d9c21e6f 2023-07-24 spnw cleanup:
219 d9c21e6f 2023-07-24 spnw if (song)
220 d9c21e6f 2023-07-24 spnw mpd_song_free(song);
221 d9c21e6f 2023-07-24 spnw if (status)
222 d9c21e6f 2023-07-24 spnw mpd_status_free(status);
223 d9c21e6f 2023-07-24 spnw
224 d9c21e6f 2023-07-24 spnw if (errorMsg)
225 d9c21e6f 2023-07-24 spnw [self showError:errorMsg];
226 d9c21e6f 2023-07-24 spnw }
227 d9c21e6f 2023-07-24 spnw - (NSMenuItem *)addControlMenuItemWithTitle:(NSString *)title
228 d9c21e6f 2023-07-24 spnw image:(NSImage *)image
229 d9c21e6f 2023-07-24 spnw action:(SEL)selector {
230 d9c21e6f 2023-07-24 spnw NSMenuItem *item =
231 d9c21e6f 2023-07-24 spnw [controlMenu addItemWithTitle:title action:selector keyEquivalent:@""];
232 d9c21e6f 2023-07-24 spnw [item setTarget:self];
233 d9c21e6f 2023-07-24 spnw [item setEnabled:NO];
234 d9c21e6f 2023-07-24 spnw [item setImage:image];
235 d9c21e6f 2023-07-24 spnw
236 d9c21e6f 2023-07-24 spnw return item;
237 d9c21e6f 2023-07-24 spnw }
238 d9c21e6f 2023-07-24 spnw - (void)initControlMenu {
239 d9c21e6f 2023-07-24 spnw controlMenu = [NSMenu new];
240 d9c21e6f 2023-07-24 spnw [controlMenu setAutoenablesItems:NO];
241 d9c21e6f 2023-07-24 spnw
242 d9c21e6f 2023-07-24 spnw #define ICON(NAME, DESC) \
243 d9c21e6f 2023-07-24 spnw [NSImage imageWithSystemSymbolName:@NAME accessibilityDescription:@DESC]
244 d9c21e6f 2023-07-24 spnw
245 d9c21e6f 2023-07-24 spnw playImage = ICON("play.fill", "Play");
246 d9c21e6f 2023-07-24 spnw pauseImage = ICON("pause.fill", "Pause");
247 d9c21e6f 2023-07-24 spnw stopImage = ICON("stop.fill", "Stop");
248 d9c21e6f 2023-07-24 spnw nextImage = ICON("forward.fill", "Next");
249 d9c21e6f 2023-07-24 spnw previousImage = ICON("backward.fill", "Previous");
250 d9c21e6f 2023-07-24 spnw singleImage = ICON("playpause.fill", "Single");
251 d9c21e6f 2023-07-24 spnw clearImage = ICON("clear.fill", "Clear");
252 d9c21e6f 2023-07-24 spnw
253 d9c21e6f 2023-07-24 spnw timeItem = [NSMenuItem new];
254 d9c21e6f 2023-07-24 spnw [timeItem setEnabled:NO];
255 d9c21e6f 2023-07-24 spnw timeSeparator = [NSMenuItem separatorItem];
256 d9c21e6f 2023-07-24 spnw
257 d9c21e6f 2023-07-24 spnw #define ADD_ITEM(TITLE, IMAGE, ACTION) \
258 d9c21e6f 2023-07-24 spnw [self addControlMenuItemWithTitle:@TITLE image:IMAGE action:@selector(ACTION)]
259 d9c21e6f 2023-07-24 spnw
260 d9c21e6f 2023-07-24 spnw playPauseItem = ADD_ITEM("Play", playImage, play);
261 d9c21e6f 2023-07-24 spnw stopItem = ADD_ITEM("Stop", stopImage, stop);
262 d9c21e6f 2023-07-24 spnw nextItem = ADD_ITEM("Next Track", nextImage, next);
263 d9c21e6f 2023-07-24 spnw previousItem = ADD_ITEM("Previous Track", previousImage, previous);
264 d9c21e6f 2023-07-24 spnw singleItem = ADD_ITEM("Pause After This Track", singleImage, single_oneshot);
265 d9c21e6f 2023-07-24 spnw
266 d9c21e6f 2023-07-24 spnw [controlMenu addItem:[NSMenuItem separatorItem]];
267 d9c21e6f 2023-07-24 spnw
268 d9c21e6f 2023-07-24 spnw updateDatabaseItem = ADD_ITEM("Update Database", nil, update);
269 d9c21e6f 2023-07-24 spnw
270 d9c21e6f 2023-07-24 spnw addToQueueItem = [controlMenu addItemWithTitle:@"Add to Queue"
271 d9c21e6f 2023-07-24 spnw action:nil
272 d9c21e6f 2023-07-24 spnw keyEquivalent:@""];
273 d9c21e6f 2023-07-24 spnw [addToQueueItem setSubmenu:songMenu];
274 d9c21e6f 2023-07-24 spnw [addToQueueItem setEnabled:NO];
275 d9c21e6f 2023-07-24 spnw
276 d9c21e6f 2023-07-24 spnw [controlMenu addItem:[NSMenuItem separatorItem]];
277 d9c21e6f 2023-07-24 spnw
278 d9c21e6f 2023-07-24 spnw clearItem = ADD_ITEM("Clear Queue", nil, clear);
279 d9c21e6f 2023-07-24 spnw
280 d9c21e6f 2023-07-24 spnw [controlMenu addItem:[NSMenuItem separatorItem]];
281 d9c21e6f 2023-07-24 spnw [controlMenu addItemWithTitle:@"Quit MPC Bar"
282 d9c21e6f 2023-07-24 spnw action:@selector(terminate:)
283 d9c21e6f 2023-07-24 spnw keyEquivalent:@"q"];
284 d9c21e6f 2023-07-24 spnw
285 d9c21e6f 2023-07-24 spnw NSStatusBar *bar = [NSStatusBar systemStatusBar];
286 d9c21e6f 2023-07-24 spnw NSStatusItem *item = [bar statusItemWithLength:NSVariableStatusItemLength];
287 d9c21e6f 2023-07-24 spnw menuButton = [item button];
288 d9c21e6f 2023-07-24 spnw [menuButton setImagePosition:NSImageLeft];
289 d9c21e6f 2023-07-24 spnw [item setMenu:controlMenu];
290 d9c21e6f 2023-07-24 spnw [self updateControlMenu];
291 d9c21e6f 2023-07-24 spnw [self updateStatus];
292 d9c21e6f 2023-07-24 spnw }
293 d9c21e6f 2023-07-24 spnw - (void)initSongMenu {
294 d9c21e6f 2023-07-24 spnw songMap = [NSMapTable new];
295 d9c21e6f 2023-07-24 spnw songMenu = [NSMenu new];
296 d9c21e6f 2023-07-24 spnw [self updateSongMenu];
297 d9c21e6f 2023-07-24 spnw }
298 d9c21e6f 2023-07-24 spnw - (void)updateSongMenu {
299 d9c21e6f 2023-07-24 spnw if (!connection)
300 d9c21e6f 2023-07-24 spnw return;
301 d9c21e6f 2023-07-24 spnw
302 d9c21e6f 2023-07-24 spnw [songMap removeAllObjects];
303 d9c21e6f 2023-07-24 spnw [songMenu removeAllItems];
304 d9c21e6f 2023-07-24 spnw if (!mpd_send_list_all(connection, "")) {
305 d9c21e6f 2023-07-24 spnw [self disconnect];
306 d9c21e6f 2023-07-24 spnw return;
307 d9c21e6f 2023-07-24 spnw }
308 d9c21e6f 2023-07-24 spnw
309 d9c21e6f 2023-07-24 spnw [addToQueueItem setEnabled:YES];
310 d9c21e6f 2023-07-24 spnw
311 d9c21e6f 2023-07-24 spnw struct mpd_entity *entity;
312 d9c21e6f 2023-07-24 spnw NSMutableArray *menus = [NSMutableArray new];
313 d9c21e6f 2023-07-24 spnw [menus addObject:songMenu];
314 d9c21e6f 2023-07-24 spnw BOOL directory;
315 d9c21e6f 2023-07-24 spnw const char *s;
316 d9c21e6f 2023-07-24 spnw while ((entity = mpd_recv_entity(connection))) {
317 d9c21e6f 2023-07-24 spnw switch (mpd_entity_get_type(entity)) {
318 d9c21e6f 2023-07-24 spnw case MPD_ENTITY_TYPE_DIRECTORY:
319 d9c21e6f 2023-07-24 spnw directory = YES;
320 d9c21e6f 2023-07-24 spnw s = mpd_directory_get_path(mpd_entity_get_directory(entity));
321 d9c21e6f 2023-07-24 spnw break;
322 d9c21e6f 2023-07-24 spnw case MPD_ENTITY_TYPE_SONG:
323 d9c21e6f 2023-07-24 spnw directory = NO;
324 d9c21e6f 2023-07-24 spnw s = mpd_song_get_uri(mpd_entity_get_song(entity));
325 d9c21e6f 2023-07-24 spnw break;
326 d9c21e6f 2023-07-24 spnw default:
327 d9c21e6f 2023-07-24 spnw continue;
328 d9c21e6f 2023-07-24 spnw }
329 d9c21e6f 2023-07-24 spnw
330 d9c21e6f 2023-07-24 spnw NSString *ss = utf8String(s);
331 d9c21e6f 2023-07-24 spnw NSArray *components = [ss pathComponents];
332 d9c21e6f 2023-07-24 spnw
333 d9c21e6f 2023-07-24 spnw while ([menus count] > [components count])
334 d9c21e6f 2023-07-24 spnw [menus removeLastObject];
335 d9c21e6f 2023-07-24 spnw
336 d9c21e6f 2023-07-24 spnw NSString *title =
337 d9c21e6f 2023-07-24 spnw directory ? [components lastObject]
338 d9c21e6f 2023-07-24 spnw : [[components lastObject] stringByDeletingPathExtension];
339 d9c21e6f 2023-07-24 spnw
340 d9c21e6f 2023-07-24 spnw NSMenuItem *item = [[NSMenuItem alloc]
341 d9c21e6f 2023-07-24 spnw initWithTitle:[title stringByReplacingOccurrencesOfString:@":"
342 d9c21e6f 2023-07-24 spnw withString:@"/"]
343 d9c21e6f 2023-07-24 spnw action:@selector(enqueue:)
344 d9c21e6f 2023-07-24 spnw keyEquivalent:@""];
345 d9c21e6f 2023-07-24 spnw
346 d9c21e6f 2023-07-24 spnw [item setTarget:self];
347 d9c21e6f 2023-07-24 spnw [songMap setObject:ss forKey:item];
348 d9c21e6f 2023-07-24 spnw [[menus lastObject] addItem:item];
349 d9c21e6f 2023-07-24 spnw if (directory) {
350 d9c21e6f 2023-07-24 spnw NSMenu *menu = [NSMenu new];
351 d9c21e6f 2023-07-24 spnw [item setSubmenu:menu];
352 d9c21e6f 2023-07-24 spnw [menus addObject:menu];
353 d9c21e6f 2023-07-24 spnw }
354 d9c21e6f 2023-07-24 spnw mpd_entity_free(entity);
355 d9c21e6f 2023-07-24 spnw }
356 d9c21e6f 2023-07-24 spnw }
357 d9c21e6f 2023-07-24 spnw - (instancetype)init {
358 d9c21e6f 2023-07-24 spnw if (self = [super init]) {
359 d9c21e6f 2023-07-24 spnw [self connect];
360 d9c21e6f 2023-07-24 spnw [self initSongMenu];
361 d9c21e6f 2023-07-24 spnw [self initControlMenu];
362 d9c21e6f 2023-07-24 spnw
363 d9c21e6f 2023-07-24 spnw [[[NSThread alloc] initWithTarget:self
364 d9c21e6f 2023-07-24 spnw selector:@selector(updateLoop)
365 d9c21e6f 2023-07-24 spnw object:nil] start];
366 d9c21e6f 2023-07-24 spnw }
367 d9c21e6f 2023-07-24 spnw return self;
368 d9c21e6f 2023-07-24 spnw }
369 d9c21e6f 2023-07-24 spnw - (void)dealloc {
370 d9c21e6f 2023-07-24 spnw mpd_connection_free(connection);
371 d9c21e6f 2023-07-24 spnw }
372 d9c21e6f 2023-07-24 spnw - (void)showError:(NSString *)msg {
373 d9c21e6f 2023-07-24 spnw [menuButton setTitle:[NSString stringWithFormat:@"MPC Bar: %@", msg]];
374 d9c21e6f 2023-07-24 spnw }
375 d9c21e6f 2023-07-24 spnw - (void)play {
376 d9c21e6f 2023-07-24 spnw mpd_run_play(connection);
377 d9c21e6f 2023-07-24 spnw }
378 d9c21e6f 2023-07-24 spnw - (void)pause {
379 d9c21e6f 2023-07-24 spnw mpd_run_pause(connection, true);
380 d9c21e6f 2023-07-24 spnw }
381 d9c21e6f 2023-07-24 spnw - (void)stop {
382 d9c21e6f 2023-07-24 spnw mpd_run_stop(connection);
383 d9c21e6f 2023-07-24 spnw }
384 d9c21e6f 2023-07-24 spnw - (void)next {
385 d9c21e6f 2023-07-24 spnw mpd_run_next(connection);
386 d9c21e6f 2023-07-24 spnw }
387 d9c21e6f 2023-07-24 spnw - (void)previous {
388 d9c21e6f 2023-07-24 spnw mpd_run_previous(connection);
389 d9c21e6f 2023-07-24 spnw }
390 d9c21e6f 2023-07-24 spnw - (void)single_oneshot {
391 d9c21e6f 2023-07-24 spnw mpd_run_single_state(connection, MPD_SINGLE_ONESHOT);
392 d9c21e6f 2023-07-24 spnw }
393 d9c21e6f 2023-07-24 spnw - (void)single_off {
394 d9c21e6f 2023-07-24 spnw mpd_run_single_state(connection, MPD_SINGLE_OFF);
395 d9c21e6f 2023-07-24 spnw }
396 d9c21e6f 2023-07-24 spnw - (void)update {
397 d9c21e6f 2023-07-24 spnw mpd_run_update(connection, NULL);
398 d9c21e6f 2023-07-24 spnw }
399 d9c21e6f 2023-07-24 spnw - (void)clear {
400 d9c21e6f 2023-07-24 spnw mpd_run_clear(connection);
401 d9c21e6f 2023-07-24 spnw }
402 d9c21e6f 2023-07-24 spnw - (void)enqueue:(id)item {
403 d9c21e6f 2023-07-24 spnw mpd_run_add(connection, [[songMap objectForKey:item]
404 d9c21e6f 2023-07-24 spnw cStringUsingEncoding:NSUTF8StringEncoding]);
405 d9c21e6f 2023-07-24 spnw }
406 d9c21e6f 2023-07-24 spnw - (void)updateStatus {
407 d9c21e6f 2023-07-24 spnw struct mpd_status *status = NULL;
408 d9c21e6f 2023-07-24 spnw struct mpd_song *song = NULL;
409 d9c21e6f 2023-07-24 spnw
410 d9c21e6f 2023-07-24 spnw if (connection)
411 d9c21e6f 2023-07-24 spnw status = mpd_run_status(connection);
412 d9c21e6f 2023-07-24 spnw
413 d9c21e6f 2023-07-24 spnw if (!status) {
414 d9c21e6f 2023-07-24 spnw if (connection)
415 d9c21e6f 2023-07-24 spnw [self disconnect];
416 d9c21e6f 2023-07-24 spnw return;
417 d9c21e6f 2023-07-24 spnw }
418 d9c21e6f 2023-07-24 spnw
419 d9c21e6f 2023-07-24 spnw enum mpd_state state = mpd_status_get_state(status);
420 d9c21e6f 2023-07-24 spnw bool active = (state == MPD_STATE_PLAY || state == MPD_STATE_PAUSE);
421 d9c21e6f 2023-07-24 spnw
422 d9c21e6f 2023-07-24 spnw if (!active || !(song = mpd_run_current_song(connection))) {
423 d9c21e6f 2023-07-24 spnw if ([controlMenu indexOfItem:timeItem] >= 0)
424 d9c21e6f 2023-07-24 spnw [controlMenu removeItem:timeItem];
425 d9c21e6f 2023-07-24 spnw if ([controlMenu indexOfItem:timeSeparator] >= 0)
426 d9c21e6f 2023-07-24 spnw [controlMenu removeItem:timeSeparator];
427 d9c21e6f 2023-07-24 spnw mpd_status_free(status);
428 d9c21e6f 2023-07-24 spnw return;
429 d9c21e6f 2023-07-24 spnw }
430 d9c21e6f 2023-07-24 spnw
431 d9c21e6f 2023-07-24 spnw unsigned int elapsed = mpd_status_get_elapsed_time(status);
432 d9c21e6f 2023-07-24 spnw unsigned int dur = mpd_song_get_duration(song);
433 d9c21e6f 2023-07-24 spnw [timeItem
434 d9c21e6f 2023-07-24 spnw setTitle:[NSString stringWithFormat:@"%@ / %@", formatTime(elapsed),
435 d9c21e6f 2023-07-24 spnw (dur > 0) ? formatTime(dur) : @"?"]];
436 d9c21e6f 2023-07-24 spnw
437 d9c21e6f 2023-07-24 spnw if ([controlMenu indexOfItem:timeItem] < 0)
438 d9c21e6f 2023-07-24 spnw [controlMenu insertItem:timeItem atIndex:0];
439 d9c21e6f 2023-07-24 spnw if ([controlMenu indexOfItem:timeSeparator] < 0)
440 d9c21e6f 2023-07-24 spnw [controlMenu insertItem:timeSeparator atIndex:1];
441 d9c21e6f 2023-07-24 spnw
442 d9c21e6f 2023-07-24 spnw mpd_song_free(song);
443 d9c21e6f 2023-07-24 spnw mpd_status_free(status);
444 d9c21e6f 2023-07-24 spnw }
445 d9c21e6f 2023-07-24 spnw @end
446 d9c21e6f 2023-07-24 spnw
447 d9c21e6f 2023-07-24 spnw int main(int argc, char *argv[]) {
448 31193ae2 2023-07-27 spnw if (argc > 1 && strcmp(argv[1], "-v") == 0) {
449 31193ae2 2023-07-27 spnw puts("MPC Bar "VERSION);
450 31193ae2 2023-07-27 spnw return 0;
451 31193ae2 2023-07-27 spnw }
452 31193ae2 2023-07-27 spnw
453 d9c21e6f 2023-07-24 spnw [NSApplication sharedApplication];
454 d9c21e6f 2023-07-24 spnw [MPDController new];
455 d9c21e6f 2023-07-24 spnw [NSApp run];
456 31193ae2 2023-07-27 spnw
457 d9c21e6f 2023-07-24 spnw return 0;
458 d9c21e6f 2023-07-24 spnw }