Config: reformat all descriptions to fill 80 columns
[ncmpcpp.git] / src / ncmpcpp.cpp
blob3893cabae78ad575d7362d4a4b84695b492a7ea3
1 /***************************************************************************
2 * Copyright (C) 2008-2017 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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 *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <cerrno>
22 #include <clocale>
23 #include <csignal>
24 #include <cstring>
26 #include <boost/date_time/posix_time/posix_time.hpp>
27 #include <boost/locale.hpp>
28 #include <iostream>
29 #include <fstream>
30 #include <stdexcept>
32 #include "mpdpp.h"
34 #include "actions.h"
35 #include "bindings.h"
36 #include "screens/browser.h"
37 #include "charset.h"
38 #include "configuration.h"
39 #include "global.h"
40 #include "helpers.h"
41 #include "screens/lyrics.h"
42 #include "screens/outputs.h"
43 #include "screens/playlist.h"
44 #include "settings.h"
45 #include "status.h"
46 #include "statusbar.h"
47 #include "screens/visualizer.h"
48 #include "title.h"
49 #include "utility/conversion.h"
51 namespace ph = std::placeholders;
53 namespace {
55 std::ofstream errorlog;
56 std::streambuf *cerr_buffer;
57 std::streambuf *clog_buffer;
59 volatile bool run_resize_screen = false;
61 void sighandler(int sig)
63 if (sig == SIGWINCH)
64 run_resize_screen = true;
65 #if defined(__sun) && defined(__SVR4)
66 // in solaris it is needed to reinstall the handler each time it's executed
67 signal(sig, sighandler);
68 #endif // __sun && __SVR4
71 void do_at_exit()
73 // restore old cerr & clog buffers
74 std::cerr.rdbuf(cerr_buffer);
75 std::clog.rdbuf(clog_buffer);
76 errorlog.close();
77 Mpd.Disconnect();
78 NC::destroyScreen();
79 windowTitle("");
84 int main(int argc, char **argv)
86 using Global::myScreen;
88 using Global::wHeader;
89 using Global::wFooter;
91 using Global::VolumeState;
92 using Global::Timer;
94 std::setlocale(LC_ALL, "");
95 std::locale::global(Charset::internalLocale());
97 // clog might be overriden in configure, so preserve the original buffer.
98 clog_buffer = std::clog.rdbuf();
100 if (!configure(argc, argv))
101 return 0;
103 // always execute these commands, even if ncmpcpp use exit function
104 atexit(do_at_exit);
106 // redirect std::cerr output to ~/.ncmpcpp/error.log file
107 errorlog.open((Config.ncmpcpp_directory + "error.log").c_str(), std::ios::app);
108 cerr_buffer = std::cerr.rdbuf();
109 std::cerr.rdbuf(errorlog.rdbuf());
111 sigignore(SIGPIPE);
112 signal(SIGWINCH, sighandler);
114 Mpd.setNoidleCallback(Status::update);
116 NC::initScreen(Config.colors_enabled, Config.mouse_support);
118 Actions::OriginalStatusbarVisibility = Config.statusbar_visibility;
120 if (Config.design == Design::Alternative)
121 Config.statusbar_visibility = 0;
123 Actions::setWindowsDimensions();
124 Actions::validateScreenSize();
125 Actions::initializeScreens();
127 wHeader = new NC::Window(0, 0, COLS, Actions::HeaderHeight, "", Config.header_color, NC::Border());
128 if (Config.header_visibility || Config.design == Design::Alternative)
129 wHeader->display();
131 wFooter = new NC::Window(0, Actions::FooterStartY, COLS, Actions::FooterHeight, "", Config.statusbar_color, NC::Border());
132 wFooter->setPromptHook(Statusbar::Helpers::mainHook);
134 // initialize global timer
135 Timer = boost::posix_time::microsec_clock::local_time();
137 // initialize global random number generator
138 Global::RNG.seed(std::random_device()());
140 // initialize playlist
141 myPlaylist->switchTo();
143 // go to startup screen
144 if (Config.startup_screen_type != myScreen->type())
146 auto startup_screen = toScreen(Config.startup_screen_type);
147 assert(startup_screen != nullptr);
148 startup_screen->switchTo();
151 // lock current screen and go to the slave one if applicable
152 if (Config.startup_slave_screen_type)
154 auto slave_screen_type = *Config.startup_slave_screen_type;
155 bool screen_locked = myScreen->lock();
156 if (screen_locked && slave_screen_type != myScreen->type())
158 auto slave_screen = toScreen(slave_screen_type);
159 assert(slave_screen != nullptr);
160 slave_screen->switchTo();
161 if (!Config.startup_slave_screen_focus)
162 Actions::get(Actions::Type::MasterScreen).execute();
166 // local variables
167 bool key_pressed = false;
168 auto input = NC::Key::None;
169 auto connect_attempt = boost::posix_time::from_time_t(0);
170 auto update_environment = static_cast<Actions::UpdateEnvironment &>(
171 Actions::get(Actions::Type::UpdateEnvironment));
173 while (!Actions::ExitMainLoop)
177 if (!Mpd.Connected() && Timer - connect_attempt > boost::posix_time::seconds(1))
179 connect_attempt = Timer;
180 // reset local status info
181 Status::clear();
182 // clear mpd callback
183 wFooter->clearFDCallbacksList();
186 Mpd.Connect();
187 if (Mpd.Version() < 16)
189 Mpd.Disconnect();
190 throw MPD::ClientError(MPD_ERROR_STATE, "MPD < 0.16.0 is not supported", false);
193 catch (MPD::ClientError &e)
195 Status::handleClientError(e);
199 if (run_resize_screen)
201 Actions::resizeScreen(true);
202 run_resize_screen = false;
205 update_environment.run(!key_pressed, key_pressed, false);
207 input = readKey(*wFooter);
208 key_pressed = input != NC::Key::None;
209 if (!key_pressed)
210 continue;
212 // The reason we want to update timer here is that if the timer is updated
213 // in Status::trace, then Key::read usually blocks for 500ms and if key is
214 // pressed 400ms after Key::read was called, we end up with Timer that is
215 // ~400ms inaccurate. On the other hand, if keys are being pressed, we don't
216 // want to update timer in both Status::trace and here. Therefore we update
217 // timer in Status::trace only if there was no recent input.
218 Timer = boost::posix_time::microsec_clock::local_time();
222 auto k = Bindings.get(input);
223 std::any_of(k.first, k.second, std::bind(&Binding::execute, ph::_1));
225 catch (ConversionError &e)
227 Statusbar::printf("Invalid value: %1%", e.value());
229 catch (OutOfBounds &e)
231 Statusbar::printf("Error: %1%", e.errorMessage());
233 catch (NC::PromptAborted &)
235 Statusbar::printf("Action aborted");
238 if (myScreen == myPlaylist)
239 myPlaylist->enableHighlighting();
241 catch (MPD::ClientError &e)
243 Status::handleClientError(e);
245 catch (MPD::ServerError &e)
247 Status::handleServerError(e);
249 catch (std::exception &e)
251 Statusbar::printf("Unexpected error: %1%", e.what());
254 return 0;