1 /*
2  *
3  * Conky, a system monitor, based on torsmo
4  *
5  * Any original torsmo code is licensed under the BSD license
6  *
7  * All code written since the fork of torsmo is licensed under the GPL
8  *
9  * Please see COPYING for details
10  *
11  * Copyright (c) 2005-2021 Brenden Matthews, Philip Kovacs, et. al.
12  *	(see AUTHORS)
13  * All rights reserved.
14  *
15  * This program is free software: you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation, either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  * You should have received a copy of the GNU General Public License
25  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26  *
27  */
28 
29 #include "mpd.h"
30 #include <cmath>
31 #include <mutex>
32 #include "conky.h"
33 #include "libmpdclient.h"
34 #include "logging.h"
35 #include "timeinfo.h"
36 #include "update-cb.hh"
37 
38 namespace {
39 
40 /* this is true if the current host was set from MPD_HOST */
41 bool mpd_environment_host = false;
42 
43 class mpd_host_setting : public conky::simple_config_setting<std::string> {
44   using Base = conky::simple_config_setting<std::string>;
45 
46  protected:
47   void lua_setter(lua::state &l, bool init) override;
48 
49  public:
mpd_host_setting()50   mpd_host_setting() : Base("mpd_host", "localhost", false) {}
51 };
52 
lua_setter(lua::state & l,bool init)53 void mpd_host_setting::lua_setter(lua::state &l, bool init) {
54   lua::stack_sentry s(l, -2);
55 
56   if (l.isnil(-2)) {
57     // get the value from environment
58     mpd_environment_host = true;
59     const char *t = getenv("MPD_HOST");
60     if (t != nullptr) {
61       l.checkstack(1);
62       const char *h = strchr(t, '@');
63       if (h != nullptr) {
64         if (h[1] != 0) { l.pushstring(h + 1); }
65       } else {
66         l.pushstring(t);
67       }
68       l.replace(-3);
69     }
70   }
71 
72   Base::lua_setter(l, init);
73 
74   ++s;
75 }
76 
77 class mpd_password_setting : public conky::simple_config_setting<std::string> {
78   using Base = conky::simple_config_setting<std::string>;
79 
80  protected:
81   void lua_setter(lua::state &l, bool init) override;
82 
83  public:
mpd_password_setting()84   mpd_password_setting() : Base("mpd_password", std::string(), false) {}
85 };
86 
lua_setter(lua::state & l,bool init)87 void mpd_password_setting::lua_setter(lua::state &l, bool init) {
88   lua::stack_sentry s(l, -2);
89 
90   /* for security, dont use environment password when user specifies host in
91    * config */
92   if (l.isnil(-2) && mpd_environment_host) {
93     // get the value from environment
94     const char *t = getenv("MPD_HOST");
95     if (t != nullptr) {
96       const char *p = strchr(t, '@');
97       if (p != nullptr) {
98         l.checkstack(1);
99         l.pushstring(t, p - t);
100         l.replace(-3);
101       }
102     }
103   }
104 
105   Base::lua_setter(l, init);
106 
107   ++s;
108 }
109 
110 conky::range_config_setting<in_port_t> mpd_port("mpd_port", 1, 65535, 6600,
111                                                 false);
112 mpd_host_setting mpd_host;
113 mpd_password_setting mpd_password;
114 
115 struct mpd_result {
116   float progress{};
117   int bitrate{};
118   int elapsed{};
119   int is_playing{};
120   int length{};
121   int vol{};
122   std::string album;
123   std::string albumartist;
124   std::string artist;
125   std::string comment;
126   std::string date;
127   std::string file;
128   std::string name;
129   std::string random;
130   std::string repeat;
131   std::string status;
132   std::string title;
133   std::string track;
134 };
135 
136 class mpd_cb : public conky::callback<mpd_result> {
137   using Base = conky::callback<mpd_result>;
138 
139   mpd_Connection *conn;
140 
141  protected:
142   void work() override;
143 
144  public:
mpd_cb(uint32_t period)145   explicit mpd_cb(uint32_t period)
146       : Base(period, false, Tuple()), conn(nullptr) {}
147 
~mpd_cb()148   ~mpd_cb() override {
149     if (conn != nullptr) { mpd_closeConnection(conn); }
150   }
151 };
152 
work()153 void mpd_cb::work() {
154   mpd_Status *status;
155   mpd_InfoEntity *entity;
156   mpd_result mpd_info;
157 
158   do {
159     if (conn == nullptr) {
160       conn = mpd_newConnection(mpd_host.get(*state).c_str(),
161                                mpd_port.get(*state), 10);
162     }
163 
164     if (static_cast<unsigned int>(!mpd_password.get(*state).empty()) != 0u) {
165       mpd_sendPasswordCommand(conn, mpd_password.get(*state).c_str());
166       mpd_finishCommand(conn);
167     }
168 
169     if (conn->error != 0) {
170       NORM_ERR("MPD error: %s\n", conn->errorStr);
171       mpd_closeConnection(conn);
172       conn = nullptr;
173 
174       mpd_info.status = "MPD not responding";
175       break;
176     }
177 
178     mpd_sendStatusCommand(conn);
179     if ((status = mpd_getStatus(conn)) == nullptr) {
180       NORM_ERR("MPD error: %s\n", conn->errorStr);
181       mpd_closeConnection(conn);
182       conn = nullptr;
183 
184       mpd_info.status = "MPD not responding";
185       break;
186     }
187     mpd_finishCommand(conn);
188     if ((conn == nullptr) || (conn->error != 0)) {
189       // fprintf(stderr, "%s\n", conn->errorStr);
190       mpd_closeConnection(conn);
191       conn = nullptr;
192       break;
193     }
194 
195     mpd_info.vol = status->volume;
196     if (status->random == 0) {
197       mpd_info.random = "Off";
198     } else if (status->random == 1) {
199       mpd_info.random = "On";
200     } else {
201       mpd_info.random = "";
202     }
203     if (status->repeat == 0) {
204       mpd_info.repeat = "Off";
205     } else if (status->repeat == 1) {
206       mpd_info.repeat = "On";
207     } else {
208       mpd_info.repeat = "";
209     }
210     /* if (status->error) {
211        printf("error: %s\n", status->error);
212        } */
213 
214     switch (status->state) {
215       case MPD_STATUS_STATE_PLAY:
216         mpd_info.status = "Playing";
217         break;
218       case MPD_STATUS_STATE_STOP:
219         mpd_info.status = "Stopped";
220         break;
221       case MPD_STATUS_STATE_PAUSE:
222         mpd_info.status = "Paused";
223         break;
224       default:
225         mpd_info.status = "";
226         break;
227     }
228 
229     if (status->state == MPD_STATUS_STATE_PLAY ||
230         status->state == MPD_STATUS_STATE_PAUSE) {
231       mpd_info.is_playing = 1;
232       mpd_info.bitrate = status->bitRate;
233       mpd_info.progress =
234           ((0 != status->totalTime)
235                ? static_cast<float>(status->elapsedTime) / status->totalTime
236                : 0.0);
237       mpd_info.elapsed = status->elapsedTime;
238       mpd_info.length = status->totalTime;
239     } else {
240       mpd_info.progress = 0;
241       mpd_info.is_playing = 0;
242       mpd_info.elapsed = 0;
243     }
244 
245     if (conn->error != 0) {
246       // fprintf(stderr, "%s\n", conn->errorStr);
247       mpd_closeConnection(conn);
248       conn = nullptr;
249       break;
250     }
251 
252     mpd_sendCurrentSongCommand(conn);
253     while ((entity = mpd_getNextInfoEntity(conn)) != nullptr) {
254       mpd_Song *song = entity->info.song;
255 
256       if (entity->type != MPD_INFO_ENTITY_TYPE_SONG) {
257         mpd_freeInfoEntity(entity);
258         continue;
259       }
260 #define SETSTRING(a, b) \
261   if (b)                \
262     (a) = b;            \
263   else                  \
264     (a) = "";
265       SETSTRING(mpd_info.album, song->album);
266       SETSTRING(mpd_info.albumartist, song->albumartist);
267       SETSTRING(mpd_info.artist, song->artist);
268       SETSTRING(mpd_info.comment, song->comment);
269       SETSTRING(mpd_info.date, song->date);
270       SETSTRING(mpd_info.file, song->file);
271       SETSTRING(mpd_info.name, song->name);
272       SETSTRING(mpd_info.title, song->title);
273       SETSTRING(mpd_info.track, song->track);
274       if (entity != nullptr) {
275         mpd_freeInfoEntity(entity);
276         entity = nullptr;
277       }
278     }
279     mpd_finishCommand(conn);
280     if ((conn != nullptr) && (conn->error != 0)) {
281       // fprintf(stderr, "%s\n", conn->errorStr);
282       mpd_closeConnection(conn);
283       conn = nullptr;
284       break;
285     }
286 
287     if (conn->error != 0) {
288       // fprintf(stderr, "%s\n", conn->errorStr);
289       mpd_closeConnection(conn);
290       conn = nullptr;
291       break;
292     }
293 
294     mpd_freeStatus(status);
295     /* if (conn) {
296        mpd_closeConnection(conn);
297        conn = 0;
298        } */
299   } while (0);
300   std::lock_guard<std::mutex> lock(Base::result_mutex);
301   result = mpd_info;  // don't forget to save results!
302 }
303 
get_mpd()304 mpd_result get_mpd() {
305   uint32_t period = std::max(
306       lround(music_player_interval.get(*state) / active_update_interval()), 1l);
307   return conky::register_cb<mpd_cb>(period)->get_result_copy();
308 }
309 }  // namespace
310 
format_media_player_time(char * buf,const int size,int seconds)311 static inline void format_media_player_time(char *buf, const int size,
312                                             int seconds) {
313   int days, hours, minutes;
314 
315   if (times_in_seconds.get(*state)) {
316     snprintf(buf, size, "%d", seconds);
317     return;
318   }
319 
320   days = seconds / (24 * 60 * 60);
321   seconds %= (24 * 60 * 60);
322   hours = seconds / (60 * 60);
323   seconds %= (60 * 60);
324   minutes = seconds / 60;
325   seconds %= 60;
326 
327   if (days > 0) {
328     snprintf(buf, size, "%i days %i:%02i:%02i", days, hours, minutes, seconds);
329   } else if (hours > 0) {
330     snprintf(buf, size, "%i:%02i:%02i", hours, minutes, seconds);
331   } else {
332     snprintf(buf, size, "%i:%02i", minutes, seconds);
333   }
334 }
335 
print_mpd_elapsed(struct text_object * obj,char * p,unsigned int p_max_size)336 void print_mpd_elapsed(struct text_object *obj, char *p,
337                        unsigned int p_max_size) {
338   (void)obj;
339   format_media_player_time(p, p_max_size, get_mpd().elapsed);
340 }
341 
print_mpd_length(struct text_object * obj,char * p,unsigned int p_max_size)342 void print_mpd_length(struct text_object *obj, char *p,
343                       unsigned int p_max_size) {
344   (void)obj;
345   format_media_player_time(p, p_max_size, get_mpd().length);
346 }
347 
mpd_percentage(struct text_object * obj)348 uint8_t mpd_percentage(struct text_object *obj) {
349   (void)obj;
350   return round_to_positive_int(get_mpd().progress * 100.0f);
351 }
352 
mpd_barval(struct text_object * obj)353 double mpd_barval(struct text_object *obj) {
354   (void)obj;
355   return get_mpd().progress;
356 }
357 
print_mpd_smart(struct text_object * obj,char * p,unsigned int p_max_size)358 void print_mpd_smart(struct text_object *obj, char *p,
359                      unsigned int p_max_size) {
360   const mpd_result &mpd_info = get_mpd();
361   int len = obj->data.i;
362   if (len == 0 || static_cast<unsigned int>(len) > p_max_size) {
363     len = p_max_size;
364   }
365 
366   memset(p, 0, p_max_size);
367   if ((static_cast<unsigned int>(!mpd_info.artist.empty()) != 0u) &&
368       (static_cast<unsigned int>(!mpd_info.title.empty()) != 0u)) {
369     snprintf(p, len, "%s - %s", mpd_info.artist.c_str(),
370              mpd_info.title.c_str());
371   } else if (static_cast<unsigned int>(!get_mpd().title.empty()) != 0u) {
372     snprintf(p, len, "%s", mpd_info.title.c_str());
373   } else if (static_cast<unsigned int>(!mpd_info.artist.empty()) != 0u) {
374     snprintf(p, len, "%s", mpd_info.artist.c_str());
375   } else if (static_cast<unsigned int>(!mpd_info.file.empty()) != 0u) {
376     snprintf(p, len, "%s", mpd_info.file.c_str());
377   } else {
378     *p = 0;
379   }
380 }
381 
check_mpd_playing(struct text_object * obj)382 int check_mpd_playing(struct text_object *obj) {
383   (void)obj;
384   return get_mpd().is_playing;
385 }
386 
387 #define MPD_PRINT_GENERATOR(name, fmt, acc)                    \
388   void print_mpd_##name(struct text_object *obj, char *p,      \
389                         unsigned int p_max_size) {             \
390     if (obj->data.i && (unsigned int)obj->data.i < p_max_size) \
391       p_max_size = obj->data.i;                                \
392     snprintf(p, p_max_size, fmt, get_mpd().name acc);          \
393   }
394 
395 MPD_PRINT_GENERATOR(album, "%s", .c_str())
396 MPD_PRINT_GENERATOR(albumartist, "%s", .c_str())
397 MPD_PRINT_GENERATOR(artist, "%s", .c_str())
398 MPD_PRINT_GENERATOR(bitrate, "%d", )
399 MPD_PRINT_GENERATOR(comment, "%s", .c_str())
400 MPD_PRINT_GENERATOR(date, "%s", .c_str())
401 MPD_PRINT_GENERATOR(file, "%s", .c_str())
402 MPD_PRINT_GENERATOR(name, "%s", .c_str())
403 MPD_PRINT_GENERATOR(random, "%s", .c_str())
404 MPD_PRINT_GENERATOR(repeat, "%s", .c_str())
405 MPD_PRINT_GENERATOR(status, "%s", .c_str())
406 MPD_PRINT_GENERATOR(title, "%s", .c_str())
407 MPD_PRINT_GENERATOR(track, "%s", .c_str())
408 MPD_PRINT_GENERATOR(vol, "%d", )
409 
410 #undef MPD_PRINT_GENERATOR
411