From dfd26025ccb652a7514f7c73429fbf948e159dd2 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 13 Oct 2020 16:04:07 +0200 Subject: [PATCH] migrate from boost::asio to the MPD EventLoop Two years ago, ncmpc 0.32 (commit 74cc24bdfcfd) made the switch from GLib's GMainLoop to boost::asio, because that appeared to be a decent non-blocking I/O event loop for C++, but it turned out that the API was complicated to use, broke all the time and the generated code was heavily bloated. Even though importing more than 7000 lines of code from MPD sounds like replacing one kind of bloat with another, this reduces ncmpc's executable size from 380 kB to 240 kB (37% smaller). The "mini" build is reduced from 149 kB to 79 kB (47% smaller). Closes https://github.com/MusicPlayerDaemon/ncmpc/issues/73 --- NEWS | 1 + meson.build | 1 - src/AsioGetIoService.hxx | 54 -------------- src/AsioServiceFwd.hxx | 42 ----------- src/AsyncUserInput.cxx | 32 +++----- src/AsyncUserInput.hxx | 17 ++--- src/DelayedSeek.cxx | 12 +-- src/DelayedSeek.hxx | 12 ++- src/Instance.cxx | 36 +++++---- src/Instance.hxx | 59 +++++---------- src/LyricsPage.cxx | 37 ++++----- src/Main.cxx | 19 ++--- src/QueuePage.cxx | 27 +++---- src/StatusBar.cxx | 14 ++-- src/StatusBar.hxx | 13 ++-- src/UserInput.cxx | 28 ------- src/UserInput.hxx | 45 ----------- src/aconnect.cxx | 64 ++++++++-------- src/aconnect.hxx | 5 +- src/gidle.cxx | 91 +++++++---------------- src/gidle.hxx | 17 ++--- src/hscroll.cxx | 9 +-- src/hscroll.hxx | 18 ++--- src/lirc.cxx | 18 ++--- src/lirc.hxx | 25 ++----- src/lyrics.cxx | 4 +- src/lyrics.hxx | 5 +- src/mpdclient.cxx | 31 ++------ src/mpdclient.hxx | 32 ++++---- src/ncmpc.hxx | 5 +- src/net/AsyncConnect.cxx | 62 ++++++++++++---- src/net/AsyncConnect.hxx | 26 ++++--- src/net/AsyncHandler.hxx | 4 +- src/net/AsyncResolveConnect.cxx | 62 ++++------------ src/net/AsyncResolveConnect.hxx | 15 +--- src/plugin.cxx | 128 ++++++++++++-------------------- src/plugin.hxx | 6 +- src/screen.hxx | 10 +-- src/screen_find.cxx | 2 +- src/screen_init.cxx | 6 +- src/signals.cxx | 16 +--- 41 files changed, 358 insertions(+), 752 deletions(-) delete mode 100644 src/AsioGetIoService.hxx delete mode 100644 src/AsioServiceFwd.hxx delete mode 100644 src/UserInput.cxx delete mode 100644 src/UserInput.hxx diff --git a/NEWS b/NEWS index bbbdda1f..4e14abf1 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,5 @@ ncmpc 0.41 - not yet released +* new main loop (copying code from MPD, replacing boost::asio) * lyrics: remove the "lyricwiki" plugin because the site is gone ncmpc 0.40 - (2020-10-07) diff --git a/meson.build b/meson.build index c46ae349..7a42ff3d 100644 --- a/meson.build +++ b/meson.build @@ -326,7 +326,6 @@ ncmpc = executable('ncmpc', 'src/Command.cxx', 'src/Bindings.cxx', 'src/GlobalBindings.cxx', - 'src/UserInput.cxx', 'src/AsyncUserInput.cxx', 'src/KeyName.cxx', 'src/Match.cxx', diff --git a/src/AsioGetIoService.hxx b/src/AsioGetIoService.hxx deleted file mode 100644 index bf18b741..00000000 --- a/src/AsioGetIoService.hxx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 Max Kellermann - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef ASIO_GET_IO_SERVICE_HXX -#define ASIO_GET_IO_SERVICE_HXX - -#if BOOST_VERSION >= 106600 -#include -#endif - -/** - * Returns the #io_service/#io_context reference from a boost::asio - * object. This is a compatibility function which works with Boost - * 1.68+ where #io_service was renamed to #io_context and - * get_io_service() was replaced with get_executor(). - */ -template -inline auto & -get_io_service(T &t) noexcept -{ -#if BOOST_VERSION >= 106600 - return (boost::asio::io_context &)t.get_executor().context(); -#else - return t.get_io_service(); -#endif -} - -#endif diff --git a/src/AsioServiceFwd.hxx b/src/AsioServiceFwd.hxx deleted file mode 100644 index 3a9b5a1b..00000000 --- a/src/AsioServiceFwd.hxx +++ /dev/null @@ -1,42 +0,0 @@ -/* ncmpc (Ncurses MPD Client) - * (c) 2004-2020 The Music Player Daemon Project - * Project homepage: http://musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef NCMPC_ASIO_SERVICE_FWD_HXX -#define NCMPC_ASIO_SERVICE_FWD_HXX - -/* This header provides a forward declaration for - boost::asio::io_service */ - -#include - -#if BOOST_VERSION >= 106600 - -/* in Boost 1.66, the API has changed for "Networking TS - compatibility"; the forward declaration above doesn't work because - boost::asio::io_service is a deprecated typedef to - boost::asio::io_context; eventually, we'll switch to the new API, - but this would require dropping support for older Boost versions */ - -#include // IWYU pragma: export - -#else -namespace boost { namespace asio { class io_service; }} -#endif - -#endif diff --git a/src/AsyncUserInput.cxx b/src/AsyncUserInput.cxx index 1da077fd..e7e9f2ca 100644 --- a/src/AsyncUserInput.cxx +++ b/src/AsyncUserInput.cxx @@ -40,18 +40,11 @@ translate_key(int key) } void -AsyncUserInput::OnReadable(const boost::system::error_code &error) +AsyncUserInput::OnSocketReady(unsigned) noexcept { - if (error) { - get_io_context().stop(); - return; - } - int key = wgetch(&w); - if (ignore_key(key)) { - AsyncWait(); + if (ignore_key(key)) return; - } #ifdef HAVE_GETMOUSE if (key == KEY_MOUSE) { @@ -68,39 +61,38 @@ AsyncUserInput::OnReadable(const boost::system::error_code &error) do_mouse_event({event.x, event.y}, event.bstate); end_input_event(); - AsyncWait(); return; } #endif Command cmd = translate_key(key); - if (cmd == Command::NONE) { - AsyncWait(); + if (cmd == Command::NONE) return; - } begin_input_event(); - if (!do_input_event(get_io_context(), cmd)) + if (!do_input_event(socket_event.GetEventLoop(), cmd)) return; end_input_event(); - AsyncWait(); + return; } -AsyncUserInput::AsyncUserInput(boost::asio::io_service &io_service, WINDOW &_w) - :UserInput(io_service), w(_w) +AsyncUserInput::AsyncUserInput(EventLoop &event_loop, WINDOW &_w) noexcept + :socket_event(event_loop, BIND_THIS_METHOD(OnSocketReady), + SocketDescriptor(STDIN_FILENO)), + w(_w) { - AsyncWait(); + socket_event.ScheduleRead(); } void -keyboard_unread(boost::asio::io_service &io_service, int key) +keyboard_unread(EventLoop &event_loop, int key) { if (ignore_key(key)) return; Command cmd = translate_key(key); if (cmd != Command::NONE) - do_input_event(io_service, cmd); + do_input_event(event_loop, cmd); } diff --git a/src/AsyncUserInput.hxx b/src/AsyncUserInput.hxx index 86662711..ac6a55a6 100644 --- a/src/AsyncUserInput.hxx +++ b/src/AsyncUserInput.hxx @@ -20,26 +20,23 @@ #ifndef ASYNC_USER_INPUT_HXX #define ASYNC_USER_INPUT_HXX -#include "UserInput.hxx" +#include "event/SocketEvent.hxx" #include -class AsyncUserInput : public UserInput { +class AsyncUserInput { + SocketEvent socket_event; + WINDOW &w; public: - AsyncUserInput(boost::asio::io_service &io_service, WINDOW &_w); + AsyncUserInput(EventLoop &event_loop, WINDOW &_w) noexcept; private: - void AsyncWait() { - UserInput::AsyncWait(std::bind(&AsyncUserInput::OnReadable, this, - std::placeholders::_1)); - } - - void OnReadable(const boost::system::error_code &error); + void OnSocketReady(unsigned flags) noexcept; }; void -keyboard_unread(boost::asio::io_service &io_service, int key); +keyboard_unread(EventLoop &event_loop, int key); #endif diff --git a/src/DelayedSeek.cxx b/src/DelayedSeek.cxx index f3e89045..c5eca3e8 100644 --- a/src/DelayedSeek.cxx +++ b/src/DelayedSeek.cxx @@ -42,25 +42,19 @@ DelayedSeek::Commit() noexcept void DelayedSeek::Cancel() noexcept { - commit_timer.cancel(); + commit_timer.Cancel(); } void -DelayedSeek::OnTimer(const boost::system::error_code &error) noexcept +DelayedSeek::OnTimer() noexcept { - if (error) - return; - Commit(); } void DelayedSeek::ScheduleTimer() noexcept { - boost::system::error_code error; - commit_timer.expires_from_now(std::chrono::milliseconds(500), error); - commit_timer.async_wait(std::bind(&DelayedSeek::OnTimer, - this, std::placeholders::_1)); + commit_timer.Schedule(std::chrono::milliseconds(500)); } bool diff --git a/src/DelayedSeek.hxx b/src/DelayedSeek.hxx index f05d2cdc..3105aff0 100644 --- a/src/DelayedSeek.hxx +++ b/src/DelayedSeek.hxx @@ -20,9 +20,7 @@ #ifndef NCMPC_DELAYED_SEEK_HXX #define NCMPC_DELAYED_SEEK_HXX -#include "AsioServiceFwd.hxx" - -#include +#include "event/TimerEvent.hxx" struct mpdclient; @@ -36,12 +34,12 @@ class DelayedSeek { int id = -1; unsigned time; - boost::asio::steady_timer commit_timer; + TimerEvent commit_timer; public: - DelayedSeek(boost::asio::io_service &io_service, + DelayedSeek(EventLoop &event_loop, struct mpdclient &_c) noexcept - :c(_c), commit_timer(io_service) {} + :c(_c), commit_timer(event_loop, BIND_THIS_METHOD(OnTimer)) {} ~DelayedSeek() noexcept { Cancel(); @@ -61,7 +59,7 @@ public: void Cancel() noexcept; private: - void OnTimer(const boost::system::error_code &error) noexcept; + void OnTimer() noexcept; void ScheduleTimer() noexcept; }; diff --git a/src/Instance.cxx b/src/Instance.cxx index 31080123..d0f6d8f6 100644 --- a/src/Instance.cxx +++ b/src/Instance.cxx @@ -19,6 +19,7 @@ #include "Instance.hxx" #include "Options.hxx" +#include "event/SignalMonitor.hxx" #include @@ -44,36 +45,32 @@ static constexpr TagMask global_tag_whitelist{ #endif Instance::Instance() - :io_service(), -#ifndef _WIN32 - sigterm(io_service, SIGTERM, SIGINT, SIGHUP), - sigwinch(io_service, SIGWINCH, SIGCONT), -#endif - client(io_service, + :client(event_loop, options.host.empty() ? nullptr : options.host.c_str(), options.port, options.timeout_ms, options.password.empty() ? nullptr : options.password.c_str()), - seek(io_service, client), - reconnect_timer(io_service), - update_timer(io_service), + seek(event_loop, client), + reconnect_timer(event_loop, BIND_THIS_METHOD(OnReconnectTimer)), + update_timer(event_loop, BIND_THIS_METHOD(OnUpdateTimer)), #ifndef NCMPC_MINI - check_key_bindings_timer(io_service), + check_key_bindings_timer(event_loop, BIND_THIS_METHOD(OnCheckKeyBindings)), #endif - screen_manager(io_service), + screen_manager(event_loop), #ifdef ENABLE_LIRC - lirc_input(io_service), + lirc_input(event_loop), #endif - user_input(io_service, *screen_manager.main_window.w) + user_input(event_loop, *screen_manager.main_window.w) { screen_manager.Init(&client); - sigterm.async_wait([this](const auto &, int){ - this->io_service.stop(); - }); - #ifndef _WIN32 - AsyncWaitSigwinch(); + SignalMonitorInit(event_loop); + SignalMonitorRegister(SIGTERM, BIND_THIS_METHOD(Quit)); + SignalMonitorRegister(SIGINT, BIND_THIS_METHOD(Quit)); + SignalMonitorRegister(SIGHUP, BIND_THIS_METHOD(Quit)); + SignalMonitorRegister(SIGWINCH, BIND_THIS_METHOD(OnSigwinch)); + SignalMonitorRegister(SIGCONT, BIND_THIS_METHOD(OnSigwinch)); #endif #ifdef HAVE_TAG_WHITELIST @@ -92,6 +89,7 @@ Instance::Instance() Instance::~Instance() { screen_manager.Exit(); + SignalMonitorFinish(); } void @@ -99,5 +97,5 @@ Instance::Run() { screen_manager.Update(client, seek); - io_service.run(); + event_loop.Run(); } diff --git a/src/Instance.hxx b/src/Instance.hxx index 9b717723..e40a4e80 100644 --- a/src/Instance.hxx +++ b/src/Instance.hxx @@ -25,26 +25,18 @@ #include "mpdclient.hxx" #include "DelayedSeek.hxx" #include "screen.hxx" +#include "event/Loop.hxx" +#include "event/TimerEvent.hxx" #ifdef ENABLE_LIRC #include "lirc.hxx" #endif -#include -#include -#ifndef _WIN32 -#include -#endif - /** * A singleton holding global instance variables. */ class Instance { - boost::asio::io_service io_service; - -#ifndef _WIN32 - boost::asio::signal_set sigterm, sigwinch; -#endif + EventLoop event_loop; struct mpdclient client; @@ -55,13 +47,13 @@ class Instance { * server is broken. It tries to recover by reconnecting * periodically. */ - boost::asio::steady_timer reconnect_timer; + TimerEvent reconnect_timer; - boost::asio::steady_timer update_timer; + TimerEvent update_timer; bool pending_update_timer = false; #ifndef NCMPC_MINI - boost::asio::steady_timer check_key_bindings_timer; + TimerEvent check_key_bindings_timer; #endif ScreenManager screen_manager; @@ -79,10 +71,6 @@ public: Instance(const Instance &) = delete; Instance &operator=(const Instance &) = delete; - auto &get_io_service() { - return io_service; - } - auto &GetClient() { return client; } @@ -95,17 +83,17 @@ public: return screen_manager; } + void Quit() noexcept { + event_loop.Break(); + } + void UpdateClient() noexcept; void Run(); template void ScheduleReconnect(const D &expiry_time) { - boost::system::error_code error; - reconnect_timer.expires_from_now(expiry_time, error); - reconnect_timer.async_wait(std::bind(&Instance::OnReconnectTimer, - this, - std::placeholders::_1)); + reconnect_timer.Schedule(expiry_time); } void EnableUpdateTimer() noexcept { @@ -116,44 +104,33 @@ public: void DisableUpdateTimer() noexcept { if (pending_update_timer) { pending_update_timer = false; - update_timer.cancel(); + update_timer.Cancel(); } } #ifndef NCMPC_MINI void ScheduleCheckKeyBindings() noexcept { - boost::system::error_code error; - check_key_bindings_timer.expires_from_now(std::chrono::seconds(10), - error); - check_key_bindings_timer.async_wait(std::bind(&Instance::OnCheckKeyBindings, - this, - std::placeholders::_1)); + check_key_bindings_timer.Schedule(std::chrono::seconds(10)); } #endif private: #ifndef _WIN32 void InitSignals(); - void OnSigwinch(); - void AsyncWaitSigwinch(); + void OnSigwinch() noexcept; #endif - void OnReconnectTimer(const boost::system::error_code &error) noexcept; + void OnReconnectTimer() noexcept; - void OnUpdateTimer(const boost::system::error_code &error) noexcept; + void OnUpdateTimer() noexcept; void ScheduleUpdateTimer() noexcept { pending_update_timer = true; - boost::system::error_code error; - update_timer.expires_from_now(std::chrono::milliseconds(500), - error); - update_timer.async_wait(std::bind(&Instance::OnUpdateTimer, - this, - std::placeholders::_1)); + update_timer.Schedule(std::chrono::milliseconds(500)); } #ifndef NCMPC_MINI - void OnCheckKeyBindings(const boost::system::error_code &error) noexcept; + void OnCheckKeyBindings() noexcept; #endif }; diff --git a/src/LyricsPage.cxx b/src/LyricsPage.cxx index 11080985..d03a095d 100644 --- a/src/LyricsPage.cxx +++ b/src/LyricsPage.cxx @@ -33,8 +33,6 @@ #include "screen_utils.hxx" #include "ncu.hxx" -#include - #include #include @@ -65,19 +63,20 @@ class LyricsPage final : public TextPage, PluginResponseHandler { PluginCycle *loader = nullptr; - boost::asio::steady_timer loader_timeout; + TimerEvent loader_timeout; public: LyricsPage(ScreenManager &_screen, WINDOW *w, Size size) :TextPage(_screen, w, size), - loader_timeout(_screen.get_io_service()) {} + loader_timeout(_screen.GetEventLoop(), + BIND_THIS_METHOD(OnTimeout)) {} ~LyricsPage() override { Cancel(); } - auto &get_io_service() noexcept { - return screen.get_io_service(); + auto &GetEventLoop() noexcept { + return loader_timeout.GetEventLoop(); } private: @@ -111,7 +110,7 @@ class LyricsPage final : public TextPage, PluginResponseHandler { /** save current lyrics to a file and run editor on it */ void Edit(); - void OnTimeout(const boost::system::error_code &error) noexcept; + void OnTimeout() noexcept; public: /* virtual methods from class Page */ @@ -135,7 +134,7 @@ LyricsPage::Cancel() loader = nullptr; } - loader_timeout.cancel(); + loader_timeout.Cancel(); plugin_name.clear(); @@ -236,7 +235,7 @@ LyricsPage::OnPluginSuccess(const char *_plugin_name, if (options.lyrics_autosave && !exists_lyr_file(artist, title)) Save(); - loader_timeout.cancel(); + loader_timeout.Cancel(); plugin_stop(loader); loader = nullptr; @@ -252,18 +251,15 @@ LyricsPage::OnPluginError(std::string error) noexcept /* translators: no lyrics were found for the song */ screen_status_message(_("No lyrics")); - loader_timeout.cancel(); + loader_timeout.Cancel(); plugin_stop(loader); loader = nullptr; } void -LyricsPage::OnTimeout(const boost::system::error_code &error) noexcept +LyricsPage::OnTimeout() noexcept { - if (error) - return; - plugin_stop(loader); loader = nullptr; @@ -286,16 +282,11 @@ LyricsPage::Load(const struct mpd_song &_song) noexcept return; } - loader = lyrics_load(get_io_service(), + loader = lyrics_load(GetEventLoop(), artist, title, *this); - if (options.lyrics_timeout > std::chrono::steady_clock::duration::zero()) { - boost::system::error_code error; - loader_timeout.expires_from_now(options.lyrics_timeout, - error); - loader_timeout.async_wait(std::bind(&LyricsPage::OnTimeout, this, - std::placeholders::_1)); - } + if (options.lyrics_timeout > std::chrono::steady_clock::duration::zero()) + loader_timeout.Schedule(options.lyrics_timeout); } void @@ -312,7 +303,7 @@ LyricsPage::Reload() { if (loader == nullptr && artist != nullptr && title != nullptr) { reloading = true; - loader = lyrics_load(get_io_service(), + loader = lyrics_load(GetEventLoop(), artist, title, *this); Repaint(); } diff --git a/src/Main.cxx b/src/Main.cxx index caf36cfe..b21aaeaa 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -123,11 +123,8 @@ Instance::UpdateClient() noexcept } void -Instance::OnReconnectTimer(const boost::system::error_code &error) noexcept +Instance::OnReconnectTimer() noexcept { - if (error) - return; - assert(client.IsDead()); screen_status_printf(_("Connecting to %s"), @@ -198,11 +195,8 @@ mpdclient_idle_callback(gcc_unused unsigned events) noexcept } void -Instance::OnUpdateTimer(const boost::system::error_code &error) noexcept +Instance::OnUpdateTimer() noexcept { - if (error) - return; - assert(pending_update_timer); pending_update_timer = false; @@ -226,10 +220,10 @@ void end_input_event() noexcept } bool -do_input_event(boost::asio::io_service &io_service, Command cmd) noexcept +do_input_event(EventLoop &event_loop, Command cmd) noexcept { if (cmd == Command::QUIT) { - io_service.stop(); + event_loop.Break(); return false; } @@ -267,11 +261,8 @@ do_mouse_event(Point p, mmask_t bstate) noexcept * message every 10 seconds. */ void -Instance::OnCheckKeyBindings(const boost::system::error_code &error) noexcept +Instance::OnCheckKeyBindings() noexcept { - if (error) - return; - char buf[256]; if (GetGlobalKeyBindings().Check(buf, sizeof(buf))) diff --git a/src/QueuePage.cxx b/src/QueuePage.cxx index 44bcf594..7ea21831 100644 --- a/src/QueuePage.cxx +++ b/src/QueuePage.cxx @@ -42,6 +42,7 @@ #include "SongPage.hxx" #include "LyricsPage.hxx" #include "db_completion.hxx" +#include "event/TimerEvent.hxx" #include "util/Compiler.h" #ifndef NCMPC_MINI @@ -50,8 +51,6 @@ #include -#include - #include #include @@ -66,7 +65,7 @@ class QueuePage final : public ListPage, ListRenderer, ListText { mutable class hscroll hscroll; #endif - boost::asio::steady_timer hide_cursor_timer; + TimerEvent hide_cursor_timer; MpdQueue *playlist = nullptr; int current_song_id = -1; @@ -83,10 +82,11 @@ class QueuePage final : public ListPage, ListRenderer, ListText { :ListPage(w, size), screen(_screen), #ifndef NCMPC_MINI - hscroll(screen.get_io_service(), + hscroll(screen.GetEventLoop(), w, options.scroll_sep.c_str()), #endif - hide_cursor_timer(screen.get_io_service()) + hide_cursor_timer(screen.GetEventLoop(), + BIND_THIS_METHOD(OnHideCursorTimer)) { } @@ -107,16 +107,12 @@ class QueuePage final : public ListPage, ListRenderer, ListText { bool OnSongChange(const struct mpd_status *status); - void OnHideCursorTimer(const boost::system::error_code &error) noexcept; + void OnHideCursorTimer() noexcept; void ScheduleHideCursor() { assert(options.hide_cursor > std::chrono::steady_clock::duration::zero()); - boost::system::error_code error; - hide_cursor_timer.expires_from_now(options.hide_cursor, - error); - hide_cursor_timer.async_wait(std::bind(&QueuePage::OnHideCursorTimer, this, - std::placeholders::_1)); + hide_cursor_timer.Schedule(options.hide_cursor); } /* virtual methods from class ListRenderer */ @@ -330,12 +326,9 @@ screen_queue_init(ScreenManager &_screen, WINDOW *w, Size size) return std::make_unique(_screen, w, size); } -void -QueuePage::OnHideCursorTimer(const boost::system::error_code &error) noexcept +inline void +QueuePage::OnHideCursorTimer() noexcept { - if (error) - return; - assert(options.hide_cursor > std::chrono::steady_clock::duration::zero()); /* hide the cursor when mpd is playing and the user is inactive */ @@ -364,7 +357,7 @@ QueuePage::OnOpen(struct mpdclient &c) noexcept void QueuePage::OnClose() noexcept { - hide_cursor_timer.cancel(); + hide_cursor_timer.Cancel(); #ifndef NCMPC_MINI if (options.scroll) diff --git a/src/StatusBar.cxx b/src/StatusBar.cxx index a5247a0d..aee47ff9 100644 --- a/src/StatusBar.cxx +++ b/src/StatusBar.cxx @@ -30,12 +30,12 @@ #include -StatusBar::StatusBar(boost::asio::io_service &io_service, +StatusBar::StatusBar(EventLoop &event_loop, Point p, unsigned width) noexcept :window(p, {width, 1u}), - message_timer(io_service) + message_timer(event_loop, BIND_THIS_METHOD(OnMessageTimer)) #ifndef NCMPC_MINI - , hscroll(io_service, window.w, options.scroll_sep.c_str()) + , hscroll(event_loop, window.w, options.scroll_sep.c_str()) #endif { leaveok(window.w, false); @@ -58,7 +58,7 @@ StatusBar::~StatusBar() noexcept void StatusBar::ClearMessage() noexcept { - message_timer.cancel(); + message_timer.Cancel(); message.clear(); Paint(); @@ -291,9 +291,5 @@ StatusBar::SetMessage(const char *msg) noexcept Paint(); doupdate(); - boost::system::error_code error; - message_timer.expires_from_now(options.status_message_time, - error); - message_timer.async_wait(std::bind(&StatusBar::OnMessageTimer, this, - std::placeholders::_1)); + message_timer.Schedule(options.status_message_time); } diff --git a/src/StatusBar.hxx b/src/StatusBar.hxx index ab18931d..0484419a 100644 --- a/src/StatusBar.hxx +++ b/src/StatusBar.hxx @@ -21,15 +21,13 @@ #define NCMPC_STATUS_BAR_HXX #include "config.h" // IWYU pragma: keep -#include "AsioServiceFwd.hxx" #include "Window.hxx" +#include "event/TimerEvent.hxx" #ifndef NCMPC_MINI #include "hscroll.hxx" #endif -#include - #include struct mpd_status; @@ -40,7 +38,7 @@ class StatusBar { Window window; std::string message; - boost::asio::steady_timer message_timer; + TimerEvent message_timer; #ifndef NCMPC_MINI class hscroll hscroll; @@ -54,7 +52,7 @@ class StatusBar { unsigned left_width, right_width; public: - StatusBar(boost::asio::io_service &io_service, + StatusBar(EventLoop &event_loop, Point p, unsigned width) noexcept; ~StatusBar() noexcept; @@ -72,9 +70,8 @@ public: void Paint() const noexcept; private: - void OnMessageTimer(const boost::system::error_code &error) noexcept { - if (!error) - ClearMessage(); + void OnMessageTimer() noexcept { + ClearMessage(); } }; diff --git a/src/UserInput.cxx b/src/UserInput.cxx deleted file mode 100644 index 54377e31..00000000 --- a/src/UserInput.cxx +++ /dev/null @@ -1,28 +0,0 @@ -/* ncmpc (Ncurses MPD Client) - * (c) 2004-2020 The Music Player Daemon Project - * Project homepage: http://musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "UserInput.hxx" - -#include - -UserInput::UserInput(boost::asio::io_service &io_service) - :d(io_service) -{ - d.assign(STDIN_FILENO); -} diff --git a/src/UserInput.hxx b/src/UserInput.hxx deleted file mode 100644 index 9208d305..00000000 --- a/src/UserInput.hxx +++ /dev/null @@ -1,45 +0,0 @@ -/* ncmpc (Ncurses MPD Client) - * (c) 2004-2020 The Music Player Daemon Project - * Project homepage: http://musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef USER_INPUT_HXX -#define USER_INPUT_HXX - -#include "AsioServiceFwd.hxx" -#include "AsioGetIoService.hxx" - -#include - -class UserInput { - boost::asio::posix::stream_descriptor d; - -public: - explicit UserInput(boost::asio::io_service &io_service); - - auto &get_io_context() noexcept { - return ::get_io_service(d); - } - - template - void AsyncWait(F &&f) noexcept { - d.async_read_some(boost::asio::null_buffers(), - std::forward(f)); - } -}; - -#endif diff --git a/src/aconnect.cxx b/src/aconnect.cxx index 6bc9d8b8..735254a0 100644 --- a/src/aconnect.cxx +++ b/src/aconnect.cxx @@ -29,57 +29,58 @@ #include "aconnect.hxx" #include "net/AsyncResolveConnect.hxx" #include "net/AsyncHandler.hxx" +#include "net/UniqueSocketDescriptor.hxx" +#include "event/SocketEvent.hxx" #include #include -#include +#include +#include +#include struct AsyncMpdConnect final : AsyncConnectHandler { AsyncMpdConnectHandler &handler; AsyncResolveConnect rconnect; - boost::asio::generic::stream_protocol::socket socket; + SocketEvent socket; - char buffer[256]; - - explicit AsyncMpdConnect(boost::asio::io_service &io_service, + explicit AsyncMpdConnect(EventLoop &event_loop, AsyncMpdConnectHandler &_handler) noexcept :handler(_handler), - rconnect(io_service, *this), socket(io_service) {} + rconnect(event_loop, *this), + socket(event_loop, BIND_THIS_METHOD(OnReceive)) {} + + ~AsyncMpdConnect() noexcept { + socket.Close(); + } - void OnReceive(const boost::system::error_code &error, - std::size_t bytes_transferred) noexcept; + void OnReceive(unsigned events) noexcept; /* virtual methods from AsyncConnectHandler */ - void OnConnect(boost::asio::generic::stream_protocol::socket socket) override; + void OnConnect(UniqueSocketDescriptor fd) noexcept override; void OnConnectError(const char *message) override; }; void -AsyncMpdConnect::OnReceive(const boost::system::error_code &error, - std::size_t bytes_transferred) noexcept +AsyncMpdConnect::OnReceive(unsigned) noexcept { - if (error) { - if (error == boost::asio::error::operation_aborted) - /* this object has already been deleted; bail out - quickly without touching anything */ - return; - - snprintf(buffer, sizeof(buffer), - "Failed to receive from MPD: %s", - error.message().c_str()); + char buffer[256]; + ssize_t nbytes = socket.GetSocket().Read(buffer, sizeof(buffer)); + + if (nbytes < 0) { + std::snprintf(buffer, sizeof(buffer), + "Failed to receive from MPD: %s", + std::strerror(errno)); handler.OnAsyncMpdConnectError(buffer); delete this; return; } - buffer[bytes_transferred] = 0; + buffer[nbytes] = 0; - /* the dup() is necessary because Boost 1.62 doesn't have the - release() method yet */ - struct mpd_async *async = mpd_async_new(dup(socket.native_handle())); + struct mpd_async *async = mpd_async_new(socket.Steal().Get()); if (async == nullptr) { handler.OnAsyncMpdConnectError("Out of memory"); delete this; @@ -99,13 +100,10 @@ AsyncMpdConnect::OnReceive(const boost::system::error_code &error, } void -AsyncMpdConnect::OnConnect(boost::asio::generic::stream_protocol::socket _socket) +AsyncMpdConnect::OnConnect(UniqueSocketDescriptor fd) noexcept { - socket = std::move(_socket); - socket.async_receive(boost::asio::buffer(buffer, sizeof(buffer) - 1), - std::bind(&AsyncMpdConnect::OnReceive, this, - std::placeholders::_1, - std::placeholders::_2)); + socket.Open(fd.Release()); + socket.ScheduleRead(); } void @@ -116,16 +114,16 @@ AsyncMpdConnect::OnConnectError(const char *message) } void -aconnect_start(boost::asio::io_service &io_service, +aconnect_start(EventLoop &event_loop, AsyncMpdConnect **acp, const char *host, unsigned port, AsyncMpdConnectHandler &handler) { - auto *ac = new AsyncMpdConnect(io_service, handler); + auto *ac = new AsyncMpdConnect(event_loop, handler); *acp = ac; - ac->rconnect.Start(io_service, host, port); + ac->rconnect.Start(host, port); } void diff --git a/src/aconnect.hxx b/src/aconnect.hxx index 1d23ca6e..394890bc 100644 --- a/src/aconnect.hxx +++ b/src/aconnect.hxx @@ -29,12 +29,11 @@ #ifndef ACONNECT_H #define ACONNECT_H -#include "AsioServiceFwd.hxx" - #include struct mpd_connection; struct AsyncMpdConnect; +class EventLoop; class AsyncMpdConnectHandler { public: @@ -43,7 +42,7 @@ public: }; void -aconnect_start(boost::asio::io_service &io_service, +aconnect_start(EventLoop &event_loop, AsyncMpdConnect **acp, const char *host, unsigned port, AsyncMpdConnectHandler &handler); diff --git a/src/gidle.cxx b/src/gidle.cxx index eb0c25c4..62baeb38 100644 --- a/src/gidle.cxx +++ b/src/gidle.cxx @@ -34,22 +34,21 @@ #include #include -MpdIdleSource::MpdIdleSource(boost::asio::io_service &io_service, +MpdIdleSource::MpdIdleSource(EventLoop &event_loop, struct mpd_connection &_connection, MpdIdleHandler &_handler) noexcept :connection(&_connection), async(mpd_connection_get_async(connection)), parser(mpd_parser_new()), - handler(_handler), - socket(io_service, mpd_async_get_fd(async)) + event(event_loop, BIND_THIS_METHOD(OnSocketReady), + SocketDescriptor(mpd_async_get_fd(async))), + handler(_handler) { /* TODO check parser!=nullptr */ } MpdIdleSource::~MpdIdleSource() noexcept { - socket.release(); - mpd_parser_free(parser); } @@ -70,6 +69,7 @@ MpdIdleSource::Feed(char *line) noexcept result = mpd_parser_feed(parser, line); switch (result) { case MPD_PARSER_MALFORMED: + event.Cancel(); io_events = 0; InvokeError(MPD_ERROR_MALFORMED, @@ -78,12 +78,14 @@ MpdIdleSource::Feed(char *line) noexcept return false; case MPD_PARSER_SUCCESS: + event.Cancel(); io_events = 0; InvokeCallback(); return false; case MPD_PARSER_ERROR: + event.Cancel(); io_events = 0; InvokeError(MPD_ERROR_SERVER, @@ -113,6 +115,7 @@ MpdIdleSource::Receive() noexcept } if (mpd_async_get_error(async) != MPD_ERROR_SUCCESS) { + event.Cancel(); io_events = 0; InvokeAsyncError(); @@ -123,74 +126,29 @@ MpdIdleSource::Receive() noexcept } void -MpdIdleSource::OnReadable(const boost::system::error_code &error) noexcept +MpdIdleSource::OnSocketReady(unsigned flags) noexcept { - if (error) { - if (error == boost::asio::error::operation_aborted) - return; - - // TODO - return; - } - - io_events &= ~MPD_ASYNC_EVENT_READ; - - if (!mpd_async_io(async, MPD_ASYNC_EVENT_READ)) { - socket.cancel(); + unsigned events = 0; + if (flags & SocketEvent::READ) + events |= MPD_ASYNC_EVENT_READ; + if (flags & SocketEvent::WRITE) + events |= MPD_ASYNC_EVENT_WRITE; + + if (!mpd_async_io(async, (enum mpd_async_event)events)) { + event.Cancel(); io_events = 0; InvokeAsyncError(); return; } - if (!Receive()) - return; - - UpdateSocket(); -} - -void -MpdIdleSource::OnWritable(const boost::system::error_code &error) noexcept -{ - if (error) { - if (error == boost::asio::error::operation_aborted) + if (flags & SocketEvent::READ) + if (!Receive()) return; - // TODO - return; - } - - io_events &= ~MPD_ASYNC_EVENT_WRITE; - - if (!mpd_async_io(async, MPD_ASYNC_EVENT_WRITE)) { - socket.cancel(); - io_events = 0; - - InvokeAsyncError(); - return; - } - UpdateSocket(); } -void -MpdIdleSource::AsyncRead() noexcept -{ - io_events |= MPD_ASYNC_EVENT_READ; - socket.async_read_some(boost::asio::null_buffers(), - std::bind(&MpdIdleSource::OnReadable, this, - std::placeholders::_1)); -} - -void -MpdIdleSource::AsyncWrite() noexcept -{ - io_events |= MPD_ASYNC_EVENT_WRITE; - socket.async_write_some(boost::asio::null_buffers(), - std::bind(&MpdIdleSource::OnWritable, this, - std::placeholders::_1)); -} - void MpdIdleSource::UpdateSocket() noexcept { @@ -198,13 +156,14 @@ MpdIdleSource::UpdateSocket() noexcept if (events == io_events) return; - socket.cancel(); - + unsigned flags = 0; if (events & MPD_ASYNC_EVENT_READ) - AsyncRead(); + flags |= SocketEvent::READ; if (events & MPD_ASYNC_EVENT_WRITE) - AsyncWrite(); + flags |= SocketEvent::WRITE; + + event.Schedule(flags); io_events = events; } @@ -232,7 +191,7 @@ MpdIdleSource::Leave() noexcept /* already left, callback was invoked */ return; - socket.cancel(); + event.Cancel(); io_events = 0; enum mpd_idle events = idle_events == 0 diff --git a/src/gidle.hxx b/src/gidle.hxx index ffe028fb..4ba8c0b3 100644 --- a/src/gidle.hxx +++ b/src/gidle.hxx @@ -29,12 +29,10 @@ #ifndef MPD_GLIB_SOURCE_H #define MPD_GLIB_SOURCE_H -#include "AsioServiceFwd.hxx" +#include "event/SocketEvent.hxx" #include -#include - class MpdIdleHandler { public: virtual void OnIdle(unsigned events) noexcept = 0; @@ -43,21 +41,21 @@ public: const char *message) noexcept = 0; }; -class MpdIdleSource { +class MpdIdleSource final { struct mpd_connection *connection; struct mpd_async *async; struct mpd_parser *parser; - MpdIdleHandler &handler; + SocketEvent event; - boost::asio::posix::stream_descriptor socket; + MpdIdleHandler &handler; unsigned io_events = 0; unsigned idle_events; public: - MpdIdleSource(boost::asio::io_service &io_service, + MpdIdleSource(EventLoop &event_loop, struct mpd_connection &_connection, MpdIdleHandler &_handler) noexcept; ~MpdIdleSource() noexcept; @@ -103,11 +101,8 @@ private: */ bool Receive() noexcept; - void OnReadable(const boost::system::error_code &error) noexcept; - void OnWritable(const boost::system::error_code &error) noexcept; + void OnSocketReady(unsigned flags) noexcept; - void AsyncRead() noexcept; - void AsyncWrite() noexcept; void UpdateSocket() noexcept; }; diff --git a/src/hscroll.cxx b/src/hscroll.cxx index 752fd7e9..7f1f74d9 100644 --- a/src/hscroll.cxx +++ b/src/hscroll.cxx @@ -22,12 +22,9 @@ #include -inline void -hscroll::TimerCallback(const boost::system::error_code &error) noexcept +void +hscroll::OnTimer() noexcept { - if (error) - return; - Step(); Paint(); wrefresh(w); @@ -56,7 +53,7 @@ void hscroll::Clear() noexcept { basic.Clear(); - timer.cancel(); + timer.Cancel(); } void diff --git a/src/hscroll.hxx b/src/hscroll.hxx index 27a6b576..200e9c37 100644 --- a/src/hscroll.hxx +++ b/src/hscroll.hxx @@ -21,12 +21,10 @@ #define HSCROLL_H #include "BasicMarquee.hxx" -#include "AsioServiceFwd.hxx" +#include "event/TimerEvent.hxx" #include -#include - enum class Style : unsigned; /** @@ -55,12 +53,13 @@ class hscroll { /** * A timer which updates the scrolled area every second. */ - boost::asio::steady_timer timer; + TimerEvent timer; public: - hscroll(boost::asio::io_service &io_service, + hscroll(EventLoop &event_loop, WINDOW *_w, const char *_separator) noexcept - :w(_w), basic(_separator), timer(io_service) + :w(_w), basic(_separator), + timer(event_loop, BIND_THIS_METHOD(OnTimer)) { } @@ -97,13 +96,10 @@ public: void Paint() const noexcept; private: - void TimerCallback(const boost::system::error_code &error) noexcept; + void OnTimer() noexcept; void ScheduleTimer() noexcept { - boost::system::error_code error; - timer.expires_from_now(std::chrono::seconds(1), error); - timer.async_wait(std::bind(&hscroll::TimerCallback, this, - std::placeholders::_1)); + timer.Schedule(std::chrono::seconds(1)); } }; diff --git a/src/lirc.cxx b/src/lirc.cxx index a86f0a05..2be010a7 100644 --- a/src/lirc.cxx +++ b/src/lirc.cxx @@ -25,11 +25,8 @@ #include void -LircInput::OnReadable(const boost::system::error_code &error) +LircInput::OnSocketReady(unsigned) noexcept { - if (error) - return; - char *code, *txt; begin_input_event(); @@ -37,17 +34,16 @@ LircInput::OnReadable(const boost::system::error_code &error) if (lirc_nextcode(&code) == 0) { while (lirc_code2char(lc, code, &txt) == 0 && txt != nullptr) { const auto cmd = get_key_command_from_name(txt); - if (!do_input_event(get_io_context(), cmd)) + if (!do_input_event(GetEventLoop(), cmd)) return; } } end_input_event(); - AsyncWait(); } -LircInput::LircInput(boost::asio::io_service &io_service) - :d(io_service) +LircInput::LircInput(EventLoop &_event_loop) noexcept + :event(_event_loop, BIND_THIS_METHOD(OnSocketReady)) { int lirc_socket = 0; @@ -59,14 +55,14 @@ LircInput::LircInput(boost::asio::io_service &io_service) return; } - d.assign(lirc_socket); - AsyncWait(); + event.Open(SocketDescriptor(lirc_socket)); + event.ScheduleRead(); } LircInput::~LircInput() { if (lc) lirc_freeconfig(lc); - if (d.is_open()) + if (event.IsDefined()) lirc_deinit(); } diff --git a/src/lirc.hxx b/src/lirc.hxx index 962e6be2..4549903d 100644 --- a/src/lirc.hxx +++ b/src/lirc.hxx @@ -20,32 +20,23 @@ #ifndef LIRC_H #define LIRC_H -#include "AsioServiceFwd.hxx" -#include "AsioGetIoService.hxx" - -#include - -class LircInput { - boost::asio::posix::stream_descriptor d; +#include "event/SocketEvent.hxx" +class LircInput final { struct lirc_config *lc = nullptr; + SocketEvent event; + public: - explicit LircInput(boost::asio::io_service &io_service); + explicit LircInput(EventLoop &event_loop) noexcept; ~LircInput(); - auto &get_io_context() noexcept { - return ::get_io_service(d); + auto &GetEventLoop() const noexcept { + return event.GetEventLoop(); } private: - void AsyncWait() { - d.async_read_some(boost::asio::null_buffers(), - std::bind(&LircInput::OnReadable, this, - std::placeholders::_1)); - } - - void OnReadable(const boost::system::error_code &error); + void OnSocketReady(unsigned flags) noexcept; }; #endif diff --git a/src/lyrics.cxx b/src/lyrics.cxx index 4e73b0b6..e908b0eb 100644 --- a/src/lyrics.cxx +++ b/src/lyrics.cxx @@ -31,7 +31,7 @@ void lyrics_init() } PluginCycle * -lyrics_load(boost::asio::io_service &io_service, +lyrics_load(EventLoop &event_loop, const char *artist, const char *title, PluginResponseHandler &handler) { @@ -40,5 +40,5 @@ lyrics_load(boost::asio::io_service &io_service, const char *args[3] = { artist, title, nullptr }; - return plugin_run(io_service, &plugins, args, handler); + return plugin_run(event_loop, &plugins, args, handler); } diff --git a/src/lyrics.hxx b/src/lyrics.hxx index 315e6c59..a289d999 100644 --- a/src/lyrics.hxx +++ b/src/lyrics.hxx @@ -20,15 +20,14 @@ #ifndef LYRICS_H #define LYRICS_H -#include "AsioServiceFwd.hxx" - struct PluginCycle; class PluginResponseHandler; +class EventLoop; void lyrics_init(); PluginCycle * -lyrics_load(boost::asio::io_service &io_service, +lyrics_load(EventLoop &event_loop, const char *artist, const char *title, PluginResponseHandler &handler); diff --git a/src/mpdclient.cxx b/src/mpdclient.cxx index ff67f006..00143d02 100644 --- a/src/mpdclient.cxx +++ b/src/mpdclient.cxx @@ -28,29 +28,14 @@ #include void -mpdclient::OnEnterIdleTimer(const boost::system::error_code &error) noexcept +mpdclient::OnEnterIdleTimer() noexcept { - if (error) - return; - assert(source != nullptr); assert(!idle); idle = source->Enter(); } -void -mpdclient::ScheduleEnterIdle() noexcept -{ - assert(source != nullptr); - - /* automatically re-enter MPD "idle" mode */ - boost::system::error_code error; - enter_idle_timer.expires_from_now(std::chrono::seconds(0), error); - enter_idle_timer.async_wait(std::bind(&mpdclient::OnEnterIdleTimer, - this, std::placeholders::_1)); -} - static void mpdclient_invoke_error_callback(enum mpd_error error, const char *message) @@ -164,14 +149,12 @@ settings_is_local_socket(const struct mpd_settings *settings) #endif #endif -mpdclient::mpdclient(boost::asio::io_service &io_service, +mpdclient::mpdclient(EventLoop &_event_loop, const char *_host, unsigned _port, unsigned _timeout_ms, const char *_password) - :timeout_ms(_timeout_ms), password(_password), -#if BOOST_VERSION >= 107000 - io_context(io_service), -#endif - enter_idle_timer(io_service) + :event_loop(_event_loop), + timeout_ms(_timeout_ms), password(_password), + enter_idle_timer(event_loop, BIND_THIS_METHOD(OnEnterIdleTimer)) { #ifdef ENABLE_ASYNC_CONNECT settings = mpd_settings_new(_host, _port, _timeout_ms, @@ -355,7 +338,7 @@ mpdclient::OnConnected(struct mpd_connection *_connection) noexcept } #endif - source = new MpdIdleSource(get_io_service(), *connection, *this); + source = new MpdIdleSource(GetEventLoop(), *connection, *this); ScheduleEnterIdle(); ++connection_id; @@ -409,7 +392,7 @@ mpdclient::OnAsyncMpdConnectError(const char *message) noexcept void mpdclient::StartConnect(const struct mpd_settings &s) noexcept { - aconnect_start(get_io_service(), &async_connect, + aconnect_start(GetEventLoop(), &async_connect, mpd_settings_get_host(&s), mpd_settings_get_port(&s), *this); diff --git a/src/mpdclient.hxx b/src/mpdclient.hxx index d20b55e1..611c3ccd 100644 --- a/src/mpdclient.hxx +++ b/src/mpdclient.hxx @@ -24,7 +24,7 @@ #include "Queue.hxx" #include "gidle.hxx" #include "util/Compiler.h" -#include "AsioServiceFwd.hxx" +#include "event/TimerEvent.hxx" #ifdef ENABLE_ASYNC_CONNECT #include "aconnect.hxx" @@ -37,8 +37,6 @@ #include "TagMask.hxx" #endif -#include - #include struct AsyncMpdConnect; @@ -49,6 +47,8 @@ struct mpdclient final , AsyncMpdConnectHandler #endif { + EventLoop &event_loop; + #ifdef ENABLE_ASYNC_CONNECT /** * These settings are used to connect to MPD asynchronously. @@ -92,15 +92,11 @@ struct mpdclient final struct mpd_status *status = nullptr; const struct mpd_song *current_song = nullptr; -#if BOOST_VERSION >= 107000 - boost::asio::io_context &io_context; -#endif - /** * A timer which re-enters MPD idle mode before the next main * loop iteration. */ - boost::asio::steady_timer enter_idle_timer; + TimerEvent enter_idle_timer; /** * This attribute is incremented whenever the connection changes @@ -142,7 +138,7 @@ struct mpdclient final */ bool playing_or_paused = false; - mpdclient(boost::asio::io_service &io_service, + mpdclient(EventLoop &_event_loop, const char *host, unsigned port, unsigned _timeout_ms, const char *_password); @@ -159,12 +155,8 @@ struct mpdclient final #endif } - auto &get_io_service() noexcept { -#if BOOST_VERSION >= 107000 - return io_context; -#else - return enter_idle_timer.get_io_service(); -#endif + auto &GetEventLoop() const noexcept { + return event_loop; } #ifdef ENABLE_ASYNC_CONNECT @@ -324,11 +316,15 @@ private: void ClearStatus() noexcept; - void ScheduleEnterIdle() noexcept; + void ScheduleEnterIdle() noexcept { + enter_idle_timer.Schedule(std::chrono::milliseconds(10)); + } + void CancelEnterIdle() noexcept { - enter_idle_timer.cancel(); + enter_idle_timer.Cancel(); } - void OnEnterIdleTimer(const boost::system::error_code &error) noexcept; + + void OnEnterIdleTimer() noexcept; #ifdef ENABLE_ASYNC_CONNECT /* virtual methods from AsyncMpdConnectHandler */ diff --git a/src/ncmpc.hxx b/src/ncmpc.hxx index 32dd7679..4c04db88 100644 --- a/src/ncmpc.hxx +++ b/src/ncmpc.hxx @@ -20,14 +20,13 @@ #ifndef NCMPC_H #define NCMPC_H -#include "AsioServiceFwd.hxx" - #ifdef HAVE_GETMOUSE #include #endif enum class Command : unsigned; struct Point; +class EventLoop; class ScreenManager; extern ScreenManager *screen; @@ -41,7 +40,7 @@ end_input_event() noexcept; * @return false if the application shall quit */ bool -do_input_event(boost::asio::io_service &io_service, Command cmd) noexcept; +do_input_event(EventLoop &event_loop, Command cmd) noexcept; #ifdef HAVE_GETMOUSE void diff --git a/src/net/AsyncConnect.cxx b/src/net/AsyncConnect.cxx index 4b5aa972..7128ae6e 100644 --- a/src/net/AsyncConnect.cxx +++ b/src/net/AsyncConnect.cxx @@ -28,28 +28,58 @@ #include "AsyncConnect.hxx" #include "AsyncHandler.hxx" +#include "net/UniqueSocketDescriptor.hxx" +#include "net/SocketAddress.hxx" +#include "system/Error.hxx" -void -AsyncConnect::OnConnected(const boost::system::error_code &error) noexcept +#include + +static UniqueSocketDescriptor +Connect(const SocketAddress address) { - if (error) { - if (error == boost::asio::error::operation_aborted) - /* this object has already been deleted; bail out - quickly without touching anything */ - return; - - socket.close(); - handler.OnConnectError(error.message().c_str()); - return; + UniqueSocketDescriptor fd; + if (!fd.CreateNonBlock(address.GetFamily(), SOCK_STREAM, 0)) + throw MakeErrno("Failed to create socket"); + + if (!fd.Connect(address) && errno != EINPROGRESS) + throw MakeErrno("Failed to connect"); + + return fd; +} + +bool +AsyncConnect::Start(const SocketAddress address) noexcept +{ + assert(!event.IsDefined()); + + try { + WaitConnected(::Connect(address)); + return true; + } catch (const std::exception &e) { + handler.OnConnectError(e.what()); + return false; } +} + +void +AsyncConnect::WaitConnected(UniqueSocketDescriptor fd) noexcept +{ + assert(!event.IsDefined()); - handler.OnConnect(std::move(socket)); + event.Open(fd.Release()); + event.ScheduleWrite(); } void -AsyncConnect::Start(const boost::asio::ip::tcp::endpoint &endpoint) noexcept +AsyncConnect::OnSocketReady(unsigned) noexcept { - socket.async_connect(endpoint, - std::bind(&AsyncConnect::OnConnected, this, - std::placeholders::_1)); + event.Cancel(); + + int s_err = event.GetSocket().GetError(); + if (s_err != 0) { + handler.OnConnectError(strerror(errno)); + return; + } + + handler.OnConnect(UniqueSocketDescriptor(event.Steal())); } diff --git a/src/net/AsyncConnect.hxx b/src/net/AsyncConnect.hxx index d785ac9d..4999ff44 100644 --- a/src/net/AsyncConnect.hxx +++ b/src/net/AsyncConnect.hxx @@ -29,30 +29,36 @@ #ifndef NET_ASYNC_CONNECT_HXX #define NET_ASYNC_CONNECT_HXX -#include "AsioServiceFwd.hxx" - -#include +#include "event/SocketEvent.hxx" +class SocketAddress; class AsyncConnectHandler; +class UniqueSocketDescriptor; class AsyncConnect { - AsyncConnectHandler &handler; + SocketEvent event; - boost::asio::ip::tcp::socket socket; + AsyncConnectHandler &handler; public: - AsyncConnect(boost::asio::io_service &io_service, + AsyncConnect(EventLoop &event_loop, AsyncConnectHandler &_handler) noexcept - :handler(_handler), - socket(io_service) {} + :event(event_loop, BIND_THIS_METHOD(OnSocketReady)), + handler(_handler) {} + + ~AsyncConnect() noexcept { + event.Close(); + } /** * Create a socket and connect it to the given address. */ - void Start(const boost::asio::ip::tcp::endpoint &endpoint) noexcept; + bool Start(SocketAddress address) noexcept; + + void WaitConnected(UniqueSocketDescriptor fd) noexcept; private: - void OnConnected(const boost::system::error_code &error) noexcept; + void OnSocketReady(unsigned events) noexcept; }; #endif diff --git a/src/net/AsyncHandler.hxx b/src/net/AsyncHandler.hxx index a6fa618d..e2bf51df 100644 --- a/src/net/AsyncHandler.hxx +++ b/src/net/AsyncHandler.hxx @@ -29,11 +29,11 @@ #ifndef NET_ASYNC_HANDLER_HXX #define NET_ASYNC_HANDLER_HXX -#include +class UniqueSocketDescriptor; class AsyncConnectHandler { public: - virtual void OnConnect(boost::asio::generic::stream_protocol::socket socket) = 0; + virtual void OnConnect(UniqueSocketDescriptor fd) noexcept = 0; virtual void OnConnectError(const char *message) = 0; }; diff --git a/src/net/AsyncResolveConnect.cxx b/src/net/AsyncResolveConnect.cxx index 97493640..d617287f 100644 --- a/src/net/AsyncResolveConnect.cxx +++ b/src/net/AsyncResolveConnect.cxx @@ -29,64 +29,28 @@ #include "AsyncResolveConnect.hxx" #include "AsyncConnect.hxx" #include "AsyncHandler.hxx" +#include "AllocatedSocketAddress.hxx" +#include "AddressInfo.hxx" +#include "Resolver.hxx" -#ifndef _WIN32 -#include -#endif - -#include - -void -AsyncResolveConnect::OnResolved(const boost::system::error_code &error, - boost::asio::ip::tcp::resolver::iterator i) noexcept -{ - if (error) { - if (error == boost::asio::error::operation_aborted) - /* this object has already been deleted; bail - out quickly without touching anything */ - return; - - handler.OnConnectError(error.message().c_str()); - return; - } - - connect.Start(*i); -} +#include void -AsyncResolveConnect::Start(boost::asio::io_service &io_service, - const char *host, unsigned port) noexcept +AsyncResolveConnect::Start(const char *host, unsigned port) noexcept { #ifndef _WIN32 if (host[0] == '/' || host[0] == '@') { - std::string s(host); - if (host[0] == '@') - /* abstract socket */ - s.front() = 0; - - boost::asio::local::stream_protocol::endpoint ep(std::move(s)); - boost::asio::local::stream_protocol::socket socket(io_service); + AllocatedSocketAddress address; + address.SetLocal(host); - boost::system::error_code error; - socket.connect(ep, error); - if (error) { - handler.OnConnectError(error.message().c_str()); - return; - } - - handler.OnConnect(std::move(socket)); + connect.Start(address); return; } -#else - (void)io_service; #endif /* _WIN32 */ - char service[20]; - snprintf(service, sizeof(service), "%u", port); - - resolver.async_resolve({host, service}, - std::bind(&AsyncResolveConnect::OnResolved, - this, - std::placeholders::_1, - std::placeholders::_2)); + try { + connect.Start(Resolve(host, port, 0, SOCK_STREAM).GetBest()); + } catch (const std::exception &e) { + handler.OnConnectError(e.what()); + } } diff --git a/src/net/AsyncResolveConnect.hxx b/src/net/AsyncResolveConnect.hxx index e38d764e..335ce593 100644 --- a/src/net/AsyncResolveConnect.hxx +++ b/src/net/AsyncResolveConnect.hxx @@ -34,25 +34,18 @@ class AsyncResolveConnect { AsyncConnectHandler &handler; - boost::asio::ip::tcp::resolver resolver; - AsyncConnect connect; public: - AsyncResolveConnect(boost::asio::io_service &io_service, + AsyncResolveConnect(EventLoop &event_loop, AsyncConnectHandler &_handler) noexcept - :handler(_handler), resolver(io_service), - connect(io_service, _handler) {} + :handler(_handler), + connect(event_loop, _handler) {} /** * Resolve a host name and connect to it asynchronously. */ - void Start(boost::asio::io_service &io_service, - const char *host, unsigned port) noexcept; - -private: - void OnResolved(const boost::system::error_code &error, - boost::asio::ip::tcp::resolver::iterator i) noexcept; + void Start(const char *host, unsigned port) noexcept; }; #endif diff --git a/src/plugin.cxx b/src/plugin.cxx index 473a61dd..7804ae28 100644 --- a/src/plugin.cxx +++ b/src/plugin.cxx @@ -19,12 +19,12 @@ #include "plugin.hxx" #include "io/Path.hxx" +#include "io/UniqueFileDescriptor.hxx" +#include "event/SocketEvent.hxx" +#include "event/TimerEvent.hxx" #include "util/ScopeExit.hxx" #include "util/UriUtil.hxx" -#include -#include - #include #include @@ -42,37 +42,32 @@ class PluginPipe { PluginCycle &cycle; /** the pipe to the plugin process */ - boost::asio::posix::stream_descriptor fd; + SocketEvent socket_event; /** the output of the current plugin */ std::string data; - std::array buffer; - public: - PluginPipe(boost::asio::io_service &io_service, + PluginPipe(EventLoop &event_loop, PluginCycle &_cycle) noexcept - :cycle(_cycle), fd(io_service) {} + :cycle(_cycle), + socket_event(event_loop, BIND_THIS_METHOD(OnRead)) {} ~PluginPipe() noexcept { - Close(); + socket_event.Close(); } - void Start(int _fd) noexcept { - fd.assign(_fd); - AsyncRead(); + void Start(UniqueFileDescriptor &&fd) noexcept { + socket_event.Open(SocketDescriptor::FromFileDescriptor(fd.Release())); + socket_event.ScheduleRead(); } void Close() noexcept { - if (!fd.is_open()) - return; - - fd.cancel(); - fd.close(); + socket_event.Close(); } bool IsOpen() const noexcept { - return fd.is_open(); + return socket_event.IsDefined(); } bool IsEmpty() const noexcept { @@ -88,15 +83,7 @@ class PluginPipe { } private: - void AsyncRead() noexcept { - fd.async_read_some(boost::asio::buffer(buffer), - std::bind(&PluginPipe::OnRead, this, - std::placeholders::_1, - std::placeholders::_2)); - } - - void OnRead(const boost::system::error_code &error, - std::size_t bytes_transferred) noexcept; + void OnRead(unsigned events) noexcept; }; struct PluginCycle { @@ -125,28 +112,23 @@ struct PluginCycle { /** list of all error messages from failed plugins */ std::string all_errors; - boost::asio::steady_timer delayed_fail_timer; + TimerEvent delayed_fail_timer; - PluginCycle(boost::asio::io_service &io_service, + PluginCycle(EventLoop &event_loop, const PluginList &_list, std::unique_ptr &&_argv, PluginResponseHandler &_handler) noexcept :list(_list), argv(std::move(_argv)), handler(_handler), - pipe_stdout(io_service, *this), - pipe_stderr(io_service, *this), - delayed_fail_timer(io_service) {} + pipe_stdout(event_loop, *this), + pipe_stderr(event_loop, *this), + delayed_fail_timer(event_loop, BIND_THIS_METHOD(OnDelayedFail)) {} void TryNextPlugin() noexcept; void Stop() noexcept; void ScheduleDelayedFail() noexcept { - boost::system::error_code error; - delayed_fail_timer.expires_from_now(std::chrono::seconds(0), - error); - delayed_fail_timer.async_wait(std::bind(&PluginCycle::OnDelayedFail, - this, - std::placeholders::_1)); + delayed_fail_timer.Schedule(std::chrono::seconds(0)); } void OnEof() noexcept; @@ -154,7 +136,7 @@ struct PluginCycle { private: int LaunchPlugin(const char *plugin_path) noexcept; - void OnDelayedFail(const boost::system::error_code &error) noexcept; + void OnDelayedFail() noexcept; }; static bool @@ -231,22 +213,19 @@ PluginCycle::OnEof() noexcept } void -PluginPipe::OnRead(const boost::system::error_code &error, - std::size_t bytes_transferred) noexcept +PluginPipe::OnRead(unsigned) noexcept { - if (error) { - if (error == boost::asio::error::operation_aborted) - /* this object has already been deleted; bail out - quickly without touching anything */ - return; - - fd.close(); + char buffer[256]; + ssize_t nbytes = read(socket_event.GetSocket().Get(), + buffer, sizeof(buffer)); + if (nbytes <= 0) { + socket_event.Close(); cycle.OnEof(); return; } - data.append(&buffer.front(), bytes_transferred); - AsyncRead(); + data.append(buffer, nbytes); + socket_event.ScheduleRead(); } /** @@ -257,11 +236,8 @@ PluginPipe::OnRead(const boost::system::error_code &error, * moment after. */ void -PluginCycle::OnDelayedFail(const boost::system::error_code &error) noexcept +PluginCycle::OnDelayedFail() noexcept { - if (error) - return; - assert(!pipe_stdout.IsOpen()); assert(!pipe_stderr.IsOpen()); assert(pid < 0); @@ -282,34 +258,25 @@ PluginCycle::LaunchPlugin(const char *plugin_path) noexcept plugin */ argv[0] = const_cast(GetUriFilename(plugin_path)); - int fds_stdout[2]; - if (pipe(fds_stdout) < 0) - return -1; - - int fds_stderr[2]; - if (pipe(fds_stderr) < 0) { - close(fds_stdout[0]); - close(fds_stdout[1]); + UniqueFileDescriptor stdout_r, stdout_w; + UniqueFileDescriptor stderr_r, stderr_w; + if (!UniqueFileDescriptor::CreatePipe(stdout_r, stdout_w) || + !UniqueFileDescriptor::CreatePipe(stderr_r, stderr_w)) return -1; - } pid = fork(); - if (pid < 0) { - close(fds_stdout[0]); - close(fds_stdout[1]); - close(fds_stderr[0]); - close(fds_stderr[1]); + if (pid < 0) return -1; - } if (pid == 0) { - dup2(fds_stdout[1], 1); - dup2(fds_stderr[1], 2); - close(fds_stdout[0]); - close(fds_stdout[1]); - close(fds_stderr[0]); - close(fds_stderr[1]); + stdout_w.Duplicate(FileDescriptor(STDOUT_FILENO)); + stderr_w.Duplicate(FileDescriptor(STDERR_FILENO)); + + stdout_r.Close(); + stdout_w.Close(); + stderr_r.Close(); + stderr_w.Close(); close(0); /* XXX close other fds? */ @@ -317,13 +284,10 @@ PluginCycle::LaunchPlugin(const char *plugin_path) noexcept _exit(1); } - close(fds_stdout[1]); - close(fds_stderr[1]); - /* XXX CLOEXEC? */ - pipe_stdout.Start(fds_stdout[0]); - pipe_stderr.Start(fds_stderr[0]); + pipe_stdout.Start(std::move(stdout_r)); + pipe_stderr.Start(std::move(stderr_r)); return 0; } @@ -377,13 +341,13 @@ make_argv(const char*const* args) noexcept } PluginCycle * -plugin_run(boost::asio::io_service &io_service, +plugin_run(EventLoop &event_loop, PluginList *list, const char *const*args, PluginResponseHandler &handler) noexcept { assert(args != nullptr); - auto *cycle = new PluginCycle(io_service, *list, make_argv(args), + auto *cycle = new PluginCycle(event_loop, *list, make_argv(args), handler); cycle->TryNextPlugin(); diff --git a/src/plugin.hxx b/src/plugin.hxx index b6220bd9..2ee4ca4c 100644 --- a/src/plugin.hxx +++ b/src/plugin.hxx @@ -20,11 +20,11 @@ #ifndef PLUGIN_H #define PLUGIN_H -#include "AsioServiceFwd.hxx" - #include #include +class EventLoop; + /** * When a plugin cycle is finished, one method of this class is called. In any * case, plugin_stop() has to be called to free all memory. @@ -71,7 +71,7 @@ plugin_list_load_directory(const char *path) noexcept; * available */ PluginCycle * -plugin_run(boost::asio::io_service &io_service, +plugin_run(EventLoop &event_loop, PluginList *list, const char *const*args, PluginResponseHandler &handler) noexcept; diff --git a/src/screen.hxx b/src/screen.hxx index ca0ec9c5..5810ef73 100644 --- a/src/screen.hxx +++ b/src/screen.hxx @@ -28,7 +28,6 @@ #include "History.hxx" #include "Point.hxx" #include "util/Compiler.h" -#include "AsioServiceFwd.hxx" #include @@ -43,9 +42,10 @@ struct mpdclient; struct PageMeta; class Page; class DelayedSeek; +class EventLoop; class ScreenManager { - boost::asio::io_service &io_service; + EventLoop &event_loop; struct Layout { Size size; @@ -102,11 +102,11 @@ public: std::string findbuf; History find_history; - explicit ScreenManager(boost::asio::io_service &io_service) noexcept; + explicit ScreenManager(EventLoop &_event_loop) noexcept; ~ScreenManager() noexcept; - auto &get_io_service() const noexcept { - return io_service; + auto &GetEventLoop() const noexcept { + return event_loop; } void Init(struct mpdclient *c) noexcept; diff --git a/src/screen_find.cxx b/src/screen_find.cxx index f9e15398..5e66f67a 100644 --- a/src/screen_find.cxx +++ b/src/screen_find.cxx @@ -134,5 +134,5 @@ screen_jump(ScreenManager &screen, ListWindow &lw, screen.findbuf = search_str; /* ncmpc should get the command */ - keyboard_unread(screen.get_io_service(), key); + keyboard_unread(screen.GetEventLoop(), key); } diff --git a/src/screen_init.cxx b/src/screen_init.cxx index 082ff785..126d82ce 100644 --- a/src/screen_init.cxx +++ b/src/screen_init.cxx @@ -28,14 +28,14 @@ static const unsigned SCREEN_MIN_COLS = 14; static const unsigned SCREEN_MIN_ROWS = 5; -ScreenManager::ScreenManager(boost::asio::io_service &_io_service) noexcept - :io_service(_io_service), +ScreenManager::ScreenManager(EventLoop &_event_loop) noexcept + :event_loop(_event_loop), layout({std::max(COLS, SCREEN_MIN_COLS), std::max(LINES, SCREEN_MIN_ROWS)}), title_bar({layout.title_x, layout.title_y}, layout.size.width), main_window({layout.main_x, layout.main_y}, layout.GetMainSize()), progress_bar({layout.progress_x, layout.GetProgressY()}, layout.size.width), - status_bar(io_service, + status_bar(event_loop, {layout.status_x, layout.GetStatusY()}, layout.size.width), mode_fn_prev(&screen_queue) { diff --git a/src/signals.cxx b/src/signals.cxx index 6f2977b1..4c005aa2 100644 --- a/src/signals.cxx +++ b/src/signals.cxx @@ -20,30 +20,16 @@ #include "Instance.hxx" void -Instance::OnSigwinch() +Instance::OnSigwinch() noexcept { endwin(); refresh(); screen_manager.OnResize(); } -void -Instance::AsyncWaitSigwinch() -{ - sigwinch.async_wait([this](const auto &error, int){ - if (error) - return; - - this->OnSigwinch(); - this->AsyncWaitSigwinch(); - }); -} - void Instance::InitSignals() { - AsyncWaitSigwinch(); - /* ignore SIGPIPE */ struct sigaction act;