BasicColors: add `noexcept`
[ncmpc-debian.git] / src / Main.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2019 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19
20 #include "config.h"
21 #include "Instance.hxx"
22 #include "ncmpc.hxx"
23 #include "mpdclient.hxx"
24 #include "callbacks.hxx"
25 #include "charset.hxx"
26 #include "Options.hxx"
27 #include "Command.hxx"
28 #include "Bindings.hxx"
29 #include "GlobalBindings.hxx"
30 #include "ncu.hxx"
31 #include "screen.hxx"
32 #include "screen_status.hxx"
33 #include "xterm_title.hxx"
34 #include "strfsong.hxx"
35 #include "i18n.h"
36 #include "util/PrintException.hxx"
37 #include "util/ScopeExit.hxx"
38 #include "util/StringUTF8.hxx"
39 #include "util/Compiler.h"
40
41 #ifndef NCMPC_MINI
42 #include "conf.hxx"
43 #endif
44
45 #ifdef ENABLE_LYRICS_SCREEN
46 #include "lyrics.hxx"
47 #endif
48
49 #include <mpd/client.h>
50
51 #include <curses.h>
52
53 #include <assert.h>
54 #include <stdio.h>
55 #include <string.h>
56
57 #ifdef ENABLE_LOCALE
58 #include <locale.h>
59 #endif
60
61 #define BUFSIZE 1024
62
63 static Instance *global_instance;
64 static struct mpdclient *mpd = nullptr;
65
66 ScreenManager *screen;
67
68 #ifndef NCMPC_MINI
69 static void
70 update_xterm_title()
71 {
72         const struct mpd_song *song = mpd->GetPlayingSong();
73
74         char tmp[BUFSIZE];
75         const char *new_title = nullptr;
76         if (!options.xterm_title_format.empty() && song != nullptr)
77                 new_title = strfsong(tmp, BUFSIZE,
78                                      options.xterm_title_format.c_str(), song) > 0
79                         ? tmp
80                         : nullptr;
81
82         if (new_title == nullptr)
83                 new_title = PACKAGE " version " VERSION;
84
85         static char title[BUFSIZE];
86         if (strncmp(title, new_title, BUFSIZE)) {
87                 strcpy(title, new_title);
88                 set_xterm_title(title);
89         }
90 }
91 #endif
92
93 static bool
94 should_enable_update_timer()
95 {
96         return mpd->playing;
97 }
98
99 static void
100 auto_update_timer()
101 {
102         if (should_enable_update_timer())
103                 global_instance->EnableUpdateTimer();
104         else
105                 global_instance->DisableUpdateTimer();
106 }
107
108 void
109 Instance::UpdateClient() noexcept
110 {
111         if (client.IsConnected() &&
112             (client.events != 0 || client.playing))
113                 client.Update();
114
115 #ifndef NCMPC_MINI
116         if (options.enable_xterm_title)
117                 update_xterm_title();
118 #endif
119
120         screen_manager.Update(client, seek);
121         client.events = (enum mpd_idle)0;
122 }
123
124 void
125 Instance::OnReconnectTimer(const boost::system::error_code &error) noexcept
126 {
127         if (error)
128                 return;
129
130         assert(client.IsDead());
131
132         screen_status_printf(_("Connecting to %s"),
133                              client.GetSettingsName().c_str());
134         doupdate();
135
136         client.Connect();
137 }
138
139 void
140 mpdclient_connected_callback()
141 {
142 #ifndef NCMPC_MINI
143         /* quit if mpd is pre 0.14 - song id not supported by mpd */
144         auto *connection = mpd->GetConnection();
145         if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0) {
146                 const unsigned *version =
147                         mpd_connection_get_server_version(connection);
148                 screen_status_printf(_("Error: MPD version %d.%d.%d is too old (%s needed)"),
149                                      version[0], version[1], version[2],
150                                      "0.19.0");
151                 mpd->Disconnect();
152                 doupdate();
153
154                 /* try again after 30 seconds */
155                 global_instance->ScheduleReconnect(std::chrono::seconds(30));
156                 return;
157         }
158 #endif
159
160         screen->status_bar.ClearMessage();
161         doupdate();
162
163         global_instance->UpdateClient();
164
165         auto_update_timer();
166 }
167
168 void
169 mpdclient_failed_callback()
170 {
171         /* try again in 5 seconds */
172         global_instance->ScheduleReconnect(std::chrono::seconds(5));
173 }
174
175 void
176 mpdclient_lost_callback()
177 {
178         screen->Update(*mpd, global_instance->GetSeek());
179
180         global_instance->ScheduleReconnect(std::chrono::seconds(1));
181 }
182
183 /**
184  * This function is called by the gidle.c library when MPD sends us an
185  * idle event (or when the connection dies).
186  */
187 void
188 mpdclient_idle_callback(gcc_unused unsigned events)
189 {
190 #ifndef NCMPC_MINI
191         if (options.enable_xterm_title)
192                 update_xterm_title();
193 #endif
194
195         screen->Update(*mpd, global_instance->GetSeek());
196         auto_update_timer();
197 }
198
199 void
200 Instance::OnUpdateTimer(const boost::system::error_code &error) noexcept
201 {
202         if (error)
203                 return;
204
205         assert(pending_update_timer);
206         pending_update_timer = false;
207
208         UpdateClient();
209
210         if (should_enable_update_timer())
211                 ScheduleUpdateTimer();
212 }
213
214 void begin_input_event()
215 {
216 }
217
218 void end_input_event()
219 {
220         screen->Update(*mpd, global_instance->GetSeek());
221         mpd->events = (enum mpd_idle)0;
222
223         auto_update_timer();
224 }
225
226 bool
227 do_input_event(boost::asio::io_service &io_service, Command cmd)
228 {
229         if (cmd == Command::QUIT) {
230                 io_service.stop();
231                 return false;
232         }
233
234         screen->OnCommand(*mpd, global_instance->GetSeek(), cmd);
235
236         if (cmd == Command::VOLUME_UP || cmd == Command::VOLUME_DOWN)
237                 /* make sure we don't update the volume yet */
238                 global_instance->DisableUpdateTimer();
239
240         return true;
241 }
242
243 #ifdef HAVE_GETMOUSE
244
245 void
246 do_mouse_event(Point p, mmask_t bstate)
247 {
248         screen->OnMouse(*mpd, global_instance->GetSeek(), p, bstate);
249 }
250
251 #endif
252
253 #ifndef NCMPC_MINI
254 /**
255  * Check the configured key bindings for errors, and display a status
256  * message every 10 seconds.
257  */
258 void
259 Instance::OnCheckKeyBindings(const boost::system::error_code &error) noexcept
260 {
261         if (error)
262                 return;
263
264         char buf[256];
265
266         if (GetGlobalKeyBindings().Check(buf, sizeof(buf)))
267                 /* no error: disable this timer for the rest of this
268                    process */
269                 return;
270
271         screen_status_message(buf);
272
273         doupdate();
274
275         ScheduleCheckKeyBindings();
276 }
277 #endif
278
279 int
280 main(int argc, const char *argv[])
281 try {
282 #ifdef ENABLE_LOCALE
283         /* time and date formatting */
284         setlocale(LC_TIME,"");
285         /* care about sorting order etc */
286         setlocale(LC_COLLATE,"");
287         /* charset */
288         setlocale(LC_CTYPE,"");
289 #ifdef HAVE_ICONV
290         /* initialize charset conversions */
291         charset_init();
292 #endif
293
294         const ScopeInitUTF8 init_utf8;
295
296         /* initialize i18n support */
297 #endif
298
299 #ifdef ENABLE_NLS
300         setlocale(LC_MESSAGES, "");
301         bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
302         textdomain(GETTEXT_PACKAGE);
303 #endif
304
305         /* parse command line options - 1 pass get configuration files */
306         options_parse(argc, argv);
307
308 #ifndef NCMPC_MINI
309         /* read configuration */
310         read_configuration();
311
312         /* check key bindings */
313         GetGlobalKeyBindings().Check(nullptr, 0);
314 #endif
315
316         /* parse command line options - 2 pass */
317         options_parse(argc, argv);
318
319         const ScopeCursesInit curses_init;
320
321 #ifdef ENABLE_LYRICS_SCREEN
322         lyrics_init();
323 #endif
324
325         /* create the global Instance */
326         Instance instance;
327         global_instance = &instance;
328         mpd = &instance.GetClient();
329         screen = &instance.GetScreenManager();
330
331         AtScopeExit() {
332                 /* this must be executed after ~Instance(), so we're
333                    using AtScopeExit() to do the trick */
334 #ifndef NCMPC_MINI
335                 set_xterm_title("");
336 #endif
337                 printf("\n");
338         };
339
340         /* attempt to connect */
341         instance.ScheduleReconnect(std::chrono::seconds(0));
342
343         auto_update_timer();
344
345 #ifndef NCMPC_MINI
346         instance.ScheduleCheckKeyBindings();
347 #endif
348
349         instance.Run();
350         return EXIT_SUCCESS;
351 } catch (...) {
352         PrintException(std::current_exception());
353         return EXIT_FAILURE;
354 }