├── po ├── meson.build ├── LINGUAS └── POTFILES ├── .gitignore ├── src ├── win │ ├── ncmpc.ico │ ├── meson.build │ └── ncmpc_win32_rc.rc.in ├── io │ ├── uring │ │ ├── Features.h │ │ └── Features.hxx │ ├── meson.build │ ├── FileAt.hxx │ ├── Path.hxx │ └── UniqueFileDescriptor.hxx ├── ui │ ├── Bell.hxx │ ├── Bell.cxx │ ├── ListRenderer.hxx │ ├── meson.build │ ├── ListText.hxx │ ├── Size.hxx │ ├── TextListRenderer.hxx │ ├── Point.hxx │ ├── Keys.hxx │ ├── TextListRenderer.cxx │ ├── Options.hxx │ ├── paint.hxx │ └── ListWindow.hxx ├── TableGlue.hxx ├── net │ ├── Features.hxx │ ├── IPv4Address.cxx │ ├── TimeoutError.hxx │ ├── LocalSocketAddress.cxx │ ├── AsyncResolveConnect.hxx │ ├── meson.build │ ├── AsyncResolveConnect.cxx │ ├── StaticSocketAddress.cxx │ ├── SocketError.cxx │ ├── HostParser.hxx │ ├── AddressInfo.cxx │ ├── Resolver.hxx │ ├── IPv6Address.cxx │ ├── MsgHdr.hxx │ └── PeerCredentials.hxx ├── TableGlue.cxx ├── xterm_title.hxx ├── ConfigParser.hxx ├── util │ ├── Concepts.hxx │ ├── meson.build │ ├── PrintException.hxx │ ├── CopyConst.hxx │ ├── SPrintf.hxx │ ├── UriUtil.cxx │ ├── StringUTF8.hxx │ ├── OffsetPointer.hxx │ ├── UriUtil.hxx │ ├── MemberPointer.hxx │ ├── TagStructs.hxx │ ├── IntrusiveHookMode.hxx │ ├── PrintException.cxx │ ├── StringCompare.cxx │ ├── OptionalCounter.hxx │ ├── StringUTF8.cxx │ ├── Cancellable.hxx │ ├── Cast.hxx │ ├── ReturnValue.hxx │ ├── StringStrip.cxx │ ├── ScopeExit.hxx │ └── Manual.hxx ├── QueuePage.hxx ├── History.hxx ├── TableStructure.hxx ├── TableColumn.hxx ├── GlobalBindings.hxx ├── SongPtr.hxx ├── CustomColors.hxx ├── HelpPage.hxx ├── ChatPage.hxx ├── LibraryPage.hxx ├── SearchPage.hxx ├── system │ ├── EpollFD.cxx │ ├── meson.build │ ├── EventFD.hxx │ ├── SignalFD.cxx │ ├── EventFD.cxx │ ├── SignalFD.hxx │ ├── EventPipe.hxx │ └── EpollFD.hxx ├── dialogs │ ├── meson.build │ ├── ModalDock.hxx │ ├── ModalDialog.cxx │ ├── KeyDialog.cxx │ ├── KeyDialog.hxx │ ├── YesNoDialog.cxx │ └── YesNoDialog.hxx ├── screen_utils.hxx ├── KeyDefPage.hxx ├── OutputsPage.hxx ├── co │ ├── meson.build │ ├── AwaitableHelper.hxx │ └── UniqueHandle.hxx ├── player_command.hxx ├── TablePaint.hxx ├── screen_client.hxx ├── save_playlist.hxx ├── ConfigFile.hxx ├── time_format.hxx ├── lib │ └── fmt │ │ ├── ToSpan.cxx │ │ ├── RuntimeError.cxx │ │ ├── ToSpan.hxx │ │ ├── meson.build │ │ ├── RuntimeError.hxx │ │ └── ToBuffer.hxx ├── page │ ├── Container.hxx │ ├── meson.build │ ├── ListPage.hxx │ ├── TextPage.hxx │ ├── Page.cxx │ ├── FindSupport.hxx │ └── ProxyPage.hxx ├── EditPlaylistPage.hxx ├── Interface.hxx ├── ncu.hxx ├── event │ ├── Chrono.hxx │ ├── BackendEvents.hxx │ ├── Backend.hxx │ ├── DeferEvent.cxx │ ├── CoarseTimerEvent.cxx │ ├── PollEvents.hxx │ ├── FarTimerEvent.hxx │ ├── WakeFD.hxx │ ├── EpollEvents.hxx │ ├── FineTimerEvent.cxx │ ├── WinSelectEvents.hxx │ ├── TimerList.cxx │ ├── IdleEvent.hxx │ ├── SignalMonitor.hxx │ ├── meson.build │ ├── PollResultGeneric.hxx │ ├── PollBackend.hxx │ ├── TimerList.hxx │ ├── EpollBackend.hxx │ ├── DeferEvent.hxx │ └── net │ │ └── ConnectSocket.hxx ├── FileBrowserPage.hxx ├── LyricsLoader.hxx ├── Deleter.hxx ├── TabBar.hxx ├── screen_list.hxx ├── TableLayout.hxx ├── strfsong.hxx ├── SongPage.hxx ├── i18n.h ├── LyricsPage.hxx ├── BasicColors.hxx ├── PageMeta.hxx ├── lirc.hxx ├── db_completion.hxx ├── WaitUserInput.hxx ├── XdgBaseDirectory.hxx ├── AsyncUserInput.hxx ├── LyricsLoader.cxx ├── TitleBar.hxx ├── TagFilter.hxx ├── client │ ├── meson.build │ ├── aconnect.hxx │ └── gidle.hxx ├── defaults.hxx ├── xterm_title.cxx ├── Completion.cxx ├── Match.hxx ├── callbacks.cxx ├── ProgressBar.hxx ├── SongRowPaint.hxx ├── Styles.hxx ├── time │ └── ClockCache.hxx ├── TagFilter.cxx ├── KeyName.hxx ├── signals.cxx ├── LyricsCache.hxx ├── hscroll.cxx ├── DelayedSeek.hxx ├── BasicMarquee.cxx ├── screen_client.cxx ├── lirc.cxx ├── DelayedSeek.cxx ├── CustomColors.cxx ├── UserInputHandler.hxx ├── screen_paint.cxx ├── TablePaint.cxx ├── BasicColors.cxx ├── XdgBaseDirectory.cxx ├── SongRowPaint.cxx ├── Match.cxx ├── StatusBar.hxx ├── Completion.hxx ├── db_completion.cxx ├── BasicMarquee.hxx ├── TableStructure.cxx ├── ncu.cxx ├── Queue.cxx ├── TableLayout.cxx ├── screen_list.cxx ├── plugin.hxx ├── time_format.cxx ├── ProgressBar.cxx └── hscroll.hxx ├── subprojects ├── .gitignore ├── libmpdclient.wrap └── fmt.wrap ├── .github └── dependabot.yml ├── test ├── meson.build └── run_hscroll.cxx ├── .readthedocs.yaml ├── valgrind.suppressions ├── doc └── meson.build ├── README.rst ├── lyrics ├── 50-genius.py ├── 25-musixmatch.py └── 40-tekstowo.py └── AUTHORS /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n = import('i18n') 2 | 3 | i18n.gettext('ncmpc', preset: 'glib') 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stgit* 2 | *~ 3 | 4 | /output/ 5 | 6 | /.clangd/ 7 | /compile_commands.json 8 | -------------------------------------------------------------------------------- /src/win/ncmpc.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MusicPlayerDaemon/ncmpc/HEAD/src/win/ncmpc.ico -------------------------------------------------------------------------------- /subprojects/.gitignore: -------------------------------------------------------------------------------- 1 | /packagecache/ 2 | /.wraplock 3 | 4 | /fmt-*/ 5 | /libmpdclient/ 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /src/io/uring/Features.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | /* no definitions, ncmpc doesn't use io_uring */ 5 | -------------------------------------------------------------------------------- /src/io/uring/Features.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | /* no definitions, ncmpc doesn't use io_uring */ 5 | -------------------------------------------------------------------------------- /subprojects/libmpdclient.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/MusicPlayerDaemon/libmpdclient 3 | revision = v2.22 4 | 5 | [provide] 6 | libmpdclient = libmpdclient_dep 7 | -------------------------------------------------------------------------------- /src/io/meson.build: -------------------------------------------------------------------------------- 1 | io = static_library( 2 | 'io', 3 | 'FileDescriptor.cxx', 4 | include_directories: inc, 5 | ) 6 | 7 | io_dep = declare_dependency( 8 | link_with: io, 9 | ) 10 | -------------------------------------------------------------------------------- /src/ui/Bell.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | /* sound an audible and/or visible bell */ 7 | void 8 | Bell() noexcept; 9 | -------------------------------------------------------------------------------- /src/TableGlue.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | struct TableStructure; 7 | 8 | extern TableStructure song_table_structure; 9 | -------------------------------------------------------------------------------- /src/net/Features.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #define HAVE_TCP 7 | 8 | #ifndef _WIN32 9 | #define HAVE_UN 10 | #endif 11 | -------------------------------------------------------------------------------- /src/TableGlue.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "TableGlue.hxx" 5 | #include "TableStructure.hxx" 6 | 7 | TableStructure song_table_structure; 8 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | cs 2 | da 3 | de 4 | en 5 | eo 6 | es 7 | fa 8 | fi 9 | fr 10 | gl 11 | he 12 | hu 13 | ie 14 | it 15 | ja 16 | lt 17 | ko 18 | nb 19 | nl 20 | ru 21 | pl 22 | pt_BR 23 | pt 24 | sk 25 | sv 26 | ta 27 | uk 28 | zh_CN 29 | -------------------------------------------------------------------------------- /src/xterm_title.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | void 9 | set_xterm_title(std::string_view title) noexcept; 10 | -------------------------------------------------------------------------------- /src/ConfigParser.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef CONFIG_PARSER_HXX 5 | #define CONFIG_PARSER_HXX 6 | 7 | bool 8 | ReadConfigFile(const char *filename); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/util/Concepts.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | template 9 | concept Disposer = std::invocable; 10 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | executable( 2 | 'run_hscroll', 3 | 'run_hscroll.cxx', 4 | objects: ncmpc.extract_objects( 5 | 'src/BasicMarquee.cxx', 6 | ), 7 | include_directories: inc, 8 | dependencies: [ 9 | util_dep, 10 | fmt_dep, 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /src/QueuePage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_QUEUE_PAGE_HXX 5 | #define NCMPC_QUEUE_PAGE_HXX 6 | 7 | struct PageMeta; 8 | 9 | extern const PageMeta screen_queue; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/History.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef HISTORY_HXX 5 | #define HISTORY_HXX 6 | 7 | #include 8 | #include 9 | 10 | using History = std::list; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/TableStructure.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "TableColumn.hxx" 7 | 8 | #include 9 | 10 | struct TableStructure { 11 | std::vector columns; 12 | }; 13 | -------------------------------------------------------------------------------- /src/TableColumn.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | struct TableColumn { 9 | std::string caption, format; 10 | 11 | unsigned min_width = 10; 12 | 13 | float fraction_width = 1; 14 | }; 15 | -------------------------------------------------------------------------------- /src/GlobalBindings.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef GLOBAL_BINDINGS_HXX 5 | #define GLOBAL_BINDINGS_HXX 6 | 7 | struct KeyBindings; 8 | 9 | [[gnu::const]] 10 | KeyBindings & 11 | GetGlobalKeyBindings() noexcept; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/SongPtr.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef SONG_PTR_HXX 5 | #define SONG_PTR_HXX 6 | 7 | #include "Deleter.hxx" 8 | 9 | #include 10 | 11 | using SongPtr = std::unique_ptr; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/CustomColors.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef CUSTOM_COLORS_HXX 5 | #define CUSTOM_COLORS_HXX 6 | 7 | void 8 | colors_define(short color, short r, short g, short b) noexcept; 9 | 10 | void 11 | ApplyCustomColors() noexcept; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/HelpPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_HELP_PAGE_HXX 5 | #define NCMPC_HELP_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_HELP_SCREEN 10 | struct PageMeta; 11 | extern const PageMeta screen_help; 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/ui/Bell.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "Bell.hxx" 5 | #include "Options.hxx" 6 | 7 | #include 8 | 9 | void 10 | Bell() noexcept 11 | { 12 | if (ui_options.audible_bell) 13 | beep(); 14 | if (ui_options.visible_bell) 15 | flash(); 16 | } 17 | -------------------------------------------------------------------------------- /src/util/meson.build: -------------------------------------------------------------------------------- 1 | util = static_library( 2 | 'util', 3 | 'LocaleString.cxx', 4 | 'Exception.cxx', 5 | 'PrintException.cxx', 6 | 'StringCompare.cxx', 7 | 'StringStrip.cxx', 8 | 'StringUTF8.cxx', 9 | 'UriUtil.cxx', 10 | include_directories: inc, 11 | ) 12 | 13 | util_dep = declare_dependency( 14 | link_with: util, 15 | ) 16 | -------------------------------------------------------------------------------- /src/ChatPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_CHAT_PAGE_HXX 5 | #define NCMPC_CHAT_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_CHAT_SCREEN 10 | 11 | struct PageMeta; 12 | extern const PageMeta screen_chat; 13 | 14 | #endif 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/LibraryPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_LIBRARY_PAGE_HXX 5 | #define NCMPC_LIBRARY_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_LIBRARY_PAGE 10 | struct PageMeta; 11 | extern const PageMeta library_page; 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/SearchPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_SEARCH_PAGE_HXX 5 | #define NCMPC_SEARCH_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_SEARCH_SCREEN 10 | struct PageMeta; 11 | extern const PageMeta screen_search; 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/system/EpollFD.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "EpollFD.hxx" 5 | #include "Error.hxx" 6 | 7 | EpollFD::EpollFD() 8 | :fd(AdoptTag{}, ::epoll_create1(EPOLL_CLOEXEC)) 9 | { 10 | if (!fd.IsDefined()) 11 | throw MakeErrno("epoll_create1() failed"); 12 | } 13 | -------------------------------------------------------------------------------- /src/dialogs/meson.build: -------------------------------------------------------------------------------- 1 | dialogs = static_library( 2 | 'dialogs', 3 | 'ModalDialog.cxx', 4 | 'YesNoDialog.cxx', 5 | 'KeyDialog.cxx', 6 | 'TextInputDialog.cxx', 7 | include_directories: inc, 8 | dependencies: [ 9 | ui_dep, 10 | intl_dep, 11 | ], 12 | ) 13 | 14 | dialogs_dep = declare_dependency( 15 | link_with: dialogs, 16 | ) 17 | -------------------------------------------------------------------------------- /src/screen_utils.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "Completion.hxx" 7 | 8 | struct Window; 9 | 10 | void 11 | screen_display_completion_list(Window window, 12 | std::string_view prefix, 13 | Completion::Range range) noexcept; 14 | -------------------------------------------------------------------------------- /src/ui/ListRenderer.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | struct Window; 7 | 8 | class ListRenderer { 9 | public: 10 | virtual void PaintListItem(Window window, unsigned i, 11 | unsigned y, unsigned width, 12 | bool selected) const noexcept = 0; 13 | }; 14 | -------------------------------------------------------------------------------- /src/KeyDefPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_KEY_DEF_PAGE_HXX 5 | #define NCMPC_KEY_DEF_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_KEYDEF_SCREEN 10 | struct PageMeta; 11 | extern const PageMeta screen_keydef; 12 | #endif /* ENABLE_KEYDEF_SCREEN */ 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/OutputsPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_OUTPUTS_PAGE_HXX 5 | #define NCMPC_OUTPUTS_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_OUTPUTS_SCREEN 10 | struct PageMeta; 11 | extern const PageMeta screen_outputs; 12 | #endif /* ENABLE_OUTPUTS_SCREEN */ 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/co/meson.build: -------------------------------------------------------------------------------- 1 | coroutines_compile_args = [] 2 | 3 | if compiler.get_id() == 'gcc' 4 | coroutines_compile_args += '-fcoroutines' 5 | elif compiler.get_id() == 'clang' and compiler.version().version_compare('<15') 6 | coroutines_compile_args += '-fcoroutines-ts' 7 | endif 8 | 9 | coroutines_dep = declare_dependency( 10 | compile_args: coroutines_compile_args, 11 | ) 12 | -------------------------------------------------------------------------------- /src/player_command.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | enum class Command : unsigned; 7 | struct mpdclient; 8 | class Interface; 9 | class DelayedSeek; 10 | 11 | bool 12 | handle_player_command(Interface &interface, 13 | struct mpdclient &c, DelayedSeek &seek, Command cmd); 14 | -------------------------------------------------------------------------------- /src/net/IPv4Address.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "IPv4Address.hxx" 5 | 6 | #include 7 | 8 | IPv4Address::IPv4Address(SocketAddress src) noexcept 9 | :address(src.CastTo()) 10 | { 11 | assert(!src.IsNull()); 12 | assert(src.GetFamily() == AF_INET); 13 | } 14 | -------------------------------------------------------------------------------- /src/ui/meson.build: -------------------------------------------------------------------------------- 1 | ui = static_library( 2 | 'ui', 3 | 'Bell.cxx', 4 | 'ListCursor.cxx', 5 | 'ListWindow.cxx', 6 | 'TextListRenderer.cxx', 7 | include_directories: inc, 8 | dependencies: [ 9 | curses_dep, 10 | pcre_dep, 11 | ], 12 | ) 13 | 14 | ui_dep = declare_dependency( 15 | link_with: ui, 16 | dependencies: [ 17 | curses_dep, 18 | ], 19 | ) 20 | -------------------------------------------------------------------------------- /src/TablePaint.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | struct Window; 7 | struct TableLayout; 8 | struct mpd_song; 9 | 10 | void 11 | PaintTableRow(Window window, unsigned width, 12 | bool selected, bool highlight, const struct mpd_song &song, 13 | const TableLayout &layout) noexcept; 14 | -------------------------------------------------------------------------------- /src/screen_client.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | struct mpdclient; 7 | class Interface; 8 | 9 | /** 10 | * Starts a (server-side) database update and displays a status 11 | * message. 12 | */ 13 | void 14 | screen_database_update(Interface &interface, struct mpdclient &c, const char *path); 15 | -------------------------------------------------------------------------------- /src/save_playlist.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | class ScreenManager; 9 | struct mpdclient; 10 | namespace Co { class InvokeTask; } 11 | 12 | [[nodiscard]] 13 | Co::InvokeTask 14 | playlist_save(ScreenManager &screen, struct mpdclient &c, 15 | std::string name={}) noexcept; 16 | -------------------------------------------------------------------------------- /src/ConfigFile.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef CONFIG_FILE_HXX 5 | #define CONFIG_FILE_HXX 6 | 7 | #include 8 | 9 | std::string 10 | MakeKeysPath(); 11 | 12 | std::string 13 | GetUserConfigPath() noexcept; 14 | 15 | std::string 16 | GetSystemConfigPath() noexcept; 17 | 18 | void 19 | read_configuration(); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/time_format.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | [[nodiscard]] [[gnu::pure]] 10 | std::string_view 11 | format_duration_short(std::span buffer, unsigned duration) noexcept; 12 | 13 | void 14 | format_duration_long(std::span buffer, unsigned long duration) noexcept; 15 | -------------------------------------------------------------------------------- /src/lib/fmt/ToSpan.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "ToSpan.hxx" 5 | 6 | std::string_view 7 | VFmtTruncate(std::span buffer, 8 | fmt::string_view format_str, fmt::format_args args) noexcept 9 | { 10 | auto [p, _] = fmt::vformat_to_n(buffer.begin(), buffer.size(), 11 | format_str, args); 12 | return {buffer.begin(), p}; 13 | } 14 | -------------------------------------------------------------------------------- /src/page/Container.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-lateryes 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "Interface.hxx" 7 | 8 | class Page; 9 | 10 | /** 11 | * An abstract interface for classes which host one (or more) #Page 12 | * instances. 13 | */ 14 | class PageContainer : public Interface { 15 | public: 16 | virtual void SchedulePaint(Page &page) noexcept = 0; 17 | }; 18 | -------------------------------------------------------------------------------- /src/ui/ListText.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | class ListText { 10 | public: 11 | /** 12 | * @return the text in the locale charset 13 | */ 14 | [[gnu::pure]] 15 | virtual std::string_view GetListItemText(std::span buffer, 16 | unsigned i) const noexcept = 0; 17 | }; 18 | -------------------------------------------------------------------------------- /src/EditPlaylistPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_EDIT_PLAYLIST_PAGE_HXX 5 | #define NCMPC_EDIT_PLAYLIST_PAGE_HXX 6 | 7 | struct PageMeta; 8 | class ScreenManager; 9 | 10 | extern const PageMeta edit_playlist_page; 11 | 12 | void 13 | EditPlaylist(ScreenManager &_screen, struct mpdclient &c, 14 | const char *name) noexcept; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/io/FileAt.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include "FileDescriptor.hxx" 8 | 9 | /** 10 | * Reference to a file by an anchor directory (which can be an 11 | * `O_PATH` descriptor) and a path name relative to it. 12 | */ 13 | struct FileAt { 14 | FileDescriptor directory; 15 | const char *name; 16 | }; 17 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | 13 | # Build documentation in the "docs/" directory with Sphinx 14 | sphinx: 15 | configuration: doc/conf.py 16 | -------------------------------------------------------------------------------- /src/util/PrintException.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | /** 10 | * Print this exception (and its nested exceptions, if any) to stderr. 11 | */ 12 | void 13 | PrintException(const std::exception &e) noexcept; 14 | 15 | void 16 | PrintException(const std::exception_ptr &ep) noexcept; 17 | -------------------------------------------------------------------------------- /src/util/CopyConst.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * Generate a type based on #To with the same const-ness as #From. 10 | */ 11 | template 12 | using CopyConst = std::conditional_t, 13 | std::add_const_t, 14 | std::remove_const_t>; 15 | -------------------------------------------------------------------------------- /src/Interface.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * An abstract interface that provides access to generic and global 10 | * user interface components. 11 | */ 12 | class Interface { 13 | public: 14 | /** 15 | * Show a message in the status bar. 16 | */ 17 | virtual void Alert(std::string message) noexcept = 0; 18 | }; 19 | -------------------------------------------------------------------------------- /src/ncu.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | /* 5 | * Basic libnucrses initialization. 6 | */ 7 | 8 | #ifndef NCU_H 9 | #define NCU_H 10 | 11 | void 12 | ncu_init(); 13 | 14 | void 15 | ncu_deinit(); 16 | 17 | class ScopeCursesInit { 18 | public: 19 | ScopeCursesInit() { 20 | ncu_init(); 21 | } 22 | 23 | ~ScopeCursesInit() { 24 | ncu_deinit(); 25 | } 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/event/Chrono.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace Event { 9 | 10 | /** 11 | * The clock used by classes #EventLoop, #CoarseTimerEvent and #FineTimerEvent. 12 | */ 13 | using Clock = std::chrono::steady_clock; 14 | 15 | using Duration = Clock::duration; 16 | using TimePoint = Clock::time_point; 17 | 18 | } // namespace Event 19 | -------------------------------------------------------------------------------- /src/event/BackendEvents.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | 3 | #pragma once 4 | 5 | #include "event/Features.h" 6 | 7 | #ifdef _WIN32 8 | 9 | #include "WinSelectEvents.hxx" 10 | using EventPollBackendEvents = WinSelectEvents; 11 | 12 | #elif defined(USE_EPOLL) 13 | 14 | #include "EpollEvents.hxx" 15 | using EventPollBackendEvents = EpollEvents; 16 | 17 | #else 18 | 19 | #include "PollEvents.hxx" 20 | using EventPollBackendEvents = PollEvents; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/page/meson.build: -------------------------------------------------------------------------------- 1 | page_sources = [ 2 | 'Page.cxx', 3 | 'ProxyPage.cxx', 4 | 'FindSupport.cxx', 5 | ] 6 | 7 | if need_screen_text 8 | page_sources += 'TextPage.cxx' 9 | endif 10 | 11 | page = static_library( 12 | 'page', 13 | page_sources, 14 | include_directories: inc, 15 | dependencies: [ 16 | ui_dep, 17 | fmt_dep, 18 | ], 19 | ) 20 | 21 | page_dep = declare_dependency( 22 | link_with: page, 23 | dependencies: [ 24 | ui_dep, 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /src/dialogs/ModalDock.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | class ModalDialog; 7 | 8 | /** 9 | * Interface for an entity which can host a #ModalDialog. 10 | */ 11 | class ModalDock { 12 | public: 13 | virtual void ShowModalDialog(ModalDialog &m) noexcept = 0; 14 | virtual void HideModalDialog(ModalDialog &m) noexcept = 0; 15 | virtual void CancelModalDialog(ModalDialog &m) noexcept = 0; 16 | }; 17 | -------------------------------------------------------------------------------- /src/FileBrowserPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_FILE_BROWSER_PAGE_HXX 5 | #define NCMPC_FILE_BROWSER_PAGE_HXX 6 | 7 | struct mpdclient; 8 | struct mpd_song; 9 | struct PageMeta; 10 | class ScreenManager; 11 | 12 | extern const PageMeta screen_browse; 13 | 14 | bool 15 | screen_file_goto_song(ScreenManager &_screen, struct mpdclient &c, 16 | const struct mpd_song &song) noexcept; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/LyricsLoader.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef LYRICS_LOADER_HXX 5 | #define LYRICS_LOADER_HXX 6 | 7 | #include "plugin.hxx" 8 | 9 | class LyricsLoader { 10 | const PluginList plugins; 11 | 12 | public: 13 | LyricsLoader() noexcept; 14 | 15 | PluginCycle *Load(EventLoop &event_loop, 16 | const char *artist, const char *title, 17 | PluginResponseHandler &handler) noexcept; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/Deleter.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | struct LibmpdclientDeleter { 9 | void operator()(struct mpd_song *song) const noexcept { 10 | mpd_song_free(song); 11 | } 12 | 13 | void operator()(struct mpd_output *o) const noexcept { 14 | mpd_output_free(o); 15 | } 16 | 17 | void operator()(struct mpd_partition *o) const noexcept { 18 | mpd_partition_free(o); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/TabBar.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | struct Window; 9 | struct PageMeta; 10 | 11 | void 12 | PaintTabBar(Window window, const PageMeta ¤t_page_meta, 13 | std::string_view current_page_title) noexcept; 14 | 15 | [[gnu::pure]] 16 | const PageMeta * 17 | GetTabAtX(const PageMeta ¤t_page_meta, 18 | std::string_view current_page_title, 19 | unsigned x) noexcept; 20 | -------------------------------------------------------------------------------- /src/screen_list.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef SCREEN_LIST_H 5 | #define SCREEN_LIST_H 6 | 7 | enum class Command : unsigned; 8 | struct PageMeta; 9 | 10 | [[gnu::const]] 11 | const PageMeta * 12 | GetPageMeta(unsigned i) noexcept; 13 | 14 | [[gnu::pure]] 15 | const PageMeta * 16 | screen_lookup_name(const char *name) noexcept; 17 | 18 | [[gnu::const]] 19 | const PageMeta * 20 | PageByCommand(Command cmd) noexcept; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/net/TimeoutError.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | /** 10 | * Some operation has timed out (e.g. connecting to a server, waiting 11 | * for reply from a server). 12 | */ 13 | class TimeoutError : public std::runtime_error { 14 | public: 15 | using std::runtime_error::runtime_error; 16 | 17 | TimeoutError() noexcept 18 | :std::runtime_error("Timeout") {} 19 | }; 20 | -------------------------------------------------------------------------------- /src/util/SPrintf.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | template 11 | static inline std::string_view 12 | SPrintf(std::span buffer, const char *fmt, Args&&... args) noexcept 13 | { 14 | int length = snprintf(buffer.data(), buffer.size(), fmt, args...); 15 | if (length < 0) 16 | return {}; 17 | 18 | return {buffer.data(), static_cast(length)}; 19 | } 20 | -------------------------------------------------------------------------------- /src/TableLayout.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | struct TableStructure; 9 | 10 | struct TableColumnLayout { 11 | unsigned width; 12 | }; 13 | 14 | struct TableLayout { 15 | const TableStructure &structure; 16 | 17 | std::array columns; 18 | 19 | explicit TableLayout(const TableStructure &_structure) noexcept 20 | :structure(_structure) {} 21 | 22 | void Calculate(unsigned screen_width) noexcept; 23 | }; 24 | -------------------------------------------------------------------------------- /src/net/LocalSocketAddress.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "LocalSocketAddress.hxx" 5 | 6 | const char * 7 | LocalSocketAddress::GetLocalPath() const noexcept 8 | { 9 | const auto raw = GetLocalRaw(); 10 | return !raw.empty() && 11 | /* must be an absolute path */ 12 | raw.front() == '/' && 13 | /* must be null-terminated and there must not be any 14 | other null byte */ 15 | raw.find('\0') == raw.size() - 1 16 | ? raw.data() 17 | : nullptr; 18 | } 19 | -------------------------------------------------------------------------------- /src/strfsong.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | struct mpd_song; 10 | class TagMask; 11 | 12 | [[nodiscard]] [[gnu::pure]] 13 | std::string_view 14 | strfsong(std::span buffer, const char *format, 15 | const struct mpd_song &song) noexcept; 16 | 17 | /** 18 | * Check which tags are referenced by the given song format. 19 | */ 20 | [[gnu::pure]] 21 | TagMask 22 | SongFormatToTagMask(const char *format) noexcept; 23 | -------------------------------------------------------------------------------- /src/util/UriUtil.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "UriUtil.hxx" 5 | 6 | #include 7 | 8 | const char * 9 | GetUriFilename(const char *uri) noexcept 10 | { 11 | const char *slash = strrchr(uri, '/'); 12 | return slash != nullptr ? slash + 1 : uri; 13 | } 14 | 15 | std::string_view 16 | GetParentUri(std::string_view uri) noexcept 17 | { 18 | const auto slash = uri.rfind('/'); 19 | if (slash == uri.npos) 20 | return {}; 21 | 22 | return uri.substr(0, slash); 23 | } 24 | -------------------------------------------------------------------------------- /src/SongPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_SONG_PAGE_HXX 5 | #define NCMPC_SONG_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_SONG_SCREEN 10 | 11 | struct mpdclient; 12 | struct mpd_song; 13 | struct PageMeta; 14 | class ScreenManager; 15 | 16 | extern const PageMeta screen_song; 17 | 18 | void 19 | screen_song_switch(ScreenManager &_screen, struct mpdclient &c, 20 | const struct mpd_song &song) noexcept; 21 | 22 | #endif /* ENABLE_SONG_SCREEN */ 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/event/Backend.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef EVENT_BACKEND_HXX 5 | #define EVENT_BACKEND_HXX 6 | 7 | #include "event/Features.h" 8 | 9 | #ifdef _WIN32 10 | 11 | #include "WinSelectBackend.hxx" 12 | using EventPollBackend = WinSelectBackend; 13 | 14 | #elif defined(USE_EPOLL) 15 | 16 | #include "EpollBackend.hxx" 17 | using EventPollBackend = EpollBackend; 18 | 19 | #else 20 | 21 | #include "PollBackend.hxx" 22 | using EventPollBackend = PollBackend; 23 | 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/util/StringUTF8.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef STRING_UTF8_HXX 5 | #define STRING_UTF8_HXX 6 | 7 | #include "config.h" 8 | 9 | class ScopeInitUTF8 { 10 | #ifdef HAVE_LOCALE_T 11 | public: 12 | ScopeInitUTF8() noexcept; 13 | ~ScopeInitUTF8() noexcept; 14 | 15 | ScopeInitUTF8(const ScopeInitUTF8 &) = delete; 16 | ScopeInitUTF8 &operator=(const ScopeInitUTF8 &) = delete; 17 | #endif 18 | }; 19 | 20 | [[gnu::pure]] 21 | int 22 | CollateUTF8(const char *a, const char *b); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/i18n.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "config.h" 7 | 8 | #ifdef ENABLE_NLS 9 | 10 | #include // IWYU pragma: export 11 | 12 | #define my_gettext(x) gettext(x) 13 | #define _(x) gettext(x) 14 | 15 | #ifdef gettext_noop 16 | #define N_(x) gettext_noop(x) 17 | #else 18 | #define N_(x) (x) 19 | #endif 20 | 21 | #else 22 | #define my_gettext(x) (x) 23 | #define _(x) x 24 | #define N_(x) x 25 | #endif 26 | 27 | #define YES_TRANSLATION _("y") 28 | #define NO_TRANSLATION _("n") 29 | -------------------------------------------------------------------------------- /src/ui/Size.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | /** 7 | * Dimensions of a curses screen/window in cells. 8 | */ 9 | struct Size { 10 | unsigned width, height; 11 | 12 | constexpr bool operator==(const Size &) const noexcept = default; 13 | 14 | constexpr Size operator+(Size other) const noexcept { 15 | return {width + other.width, height + other.height}; 16 | } 17 | 18 | constexpr Size operator-(Size other) const noexcept { 19 | return {width - other.width, height - other.height}; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/ui/TextListRenderer.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "ListRenderer.hxx" 7 | 8 | class ListText; 9 | 10 | class TextListRenderer final : public ListRenderer { 11 | const ListText &text; 12 | 13 | public: 14 | explicit TextListRenderer(const ListText &_text) noexcept 15 | :text(_text) {} 16 | 17 | /* virtual methods from class ListRenderer */ 18 | void PaintListItem(const Window window, unsigned i, unsigned y, unsigned width, 19 | bool selected) const noexcept override; 20 | }; 21 | -------------------------------------------------------------------------------- /src/LyricsPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_LYRICS_PAGE_HXX 5 | #define NCMPC_LYRICS_PAGE_HXX 6 | 7 | #include "config.h" 8 | 9 | #ifdef ENABLE_LYRICS_SCREEN 10 | 11 | struct mpdclient; 12 | struct mpd_song; 13 | struct PageMeta; 14 | class ScreenManager; 15 | 16 | extern const PageMeta screen_lyrics; 17 | 18 | void 19 | screen_lyrics_switch(ScreenManager &_screen, struct mpdclient &c, 20 | const struct mpd_song &song, 21 | bool follow); 22 | 23 | #endif /* ENABLE_LYRICS_SCREEN */ 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/BasicColors.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef BASIC_COLORS_HXX 5 | #define BASIC_COLORS_HXX 6 | 7 | /** 8 | * Parse an ncurses color name. 9 | * 10 | * @return the COLOR_* integer value or -1 on error 11 | */ 12 | [[gnu::pure]] 13 | short 14 | ParseBasicColorName(const char *name) noexcept; 15 | 16 | /** 17 | * Like ParseBasicColorName(), but also allow numeric colors. 18 | * 19 | * @return the color integer value or -1 on error 20 | */ 21 | [[gnu::pure]] 22 | short 23 | ParseColorNameOrNumber(const char *s) noexcept; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/PageMeta.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | enum class Command : unsigned; 9 | class Page; 10 | class ScreenManager; 11 | struct Window; 12 | 13 | struct PageMeta { 14 | const char *name; 15 | 16 | /** 17 | * A title/caption for this page, to be translated using 18 | * gettext(). 19 | */ 20 | const char *title; 21 | 22 | /** 23 | * The command which switches to this page. 24 | */ 25 | Command command; 26 | 27 | std::unique_ptr (*init)(ScreenManager &screen, Window window); 28 | }; 29 | -------------------------------------------------------------------------------- /src/event/DeferEvent.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "DeferEvent.hxx" 5 | #include "Loop.hxx" 6 | 7 | void 8 | DeferEvent::Schedule() noexcept 9 | { 10 | if (!IsPending()) 11 | loop.AddDefer(*this); 12 | 13 | assert(IsPending()); 14 | } 15 | 16 | void 17 | DeferEvent::ScheduleIdle() noexcept 18 | { 19 | if (!IsPending()) 20 | loop.AddIdle(*this); 21 | 22 | assert(IsPending()); 23 | } 24 | 25 | void 26 | DeferEvent::ScheduleNext() noexcept 27 | { 28 | if (!IsPending()) 29 | loop.AddNext(*this); 30 | 31 | assert(IsPending()); 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/fmt/RuntimeError.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "RuntimeError.hxx" 5 | #include "ToBuffer.hxx" 6 | 7 | std::runtime_error 8 | VFmtRuntimeError(fmt::string_view format_str, fmt::format_args args) noexcept 9 | { 10 | const auto msg = VFmtBuffer<512>(format_str, args); 11 | return std::runtime_error{msg}; 12 | } 13 | 14 | std::invalid_argument 15 | VFmtInvalidArgument(fmt::string_view format_str, fmt::format_args args) noexcept 16 | { 17 | const auto msg = VFmtBuffer<512>(format_str, args); 18 | return std::invalid_argument{msg}; 19 | } 20 | -------------------------------------------------------------------------------- /subprojects/fmt.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = fmt-12.0.0 3 | source_url = https://github.com/fmtlib/fmt/archive/12.0.0.tar.gz 4 | source_filename = fmt-12.0.0.tar.gz 5 | source_hash = aa3e8fbb6a0066c03454434add1f1fc23299e85758ceec0d7d2d974431481e40 6 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_12.0.0-1/fmt-12.0.0.tar.gz 7 | patch_filename = fmt_12.0.0-1_patch.zip 8 | patch_url = https://wrapdb.mesonbuild.com/v2/fmt_12.0.0-1/get_patch 9 | patch_hash = 307f288ebf3850abf2f0c50ac1fb07de97df9538d39146d802f3c0d6cada8998 10 | wrapdb_version = 12.0.0-1 11 | 12 | [provide] 13 | dependency_names = fmt 14 | -------------------------------------------------------------------------------- /src/lirc.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "event/SocketEvent.hxx" 7 | 8 | class UserInputHandler; 9 | 10 | class LircInput final { 11 | struct lirc_config *lc = nullptr; 12 | 13 | UserInputHandler &handler; 14 | 15 | SocketEvent event; 16 | 17 | public: 18 | LircInput(EventLoop &event_loop, 19 | UserInputHandler &_handler) noexcept; 20 | ~LircInput(); 21 | 22 | auto &GetEventLoop() const noexcept { 23 | return event.GetEventLoop(); 24 | } 25 | 26 | private: 27 | void OnSocketReady(unsigned flags) noexcept; 28 | }; 29 | -------------------------------------------------------------------------------- /src/net/AsyncResolveConnect.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "event/net/ConnectSocket.hxx" 7 | 8 | class AsyncResolveConnect { 9 | ConnectSocketHandler &handler; 10 | 11 | ConnectSocket connect; 12 | 13 | public: 14 | AsyncResolveConnect(EventLoop &event_loop, 15 | ConnectSocketHandler &_handler) noexcept 16 | :handler(_handler), 17 | connect(event_loop, _handler) {} 18 | 19 | /** 20 | * Resolve a host name and connect to it asynchronously. 21 | */ 22 | void Start(const char *host, unsigned port) noexcept; 23 | }; 24 | -------------------------------------------------------------------------------- /src/system/meson.build: -------------------------------------------------------------------------------- 1 | system_sources = [ 2 | 'EventPipe.cxx', 3 | ] 4 | 5 | if host_machine.system() == 'linux' 6 | system_sources += [ 7 | 'EventFD.cxx', 8 | 'SignalFD.cxx', 9 | 'EpollFD.cxx', 10 | ] 11 | endif 12 | 13 | system = static_library( 14 | 'system', 15 | system_sources, 16 | include_directories: inc, 17 | ) 18 | 19 | if is_windows 20 | winsock_dep = c_compiler.find_library('ws2_32') 21 | else 22 | winsock_dep = dependency('', required: false) 23 | endif 24 | 25 | system_dep = declare_dependency( 26 | link_with: system, 27 | dependencies: [ 28 | io_dep, 29 | winsock_dep, 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /src/util/OffsetPointer.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * Offset the given pointer by the specified number of bytes. 10 | */ 11 | constexpr void * 12 | OffsetPointer(void *p, std::ptrdiff_t offset) noexcept 13 | { 14 | return static_cast(p) + offset; 15 | } 16 | 17 | /** 18 | * Offset the given pointer by the specified number of bytes. 19 | */ 20 | constexpr const void * 21 | OffsetPointer(const void *p, std::ptrdiff_t offset) noexcept 22 | { 23 | return static_cast(p) + offset; 24 | } 25 | -------------------------------------------------------------------------------- /src/db_completion.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | struct mpdclient; 7 | class Completion; 8 | 9 | #define GCMP_TYPE_DIR (0x01 << 0) 10 | #define GCMP_TYPE_FILE (0x01 << 1) 11 | #define GCMP_TYPE_PLAYLIST (0x01 << 2) 12 | #define GCMP_TYPE_RFILE (GCMP_TYPE_DIR | GCMP_TYPE_FILE) 13 | #define GCMP_TYPE_RPLAYLIST (GCMP_TYPE_DIR | GCMP_TYPE_PLAYLIST) 14 | 15 | /** 16 | * Create a list suitable for #Completion from path. 17 | */ 18 | void 19 | gcmp_list_from_path(struct mpdclient &c, const char *path, 20 | Completion &completion, 21 | int types); 22 | -------------------------------------------------------------------------------- /src/WaitUserInput.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef WAIT_USER_INPUT_HXX 5 | #define WAIT_USER_INPUT_HXX 6 | 7 | #include 8 | #include 9 | 10 | class WaitUserInput { 11 | struct pollfd pfd; 12 | 13 | public: 14 | WaitUserInput() noexcept { 15 | pfd.fd = STDIN_FILENO; 16 | pfd.events = POLLIN; 17 | } 18 | 19 | bool IsReady() noexcept { 20 | return Poll(0); 21 | } 22 | 23 | bool Wait() noexcept { 24 | return Poll(-1); 25 | } 26 | 27 | private: 28 | bool Poll(int timeout) noexcept { 29 | return poll(&pfd, 1, timeout) > 0; 30 | } 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/XdgBaseDirectory.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * Returns the absolute path of the XDG config directory for the 11 | * specified package (or an empty string on error). 12 | */ 13 | [[gnu::pure]] 14 | std::string 15 | GetUserConfigDirectory(std::string_view package) noexcept; 16 | 17 | /** 18 | * Returns the absolute path of the XDG cache directory for the 19 | * specified package (or an empty string on error). 20 | */ 21 | [[gnu::pure]] 22 | std::string 23 | GetUserCacheDirectory(std::string_view package) noexcept; 24 | -------------------------------------------------------------------------------- /src/lib/fmt/ToSpan.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | [[nodiscard]] [[gnu::pure]] 12 | std::string_view 13 | VFmtTruncate(std::span buffer, 14 | fmt::string_view format_str, fmt::format_args args) noexcept; 15 | 16 | template 17 | [[nodiscard]] [[gnu::pure]] 18 | inline std::string_view 19 | FmtTruncate(std::span buffer, const S &format_str, Args&&... args) noexcept 20 | { 21 | return VFmtTruncate(buffer, format_str, fmt::make_format_args(args...)); 22 | } 23 | -------------------------------------------------------------------------------- /src/net/meson.build: -------------------------------------------------------------------------------- 1 | net_sources = [] 2 | 3 | if host_machine.system() != 'windows' 4 | net_sources += 'LocalSocketAddress.cxx' 5 | endif 6 | 7 | net = static_library( 8 | 'net', 9 | net_sources, 10 | 'HostParser.cxx', 11 | 'Resolver.cxx', 12 | 'AddressInfo.cxx', 13 | 'AllocatedSocketAddress.cxx', 14 | 'LocalSocketAddress.cxx', 15 | 'IPv4Address.cxx', 16 | 'IPv6Address.cxx', 17 | 'SocketAddress.cxx', 18 | 'SocketDescriptor.cxx', 19 | include_directories: inc, 20 | dependencies: [ 21 | fmt_dep, 22 | ], 23 | ) 24 | 25 | net_dep = declare_dependency( 26 | link_with: net, 27 | dependencies: [ 28 | system_dep, 29 | io_dep, 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /src/event/CoarseTimerEvent.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #include "CoarseTimerEvent.hxx" 6 | #include "Loop.hxx" 7 | 8 | void 9 | CoarseTimerEvent::Schedule(Event::Duration d) noexcept 10 | { 11 | Cancel(); 12 | 13 | due = loop.SteadyNow() + d; 14 | loop.Insert(*this); 15 | } 16 | 17 | void 18 | CoarseTimerEvent::ScheduleEarlier(Event::Duration d) noexcept 19 | { 20 | const auto new_due = loop.SteadyNow() + d; 21 | 22 | if (IsPending()) { 23 | if (new_due >= due) 24 | return; 25 | 26 | Cancel(); 27 | } 28 | 29 | due = new_due; 30 | loop.Insert(*this); 31 | } 32 | -------------------------------------------------------------------------------- /valgrind.suppressions: -------------------------------------------------------------------------------- 1 | { 2 | ncurses 3 | Memcheck:Leak 4 | match-leak-kinds: reachable 5 | fun:*alloc 6 | ... 7 | fun:_nc_setup_tinfo 8 | } 9 | 10 | { 11 | ncurses 12 | Memcheck:Leak 13 | match-leak-kinds: reachable 14 | fun:realloc 15 | ... 16 | fun:get_space 17 | ... 18 | fun:tparam_internal 19 | } 20 | 21 | { 22 | ncurses 23 | Memcheck:Leak 24 | match-leak-kinds: reachable 25 | fun:realloc 26 | ... 27 | fun:_nc_tparm_analyze 28 | fun:tparam_internal 29 | } 30 | 31 | { 32 | ncurses 33 | Memcheck:Leak 34 | match-leak-kinds: reachable 35 | fun:calloc 36 | fun:_nc_build_names 37 | fun:_nc_find_type_entry 38 | } 39 | -------------------------------------------------------------------------------- /src/AsyncUserInput.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "event/PipeEvent.hxx" 7 | 8 | #include 9 | 10 | class UserInputHandler; 11 | 12 | class AsyncUserInput { 13 | PipeEvent stdin_event; 14 | 15 | WINDOW &w; 16 | 17 | UserInputHandler &handler; 18 | 19 | public: 20 | AsyncUserInput(EventLoop &event_loop, WINDOW &_w, 21 | UserInputHandler &_handler) noexcept; 22 | 23 | // TODO remove this kludge 24 | void InjectKey(int key) noexcept; 25 | 26 | private: 27 | void OnSocketReady(unsigned flags) noexcept; 28 | }; 29 | 30 | void 31 | keyboard_unread(int key) noexcept; 32 | -------------------------------------------------------------------------------- /src/LyricsLoader.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "LyricsLoader.hxx" 5 | #include "config.h" 6 | 7 | #include 8 | 9 | LyricsLoader::LyricsLoader() noexcept 10 | :plugins(plugin_list_load_directory(LYRICS_PLUGIN_DIR)) 11 | { 12 | } 13 | 14 | PluginCycle * 15 | LyricsLoader::Load(EventLoop &event_loop, 16 | const char *artist, const char *title, 17 | PluginResponseHandler &handler) noexcept 18 | { 19 | assert(artist != nullptr); 20 | assert(title != nullptr); 21 | 22 | const char *args[3] = { artist, title, nullptr }; 23 | 24 | return plugin_run(event_loop, plugins, args, handler); 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/Point.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_POINT_HXX 5 | #define NCMPC_POINT_HXX 6 | 7 | #include "Size.hxx" 8 | 9 | /** 10 | * Coordinates of a cell on a curses screen/window. 11 | */ 12 | struct Point { 13 | int x, y; 14 | 15 | constexpr Point operator+(Point other) const noexcept { 16 | return {x + other.x, y + other.y}; 17 | } 18 | 19 | constexpr Point operator-(Point other) const noexcept { 20 | return {x - other.x, y - other.y}; 21 | } 22 | 23 | constexpr Point operator+(Size size) const noexcept { 24 | return {x + int(size.width), y + int(size.height)}; 25 | } 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/ui/Keys.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */ 9 | 10 | enum : int { 11 | KEY_BACKSPACE2 = 8, 12 | KEY_TAB = 9, 13 | KEY_LINEFEED = '\n', 14 | KEY_RETURN = '\r', 15 | KEY_ESCAPE = 0x1b, 16 | KEY_BACKSPACE3 = 0x7f, 17 | }; 18 | 19 | constexpr bool 20 | IsFKey(int key) noexcept 21 | { 22 | return key >= KEY_F(0) && key <= KEY_F(63); 23 | } 24 | 25 | constexpr bool 26 | IsBackspace(int key) noexcept 27 | { 28 | return key == KEY_BACKSPACE || 29 | key == KEY_BACKSPACE2 || 30 | key == KEY_BACKSPACE3; 31 | } 32 | -------------------------------------------------------------------------------- /src/TitleBar.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "ui/Window.hxx" 7 | 8 | #include 9 | 10 | struct mpd_status; 11 | struct PageMeta; 12 | 13 | class TitleBar { 14 | UniqueWindow window; 15 | 16 | int volume; 17 | char flags[8]; 18 | 19 | public: 20 | TitleBar(Point p, unsigned width) noexcept; 21 | 22 | static constexpr unsigned GetHeight() noexcept { 23 | return 2; 24 | } 25 | 26 | void OnResize(unsigned width) noexcept; 27 | void Update(const struct mpd_status *status) noexcept; 28 | void Paint(const PageMeta ¤t_page_meta, 29 | std::string_view title) const noexcept; 30 | }; 31 | -------------------------------------------------------------------------------- /src/win/meson.build: -------------------------------------------------------------------------------- 1 | splitted_version = meson.project_version().split('.') 2 | 3 | wconf = configuration_data() 4 | wconf.set('VERSION_MAJOR', splitted_version[0]) 5 | wconf.set('VERSION_MINOR', splitted_version[1]) 6 | if splitted_version.length() >= 3 7 | wconf.set('VERSION_REVISION', splitted_version[2]) 8 | else 9 | wconf.set('VERSION_REVISION', '0') 10 | endif 11 | if splitted_version.length() >= 4 12 | wconf.set('VERSION_EXTRA', splitted_version[3]) 13 | else 14 | wconf.set('VERSION_EXTRA', '0') 15 | endif 16 | configure_file(input: 'ncmpc_win32_rc.rc.in', output: 'ncmpc_win32_rc.rc', configuration: wconf) 17 | 18 | windows = import('windows') 19 | sources += windows.compile_resources('ncmpc_win32_rc.rc') 20 | -------------------------------------------------------------------------------- /src/TagFilter.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_TAG_FILTER_HXX 5 | #define NCMPC_TAG_FILTER_HXX 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | class ScreenManager; 13 | 14 | using TagFilter = std::forward_list>; 15 | 16 | [[gnu::pure]] 17 | const char * 18 | FindTag(const TagFilter &filter, enum mpd_tag_type tag) noexcept; 19 | 20 | void 21 | AddConstraints(struct mpd_connection *connection, 22 | const TagFilter &filter) noexcept; 23 | 24 | [[gnu::pure]] 25 | std::string 26 | ToString(const TagFilter &filter) noexcept; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/client/meson.build: -------------------------------------------------------------------------------- 1 | libmpdclient_dep = dependency('libmpdclient', version: '>= 2.19') 2 | 3 | client_sources = [ 4 | 'gidle.cxx', 5 | 'mpdclient.cxx', 6 | ] 7 | 8 | if async_connect 9 | client_sources += [ 10 | '../net/SocketError.cxx', 11 | '../net/AsyncResolveConnect.cxx', 12 | '../event/net/ConnectSocket.cxx', 13 | 'aconnect.cxx', 14 | ] 15 | endif 16 | 17 | client = static_library( 18 | 'client', 19 | client_sources, 20 | include_directories: inc, 21 | dependencies: [ 22 | libmpdclient_dep, 23 | event_dep, 24 | ], 25 | ) 26 | 27 | client_dep = declare_dependency( 28 | link_with: client, 29 | dependencies: [ 30 | libmpdclient_dep, 31 | event_dep, 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | src/Bindings.cxx 2 | src/ChatPage.cxx 3 | src/Command.cxx 4 | src/ConfigParser.cxx 5 | src/CustomColors.cxx 6 | src/EditPlaylistPage.cxx 7 | src/FileBrowserPage.cxx 8 | src/FileListPage.cxx 9 | src/HelpPage.cxx 10 | src/KeyDefPage.cxx 11 | src/KeyName.cxx 12 | src/LibraryPage.cxx 13 | src/LyricsPage.cxx 14 | src/Main.cxx 15 | src/Options.cxx 16 | src/OutputsPage.cxx 17 | src/QueuePage.cxx 18 | src/SearchPage.cxx 19 | src/SongPage.cxx 20 | src/StatusBar.cxx 21 | src/Styles.cxx 22 | src/TabBar.cxx 23 | src/TagListPage.cxx 24 | src/TitleBar.cxx 25 | src/dialogs/YesNoDialog.cxx 26 | src/page/FindSupport.cxx 27 | src/player_command.cxx 28 | src/save_playlist.cxx 29 | src/screen.cxx 30 | src/screen_client.cxx 31 | src/time_format.cxx 32 | -------------------------------------------------------------------------------- /src/client/aconnect.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | struct mpd_connection; 11 | struct AsyncMpdConnect; 12 | class EventLoop; 13 | 14 | class AsyncMpdConnectHandler { 15 | public: 16 | virtual void OnAsyncMpdConnect(struct mpd_connection *c) noexcept = 0; 17 | virtual void OnAsyncMpdConnectError(std::exception_ptr e) noexcept = 0; 18 | }; 19 | 20 | void 21 | aconnect_start(EventLoop &event_loop, 22 | AsyncMpdConnect **acp, 23 | const char *host, unsigned port, 24 | AsyncMpdConnectHandler &handler); 25 | 26 | void 27 | aconnect_cancel(AsyncMpdConnect *ac); 28 | -------------------------------------------------------------------------------- /src/defaults.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef DEFAULTS_H 5 | #define DEFAULTS_H 6 | 7 | /* mpd crossfade time [s] */ 8 | #define DEFAULT_CROSSFADE_TIME 10 9 | 10 | /* screen list */ 11 | #define DEFAULT_SCREEN_LIST {"playlist", "browse"} 12 | 13 | /* song format - list window */ 14 | #define DEFAULT_LIST_FORMAT "%name%|[[%artist%|%performer%|%composer%|%albumartist%] - ][%title%|%shortfile%]" 15 | 16 | /* song format - status window */ 17 | #define DEFAULT_STATUS_FORMAT "[[%artist%|%performer%|%composer%|%albumartist%] - ][%title%|%shortfile%]" 18 | 19 | #define DEFAULT_LYRICS_TIMEOUT 100 20 | 21 | #define DEFAULT_SCROLL true 22 | #define DEFAULT_SCROLL_SEP " *** " 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/xterm_title.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "xterm_title.hxx" 5 | #include "Options.hxx" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | using std::string_view_literals::operator""sv; 13 | 14 | void 15 | set_xterm_title(std::string_view title) noexcept 16 | { 17 | /* the current xterm title exists under the WM_NAME property */ 18 | /* and can be retrieved with xprop -id $WINDOWID */ 19 | 20 | if (!options.enable_xterm_title) 21 | return; 22 | 23 | if (getenv("WINDOWID") == nullptr) { 24 | options.enable_xterm_title = false; 25 | return; 26 | } 27 | 28 | fmt::print("\033]0;{}\033\\"sv, title); 29 | fflush(stdout); 30 | } 31 | -------------------------------------------------------------------------------- /src/util/UriUtil.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef URI_UTIL_HXX 5 | #define URI_UTIL_HXX 6 | 7 | #include 8 | 9 | /** 10 | * Determins the last segment of the given URI path, i.e. the portion 11 | * after the last slash. May return an empty string if the URI ends 12 | * with a slash. 13 | */ 14 | [[gnu::pure]] 15 | const char * 16 | GetUriFilename(const char *uri) noexcept; 17 | 18 | /** 19 | * Return the "parent directory" of the given URI path, i.e. the 20 | * portion up to the last (forward) slash. Returns an empty string if 21 | * there is no parent. 22 | */ 23 | [[gnu::pure]] 24 | std::string_view 25 | GetParentUri(std::string_view uri) noexcept; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/util/MemberPointer.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | template 8 | struct MemberPointerHelper; 9 | 10 | template 11 | struct MemberPointerHelper { 12 | using ContainerType = C; 13 | using MemberType = M; 14 | }; 15 | 16 | /** 17 | * Given a member pointer, this determines the member type. 18 | */ 19 | template 20 | using MemberPointerType = 21 | typename MemberPointerHelper::MemberType; 22 | 23 | /** 24 | * Given a member pointer, this determines the container type. 25 | */ 26 | template 27 | using MemberPointerContainerType = 28 | typename MemberPointerHelper::ContainerType; 29 | -------------------------------------------------------------------------------- /src/event/PollEvents.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | struct PollEvents { 8 | static constexpr unsigned READ = POLLIN; 9 | static constexpr unsigned WRITE = POLLOUT; 10 | static constexpr unsigned ERROR = POLLERR; 11 | static constexpr unsigned HANGUP = POLLHUP; 12 | 13 | /** 14 | * Any kind of hangup, i.e. the normal EPOLLHUP plus the 15 | * Linux-specific EPOLLRDHUP. 16 | */ 17 | static constexpr unsigned ANY_HANGUP = HANGUP; 18 | 19 | /** 20 | * A mask containing all events which indicate a dead socket 21 | * connection (i.e. error or hangup). It may still be 22 | * possible to receive pending data from the socket buffer. 23 | */ 24 | static constexpr unsigned DEAD_MASK = ERROR|ANY_HANGUP; 25 | }; 26 | -------------------------------------------------------------------------------- /src/ui/TextListRenderer.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "TextListRenderer.hxx" 5 | #include "ListText.hxx" 6 | #include "paint.hxx" 7 | 8 | #include 9 | 10 | static void 11 | list_window_paint_row(const Window window, unsigned width, bool selected, 12 | std::string_view text) noexcept 13 | { 14 | row_paint_text(window, width, Style::LIST, 15 | selected, text); 16 | } 17 | 18 | void 19 | TextListRenderer::PaintListItem(const Window window, unsigned i, unsigned, 20 | unsigned width, bool selected) const noexcept 21 | { 22 | char buffer[1024]; 23 | const std::string_view label = text.GetListItemText(buffer, i); 24 | 25 | list_window_paint_row(window, width, selected, label); 26 | } 27 | -------------------------------------------------------------------------------- /src/event/FarTimerEvent.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include "FineTimerEvent.hxx" 8 | 9 | /** 10 | * A coarse timer event which schedules far into the future. Use this 11 | * when you need a coarse resolution, but the supported time span of 12 | * #CoarseTimerEvent is not enough. For example, a good use case is 13 | * timers which fire only every few minutes and do periodic cleanup. 14 | * 15 | * Right now, this is just an alias for #FineTimerEvent. This class 16 | * supports arbitrary time spans, but uses a high-resolution timer. 17 | * Eventually, we may turn this into a timer wheel with minute 18 | * resolution. 19 | */ 20 | using FarTimerEvent = FineTimerEvent; 21 | -------------------------------------------------------------------------------- /src/system/EventFD.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #ifndef EVENT_FD_HXX 5 | #define EVENT_FD_HXX 6 | 7 | #include "io/UniqueFileDescriptor.hxx" 8 | 9 | /** 10 | * A class that wraps eventfd(). 11 | */ 12 | class EventFD { 13 | UniqueFileDescriptor fd; 14 | 15 | public: 16 | /** 17 | * Throws on error. 18 | */ 19 | EventFD(); 20 | 21 | FileDescriptor Get() const noexcept { 22 | return fd; 23 | } 24 | 25 | /** 26 | * Checks if Write() was called at least once since the last 27 | * Read() call. 28 | */ 29 | bool Read() noexcept; 30 | 31 | /** 32 | * Wakes up the reader. Multiple calls to this function will 33 | * be combined to one wakeup. 34 | */ 35 | void Write() noexcept; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/ui/Options.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "config.h" 7 | 8 | struct UiOptions { 9 | unsigned scroll_offset = 0; 10 | 11 | bool find_wrap = true; 12 | bool find_show_last_pattern = false; 13 | bool list_wrap = false; 14 | bool wide_cursor = true; 15 | bool hardware_cursor = false; 16 | 17 | #ifdef ENABLE_COLORS 18 | bool enable_colors = true; 19 | #else 20 | static constexpr bool enable_colors = false; 21 | #endif 22 | 23 | bool audible_bell = true; 24 | bool visible_bell = false; 25 | bool bell_on_wrap = true; 26 | #ifdef NCMPC_MINI 27 | static constexpr bool jump_prefix_only = true; 28 | #else 29 | bool jump_prefix_only = true; 30 | #endif 31 | }; 32 | 33 | inline UiOptions ui_options; 34 | -------------------------------------------------------------------------------- /src/util/TagStructs.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | /* 8 | * This header contains empty struct definitiosn that are supposed to 9 | * select special overloads of functions/methods/constructors. 10 | */ 11 | 12 | /** 13 | * A tag for overloading copying constructors, telling them to make 14 | * shallow copies of source data (e.g. copy pointers instead of 15 | * duplicating the referenced objects). 16 | */ 17 | struct ShallowCopy {}; 18 | 19 | /** 20 | * A tag that signals that the callee shall take ownership of the 21 | * object that is being passed to it. Usually, that owned is an 22 | * unmanaged reference and rvalues do not make sense. 23 | */ 24 | struct AdoptTag {}; 25 | -------------------------------------------------------------------------------- /src/Completion.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "Completion.hxx" 5 | 6 | #include // for std::mismatch() 7 | 8 | #include 9 | 10 | Completion::Result 11 | Completion::Complete(const std::string_view prefix) const noexcept 12 | { 13 | auto lower = list.lower_bound(prefix); 14 | if (lower == list.end() || !lower->starts_with(prefix)) 15 | return {std::string(), {lower, lower}}; 16 | 17 | auto upper = list.upper_bound(prefix); 18 | while (upper != list.end() && upper->starts_with(prefix)) 19 | ++upper; 20 | 21 | assert(upper != lower); 22 | 23 | auto m = std::mismatch(lower->begin(), lower->end(), 24 | std::prev(upper)->begin()).first; 25 | 26 | return {{lower->begin(), m}, {lower, upper}}; 27 | } 28 | -------------------------------------------------------------------------------- /src/Match.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "config.h" 7 | 8 | #ifdef HAVE_PCRE 9 | #include 10 | #else 11 | #include 12 | #endif 13 | 14 | #include 15 | 16 | class MatchExpression { 17 | #ifndef HAVE_PCRE 18 | std::string_view expression; 19 | bool anchored; 20 | #else 21 | pcre2_code_8 *re = nullptr; 22 | #endif 23 | 24 | public: 25 | MatchExpression() = default; 26 | ~MatchExpression() noexcept; 27 | 28 | MatchExpression(const MatchExpression &) = delete; 29 | MatchExpression &operator=(const MatchExpression &) = delete; 30 | 31 | bool Compile(std::string_view src, bool anchor) noexcept; 32 | 33 | [[gnu::pure]] 34 | bool operator()(std::string_view line) const noexcept; 35 | }; 36 | -------------------------------------------------------------------------------- /src/system/SignalFD.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "SignalFD.hxx" 5 | #include "Error.hxx" 6 | #include "util/SpanCast.hxx" 7 | 8 | #include 9 | 10 | #include 11 | 12 | void 13 | SignalFD::Create(const sigset_t &mask) 14 | { 15 | int new_fd = ::signalfd(fd.Get(), &mask, SFD_NONBLOCK|SFD_CLOEXEC); 16 | if (new_fd < 0) 17 | throw MakeErrno("signalfd() failed"); 18 | 19 | if (!fd.IsDefined()) { 20 | fd = UniqueFileDescriptor{AdoptTag{}, new_fd}; 21 | } 22 | 23 | assert(new_fd == fd.Get()); 24 | } 25 | 26 | int 27 | SignalFD::Read() noexcept 28 | { 29 | assert(fd.IsDefined()); 30 | 31 | signalfd_siginfo info; 32 | return fd.Read(ReferenceAsWritableBytes(info)) > 0 33 | ? info.ssi_signo 34 | : -1; 35 | } 36 | -------------------------------------------------------------------------------- /src/event/WakeFD.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef MPD_WAKE_FD_HXX 5 | #define MPD_WAKE_FD_HXX 6 | 7 | #include "net/SocketDescriptor.hxx" 8 | #include "event/Features.h" 9 | 10 | #ifdef USE_EVENTFD 11 | #include "system/EventFD.hxx" 12 | #else 13 | #include "system/EventPipe.hxx" 14 | #endif 15 | 16 | class WakeFD { 17 | #ifdef USE_EVENTFD 18 | EventFD fd; 19 | #else 20 | EventPipe fd; 21 | #endif 22 | 23 | public: 24 | SocketDescriptor GetSocket() const noexcept { 25 | #ifdef _WIN32 26 | return fd.Get(); 27 | #else 28 | return SocketDescriptor::FromFileDescriptor(fd.Get()); 29 | #endif 30 | } 31 | 32 | bool Read() noexcept { 33 | return fd.Read(); 34 | } 35 | 36 | void Write() noexcept { 37 | fd.Write(); 38 | } 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/net/AsyncResolveConnect.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "AsyncResolveConnect.hxx" 5 | #include "LocalSocketAddress.hxx" 6 | #include "AddressInfo.hxx" 7 | #include "Resolver.hxx" 8 | #include "Features.hxx" 9 | 10 | #include 11 | 12 | void 13 | AsyncResolveConnect::Start(const char *host, unsigned port) noexcept 14 | { 15 | #ifdef HAVE_UN 16 | if (host[0] == '/' || host[0] == '@') { 17 | connect.Connect(LocalSocketAddress{host}, 18 | std::chrono::seconds(30)); 19 | return; 20 | } 21 | #endif // HAVE_UN 22 | 23 | try { 24 | connect.Connect(Resolve(host, port, AI_ADDRCONFIG, SOCK_STREAM).GetBest(), 25 | std::chrono::seconds(30)); 26 | } catch (...) { 27 | handler.OnSocketConnectError(std::current_exception()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/run_hscroll.cxx: -------------------------------------------------------------------------------- 1 | #include "BasicMarquee.hxx" 2 | #include "config.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | #ifdef ENABLE_LOCALE 9 | #include 10 | #endif 11 | 12 | #include 13 | 14 | int main(int argc, char **argv) 15 | { 16 | unsigned width, count; 17 | 18 | if (argc != 5) { 19 | fprintf(stderr, "Usage: %s TEXT SEPARATOR WIDTH COUNT\n", argv[0]); 20 | return 1; 21 | } 22 | 23 | #ifdef ENABLE_LOCALE 24 | setlocale(LC_CTYPE,""); 25 | #endif 26 | 27 | width = atoi(argv[3]); 28 | count = atoi(argv[4]); 29 | 30 | BasicMarquee hscroll(argv[2]); 31 | hscroll.Set(width, argv[1]); 32 | 33 | for (unsigned i = 0; i < count; ++i) { 34 | const auto s = hscroll.ScrollString(); 35 | fmt::print("{}\n", s); 36 | 37 | hscroll.Step(); 38 | } 39 | 40 | hscroll.Clear(); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/fmt/meson.build: -------------------------------------------------------------------------------- 1 | # using include_type:system to work around -Wfloat-equal 2 | libfmt = dependency('fmt', version: '>= 9', 3 | include_type: 'system', 4 | fallback: ['fmt', 'fmt_dep']) 5 | 6 | if compiler.get_id() == 'gcc' and compiler.version().version_compare('>=13') and compiler.version().version_compare('<15') 7 | libfmt = declare_dependency( 8 | dependencies: libfmt, 9 | # suppress bogus GCC 13 warnings: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109717 10 | compile_args: ['-Wno-array-bounds', '-Wno-stringop-overflow'] 11 | ) 12 | endif 13 | 14 | fmt = static_library( 15 | 'fmt', 16 | 'RuntimeError.cxx', 17 | 'ToSpan.cxx', 18 | include_directories: inc, 19 | dependencies: libfmt, 20 | ) 21 | 22 | fmt_dep = declare_dependency( 23 | link_with: fmt, 24 | dependencies: libfmt, 25 | ) 26 | -------------------------------------------------------------------------------- /src/system/EventFD.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "EventFD.hxx" 5 | #include "system/Error.hxx" 6 | #include "util/SpanCast.hxx" 7 | 8 | #include 9 | 10 | #include 11 | 12 | EventFD::EventFD() 13 | :fd(AdoptTag{}, ::eventfd(0, EFD_NONBLOCK|EFD_CLOEXEC)) 14 | { 15 | if (!fd.IsDefined()) 16 | throw MakeErrno("eventfd() failed"); 17 | } 18 | 19 | bool 20 | EventFD::Read() noexcept 21 | { 22 | assert(fd.IsDefined()); 23 | 24 | eventfd_t value; 25 | return fd.Read(std::as_writable_bytes(std::span{&value, 1})) == (ssize_t)sizeof(value); 26 | } 27 | 28 | void 29 | EventFD::Write() noexcept 30 | { 31 | assert(fd.IsDefined()); 32 | 33 | static constexpr eventfd_t value = 1; 34 | [[maybe_unused]] ssize_t nbytes = 35 | fd.Write(ReferenceAsBytes(value)); 36 | } 37 | -------------------------------------------------------------------------------- /src/system/SignalFD.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #ifndef SIGNAL_FD_HXX 5 | #define SIGNAL_FD_HXX 6 | 7 | #include "io/UniqueFileDescriptor.hxx" 8 | 9 | #include 10 | 11 | /** 12 | * A class that wraps signalfd(). 13 | */ 14 | class SignalFD { 15 | UniqueFileDescriptor fd; 16 | 17 | public: 18 | /** 19 | * Create the signalfd or update its mask. 20 | * 21 | * Throws on error. 22 | */ 23 | void Create(const sigset_t &mask); 24 | 25 | void Close() noexcept { 26 | fd.Close(); 27 | } 28 | 29 | int Get() const noexcept { 30 | return fd.Get(); 31 | } 32 | 33 | /** 34 | * Read the next signal from the file descriptor. Returns the 35 | * signal number on success or -1 if there are no more 36 | * signals. 37 | */ 38 | int Read() noexcept; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/callbacks.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "Instance.hxx" 5 | #include "ui/Bell.hxx" 6 | #include "util/Exception.hxx" 7 | 8 | #include 9 | 10 | bool 11 | Instance::OnMpdAuth() noexcept 12 | { 13 | auto *connection = client.GetConnection(); 14 | if (connection == nullptr) 15 | return false; 16 | 17 | if (!mpd_connection_clear_error(connection)) 18 | return false; 19 | 20 | screen_manager.QueryPassword(client); 21 | return false; 22 | } 23 | 24 | void 25 | Instance::OnMpdError(std::string_view message) noexcept 26 | { 27 | screen_manager.Alert(std::string{message}); 28 | Bell(); 29 | doupdate(); 30 | } 31 | 32 | void 33 | Instance::OnMpdError(std::exception_ptr e) noexcept 34 | { 35 | screen_manager.Alert(GetFullMessage(std::move(e))); 36 | Bell(); 37 | doupdate(); 38 | } 39 | -------------------------------------------------------------------------------- /src/ProgressBar.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_PROGRESS_BAR_HXX 5 | #define NCMPC_PROGRESS_BAR_HXX 6 | 7 | #include "ui/Window.hxx" 8 | 9 | class ProgressBar { 10 | UniqueWindow window; 11 | 12 | unsigned current = 0, max = 0; 13 | 14 | unsigned width = 0; 15 | 16 | public: 17 | ProgressBar(Point p, unsigned _width) noexcept; 18 | 19 | void OnResize(Point p, unsigned _width) noexcept; 20 | 21 | bool Set(unsigned current, unsigned max) noexcept; 22 | 23 | int ValueAtX(int x) const noexcept { 24 | if (max == 0 || x < 0) 25 | return -1; 26 | 27 | const unsigned ux = static_cast(x); 28 | const unsigned window_width = window.GetWidth(); 29 | return ux * max / window_width; 30 | } 31 | 32 | void Paint() const noexcept; 33 | 34 | private: 35 | bool Calculate() noexcept; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/SongRowPaint.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | struct mpd_song; 7 | struct Window; 8 | class hscroll; 9 | 10 | /** 11 | * Paints a song into a list window row. The cursor must be set to 12 | * the first character in the row prior to calling this function. 13 | * 14 | * @param w the ncurses window 15 | * @param y the row number in the window 16 | * @param width the width of the row 17 | * @param selected true if the row is selected 18 | * @param highlight true if the row is highlighted 19 | * @param song the song object 20 | * @param hscroll an optional hscroll object 21 | * @param format the song format 22 | */ 23 | void 24 | paint_song_row(Window window, int y, unsigned width, 25 | bool selected, bool highlight, const struct mpd_song &song, 26 | class hscroll *hscroll, const char *format); 27 | -------------------------------------------------------------------------------- /src/util/IntrusiveHookMode.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | /** 7 | * Specifies the mode in which a hook for intrusive containers 8 | * operates. This is meant to be used as a template argument to the 9 | * hook class (e.g. #IntrusiveListHook). 10 | */ 11 | enum class IntrusiveHookMode { 12 | /** 13 | * No implicit initialization. 14 | */ 15 | NORMAL, 16 | 17 | /** 18 | * Keep track of whether the item is currently linked, allows 19 | * using method is_linked(). This requires implicit 20 | * initialization and requires iterating all items when 21 | * deleting them which adds a considerable amount of overhead. 22 | */ 23 | TRACK, 24 | 25 | /** 26 | * Automatically unlinks the item in the destructor. This 27 | * implies #TRACK and adds code to the destructor. 28 | */ 29 | AUTO_UNLINK, 30 | }; 31 | -------------------------------------------------------------------------------- /src/event/EpollEvents.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | struct EpollEvents { 8 | static constexpr unsigned READ = EPOLLIN; 9 | static constexpr unsigned EXCEPTIONAL = EPOLLPRI; 10 | static constexpr unsigned WRITE = EPOLLOUT; 11 | static constexpr unsigned ERROR = EPOLLERR; 12 | static constexpr unsigned HANGUP = EPOLLHUP; 13 | static constexpr unsigned READ_HANGUP = EPOLLRDHUP; 14 | 15 | /** 16 | * Any kind of hangup, i.e. the normal EPOLLHUP plus the 17 | * Linux-specific EPOLLRDHUP. 18 | */ 19 | static constexpr unsigned ANY_HANGUP = HANGUP|READ_HANGUP; 20 | 21 | /** 22 | * A mask containing all events which indicate a dead socket 23 | * connection (i.e. error or hangup). It may still be 24 | * possible to receive pending data from the socket buffer. 25 | */ 26 | static constexpr unsigned DEAD_MASK = ERROR|ANY_HANGUP; 27 | }; 28 | -------------------------------------------------------------------------------- /src/Styles.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "config.h" 7 | 8 | struct Window; 9 | 10 | enum class Style : unsigned { 11 | /** 12 | * The ncurses default style. 13 | * 14 | * @see assume_default_colors(3ncurses) 15 | */ 16 | DEFAULT, 17 | 18 | TITLE, 19 | TITLE_BOLD, 20 | LINE, 21 | LINE_BOLD, 22 | LINE_FLAGS, 23 | LIST, 24 | LIST_BOLD, 25 | PROGRESSBAR, 26 | PROGRESSBAR_BACKGROUND, 27 | STATUS, 28 | STATUS_BOLD, 29 | STATUS_TIME, 30 | STATUS_ALERT, 31 | DIRECTORY, 32 | PLAYLIST, 33 | INPUT, 34 | BACKGROUND, 35 | END 36 | }; 37 | 38 | #ifdef ENABLE_COLORS 39 | 40 | /** 41 | * Throws on error. 42 | */ 43 | void 44 | ModifyStyle(const char *name, const char *value); 45 | 46 | void 47 | ApplyStyles() noexcept; 48 | 49 | #endif 50 | 51 | void 52 | SelectStyle(Window window, Style style) noexcept; 53 | -------------------------------------------------------------------------------- /src/time/ClockCache.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | /** 10 | * Cache the now() method of a clock. 11 | */ 12 | template 13 | class ClockCache { 14 | using value_type = typename Clock::time_point; 15 | mutable value_type value; 16 | 17 | public: 18 | ClockCache() = default; 19 | ClockCache(const ClockCache &) = delete; 20 | ClockCache &operator=(const ClockCache &) = delete; 21 | 22 | [[gnu::pure]] 23 | const auto &now() const noexcept { 24 | if (value <= value_type()) 25 | value = Clock::now(); 26 | return value; 27 | } 28 | 29 | void flush() noexcept { 30 | value = {}; 31 | } 32 | 33 | /** 34 | * Inject a fake value. This can be helpful for unit tests. 35 | */ 36 | void Mock(value_type _value) noexcept { 37 | value = _value; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/event/FineTimerEvent.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #include "FineTimerEvent.hxx" 6 | #include "Loop.hxx" 7 | 8 | void 9 | FineTimerEvent::SetDue(Event::Duration d) noexcept 10 | { 11 | assert(!IsPending()); 12 | 13 | SetDue(loop.SteadyNow() + d); 14 | } 15 | 16 | void 17 | FineTimerEvent::ScheduleCurrent() noexcept 18 | { 19 | assert(!IsPending()); 20 | 21 | loop.Insert(*this); 22 | } 23 | 24 | void 25 | FineTimerEvent::Schedule(Event::Duration d) noexcept 26 | { 27 | Cancel(); 28 | 29 | SetDue(d); 30 | ScheduleCurrent(); 31 | } 32 | 33 | void 34 | FineTimerEvent::ScheduleEarlier(Event::Duration d) noexcept 35 | { 36 | const auto new_due = loop.SteadyNow() + d; 37 | 38 | if (IsPending()) { 39 | if (new_due >= due) 40 | return; 41 | 42 | Cancel(); 43 | } 44 | 45 | SetDue(due); 46 | ScheduleCurrent(); 47 | } 48 | -------------------------------------------------------------------------------- /src/event/WinSelectEvents.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | /* ERROR is a WIN32 macro that poisons our namespace; this is a kludge 8 | to allow us to use it anyway */ 9 | #ifdef ERROR 10 | #undef ERROR 11 | #endif 12 | 13 | struct WinSelectEvents { 14 | static constexpr unsigned READ = 1; 15 | static constexpr unsigned WRITE = 2; 16 | static constexpr unsigned ERROR = 0; 17 | static constexpr unsigned HANGUP = 0; 18 | 19 | /** 20 | * Any kind of hangup, i.e. the normal EPOLLHUP plus the 21 | * Linux-specific EPOLLRDHUP. 22 | */ 23 | static constexpr unsigned ANY_HANGUP = HANGUP; 24 | 25 | /** 26 | * A mask containing all events which indicate a dead socket 27 | * connection (i.e. error or hangup). It may still be 28 | * possible to receive pending data from the socket buffer. 29 | */ 30 | static constexpr unsigned DEAD_MASK = ERROR|ANY_HANGUP; 31 | }; 32 | -------------------------------------------------------------------------------- /src/util/PrintException.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #include "PrintException.hxx" 6 | 7 | #include 8 | 9 | void 10 | PrintException(const std::exception &e) noexcept 11 | { 12 | fprintf(stderr, "%s\n", e.what()); 13 | try { 14 | std::rethrow_if_nested(e); 15 | } catch (const std::exception &nested) { 16 | PrintException(nested); 17 | } catch (const char *s) { 18 | fprintf(stderr, "%s\n", s); 19 | } catch (...) { 20 | fprintf(stderr, "Unrecognized nested exception\n"); 21 | } 22 | } 23 | 24 | void 25 | PrintException(const std::exception_ptr &ep) noexcept 26 | { 27 | try { 28 | std::rethrow_exception(ep); 29 | } catch (const std::exception &e) { 30 | PrintException(e); 31 | } catch (const char *s) { 32 | fprintf(stderr, "%s\n", s); 33 | } catch (...) { 34 | fprintf(stderr, "Unrecognized exception\n"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/TagFilter.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "TagFilter.hxx" 5 | 6 | #include 7 | 8 | const char * 9 | FindTag(const TagFilter &filter, enum mpd_tag_type tag) noexcept 10 | { 11 | for (const auto &i : filter) 12 | if (i.first == tag) 13 | return i.second.c_str(); 14 | 15 | return nullptr; 16 | } 17 | 18 | void 19 | AddConstraints(struct mpd_connection *connection, 20 | const TagFilter &filter) noexcept 21 | { 22 | for (const auto &i : filter) 23 | mpd_search_add_tag_constraint(connection, 24 | MPD_OPERATOR_DEFAULT, 25 | i.first, i.second.c_str()); 26 | } 27 | 28 | std::string 29 | ToString(const TagFilter &filter) noexcept 30 | { 31 | std::string result; 32 | 33 | for (const auto &i : filter) { 34 | if (!result.empty()) 35 | result.insert(0, " - "); 36 | result.insert(0, i.second); 37 | } 38 | 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /src/KeyName.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef KEY_NAME_HXX 5 | #define KEY_NAME_HXX 6 | 7 | #include 8 | 9 | /** 10 | * Parse a string (from the configuration file) to a keycode. 11 | * 12 | * @return the keycode and the first unparsed character; -1 indicates 13 | * error 14 | */ 15 | [[gnu::pure]] 16 | std::pair 17 | ParseKeyName(const char *s) noexcept; 18 | 19 | /** 20 | * Convert a keycode to a canonical string, to be used in the 21 | * configuration file. 22 | * 23 | * The returned pointer is invalidated by the next call. 24 | */ 25 | [[gnu::pure]] 26 | const char * 27 | GetKeyName(int key) noexcept; 28 | 29 | /** 30 | * Convert a keycode to a human-readable localized string. 31 | * 32 | * The returned pointer is invalidated by the next call. 33 | */ 34 | [[gnu::pure]] 35 | const char * 36 | GetLocalizedKeyName(int key) noexcept; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/signals.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include 5 | #include "Instance.hxx" 6 | 7 | void 8 | Instance::OnSigwinch() noexcept 9 | { 10 | endwin(); 11 | refresh(); 12 | 13 | /* re-enable keypad mode; it got disabled by endwin() and 14 | ncurses will only automatically re-enable it when calling 15 | wgetch(), but we will do that only after STDIN has become 16 | readable, when a keystroke without keypad mode has already 17 | been put into STDIN by the terminal */ 18 | keypad(stdscr, true); 19 | 20 | screen_manager.OnResize(); 21 | } 22 | 23 | void 24 | Instance::InitSignals() 25 | { 26 | /* ignore SIGPIPE */ 27 | 28 | struct sigaction act; 29 | sigemptyset(&act.sa_mask); 30 | act.sa_flags = SA_RESTART; 31 | act.sa_handler = SIG_IGN; 32 | if (sigaction(SIGPIPE, &act, nullptr) < 0) { 33 | perror("sigaction(SIGPIPE)"); 34 | exit(EXIT_FAILURE); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/net/StaticSocketAddress.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "StaticSocketAddress.hxx" 5 | #include "IPv4Address.hxx" 6 | #include "IPv6Address.hxx" 7 | 8 | #include 9 | 10 | #include 11 | 12 | StaticSocketAddress & 13 | StaticSocketAddress::operator=(SocketAddress other) noexcept 14 | { 15 | size = std::min(other.GetSize(), GetCapacity()); 16 | memcpy(&address, other.GetAddress(), size); 17 | return *this; 18 | } 19 | 20 | #ifdef HAVE_TCP 21 | 22 | bool 23 | StaticSocketAddress::SetPort(unsigned port) noexcept 24 | { 25 | switch (GetFamily()) { 26 | case AF_INET: 27 | { 28 | auto &a = *(IPv4Address *)(void *)&address; 29 | a.SetPort(port); 30 | return true; 31 | } 32 | 33 | case AF_INET6: 34 | { 35 | auto &a = *(IPv6Address *)(void *)&address; 36 | a.SetPort(port); 37 | return true; 38 | } 39 | } 40 | 41 | return false; 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/LyricsCache.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef LYRICS_CACHE_HXX 5 | #define LYRICS_CACHE_HXX 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | class LyricsCache { 13 | const std::string directory; 14 | 15 | const std::string legacy_directory; 16 | 17 | public: 18 | LyricsCache() noexcept; 19 | 20 | bool IsAvailable() const noexcept { 21 | return !directory.empty(); 22 | } 23 | 24 | std::string MakePath(const char *artist, 25 | const char *title) const noexcept; 26 | 27 | [[gnu::pure]] 28 | bool Exists(const char *artist, const char *title) const noexcept; 29 | 30 | std::string Load(const char *artist, const char *title) const noexcept; 31 | 32 | FILE *Save(const char *artist, const char *title) noexcept; 33 | 34 | /** 35 | * @return true on success 36 | */ 37 | bool Delete(const char *artist, const char *title) noexcept; 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/event/TimerList.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #include "TimerList.hxx" 6 | #include "FineTimerEvent.hxx" 7 | 8 | constexpr Event::TimePoint 9 | TimerList::GetDue::operator()(const FineTimerEvent &timer) const noexcept 10 | { 11 | return timer.GetDue(); 12 | } 13 | 14 | TimerList::TimerList() = default; 15 | 16 | TimerList::~TimerList() noexcept 17 | { 18 | assert(timers.empty()); 19 | } 20 | 21 | void 22 | TimerList::Insert(FineTimerEvent &t) noexcept 23 | { 24 | timers.insert(t); 25 | } 26 | 27 | Event::Duration 28 | TimerList::Run(const Event::TimePoint now) noexcept 29 | { 30 | while (true) { 31 | auto i = timers.begin(); 32 | if (i == timers.end()) 33 | break; 34 | 35 | auto &t = *i; 36 | const auto timeout = t.due - now; 37 | if (timeout > timeout.zero()) 38 | return timeout; 39 | 40 | timers.pop_front(); 41 | 42 | t.Run(); 43 | } 44 | 45 | return Event::Duration(-1); 46 | } 47 | -------------------------------------------------------------------------------- /src/lib/fmt/RuntimeError.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include // IWYU pragma: export 9 | 10 | [[nodiscard]] [[gnu::pure]] 11 | std::runtime_error 12 | VFmtRuntimeError(fmt::string_view format_str, fmt::format_args args) noexcept; 13 | 14 | template 15 | [[nodiscard]] [[gnu::pure]] 16 | auto 17 | FmtRuntimeError(const S &format_str, Args&&... args) noexcept 18 | { 19 | return VFmtRuntimeError(format_str, 20 | fmt::make_format_args(args...)); 21 | } 22 | 23 | [[nodiscard]] [[gnu::pure]] 24 | std::invalid_argument 25 | VFmtInvalidArgument(fmt::string_view format_str, fmt::format_args args) noexcept; 26 | 27 | template 28 | [[nodiscard]] [[gnu::pure]] 29 | auto 30 | FmtInvalidArgument(const S &format_str, Args&&... args) noexcept 31 | { 32 | return VFmtInvalidArgument(format_str, 33 | fmt::make_format_args(args...)); 34 | } 35 | -------------------------------------------------------------------------------- /src/event/IdleEvent.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "DeferEvent.hxx" 7 | 8 | class EventLoop; 9 | 10 | /** 11 | * An event that runs when the EventLoop has become idle, before 12 | * waiting for more events. 13 | * 14 | * This class is not thread-safe, all methods must be called from the 15 | * thread that runs the #EventLoop, except where explicitly documented 16 | * as thread-safe. 17 | */ 18 | class IdleEvent final { 19 | DeferEvent event; 20 | 21 | using Callback = BoundMethod; 22 | 23 | public: 24 | IdleEvent(EventLoop &_loop, Callback _callback) noexcept 25 | :event(_loop, _callback) {} 26 | 27 | auto &GetEventLoop() const noexcept { 28 | return event.GetEventLoop(); 29 | } 30 | 31 | bool IsPending() const noexcept { 32 | return event.IsPending(); 33 | } 34 | 35 | void Schedule() noexcept { 36 | event.ScheduleIdle(); 37 | } 38 | 39 | void Cancel() noexcept { 40 | event.Cancel(); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/event/SignalMonitor.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef MPD_SOCKET_SIGNAL_MONITOR_HXX 5 | #define MPD_SOCKET_SIGNAL_MONITOR_HXX 6 | 7 | class EventLoop; 8 | 9 | #ifndef _WIN32 10 | 11 | #include "util/BindMethod.hxx" 12 | 13 | using SignalHandler = BoundMethod; 14 | 15 | /** 16 | * Initialise the signal monitor subsystem. 17 | * 18 | * Throws on error. 19 | */ 20 | void 21 | SignalMonitorInit(EventLoop &loop); 22 | 23 | /** 24 | * Deinitialise the signal monitor subsystem. 25 | */ 26 | void 27 | SignalMonitorFinish() noexcept; 28 | 29 | /** 30 | * Register a handler for the specified signal. The handler will be 31 | * invoked in a safe context. 32 | */ 33 | void 34 | SignalMonitorRegister(int signo, SignalHandler handler); 35 | 36 | #else 37 | 38 | static inline void 39 | SignalMonitorInit(EventLoop &) 40 | { 41 | } 42 | 43 | static inline void 44 | SignalMonitorFinish() noexcept 45 | { 46 | } 47 | 48 | #endif 49 | 50 | #endif /* MAIN_NOTIFY_H */ 51 | -------------------------------------------------------------------------------- /src/event/meson.build: -------------------------------------------------------------------------------- 1 | event_features = configuration_data() 2 | event_features.set('USE_EVENTFD', is_linux and get_option('eventfd')) 3 | event_features.set('USE_SIGNALFD', is_linux and get_option('signalfd')) 4 | event_features.set('USE_EPOLL', is_linux and get_option('epoll')) 5 | configure_file(output: 'Features.h', configuration: event_features) 6 | 7 | event_sources = [] 8 | 9 | if is_windows 10 | event_sources += 'WinSelectBackend.cxx' 11 | elif is_linux and get_option('epoll') 12 | # epoll support is header-only 13 | else 14 | event_sources += 'PollBackend.cxx' 15 | endif 16 | 17 | event = static_library( 18 | 'event', 19 | 'SignalMonitor.cxx', 20 | 'TimerWheel.cxx', 21 | 'TimerList.cxx', 22 | 'CoarseTimerEvent.cxx', 23 | 'FineTimerEvent.cxx', 24 | 'DeferEvent.cxx', 25 | 'SocketEvent.cxx', 26 | 'Loop.cxx', 27 | event_sources, 28 | include_directories: inc, 29 | dependencies: [ 30 | ], 31 | ) 32 | 33 | event_dep = declare_dependency( 34 | link_with: event, 35 | dependencies: [ 36 | thread_dep, 37 | net_dep, 38 | system_dep, 39 | ], 40 | ) 41 | -------------------------------------------------------------------------------- /src/win/ncmpc_win32_rc.rc.in: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@ 4 | #define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@" 5 | 6 | NCMPC_ICON ICON "@top_srcdir@/src/win/ncmpc.ico" 7 | 8 | 1 VERSIONINFO 9 | FILETYPE VFT_APP 10 | FILEOS VOS__WINDOWS32 11 | PRODUCTVERSION VERSION_NUMBER 12 | 13 | FILEVERSION VERSION_NUMBER 14 | BEGIN 15 | BLOCK "StringFileInfo" 16 | BEGIN 17 | BLOCK "040904B0" 18 | BEGIN 19 | VALUE "CompanyName", "The Music Player Daemon Project" 20 | VALUE "ProductName", "aN Curses Music Player Client" 21 | VALUE "ProductVersion", VERSION_NUMBER_STR 22 | VALUE "InternalName", "ncmpc" 23 | VALUE "OriginalFilename", "ncmpc.exe" 24 | VALUE "FileVersion", "@VERSION@" 25 | VALUE "FileDescription", "aN Curses Music Player Client @VERSION@" 26 | VALUE "LegalCopyright", "Copyright \251 The Music Player Daemon Project" 27 | END 28 | END 29 | 30 | BLOCK "VarFileInfo" 31 | BEGIN 32 | VALUE "Translation", 0x409, 1200 33 | END 34 | END 35 | -------------------------------------------------------------------------------- /src/event/PollResultGeneric.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef MPD_EVENT_POLLRESULT_GENERIC_HXX 5 | #define MPD_EVENT_POLLRESULT_GENERIC_HXX 6 | 7 | #include 8 | #include 9 | 10 | #ifdef _WIN32 11 | #include 12 | /* damn you, windows.h! */ 13 | #ifdef GetObject 14 | #undef GetObject 15 | #endif 16 | #endif 17 | 18 | class PollResultGeneric 19 | { 20 | struct Item 21 | { 22 | unsigned events; 23 | void *obj; 24 | 25 | Item() = default; 26 | constexpr Item(unsigned _events, void *_obj) noexcept 27 | : events(_events), obj(_obj) { } 28 | }; 29 | 30 | std::vector items; 31 | public: 32 | size_t GetSize() const noexcept { 33 | return items.size(); 34 | } 35 | 36 | unsigned GetEvents(size_t i) const noexcept { 37 | return items[i].events; 38 | } 39 | 40 | void *GetObject(size_t i) const noexcept { 41 | return items[i].obj; 42 | } 43 | 44 | void Add(unsigned events, void *obj) noexcept { 45 | items.emplace_back(events, obj); 46 | } 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/hscroll.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "hscroll.hxx" 5 | #include "Styles.hxx" 6 | 7 | #include 8 | 9 | void 10 | hscroll::OnTimer() noexcept 11 | { 12 | Step(); 13 | Paint(); 14 | window.Refresh(); 15 | ScheduleTimer(); 16 | } 17 | 18 | void 19 | hscroll::Set(Point _position, unsigned _width, std::string_view _text, 20 | Style _style, attr_t _attr) noexcept 21 | { 22 | position = _position; 23 | style = _style; 24 | attr = _attr; 25 | 26 | if (!basic.Set(_width, _text)) 27 | return; 28 | 29 | ScheduleTimer(); 30 | } 31 | 32 | void 33 | hscroll::Clear() noexcept 34 | { 35 | basic.Clear(); 36 | timer.Cancel(); 37 | } 38 | 39 | void 40 | hscroll::Paint() const noexcept 41 | { 42 | assert(basic.IsDefined()); 43 | 44 | SelectStyle(window, style); 45 | 46 | if (attr != 0) 47 | window.AttributeOn(attr); 48 | 49 | /* scroll the string, and draw it */ 50 | const auto s = basic.ScrollString(); 51 | window.String(position, s); 52 | 53 | if (attr != 0) 54 | window.AttributeOff(attr); 55 | } 56 | -------------------------------------------------------------------------------- /src/dialogs/ModalDialog.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "ModalDialog.hxx" 5 | #include "ModalDock.hxx" 6 | #include "ui/Window.hxx" 7 | 8 | #include 9 | 10 | ModalDialog::~ModalDialog() noexcept 11 | { 12 | assert(!visible); 13 | } 14 | 15 | void 16 | ModalDialog::OnLeave([[maybe_unused]] Window window) noexcept 17 | { 18 | } 19 | 20 | void 21 | ModalDialog::Show() noexcept 22 | { 23 | assert(!visible); 24 | 25 | dock.ShowModalDialog(*this); 26 | visible = true; 27 | } 28 | 29 | void 30 | ModalDialog::Hide() noexcept 31 | { 32 | if (!visible) 33 | return; 34 | 35 | visible = false; 36 | dock.HideModalDialog(*this); 37 | } 38 | 39 | void 40 | ModalDialog::InvokeCancel() noexcept 41 | { 42 | assert(visible); 43 | 44 | visible = false; 45 | OnCancel(); 46 | } 47 | 48 | void 49 | ModalDialog::Cancel() noexcept 50 | { 51 | assert(visible); 52 | 53 | dock.CancelModalDialog(*this); 54 | } 55 | 56 | void 57 | ModalDialog::OnResize([[maybe_unused]] Window window, 58 | [[maybe_unused]] Size size) noexcept 59 | { 60 | } 61 | -------------------------------------------------------------------------------- /src/net/SocketError.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "SocketError.hxx" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #ifdef _WIN32 11 | 12 | SocketErrorMessage::SocketErrorMessage(socket_error_t code) noexcept 13 | { 14 | #ifdef _UNICODE 15 | wchar_t buffer[msg_size]; 16 | #else 17 | auto *buffer = msg; 18 | #endif 19 | 20 | DWORD nbytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | 21 | FORMAT_MESSAGE_IGNORE_INSERTS | 22 | FORMAT_MESSAGE_MAX_WIDTH_MASK, 23 | nullptr, code, 0, 24 | buffer, msg_size, nullptr); 25 | if (nbytes == 0) { 26 | strcpy(msg, "Unknown error"); 27 | return; 28 | } 29 | 30 | #ifdef _UNICODE 31 | auto length = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, 32 | msg, std::size(msg), 33 | nullptr, nullptr); 34 | if (length <= 0) { 35 | strcpy(msg, "WideCharToMultiByte() error"); 36 | return; 37 | } 38 | #endif 39 | } 40 | 41 | #else 42 | 43 | SocketErrorMessage::SocketErrorMessage(socket_error_t code) noexcept 44 | :msg(strerror(code)) {} 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/net/HostParser.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | /** 10 | * Result type for ExtractHost(). 11 | */ 12 | struct ExtractHostResult { 13 | /** 14 | * The host part of the address. 15 | * 16 | * If nothing was parsed, then this is nullptr. 17 | */ 18 | std::string_view host; 19 | 20 | /** 21 | * Pointer to the first character that was not parsed. On 22 | * success, this is usually a pointer to the zero terminator or to 23 | * a colon followed by a port number. 24 | * 25 | * If nothing was parsed, then this is a pointer to the given 26 | * source string. 27 | */ 28 | const char *end; 29 | 30 | constexpr bool HasFailed() const noexcept { 31 | return host.data() == nullptr; 32 | } 33 | }; 34 | 35 | /** 36 | * Extract the host from a string in the form "IP:PORT" or 37 | * "[IPv6]:PORT". Stops at the first invalid character (e.g. the 38 | * colon). 39 | * 40 | * @param src the input string 41 | */ 42 | [[gnu::pure]] 43 | ExtractHostResult 44 | ExtractHost(const char *src) noexcept; 45 | -------------------------------------------------------------------------------- /src/DelayedSeek.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "event/CoarseTimerEvent.hxx" 7 | 8 | struct mpdclient; 9 | 10 | /** 11 | * Helper class which handles user seek commands; it will delay 12 | * actually sending the seek command to MPD. 13 | */ 14 | class DelayedSeek { 15 | struct mpdclient &c; 16 | 17 | int id = -1; 18 | unsigned time; 19 | 20 | CoarseTimerEvent commit_timer; 21 | 22 | public: 23 | DelayedSeek(EventLoop &event_loop, 24 | struct mpdclient &_c) noexcept 25 | :c(_c), commit_timer(event_loop, BIND_THIS_METHOD(OnTimer)) {} 26 | 27 | bool IsSeeking(int _id) const noexcept { 28 | return id >= 0 && _id == id; 29 | } 30 | 31 | unsigned GetTime() const noexcept { 32 | return time; 33 | } 34 | 35 | bool Seek(int offset) noexcept; 36 | 37 | void Commit() noexcept; 38 | 39 | void Cancel() noexcept { 40 | commit_timer.Cancel(); 41 | } 42 | 43 | 44 | private: 45 | void OnTimer() noexcept { 46 | Commit(); 47 | } 48 | 49 | void ScheduleTimer() noexcept { 50 | commit_timer.Schedule(std::chrono::milliseconds(500)); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/BasicMarquee.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "BasicMarquee.hxx" 5 | #include "util/LocaleString.hxx" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | using std::string_view_literals::operator""sv; 13 | 14 | std::string_view 15 | BasicMarquee::ScrollString() const noexcept 16 | { 17 | return TruncateAtWidthMB(SplitAtCharMB(buffer, offset).second, width); 18 | } 19 | 20 | bool 21 | BasicMarquee::Set(unsigned _width, std::string_view _text) noexcept 22 | { 23 | if (_width == width && text == _text) 24 | /* no change, do nothing (and, most importantly, do 25 | not reset the current offset!) */ 26 | return false; 27 | 28 | width = _width; 29 | offset = 0; 30 | 31 | text = _text; 32 | 33 | /* create a buffer containing the string and the separator */ 34 | buffer = fmt::format("{}{}{}{}"sv, text, separator, text, separator); 35 | max_offset = StringLengthMB(buffer.substr(buffer.size() / 2)); 36 | 37 | return true; 38 | } 39 | 40 | void 41 | BasicMarquee::Clear() noexcept 42 | { 43 | width = 0; 44 | text.clear(); 45 | buffer.clear(); 46 | } 47 | -------------------------------------------------------------------------------- /src/screen_client.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "screen_client.hxx" 5 | #include "client/mpdclient.hxx" 6 | #include "i18n.h" 7 | #include "charset.hxx" 8 | #include "Interface.hxx" 9 | 10 | #include 11 | 12 | void 13 | screen_database_update(Interface &interface, struct mpdclient &c, const char *path) 14 | { 15 | assert(c.IsConnected()); 16 | 17 | auto *connection = c.GetConnection(); 18 | if (connection == nullptr) 19 | return; 20 | 21 | unsigned id = mpd_run_update(connection, path); 22 | if (id == 0) { 23 | if (mpd_connection_get_error(connection) == MPD_ERROR_SERVER && 24 | mpd_connection_get_server_error(connection) == MPD_SERVER_ERROR_UPDATE_ALREADY && 25 | mpd_connection_clear_error(connection)) 26 | interface.Alert(_("Database update running")); 27 | else 28 | c.HandleError(); 29 | return; 30 | } 31 | 32 | if (path != nullptr && *path != 0) { 33 | interface.Alert(fmt::format(fmt::runtime(_("Database update of {} started")), 34 | (std::string_view)Utf8ToLocale{path})); 35 | } else 36 | interface.Alert(_("Database update started")); 37 | } 38 | -------------------------------------------------------------------------------- /src/lirc.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "lirc.hxx" 5 | #include "UserInputHandler.hxx" 6 | #include "Command.hxx" 7 | #include "config.h" 8 | 9 | #include 10 | 11 | void 12 | LircInput::OnSocketReady(unsigned) noexcept 13 | { 14 | char *code, *txt; 15 | 16 | if (lirc_nextcode(&code) == 0) { 17 | while (lirc_code2char(lc, code, &txt) == 0 && txt != nullptr) { 18 | const auto cmd = get_key_command_from_name(txt); 19 | handler.OnCommand(cmd); 20 | } 21 | } 22 | } 23 | 24 | LircInput::LircInput(EventLoop &_event_loop, 25 | UserInputHandler &_handler) noexcept 26 | :handler(_handler), 27 | event(_event_loop, BIND_THIS_METHOD(OnSocketReady)) 28 | { 29 | int lirc_socket = 0; 30 | 31 | if ((lirc_socket = lirc_init(PACKAGE, 0)) == -1) 32 | return; 33 | 34 | if (lirc_readconfig(nullptr, &lc, nullptr)) { 35 | lirc_deinit(); 36 | return; 37 | } 38 | 39 | event.Open(SocketDescriptor(lirc_socket)); 40 | event.ScheduleRead(); 41 | } 42 | 43 | LircInput::~LircInput() 44 | { 45 | if (lc) 46 | lirc_freeconfig(lc); 47 | if (event.IsDefined()) 48 | lirc_deinit(); 49 | } 50 | -------------------------------------------------------------------------------- /src/event/PollBackend.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef EVENT_POLL_BACKEND_HXX 5 | #define EVENT_POLL_BACKEND_HXX 6 | 7 | #include "PollResultGeneric.hxx" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | class PollBackend 16 | { 17 | struct Item 18 | { 19 | std::size_t index; 20 | void *obj; 21 | 22 | constexpr Item(std::size_t _index, void *_obj) noexcept 23 | :index(_index), obj(_obj) {} 24 | 25 | Item(const Item &) = delete; 26 | Item &operator=(const Item &) = delete; 27 | }; 28 | 29 | std::vector poll_events; 30 | std::unordered_map items; 31 | 32 | PollBackend(PollBackend &) = delete; 33 | PollBackend &operator=(PollBackend &) = delete; 34 | public: 35 | PollBackend() noexcept; 36 | ~PollBackend() noexcept; 37 | 38 | PollResultGeneric ReadEvents(int timeout_ms) noexcept; 39 | bool Add(int fd, unsigned events, void *obj) noexcept; 40 | bool Modify(int fd, unsigned events, void *obj) noexcept; 41 | bool Remove(int fd) noexcept; 42 | bool Abandon(int fd) noexcept { 43 | return Remove(fd); 44 | } 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/io/Path.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef IO_PATH_HXX 5 | #define IO_PATH_HXX 6 | 7 | #include 8 | #include 9 | 10 | namespace PathDetail { 11 | 12 | #ifdef _WIN32 13 | static constexpr char SEPARATOR = '\\'; 14 | #else 15 | static constexpr char SEPARATOR = '/'; 16 | #endif 17 | 18 | inline void 19 | AppendWithSeparator(std::string &dest, std::string_view src) noexcept 20 | { 21 | dest.push_back(SEPARATOR); 22 | dest.append(src); 23 | } 24 | 25 | template 26 | std::string 27 | BuildPath(std::string_view first, Args&&... args) noexcept 28 | { 29 | constexpr size_t n = sizeof...(args); 30 | 31 | const std::size_t total = first.size() + (args.size() + ...); 32 | 33 | std::string result; 34 | result.reserve(total + n); 35 | 36 | result.append(first); 37 | (AppendWithSeparator(result, args), ...); 38 | 39 | return result; 40 | } 41 | 42 | } // namespace PathDetail 43 | 44 | template 45 | std::string 46 | BuildPath(std::string_view first, Args&&... args) noexcept 47 | { 48 | return PathDetail::BuildPath(first, static_cast(args)...); 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/event/TimerList.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include "Chrono.hxx" 8 | #include "event/Features.h" 9 | #include "util/IntrusiveTreeSet.hxx" 10 | 11 | class FineTimerEvent; 12 | 13 | /** 14 | * A list of #FineTimerEvent instances sorted by due time point. 15 | */ 16 | class TimerList final { 17 | struct GetDue { 18 | constexpr Event::TimePoint operator()(const FineTimerEvent &timer) const noexcept; 19 | }; 20 | 21 | IntrusiveTreeSet> timers; 23 | 24 | public: 25 | TimerList(); 26 | ~TimerList() noexcept; 27 | 28 | TimerList(const TimerList &other) = delete; 29 | TimerList &operator=(const TimerList &other) = delete; 30 | 31 | bool IsEmpty() const noexcept { 32 | return timers.empty(); 33 | } 34 | 35 | void Insert(FineTimerEvent &t) noexcept; 36 | 37 | /** 38 | * Invoke all expired #FineTimerEvent instances and return the 39 | * duration until the next timer expires. Returns a negative 40 | * duration if there is no timeout. 41 | */ 42 | Event::Duration Run(Event::TimePoint now) noexcept; 43 | }; 44 | -------------------------------------------------------------------------------- /src/page/ListPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "Page.hxx" 7 | #include "ui/ListWindow.hxx" 8 | 9 | /** 10 | * An abstract #Page implementation which shows a #ListWindow. 11 | */ 12 | class ListPage : public Page { 13 | protected: 14 | ListWindow lw; 15 | 16 | ListPage(PageContainer &_parent, Window window) noexcept 17 | :Page(_parent), lw(window, window.GetSize()) {} 18 | 19 | public: 20 | unsigned GetWidth() const noexcept { 21 | return lw.GetWidth(); 22 | } 23 | 24 | /* virtual methods from class Page */ 25 | void OnResize(Size size) noexcept override { 26 | lw.Resize(size); 27 | } 28 | 29 | bool OnCommand(struct mpdclient &, Command cmd) override { 30 | if (lw.HasCursor() 31 | ? lw.HandleCommand(cmd) 32 | : lw.HandleScrollCommand(cmd)) { 33 | SchedulePaint(); 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | #ifdef HAVE_GETMOUSE 41 | bool OnMouse(struct mpdclient &, Point p, 42 | mmask_t bstate) override { 43 | if (lw.HandleMouse(bstate, p.y)) { 44 | SchedulePaint(); 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | 51 | #endif 52 | }; 53 | -------------------------------------------------------------------------------- /src/DelayedSeek.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "DelayedSeek.hxx" 5 | #include "client/mpdclient.hxx" 6 | 7 | void 8 | DelayedSeek::Commit() noexcept 9 | { 10 | if (id < 0) 11 | return; 12 | 13 | struct mpd_connection *connection = c.GetConnection(); 14 | if (connection == nullptr) { 15 | id = -1; 16 | return; 17 | } 18 | 19 | if (id == c.GetCurrentSongId()) 20 | if (!mpd_run_seek_id(connection, id, time)) 21 | c.HandleError(); 22 | 23 | id = -1; 24 | } 25 | 26 | bool 27 | DelayedSeek::Seek(int offset) noexcept 28 | { 29 | if (!c.playing_or_paused) 30 | return false; 31 | 32 | int current_id = mpd_status_get_song_id(c.status); 33 | if (current_id < 0) 34 | return false; 35 | 36 | int new_time; 37 | if (current_id == id) { 38 | new_time = time; 39 | } else { 40 | id = current_id; 41 | new_time = mpd_status_get_elapsed_time(c.status); 42 | } 43 | 44 | new_time += offset; 45 | if (new_time < 0) 46 | new_time = 0; 47 | else if ((unsigned)new_time > mpd_status_get_total_time(c.status)) 48 | new_time = mpd_status_get_total_time(c.status); 49 | 50 | time = new_time; 51 | 52 | ScheduleTimer(); 53 | 54 | return true; 55 | } 56 | -------------------------------------------------------------------------------- /src/CustomColors.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "CustomColors.hxx" 5 | #include "i18n.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | struct CustomColor { 14 | short color; 15 | short r,g,b; 16 | 17 | constexpr CustomColor(short _color, 18 | short _r, short _g, short _b) noexcept 19 | :color(_color), r(_r), g(_g), b(_b) {} 20 | }; 21 | 22 | static std::forward_list custom_colors; 23 | 24 | /* This function is called from conf.c before curses have been started, 25 | * it adds the definition to the color_definition_list and init_color() is 26 | * done in colors_start() */ 27 | void 28 | colors_define(short color, short r, short g, short b) noexcept 29 | { 30 | custom_colors.emplace_front(color, r, g, b); 31 | } 32 | 33 | void 34 | ApplyCustomColors() noexcept 35 | { 36 | if (custom_colors.empty()) 37 | return; 38 | 39 | if (!can_change_color()) { 40 | fprintf(stderr, "%s\n", 41 | _("Terminal lacks support for changing colors")); 42 | return; 43 | } 44 | 45 | for (const auto &i : custom_colors) 46 | if (i.color < COLORS) 47 | init_color(i.color, i.r, i.g, i.b); 48 | } 49 | -------------------------------------------------------------------------------- /src/co/AwaitableHelper.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | #include // for std::rethrow_exception() 9 | 10 | namespace Co { 11 | 12 | /** 13 | * This class provides some common boilerplate code for implementing 14 | * an awaitable for a coroutine task. The task must have the field 15 | * "continuation" and the methods IsReady() and TakeValue(). If 16 | * #rethrow_error is true, then it must also have an "error" field. 17 | */ 18 | template 20 | class AwaitableHelper { 21 | protected: 22 | T &task; 23 | 24 | public: 25 | constexpr AwaitableHelper(T &_task) noexcept 26 | :task(_task) {} 27 | 28 | [[nodiscard]] 29 | constexpr bool await_ready() const noexcept { 30 | return task.IsReady(); 31 | } 32 | 33 | void await_suspend(std::coroutine_handle<> _continuation) noexcept { 34 | task.continuation = _continuation; 35 | } 36 | 37 | decltype(auto) await_resume() { 38 | if constexpr (rethrow_error) 39 | if (this->task.error) 40 | std::rethrow_exception(this->task.error); 41 | 42 | return this->task.TakeValue(); 43 | } 44 | }; 45 | 46 | } // namespace Co 47 | 48 | -------------------------------------------------------------------------------- /src/util/StringCompare.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "StringCompare.hxx" 5 | 6 | #include 7 | 8 | bool 9 | StringEndsWith(const char *haystack, const char *needle) noexcept 10 | { 11 | const size_t haystack_length = StringLength(haystack); 12 | const size_t needle_length = StringLength(needle); 13 | 14 | return haystack_length >= needle_length && 15 | std::memcmp(haystack + haystack_length - needle_length, 16 | needle, needle_length) == 0; 17 | } 18 | 19 | bool 20 | StringEndsWithIgnoreCase(const char *haystack, const char *needle) noexcept 21 | { 22 | const size_t haystack_length = StringLength(haystack); 23 | const size_t needle_length = StringLength(needle); 24 | 25 | return haystack_length >= needle_length && 26 | StringIsEqualIgnoreCase(haystack + haystack_length - needle_length, 27 | needle); 28 | } 29 | 30 | const char * 31 | FindStringSuffix(const char *p, const char *suffix) noexcept 32 | { 33 | const size_t p_length = StringLength(p); 34 | const size_t suffix_length = StringLength(suffix); 35 | 36 | if (p_length < suffix_length) 37 | return nullptr; 38 | 39 | const char *q = p + p_length - suffix_length; 40 | return std::memcmp(q, suffix, suffix_length) == 0 41 | ? q 42 | : nullptr; 43 | } 44 | -------------------------------------------------------------------------------- /src/page/TextPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "ListPage.hxx" 7 | #include "ui/ListText.hxx" 8 | 9 | #include 10 | #include 11 | 12 | struct mpdclient; 13 | class FindSupport; 14 | 15 | class TextPage : public ListPage, ListText { 16 | FindSupport &find_support; 17 | 18 | protected: 19 | /** 20 | * Strings are UTF-8. 21 | */ 22 | std::vector lines; 23 | 24 | public: 25 | TextPage(PageContainer &_parent, Window window, 26 | FindSupport &_find_support) noexcept; 27 | 28 | protected: 29 | bool IsEmpty() const noexcept { 30 | return lines.empty(); 31 | } 32 | 33 | void Clear() noexcept; 34 | 35 | /** 36 | * @param str a UTF-8 string 37 | */ 38 | void Append(const char *str) noexcept; 39 | 40 | /** 41 | * @param str a UTF-8 string 42 | */ 43 | void Set(const char *str) noexcept { 44 | Clear(); 45 | Append(str); 46 | } 47 | 48 | public: 49 | /* virtual methods from class Page */ 50 | void Paint() const noexcept override; 51 | bool OnCommand(struct mpdclient &c, Command cmd) override; 52 | 53 | private: 54 | /* virtual methods from class ListText */ 55 | std::string_view GetListItemText(std::span buffer, 56 | unsigned i) const noexcept override; 57 | }; 58 | -------------------------------------------------------------------------------- /src/system/EventPipe.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef MPD_EVENT_PIPE_HXX 5 | #define MPD_EVENT_PIPE_HXX 6 | 7 | #ifdef _WIN32 8 | #include "net/UniqueSocketDescriptor.hxx" 9 | #else 10 | #include "io/UniqueFileDescriptor.hxx" 11 | #endif 12 | 13 | /** 14 | * A pipe that can be used to trigger an event to the read side. 15 | * 16 | * Errors in the constructor are fatal. 17 | */ 18 | class EventPipe { 19 | #ifdef _WIN32 20 | UniqueSocketDescriptor r, w; 21 | #else 22 | UniqueFileDescriptor r, w; 23 | #endif 24 | 25 | public: 26 | /** 27 | * Throws on error. 28 | */ 29 | EventPipe(); 30 | 31 | ~EventPipe() noexcept; 32 | 33 | EventPipe(const EventPipe &other) = delete; 34 | EventPipe &operator=(const EventPipe &other) = delete; 35 | 36 | #ifdef _WIN32 37 | SocketDescriptor Get() const noexcept { 38 | return r; 39 | } 40 | #else 41 | FileDescriptor Get() const noexcept { 42 | return r; 43 | } 44 | #endif 45 | 46 | /** 47 | * Checks if Write() was called at least once since the last 48 | * Read() call. 49 | */ 50 | bool Read() noexcept; 51 | 52 | /** 53 | * Wakes up the reader. Multiple calls to this function will 54 | * be combined to one wakeup. 55 | */ 56 | void Write() noexcept; 57 | }; 58 | 59 | #endif /* MAIN_NOTIFY_H */ 60 | -------------------------------------------------------------------------------- /src/net/AddressInfo.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "AddressInfo.hxx" 5 | #include "net/Features.hxx" // for HAVE_UN 6 | 7 | #include 8 | #include 9 | 10 | static constexpr auto address_family_ranking = std::array { 11 | #ifdef HAVE_UN 12 | AF_LOCAL, 13 | #endif 14 | AF_INET6, 15 | }; 16 | 17 | static constexpr bool 18 | IsAddressFamilyBetter(int previous, int next) noexcept 19 | { 20 | for (auto i : address_family_ranking) { 21 | if (next == i) 22 | return previous != i; 23 | if (previous == i) 24 | return false; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | static constexpr bool 31 | IsBetter(const AddressInfo &previous, const AddressInfo &next) noexcept 32 | { 33 | return IsAddressFamilyBetter(previous.GetFamily(), 34 | next.GetFamily()); 35 | } 36 | 37 | static constexpr bool 38 | IsBetter(const AddressInfo *previous, const AddressInfo &next) noexcept 39 | { 40 | return previous == nullptr || IsBetter(*previous, next); 41 | } 42 | 43 | const AddressInfo & 44 | AddressInfoList::GetBest() const noexcept 45 | { 46 | assert(!empty()); 47 | 48 | const AddressInfo *best = nullptr; 49 | 50 | for (const auto &i : *this) 51 | if (IsBetter(best, i)) 52 | best = &i; 53 | 54 | assert(best != nullptr); 55 | return *best; 56 | } 57 | -------------------------------------------------------------------------------- /src/page/Page.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "Page.hxx" 5 | #include "Container.hxx" 6 | #include "ui/Window.hxx" 7 | #include "util/Exception.hxx" 8 | 9 | void 10 | Page::OnClose() noexcept 11 | { 12 | // cancel any pending coroutine 13 | CoCancel(); 14 | } 15 | 16 | void 17 | Page::SchedulePaint() noexcept 18 | { 19 | parent.SchedulePaint(*this); 20 | } 21 | 22 | Interface & 23 | Page::GetInterface() const noexcept 24 | { 25 | return parent; 26 | } 27 | 28 | void 29 | Page::Alert(std::string message) noexcept 30 | { 31 | parent.Alert(std::move(message)); 32 | } 33 | 34 | void 35 | Page::VFmtAlert(fmt::string_view format_str, fmt::format_args args) noexcept 36 | { 37 | Alert(fmt::vformat(format_str, std::move(args))); 38 | } 39 | 40 | bool 41 | Page::PaintStatusBarOverride(Window) const noexcept 42 | { 43 | return false; 44 | } 45 | 46 | void 47 | Page::OnCoComplete() noexcept 48 | { 49 | SchedulePaint(); 50 | } 51 | 52 | void 53 | Page::CoStart(Co::InvokeTask _task) noexcept 54 | { 55 | co_task = std::move(_task); 56 | co_task.Start(BIND_THIS_METHOD(_OnCoComplete)); 57 | } 58 | 59 | inline void 60 | Page::_OnCoComplete(std::exception_ptr &&error) noexcept 61 | { 62 | if (error) 63 | parent.Alert(GetFullMessage(std::move(error))); 64 | else 65 | OnCoComplete(); 66 | } 67 | -------------------------------------------------------------------------------- /src/UserInputHandler.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "config.h" 7 | 8 | #include 9 | 10 | enum class Command : unsigned; 11 | struct Point; 12 | 13 | /** 14 | * Handler for input events from the user, called by #AsyncUserInput. 15 | */ 16 | class UserInputHandler { 17 | public: 18 | /** 19 | * Implementing this method gives the handler a chance to 20 | * handle a key code pressed by the user. This is called 21 | * before looking up hot keys. 22 | * 23 | * @return true if the character was consumed, false to pass 24 | * it on to the hot key checker 25 | */ 26 | virtual bool OnRawKey(int key) noexcept = 0; 27 | 28 | /** 29 | * A hot key for a #Command was pressed. 30 | */ 31 | virtual void OnCommand(Command cmd) noexcept = 0; 32 | 33 | #ifdef HAVE_GETMOUSE 34 | /** 35 | * An input event from the mouse was detected. 36 | * 37 | * @param p the position of the mouse cursor on the screen 38 | * @param bstate the state of all mouse buttons 39 | */ 40 | virtual void OnMouse(Point p, mmask_t bstate) noexcept = 0; 41 | #endif 42 | 43 | /** 44 | * Cancel the topmost modal dialog. 45 | * 46 | * @return true if a modal dialog was canceled, false if there 47 | * was none 48 | */ 49 | virtual bool CancelModalDialog() noexcept = 0; 50 | }; 51 | -------------------------------------------------------------------------------- /src/lib/fmt/ToBuffer.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "util/StringBuffer.hxx" 7 | 8 | #include 9 | 10 | template 11 | constexpr StringBuffer & 12 | VFmtToBuffer(StringBuffer &buffer, 13 | fmt::string_view format_str, fmt::format_args args) noexcept 14 | { 15 | auto [p, _] = fmt::vformat_to_n(buffer.begin(), buffer.capacity() - 1, 16 | format_str, args); 17 | *p = 0; 18 | return buffer; 19 | } 20 | 21 | template 22 | [[nodiscard]] [[gnu::pure]] 23 | constexpr auto 24 | VFmtBuffer(fmt::string_view format_str, fmt::format_args args) noexcept 25 | { 26 | StringBuffer buffer; 27 | return VFmtToBuffer(buffer, format_str, args); 28 | } 29 | 30 | template 31 | constexpr StringBuffer & 32 | FmtToBuffer(StringBuffer &buffer, 33 | const S &format_str, Args&&... args) noexcept 34 | { 35 | return VFmtToBuffer(buffer, format_str, 36 | fmt::make_format_args(args...)); 37 | } 38 | 39 | template 40 | [[nodiscard]] [[gnu::pure]] 41 | constexpr auto 42 | FmtBuffer(const S &format_str, Args&&... args) noexcept 43 | { 44 | return VFmtBuffer(format_str, 45 | fmt::make_format_args(args...)); 46 | } 47 | -------------------------------------------------------------------------------- /src/util/OptionalCounter.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | template class OptionalCounter; 11 | 12 | template<> 13 | class OptionalCounter 14 | { 15 | public: 16 | constexpr void reset() noexcept {} 17 | constexpr auto &operator++() noexcept { return *this; } 18 | constexpr auto &operator--() noexcept { return *this; } 19 | constexpr auto &operator+=(std::size_t) noexcept { return *this; } 20 | constexpr auto &operator-=(std::size_t) noexcept { return *this; } 21 | }; 22 | 23 | template<> 24 | class OptionalCounter 25 | { 26 | std::size_t value = 0; 27 | 28 | public: 29 | constexpr operator std::size_t() const noexcept { 30 | return value; 31 | } 32 | 33 | constexpr void reset() noexcept { 34 | value = 0; 35 | } 36 | 37 | constexpr auto &operator++() noexcept { 38 | ++value; 39 | return *this; 40 | } 41 | 42 | constexpr auto &operator--() noexcept { 43 | assert(value > 0); 44 | 45 | --value; 46 | return *this; 47 | } 48 | 49 | constexpr auto &operator+=(std::size_t n) noexcept { 50 | value += n; 51 | return *this; 52 | } 53 | 54 | constexpr auto &operator-=(std::size_t n) noexcept { 55 | assert(value >= n); 56 | 57 | value -= n; 58 | return *this; 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /src/dialogs/KeyDialog.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "KeyDialog.hxx" 5 | #include "ui/Keys.hxx" 6 | #include "ui/Options.hxx" 7 | #include "ui/Window.hxx" 8 | #include "Styles.hxx" 9 | 10 | using std::string_view_literals::operator""sv; 11 | 12 | void 13 | KeyDialog::OnLeave(const Window window) noexcept 14 | { 15 | curs_set(0); 16 | 17 | if (ui_options.enable_colors) 18 | window.SetBackgroundStyle(Style::STATUS); 19 | } 20 | 21 | void 22 | KeyDialog::OnCancel() noexcept 23 | { 24 | SetResult(-1); 25 | } 26 | 27 | void 28 | KeyDialog::Paint(const Window window) const noexcept 29 | { 30 | if (ui_options.enable_colors) 31 | window.SetBackgroundStyle(Style::INPUT); 32 | 33 | SelectStyle(window, Style::STATUS_ALERT); 34 | window.String({0, 0}, prompt); 35 | window.String(": "sv); 36 | 37 | SelectStyle(window, Style::INPUT); 38 | window.ClearToEol(); 39 | 40 | curs_set(1); 41 | } 42 | 43 | static constexpr bool 44 | IsCancelKey(int key) noexcept 45 | { 46 | return key == KEY_CANCEL || key == KEY_SCANCEL || 47 | key == KEY_CLOSE || 48 | key == KEY_CTL('C') || 49 | key == KEY_CTL('G') || 50 | key == KEY_ESCAPE; 51 | } 52 | 53 | bool 54 | KeyDialog::OnKey(Window, int key) 55 | { 56 | if (IsCancelKey(key)) 57 | Cancel(); 58 | else 59 | SetResult(key); 60 | 61 | return true; 62 | } 63 | -------------------------------------------------------------------------------- /src/system/EpollFD.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "io/UniqueFileDescriptor.hxx" 7 | 8 | #include 9 | 10 | #include 11 | 12 | /** 13 | * A class that wraps Linux epoll. 14 | */ 15 | class EpollFD { 16 | UniqueFileDescriptor fd; 17 | 18 | public: 19 | /** 20 | * Throws on error. 21 | */ 22 | EpollFD(); 23 | 24 | EpollFD(EpollFD &&) = default; 25 | EpollFD &operator=(EpollFD &&) = default; 26 | 27 | FileDescriptor GetFileDescriptor() const noexcept { 28 | return fd; 29 | } 30 | 31 | int Wait(epoll_event *events, int maxevents, int timeout) noexcept { 32 | return ::epoll_wait(fd.Get(), events, maxevents, timeout); 33 | } 34 | 35 | bool Control(int op, int _fd, epoll_event *event) noexcept { 36 | return ::epoll_ctl(fd.Get(), op, _fd, event) >= 0; 37 | } 38 | 39 | bool Add(int _fd, uint32_t events, void *ptr) noexcept { 40 | epoll_event e; 41 | e.events = events; 42 | e.data.ptr = ptr; 43 | 44 | return Control(EPOLL_CTL_ADD, _fd, &e); 45 | } 46 | 47 | bool Modify(int _fd, uint32_t events, void *ptr) noexcept { 48 | epoll_event e; 49 | e.events = events; 50 | e.data.ptr = ptr; 51 | 52 | return Control(EPOLL_CTL_MOD, _fd, &e); 53 | } 54 | 55 | bool Remove(int _fd) noexcept { 56 | return Control(EPOLL_CTL_DEL, _fd, nullptr); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/screen_paint.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "screen.hxx" 5 | #include "Options.hxx" 6 | #include "page/Page.hxx" 7 | #include "dialogs/ModalDialog.hxx" 8 | #include "ui/Options.hxx" 9 | 10 | inline void 11 | ScreenManager::PaintTopWindow() noexcept 12 | { 13 | assert(options.show_title_bar); 14 | 15 | const auto title = GetCurrentPage().GetTitle({buf, buf_size}); 16 | title_bar.Paint(GetCurrentPageMeta(), title); 17 | } 18 | 19 | void 20 | ScreenManager::Paint() noexcept 21 | { 22 | /* update title/header window */ 23 | if (options.show_title_bar) 24 | PaintTopWindow(); 25 | 26 | /* paint the bottom window */ 27 | 28 | progress_bar.Paint(); 29 | 30 | const auto &page = GetCurrentPage(); 31 | if (modal == nullptr && 32 | !page.PaintStatusBarOverride(status_bar.GetWindow())) 33 | status_bar.Paint(); 34 | 35 | /* paint the main window */ 36 | 37 | if (main_dirty) { 38 | main_dirty = false; 39 | page.Paint(); 40 | } 41 | 42 | /* move the cursor to the origin */ 43 | 44 | if (modal == nullptr && !ui_options.hardware_cursor) 45 | main_window.MoveCursor({0, 0}); 46 | 47 | main_window.RefreshNoOut(); 48 | 49 | if (modal != nullptr) { 50 | const auto &window = status_bar.GetWindow(); 51 | modal->Paint(window); 52 | window.RefreshNoOut(); 53 | } 54 | 55 | /* tell curses to update */ 56 | doupdate(); 57 | } 58 | -------------------------------------------------------------------------------- /src/net/Resolver.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | class AddressInfoList; 10 | 11 | class ResolverErrorCategory final : public std::error_category { 12 | public: 13 | const char *name() const noexcept override { 14 | return "gai"; 15 | } 16 | 17 | std::string message(int condition) const override; 18 | }; 19 | 20 | extern ResolverErrorCategory resolver_error_category; 21 | 22 | /** 23 | * Thin wrapper for getaddrinfo() which throws on error and returns a 24 | * RAII object. 25 | * 26 | * getaddrinfo() errors are thrown as std::system_error with 27 | * #resolver_error_category. 28 | */ 29 | AddressInfoList 30 | Resolve(const char *node, const char *service, 31 | const struct addrinfo *hints); 32 | 33 | /** 34 | * Resolve the given host name (which may include a port), and fall 35 | * back to the given default port. 36 | * 37 | * This is a wrapper for getaddrinfo() and it does not support local 38 | * sockets. 39 | * 40 | * Throws on error. Resolver errors are thrown as std::system_error 41 | * with #resolver_error_category. 42 | */ 43 | AddressInfoList 44 | Resolve(const char *host_and_port, int default_port, 45 | const struct addrinfo *hints); 46 | 47 | AddressInfoList 48 | Resolve(const char *host_port, unsigned default_port, int flags, int socktype); 49 | -------------------------------------------------------------------------------- /doc/meson.build: -------------------------------------------------------------------------------- 1 | if not get_option('html_manual') and not get_option('manual') 2 | subdir_done() 3 | endif 4 | 5 | sphinx = find_program('sphinx-build', required: get_option('documentation')) 6 | if not sphinx.found() 7 | subdir_done() 8 | endif 9 | 10 | if get_option('html_manual') 11 | sphinx_output = custom_target( 12 | 'HTML documentation', 13 | output: 'html', 14 | input: ['index.rst', 'conf.py'], 15 | command: [sphinx, '-q', '-b', 'html', '-d', '@OUTDIR@/html_doctrees', meson.current_source_dir(), '@OUTPUT@'], 16 | build_by_default: true, 17 | install: true, 18 | install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()), 19 | ) 20 | 21 | custom_target( 22 | 'upload', 23 | input: sphinx_output, 24 | output: 'upload', 25 | build_always_stale: true, 26 | command: [ 27 | find_program('rsync', required: false, disabler: true) 28 | , '-vpruz', '--delete', '@INPUT@', 29 | 'www.musicpd.org:/var/www/mpd/doc/ncmpc/', 30 | '--chmod=a+rX', 31 | ], 32 | ) 33 | endif 34 | 35 | if get_option('manual') 36 | custom_target( 37 | 'Manpage documentation', 38 | output: 'man1', 39 | input: ['index.rst', 'conf.py'], 40 | command: [sphinx, '-q', '-b', 'man', '-d', '@OUTDIR@/man_doctrees', meson.current_source_dir(), '@OUTPUT@'], 41 | build_by_default: true, 42 | install: true, 43 | install_dir: get_option('mandir'), 44 | ) 45 | endif 46 | -------------------------------------------------------------------------------- /src/util/StringUTF8.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "StringUTF8.hxx" 5 | #include "StringAPI.hxx" 6 | 7 | #include 8 | 9 | #ifdef HAVE_LOCALE_T 10 | #include 11 | #include 12 | 13 | #ifdef __APPLE__ 14 | #include // for strcoll_l() (non-standard macOS-specific header) 15 | #endif 16 | 17 | static locale_t utf8_locale = locale_t(0); 18 | 19 | ScopeInitUTF8::ScopeInitUTF8() noexcept 20 | { 21 | const char *charset = nl_langinfo(CODESET); 22 | if (charset == nullptr || StringIsEqualIgnoreCase(charset, "utf-8")) 23 | /* if we're already UTF-8, we don't need a special 24 | UTF-8 locale */ 25 | return; 26 | 27 | locale_t l = duplocale(LC_GLOBAL_LOCALE); 28 | if (l == locale_t(0)) 29 | return; 30 | 31 | locale_t l2 = newlocale(LC_COLLATE_MASK, "en_US.UTF-8", l); 32 | if (l2 == locale_t(0)) { 33 | freelocale(l); 34 | return; 35 | } 36 | 37 | utf8_locale = l2; 38 | } 39 | 40 | ScopeInitUTF8::~ScopeInitUTF8() noexcept 41 | { 42 | if (utf8_locale != locale_t(0)) { 43 | freelocale(utf8_locale); 44 | utf8_locale = locale_t(0); 45 | } 46 | } 47 | 48 | #endif 49 | 50 | [[gnu::pure]] 51 | int 52 | CollateUTF8(const char *a, const char *b) 53 | { 54 | #ifdef HAVE_LOCALE_T 55 | if (utf8_locale != locale_t(0)) 56 | return strcoll_l(a, b, utf8_locale); 57 | #endif 58 | 59 | return strcoll(a, b); 60 | } 61 | -------------------------------------------------------------------------------- /src/page/FindSupport.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "History.hxx" 7 | 8 | #include 9 | 10 | namespace Co { class InvokeTask; } 11 | enum class Command : unsigned; 12 | class ScreenManager; 13 | class ListWindow; 14 | class ListRenderer; 15 | class ListText; 16 | 17 | class FindSupport { 18 | ScreenManager &screen; 19 | 20 | std::string last; 21 | History history; 22 | 23 | public: 24 | explicit FindSupport(ScreenManager &_screen) noexcept 25 | :screen(_screen) {} 26 | 27 | /** 28 | * query user for a string and find it in a list window 29 | * 30 | * @param lw the list window to search 31 | * @param findcmd the search command/mode 32 | * @param callback_fn a function returning the text of a given line 33 | * @param callback_data a pointer passed to callback_fn 34 | * @return a task if the command has been handled, an empty task if not 35 | */ 36 | [[nodiscard]] 37 | Co::InvokeTask Find(ListWindow &lw, const ListText &text, Command cmd) noexcept; 38 | 39 | /* query user for a string and jump to the entry 40 | * which begins with this string while the users types */ 41 | [[nodiscard]] 42 | Co::InvokeTask Jump(ListWindow &lw, const ListText &text, const ListRenderer &renderer) noexcept; 43 | 44 | private: 45 | [[nodiscard]] 46 | Co::InvokeTask DoFind(ListWindow &lw, const ListText &text, bool reversed) noexcept; 47 | }; 48 | -------------------------------------------------------------------------------- /src/TablePaint.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "TablePaint.hxx" 5 | #include "TableLayout.hxx" 6 | #include "TableStructure.hxx" 7 | #include "strfsong.hxx" 8 | #include "ui/paint.hxx" 9 | #include "util/LocaleString.hxx" 10 | 11 | static void 12 | FillSpace(const Window window, unsigned n) noexcept 13 | { 14 | // TODO: use whline(), which unfortunately doesn't move the cursor 15 | while (n-- > 0) 16 | window.Char(' '); 17 | } 18 | 19 | void 20 | PaintTableRow(const Window window, unsigned width, 21 | bool selected, bool highlight, const struct mpd_song &song, 22 | const TableLayout &layout) noexcept 23 | { 24 | const auto color = highlight ? Style::LIST_BOLD : Style::LIST; 25 | row_color(window, color, selected); 26 | 27 | const size_t n_columns = layout.structure.columns.size(); 28 | for (size_t i = 0; i < n_columns; ++i) { 29 | const auto &cl = layout.columns[i]; 30 | const auto &cs = layout.structure.columns[i]; 31 | if (cl.width == 0) 32 | break; 33 | 34 | if (i > 0) { 35 | SelectStyle(window, Style::LINE); 36 | window.Char(ACS_VLINE); 37 | row_color(window, color, selected); 38 | } 39 | 40 | char buffer[1024]; 41 | 42 | std::string_view s = TruncateAtWidthMB(strfsong(buffer, cs.format.c_str(), song), cl.width); 43 | window.String(s); 44 | FillSpace(window, cl.width - StringWidthMB(s)); 45 | } 46 | 47 | row_clear_to_eol(window, width, selected); 48 | } 49 | -------------------------------------------------------------------------------- /src/util/Cancellable.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | #include // for std::nullptr_t 8 | #include 9 | 10 | /** 11 | * An asynchronous operation that can be cancelled. Upon 12 | * cancellation, the operation's handler must not be invoked. 13 | */ 14 | class Cancellable { 15 | public: 16 | virtual void Cancel() noexcept = 0; 17 | }; 18 | 19 | class CancellablePointer { 20 | Cancellable *cancellable = nullptr; 21 | 22 | public: 23 | constexpr CancellablePointer() = default; 24 | 25 | constexpr CancellablePointer(std::nullptr_t n) noexcept 26 | :cancellable(n) {} 27 | 28 | CancellablePointer(CancellablePointer &&src) noexcept 29 | :cancellable(std::exchange(src.cancellable, nullptr)) {} 30 | 31 | CancellablePointer &operator=(CancellablePointer &&src) noexcept { 32 | using std::swap; 33 | swap(cancellable, src.cancellable); 34 | return *this; 35 | } 36 | 37 | CancellablePointer &operator=(std::nullptr_t n) noexcept { 38 | cancellable = n; 39 | return *this; 40 | } 41 | 42 | CancellablePointer &operator=(Cancellable &_cancellable) noexcept { 43 | cancellable = &_cancellable; 44 | return *this; 45 | } 46 | 47 | constexpr operator bool() const noexcept { 48 | return cancellable != nullptr; 49 | } 50 | 51 | void Cancel() noexcept { 52 | assert(cancellable != nullptr); 53 | 54 | std::exchange(cancellable, nullptr)->Cancel(); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/BasicColors.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "BasicColors.hxx" 5 | #include "util/StringAPI.hxx" 6 | 7 | #include 8 | 9 | #include 10 | 11 | static constexpr const char *basic_color_names[] = { 12 | "black", 13 | "red", 14 | "green", 15 | "yellow", 16 | "blue", 17 | "magenta", 18 | "cyan", 19 | "white", 20 | nullptr 21 | }; 22 | 23 | static_assert(COLOR_BLACK == 0, "Unexpected color value"); 24 | static_assert(COLOR_RED == 1, "Unexpected color value"); 25 | static_assert(COLOR_GREEN == 2, "Unexpected color value"); 26 | static_assert(COLOR_YELLOW == 3, "Unexpected color value"); 27 | static_assert(COLOR_BLUE == 4, "Unexpected color value"); 28 | static_assert(COLOR_MAGENTA == 5, "Unexpected color value"); 29 | static_assert(COLOR_CYAN == 6, "Unexpected color value"); 30 | static_assert(COLOR_WHITE == 7, "Unexpected color value"); 31 | 32 | short 33 | ParseBasicColorName(const char *name) noexcept 34 | { 35 | for (size_t i = 0; basic_color_names[i] != nullptr; ++i) 36 | if (StringIsEqualIgnoreCase(basic_color_names[i], name)) 37 | return i; 38 | 39 | return -1; 40 | } 41 | 42 | short 43 | ParseColorNameOrNumber(const char *s) noexcept 44 | { 45 | short basic = ParseBasicColorName(s); 46 | if (basic >= 0) 47 | return basic; 48 | 49 | char *endptr; 50 | long numeric = strtol(s, &endptr, 10); 51 | if (endptr > s && *endptr == 0 && numeric >= 0 && numeric <= 0xff) 52 | return numeric; 53 | 54 | return -1; 55 | } 56 | -------------------------------------------------------------------------------- /src/XdgBaseDirectory.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "XdgBaseDirectory.hxx" 5 | #include "io/Path.hxx" 6 | 7 | #include 8 | 9 | [[gnu::const]] 10 | static const char * 11 | GetUserDirectory() noexcept 12 | { 13 | return getenv("HOME"); 14 | } 15 | 16 | [[gnu::const]] 17 | static std::string 18 | GetUserConfigDirectory() noexcept 19 | { 20 | const char *config_home = getenv("XDG_CONFIG_HOME"); 21 | if (config_home != nullptr && *config_home != 0) 22 | return config_home; 23 | 24 | const char *home = GetUserDirectory(); 25 | if (home != nullptr) 26 | return BuildPath(home, ".config"); 27 | 28 | return {}; 29 | } 30 | 31 | std::string 32 | GetUserConfigDirectory(std::string_view package) noexcept 33 | { 34 | const auto dir = GetUserConfigDirectory(); 35 | if (dir.empty()) 36 | return {}; 37 | 38 | return BuildPath(dir, package); 39 | } 40 | 41 | [[gnu::const]] 42 | static std::string 43 | GetUserCacheDirectory() noexcept 44 | { 45 | const char *cache_home = getenv("XDG_CACHE_HOME"); 46 | if (cache_home != nullptr && *cache_home != 0) 47 | return cache_home; 48 | 49 | const char *home = GetUserDirectory(); 50 | if (home != nullptr) 51 | return BuildPath(home, ".cache"); 52 | 53 | return {}; 54 | } 55 | 56 | std::string 57 | GetUserCacheDirectory(std::string_view package) noexcept 58 | { 59 | const auto dir = GetUserCacheDirectory(); 60 | if (dir.empty()) 61 | return {}; 62 | 63 | return BuildPath(dir, package); 64 | } 65 | -------------------------------------------------------------------------------- /src/util/Cast.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "OffsetPointer.hxx" 7 | 8 | #include 9 | 10 | template 11 | constexpr T * 12 | OffsetCast(U *p, std::ptrdiff_t offset) 13 | { 14 | return reinterpret_cast(OffsetPointer(p, offset)); 15 | } 16 | 17 | template 18 | constexpr T * 19 | OffsetCast(const U *p, std::ptrdiff_t offset) 20 | { 21 | return reinterpret_cast(OffsetPointer(p, offset)); 22 | } 23 | 24 | template 25 | constexpr std::ptrdiff_t 26 | ContainerAttributeOffset(const C *null_c, const A C::*p) 27 | { 28 | return std::ptrdiff_t((const char *)&(null_c->*p) - (const char *)null_c); 29 | } 30 | 31 | template 32 | constexpr std::ptrdiff_t 33 | ContainerAttributeOffset(const A C::*p) 34 | { 35 | return ContainerAttributeOffset(nullptr, p); 36 | } 37 | 38 | /** 39 | * Cast the given pointer to a struct member to its parent structure. 40 | */ 41 | template 42 | constexpr C & 43 | ContainerCast(A &a, const A C::*member) 44 | { 45 | return *OffsetCast(&a, -ContainerAttributeOffset(member)); 46 | } 47 | 48 | /** 49 | * Cast the given pointer to a struct member to its parent structure. 50 | */ 51 | template 52 | constexpr const C & 53 | ContainerCast(const A &a, const A C::*member) 54 | { 55 | return *OffsetCast(&a, -ContainerAttributeOffset(member)); 56 | } 57 | -------------------------------------------------------------------------------- /src/SongRowPaint.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "SongRowPaint.hxx" 5 | #include "strfsong.hxx" 6 | #include "time_format.hxx" 7 | #include "hscroll.hxx" 8 | #include "Options.hxx" 9 | #include "ui/Window.hxx" 10 | #include "ui/paint.hxx" 11 | #include "util/LocaleString.hxx" 12 | #include "config.h" // IWYU pragma: keep 13 | 14 | #include 15 | 16 | #include 17 | 18 | void 19 | paint_song_row(const Window window, [[maybe_unused]] int y, unsigned width, 20 | bool selected, bool highlight, const struct mpd_song &song, 21 | [[maybe_unused]] class hscroll *hscroll, const char *format) 22 | { 23 | char buffer[1024]; 24 | 25 | const std::string_view text = strfsong(buffer, format, song); 26 | row_paint_text(window, width, highlight ? Style::LIST_BOLD : Style::LIST, 27 | selected, text); 28 | 29 | #ifndef NCMPC_MINI 30 | if (options.second_column && mpd_song_get_duration(&song) > 0) { 31 | char duration_buffer[32]; 32 | const auto duration = format_duration_short(duration_buffer, mpd_song_get_duration(&song)); 33 | width -= duration.size() + 1; 34 | window.MoveCursor({(int)width, y}); 35 | window.Char(' '); 36 | window.String(duration); 37 | } 38 | 39 | if (hscroll != nullptr && width > 3 && 40 | StringWidthMB(text) >= width) { 41 | hscroll->Set({0, y}, width, text, 42 | highlight ? Style::LIST_BOLD : Style::LIST, 43 | selected ? A_REVERSE : 0); 44 | hscroll->Paint(); 45 | } 46 | #endif 47 | } 48 | -------------------------------------------------------------------------------- /src/Match.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "Match.hxx" 5 | #include "util/ScopeExit.hxx" 6 | 7 | #include 8 | #include 9 | 10 | MatchExpression::~MatchExpression() noexcept 11 | { 12 | #ifdef HAVE_PCRE 13 | pcre2_code_free_8(re); 14 | #endif 15 | } 16 | 17 | bool 18 | MatchExpression::Compile(std::string_view src, bool anchor) noexcept 19 | { 20 | #ifndef HAVE_PCRE 21 | expression = src; 22 | anchored = anchor; 23 | 24 | return true; 25 | #else 26 | assert(re == nullptr); 27 | 28 | int options = PCRE2_CASELESS|PCRE2_DOTALL|PCRE2_NO_AUTO_CAPTURE; 29 | if (anchor) 30 | options |= PCRE2_ANCHORED; 31 | 32 | int error_number; 33 | PCRE2_SIZE error_offset; 34 | re = pcre2_compile_8(PCRE2_SPTR8(src.data()), src.size(), 35 | options, &error_number, &error_offset, nullptr); 36 | return re != nullptr; 37 | #endif 38 | } 39 | 40 | bool 41 | MatchExpression::operator()(std::string_view line) const noexcept 42 | { 43 | #ifndef HAVE_PCRE 44 | return anchored 45 | ? strncasecmp(line.data(), expression.data(), std::min(expression.size(), line.size())) == 0 46 | : expression.find(line) != expression.npos; 47 | #else 48 | assert(re != nullptr); 49 | 50 | const auto match_data = 51 | pcre2_match_data_create_from_pattern_8(re, nullptr); 52 | AtScopeExit(match_data) { 53 | pcre2_match_data_free_8(match_data); 54 | }; 55 | 56 | return pcre2_match_8(re, (PCRE2_SPTR8)line.data(), line.size(), 57 | 0, 0, match_data, nullptr) >= 0; 58 | #endif 59 | } 60 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ncmpc 2 | ===== 3 | 4 | ncmpc is a curses client for the `Music Player Daemon 5 | `__. 6 | 7 | .. image:: https://www.musicpd.org/clients/ncmpc/screenshot.png 8 | :alt: Screenshot of ncmpc 9 | 10 | 11 | How to compile and install ncmpc 12 | -------------------------------- 13 | 14 | You need: 15 | 16 | - a C++23 compliant compiler (e.g. gcc or clang) 17 | - `libfmt `__ 18 | - `libmpdclient `__ 2.16 19 | - `ncurses `__ 20 | - `Meson 1.2 `__ and `Ninja `__ 21 | 22 | Optional: 23 | 24 | - `PCRE `__ (for regular expression support in 25 | the "find" command) 26 | - `liblirc `__ (for infrared 27 | remote support) 28 | - `Sphinx `__ (for building 29 | documentation) 30 | 31 | Run ``meson``:: 32 | 33 | meson . output --buildtype=debugoptimized -Db_ndebug=true 34 | 35 | Compile and install:: 36 | 37 | ninja -C output 38 | ninja -C output install 39 | 40 | 41 | Links 42 | ----- 43 | 44 | - `Home page and download `__ 45 | - `Documentation `__ 46 | - `git repository `__ 47 | - `Bug tracker `__ 48 | - `Help translate ncmpc to your native language `__ 49 | - `Forum `__ 50 | -------------------------------------------------------------------------------- /src/StatusBar.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef NCMPC_STATUS_BAR_HXX 5 | #define NCMPC_STATUS_BAR_HXX 6 | 7 | #include "config.h" // IWYU pragma: keep 8 | #include "ui/Window.hxx" 9 | #include "event/CoarseTimerEvent.hxx" 10 | 11 | #ifndef NCMPC_MINI 12 | #include "hscroll.hxx" 13 | #endif 14 | 15 | #include 16 | 17 | struct mpd_status; 18 | struct mpd_song; 19 | class DelayedSeek; 20 | 21 | class StatusBar { 22 | UniqueWindow window; 23 | 24 | std::string message; 25 | CoarseTimerEvent message_timer; 26 | 27 | #ifndef NCMPC_MINI 28 | class hscroll hscroll; 29 | #endif 30 | 31 | const char *left_text; 32 | char right_buffer[64]; 33 | std::size_t right_length; 34 | 35 | std::string center_text; 36 | 37 | unsigned left_width, right_width; 38 | 39 | public: 40 | StatusBar(EventLoop &event_loop, 41 | Point p, unsigned width) noexcept; 42 | ~StatusBar() noexcept; 43 | 44 | Window GetWindow() const noexcept { 45 | return window; 46 | } 47 | 48 | void SetMessage(std::string &&msg) noexcept; 49 | void ClearMessage() noexcept; 50 | 51 | void OnResize(Point p, unsigned width) noexcept; 52 | void Update(const struct mpd_status *status, 53 | const struct mpd_song *song, 54 | const DelayedSeek &seek) noexcept; 55 | void Paint() const noexcept; 56 | 57 | private: 58 | /** 59 | * Updates #hscroll after a content or size change. 60 | */ 61 | void UpdateScrollLayout() noexcept; 62 | 63 | void OnMessageTimer() noexcept { 64 | ClearMessage(); 65 | } 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/Completion.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class Completion { 11 | protected: 12 | using List = std::set>; 13 | List list; 14 | 15 | public: 16 | Completion() = default; 17 | 18 | Completion(const Completion &) = delete; 19 | Completion &operator=(const Completion &) = delete; 20 | 21 | bool empty() const noexcept { 22 | return list.empty(); 23 | } 24 | 25 | void clear() noexcept { 26 | list.clear(); 27 | } 28 | 29 | template 30 | void emplace(T &&value) noexcept { 31 | list.emplace(std::forward(value)); 32 | } 33 | 34 | template 35 | void remove(T &&value) noexcept { 36 | auto i = list.find(std::forward(value)); 37 | if (i != list.end()) 38 | list.erase(i); 39 | } 40 | 41 | struct Range { 42 | using const_iterator = List::const_iterator; 43 | const_iterator _begin, _end; 44 | 45 | bool operator==(const Range &) const noexcept = default; 46 | 47 | const_iterator begin() const noexcept { 48 | return _begin; 49 | } 50 | 51 | const_iterator end() const noexcept { 52 | return _end; 53 | } 54 | }; 55 | 56 | struct Result { 57 | std::string new_prefix; 58 | 59 | Range range; 60 | }; 61 | 62 | [[nodiscard]] [[gnu::pure]] 63 | Result Complete(std::string_view prefix) const noexcept; 64 | 65 | virtual void Pre(std::string_view value) noexcept = 0; 66 | virtual void Post(std::string_view value, Range range) noexcept = 0; 67 | }; 68 | -------------------------------------------------------------------------------- /lyrics/50-genius.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | # Copyright The Music Player Daemon Project 5 | 6 | # 7 | # Load lyrics from genius.com if lyrics weren't found in the lyrics directory 8 | # 9 | import bs4 10 | import re 11 | import requests 12 | import sys 13 | 14 | try: 15 | # using the "unidecode" library if installed 16 | # (https://pypi.org/project/Unidecode/ or package 17 | # "python3-unidecode" on Debian) 18 | from unidecode import unidecode 19 | except ImportError: 20 | # Dummy fallback (don't crash if "unidecode" is not installed) 21 | def unidecode(s): 22 | return s 23 | 24 | 25 | def normalize_parameter(s): 26 | return unidecode(s).lower() 27 | 28 | 29 | base_url = "https://genius.com/" 30 | artists = normalize_parameter(sys.argv[1]) 31 | title = normalize_parameter(sys.argv[2]) 32 | artists = [artist for artist in artists.split(",")] 33 | 34 | title = re.sub("\(.*\)", "", title) 35 | 36 | for artist in artists: 37 | title = title.replace(" ", "-").replace("'", "") 38 | artist = artist.replace(" ", "-").replace("'", "") 39 | r = requests.get(f"{base_url}{artist}-{title}-lyrics") 40 | try: 41 | r.raise_for_status() 42 | except: 43 | exit(1) 44 | soup = bs4.BeautifulSoup(r.text, "html5lib") 45 | lyrics = [ 46 | x.getText("\n") for x in soup.find(attrs={"data-lyrics-container": "true"}) 47 | ] 48 | print( 49 | re.sub( 50 | r"\n\n+", 51 | "\n", 52 | "".join([x.replace("", "\n") if not x else x for x in lyrics]), 53 | ) 54 | ) 55 | -------------------------------------------------------------------------------- /src/ui/paint.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "Styles.hxx" 7 | #include "Options.hxx" 8 | #include "Window.hxx" 9 | 10 | /** 11 | * Sets the specified color, and enables "reverse" mode if selected is 12 | * true. 13 | */ 14 | static inline void 15 | row_color(const Window window, Style style, bool selected) noexcept 16 | { 17 | SelectStyle(window, style); 18 | 19 | if (selected) 20 | window.AttributeOn(A_REVERSE); 21 | else 22 | window.AttributeOff(A_REVERSE); 23 | } 24 | 25 | /** 26 | * Call this when you are done with painting rows. It resets the 27 | * "reverse" mode. 28 | */ 29 | static inline void 30 | row_color_end(const Window window) noexcept 31 | { 32 | window.AttributeOff(A_REVERSE); 33 | } 34 | 35 | /** 36 | * Clears the remaining space on the current row. If the row is 37 | * selected and the wide_cursor option is enabled, it draws the cursor 38 | * on the space. 39 | */ 40 | static inline void 41 | row_clear_to_eol(const Window window, unsigned width, bool selected) noexcept 42 | { 43 | if (selected && ui_options.wide_cursor) 44 | window.HLine(width, ' '); 45 | else 46 | window.ClearToEol(); 47 | } 48 | 49 | /** 50 | * Paint a plain-text row. 51 | */ 52 | static inline void 53 | row_paint_text(const Window window, unsigned width, 54 | Style style, bool selected, 55 | std::string_view text) noexcept 56 | { 57 | row_color(window, style, selected); 58 | 59 | window.String(text); 60 | 61 | /* erase the unused space after the text */ 62 | row_clear_to_eol(window, width, selected); 63 | } 64 | -------------------------------------------------------------------------------- /lyrics/25-musixmatch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | import bs4 4 | import sys 5 | import html 6 | 7 | def normalize(s): 8 | for i in [' ', '\'']: 9 | s = s.replace(i, '-') 10 | for i in [',', '(', ')']: 11 | s = s.replace(i, '') 12 | return html.escape(s) 13 | 14 | def main(): 15 | artist = normalize(sys.argv[1]) 16 | title = normalize(sys.argv[2]) 17 | 18 | try: 19 | musixmatch_url = "https://www.musixmatch.com/lyrics/" 20 | r = requests.get( 21 | musixmatch_url + artist + "/" + title, 22 | headers = { 23 | "Host": "www.musixmatch.com", 24 | # emulate a linux user 25 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", 26 | "Accept": "*/*" 27 | } 28 | ) 29 | 30 | if r.status_code == 404: 31 | print("Lyrics not found :(", file=sys.stderr) 32 | exit(1) 33 | 34 | soup = bs4.BeautifulSoup(r.text, features="lxml") 35 | p_tags = soup.find_all("p", {"class": "mxm-lyrics__content"}) 36 | if len(p_tags) == 0: 37 | # Sometimes musixmatch shows a "Restricted Lyrics" page with a 200 status code 38 | print("Unable to get lyrics :(") 39 | exit(1) 40 | 41 | for p in p_tags: 42 | print(p.text) 43 | exit(1) 44 | 45 | except Exception as e: 46 | print("Unknown error: ", e, file=sys.stderr) 47 | exit(2) 48 | 49 | if __name__ == '__main__': 50 | main() 51 | -------------------------------------------------------------------------------- /src/db_completion.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "db_completion.hxx" 5 | #include "Completion.hxx" 6 | #include "charset.hxx" 7 | #include "client/mpdclient.hxx" 8 | #include "util/ScopeExit.hxx" 9 | 10 | #include 11 | 12 | void 13 | gcmp_list_from_path(struct mpdclient &c, const char *path, 14 | Completion &completion, 15 | int types) 16 | { 17 | auto *connection = c.GetConnection(); 18 | if (connection == nullptr) 19 | return; 20 | 21 | mpd_send_list_meta(connection, path); 22 | 23 | struct mpd_entity *entity; 24 | while ((entity = mpd_recv_entity(connection)) != nullptr) { 25 | AtScopeExit(entity) { mpd_entity_free(entity); }; 26 | 27 | std::string name; 28 | if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_DIRECTORY && 29 | types & GCMP_TYPE_DIR) { 30 | const struct mpd_directory *dir = 31 | mpd_entity_get_directory(entity); 32 | name = Utf8ToLocale{mpd_directory_get_path(dir)}; 33 | name.push_back('/'); 34 | } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG && 35 | types & GCMP_TYPE_FILE) { 36 | const struct mpd_song *song = 37 | mpd_entity_get_song(entity); 38 | name = Utf8ToLocale{mpd_song_get_uri(song)}; 39 | } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST && 40 | types & GCMP_TYPE_PLAYLIST) { 41 | const struct mpd_playlist *playlist = 42 | mpd_entity_get_playlist(entity); 43 | name = Utf8ToLocale{mpd_playlist_get_path(playlist)}; 44 | } else { 45 | continue; 46 | } 47 | 48 | completion.emplace(std::move(name)); 49 | } 50 | 51 | c.FinishCommand(); 52 | } 53 | -------------------------------------------------------------------------------- /src/page/ProxyPage.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "Page.hxx" 7 | #include "Container.hxx" 8 | #include "ui/Window.hxx" 9 | #include "config.h" 10 | 11 | class ProxyPage : public Page, public PageContainer { 12 | const Window window; 13 | 14 | Page *current_page = nullptr; 15 | 16 | bool is_open = false; 17 | 18 | public: 19 | explicit ProxyPage(PageContainer &_parent, const Window _window) noexcept 20 | :Page(_parent), window(_window) {} 21 | 22 | [[nodiscard]] 23 | const Page *GetCurrentPage() const noexcept { 24 | return current_page; 25 | } 26 | 27 | [[nodiscard]] 28 | Page *GetCurrentPage() noexcept { 29 | return current_page; 30 | } 31 | 32 | void SetCurrentPage(struct mpdclient &c, Page *new_page) noexcept; 33 | 34 | using Page::SchedulePaint; 35 | 36 | /* virtual methods from Page */ 37 | void OnOpen(struct mpdclient &c) noexcept override; 38 | void OnClose() noexcept override; 39 | void OnResize(Size size) noexcept override; 40 | void Paint() const noexcept override; 41 | void Update(struct mpdclient &c, unsigned events) noexcept override; 42 | bool OnCommand(struct mpdclient &c, Command cmd) override; 43 | 44 | #ifdef HAVE_GETMOUSE 45 | bool OnMouse(struct mpdclient &c, Point p, mmask_t bstate) override; 46 | #endif 47 | 48 | std::string_view GetTitle(std::span buffer) const noexcept override; 49 | const struct mpd_song *GetSelectedSong() const noexcept override; 50 | 51 | // virtual methods from PageContainer 52 | void SchedulePaint(Page &page) noexcept override; 53 | void Alert(std::string message) noexcept override; 54 | }; 55 | -------------------------------------------------------------------------------- /src/net/IPv6Address.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "IPv6Address.hxx" 5 | #include "IPv4Address.hxx" 6 | 7 | #include 8 | 9 | #include 10 | 11 | IPv6Address::IPv6Address(SocketAddress src) noexcept 12 | :address(src.CastTo()) 13 | { 14 | assert(!src.IsNull()); 15 | assert(src.GetFamily() == AF_INET6); 16 | } 17 | 18 | bool 19 | IPv6Address::IsAny() const noexcept 20 | { 21 | assert(IsValid()); 22 | 23 | return memcmp(&address.sin6_addr, 24 | &in6addr_any, sizeof(in6addr_any)) == 0; 25 | } 26 | 27 | IPv4Address 28 | IPv6Address::UnmapV4() const noexcept 29 | { 30 | assert(IsV4Mapped()); 31 | 32 | struct sockaddr_in buffer{}; 33 | buffer.sin_family = AF_INET; 34 | memcpy(&buffer.sin_addr, ((const char *)&address.sin6_addr) + 12, 35 | sizeof(buffer.sin_addr)); 36 | buffer.sin_port = address.sin6_port; 37 | 38 | return buffer; 39 | } 40 | 41 | template 42 | static void 43 | BitwiseAndT(T *dest, const T *a, const T *b, size_t n) 44 | { 45 | while (n-- > 0) 46 | *dest++ = *a++ & *b++; 47 | } 48 | 49 | static void 50 | BitwiseAnd32(void *dest, const void *a, const void *b, size_t n) 51 | { 52 | using value_type = uint32_t; 53 | using pointer = value_type *; 54 | using const_pointer = const value_type *; 55 | 56 | BitwiseAndT(pointer(dest), const_pointer(a), const_pointer(b), 57 | n / sizeof(value_type)); 58 | } 59 | 60 | IPv6Address 61 | IPv6Address::operator&(const IPv6Address &other) const 62 | { 63 | IPv6Address result; 64 | BitwiseAnd32(&result, this, &other, 65 | sizeof(result)); 66 | return result; 67 | } 68 | -------------------------------------------------------------------------------- /src/event/EpollBackend.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "system/EpollFD.hxx" 7 | 8 | #include 9 | #include 10 | 11 | class EpollBackendResult 12 | { 13 | friend class EpollBackend; 14 | 15 | std::array events; 16 | size_t n_events = 0; 17 | 18 | public: 19 | size_t GetSize() const noexcept { 20 | return n_events; 21 | } 22 | 23 | unsigned GetEvents(size_t i) const noexcept { 24 | return events[i].events; 25 | } 26 | 27 | void *GetObject(size_t i) const noexcept { 28 | return events[i].data.ptr; 29 | } 30 | }; 31 | 32 | class EpollBackend 33 | { 34 | EpollFD epoll; 35 | 36 | EpollBackend(EpollBackend &) = delete; 37 | EpollBackend &operator=(EpollBackend &) = delete; 38 | public: 39 | EpollBackend() = default; 40 | 41 | FileDescriptor GetFileDescriptor() const noexcept { 42 | return epoll.GetFileDescriptor(); 43 | } 44 | 45 | auto ReadEvents(int timeout_ms) noexcept { 46 | EpollBackendResult result; 47 | int ret = epoll.Wait(result.events.data(), result.events.size(), 48 | timeout_ms); 49 | result.n_events = std::max(0, ret); 50 | return result; 51 | } 52 | 53 | bool Add(int fd, unsigned events, void *obj) noexcept { 54 | return epoll.Add(fd, events, obj); 55 | } 56 | 57 | bool Modify(int fd, unsigned events, void *obj) noexcept { 58 | return epoll.Modify(fd, events, obj); 59 | } 60 | 61 | bool Remove(int fd) noexcept { 62 | return epoll.Remove(fd); 63 | } 64 | 65 | bool Abandon([[maybe_unused]] int fd) noexcept { 66 | // Nothing to do in this implementation. 67 | // Closed descriptors are automatically unregistered. 68 | return true; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/BasicMarquee.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * This class is used to auto-scroll text which does not fit on the 12 | * screen. Call Set() to begin scrolling. 13 | */ 14 | class BasicMarquee { 15 | const std::string_view separator; 16 | 17 | /** 18 | * The scrolled text, in the current locale. 19 | */ 20 | std::string text; 21 | 22 | /** 23 | * A buffer containing the text plus the separator twice. 24 | */ 25 | std::string buffer; 26 | 27 | /** 28 | * The text plus separator length in characters. 29 | */ 30 | size_t max_offset; 31 | 32 | /** 33 | * The available screen width (in cells). 34 | */ 35 | unsigned width = 0; 36 | 37 | /** 38 | * The current scrolling offset. This is a character 39 | * position, not a screen column. 40 | */ 41 | unsigned offset; 42 | 43 | public: 44 | explicit BasicMarquee(std::string_view _separator) noexcept 45 | :separator(_separator) {} 46 | 47 | bool IsDefined() const noexcept { 48 | return width > 0; 49 | } 50 | 51 | /** 52 | * Sets a text to scroll. Call Clear() to disable it. 53 | * 54 | * @param text the text in the locale charset 55 | * @return false if nothing was changed 56 | */ 57 | bool Set(unsigned width, std::string_view _text) noexcept; 58 | 59 | /** 60 | * Removes the text. It may be reused with Set(). 61 | */ 62 | void Clear() noexcept; 63 | 64 | void Rewind() noexcept { 65 | offset = 0; 66 | } 67 | 68 | void Step() noexcept { 69 | ++offset; 70 | 71 | if (offset >= max_offset) 72 | offset = 0; 73 | } 74 | 75 | [[gnu::pure]] 76 | std::string_view ScrollString() const noexcept; 77 | }; 78 | -------------------------------------------------------------------------------- /src/TableStructure.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "TableLayout.hxx" 5 | 6 | #include 7 | 8 | void 9 | TableLayout::Calculate(unsigned screen_width) noexcept 10 | { 11 | if (columns.empty()) 12 | /* shouldn't happen */ 13 | return; 14 | 15 | for (auto &i : columns) 16 | i.width = 0; 17 | 18 | if (screen_width <= columns.front().min_width) { 19 | /* very narrow window, there's only space for one 20 | column */ 21 | columns.front().width = screen_width; 22 | return; 23 | } 24 | 25 | /* check how many columns fit on the screen */ 26 | 27 | unsigned remaining_width = screen_width - columns.front().min_width - 1; 28 | size_t n_visible = 1; 29 | float fraction_sum = columns.front().fraction_width; 30 | 31 | for (size_t i = 1; i < columns.size(); ++i) { 32 | auto &c = columns[i]; 33 | 34 | if (remaining_width < c.min_width) 35 | /* this column doesn't fit, stop here */ 36 | break; 37 | 38 | ++n_visible; 39 | fraction_sum += c.fraction_width; 40 | remaining_width -= c.min_width; 41 | 42 | if (remaining_width == 0) 43 | /* no room for the vertical line */ 44 | break; 45 | 46 | /* subtract the vertical line */ 47 | --remaining_width; 48 | } 49 | 50 | /* distribute the remaining width */ 51 | 52 | for (size_t i = n_visible; i > 0;) { 53 | auto &c = columns[--i]; 54 | 55 | unsigned width = c.min_width; 56 | if (fraction_sum > 0 && c.fraction_width > 0) { 57 | unsigned add = lrint(remaining_width * c.fraction_width 58 | / fraction_sum); 59 | if (add > remaining_width) 60 | add = remaining_width; 61 | 62 | width += add; 63 | remaining_width -= add; 64 | fraction_sum -= c.fraction_width; 65 | } 66 | 67 | c.width = width; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lyrics/40-tekstowo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | # Copyright The Music Player Daemon Project 5 | 6 | # 7 | # Load lyrics from tekstowo.pl if lyrics weren't found in the lyrics directory 8 | # 9 | import bs4 10 | import re 11 | import requests 12 | import sys 13 | 14 | try: 15 | # using the "unidecode" library if installed 16 | # (https://pypi.org/project/Unidecode/ or package 17 | # "python3-unidecode" on Debian) 18 | from unidecode import unidecode 19 | except ImportError: 20 | # Dummy fallback (don't crash if "unidecode" is not installed) 21 | def unidecode(s): 22 | return s 23 | 24 | 25 | def normalize_parameter(s): 26 | return unidecode(s).lower() 27 | 28 | 29 | artists = normalize_parameter(sys.argv[1]) 30 | title = normalize_parameter(sys.argv[2]) 31 | artists = [artist.removeprefix("the ") for artist in artists.split(",")] 32 | 33 | title = re.sub("\(.*\)", "", title) 34 | 35 | for artist in artists: 36 | r = requests.get( 37 | f"https://www.tekstowo.pl/wyszukaj.html?search-artist={artist}&search-title={title}" 38 | ) 39 | r.raise_for_status() 40 | soup = bs4.BeautifulSoup(r.text, "html5lib") 41 | results = soup.find(attrs={"class": "card-body p-0"}).select("a") 42 | matches = [ 43 | x 44 | for x in results 45 | if re.search(title, normalize_parameter(x.text).split(" - ")[1]) 46 | ] 47 | if not matches: 48 | print("Lyrics not found :(", file=sys.stderr) 49 | exit(1) 50 | 51 | # first match is a good match 52 | song_url = f'https://www.tekstowo.pl{matches[0].get("href")}' 53 | r = requests.get(song_url) 54 | r.raise_for_status() 55 | soup = bs4.BeautifulSoup(r.text, "lxml") 56 | print(soup.select_one("#songText .inner-text").text) 57 | -------------------------------------------------------------------------------- /src/ncu.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "ncu.hxx" 5 | #include "config.h" 6 | 7 | #ifdef ENABLE_COLORS 8 | #include "Styles.hxx" 9 | #endif 10 | 11 | #ifdef HAVE_GETMOUSE 12 | #include "Options.hxx" 13 | #endif 14 | 15 | #include 16 | 17 | #ifdef NCURSES_VERSION 18 | #include 19 | #endif 20 | 21 | static SCREEN *ncu_screen; 22 | 23 | void 24 | ncu_init() 25 | { 26 | /* initialize the curses library */ 27 | ncu_screen = newterm(nullptr, stdout, stdin); 28 | 29 | /* initialize color support */ 30 | #ifdef ENABLE_COLORS 31 | ApplyStyles(); 32 | #endif 33 | 34 | /* Ctrl-C generates keycode 0x03 instead of SIGINT */ 35 | raw(); 36 | 37 | /* tell curses not to do NL->CR/NL on output */ 38 | nonl(); 39 | 40 | /* don't echo input */ 41 | noecho(); 42 | 43 | /* set cursor invisible */ 44 | curs_set(0); 45 | 46 | /* enable extra keys */ 47 | keypad(stdscr, true); 48 | 49 | #ifdef NCURSES_VERSION 50 | /* define Alt-* keys which for some reasons aren't defined by 51 | default (tested with ncurses 6.1 on Linux) */ 52 | 53 | if (!key_defined("M-^@")) { 54 | char buffer[8]; 55 | buffer[0] = 033; 56 | 57 | for (int i = 0x80; i <= 0xff; ++i) { 58 | const char *name = keyname(i); 59 | if (name != nullptr && name[0] == 'M' && 60 | name[1] == '-' && name[2] != 0 && 61 | (name[3] == 0 || name[4] == 0)) { 62 | strcpy(buffer + 1, name + 2); 63 | define_key(buffer, i); 64 | } 65 | } 66 | } 67 | #endif 68 | 69 | /* initialize mouse support */ 70 | #ifdef HAVE_GETMOUSE 71 | if (options.enable_mouse) 72 | mousemask(ALL_MOUSE_EVENTS, nullptr); 73 | #endif 74 | 75 | refresh(); 76 | } 77 | 78 | void 79 | ncu_deinit() 80 | { 81 | endwin(); 82 | 83 | delscreen(ncu_screen); 84 | } 85 | -------------------------------------------------------------------------------- /src/co/UniqueHandle.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace Co { 11 | 12 | /** 13 | * Manage a std::coroutine_handle<> which is destroyed by the 14 | * destructor. 15 | */ 16 | template 17 | class UniqueHandle { 18 | std::coroutine_handle value; 19 | 20 | public: 21 | UniqueHandle() = default; 22 | 23 | explicit constexpr UniqueHandle(std::coroutine_handle h) noexcept 24 | :value(h) {} 25 | 26 | UniqueHandle(UniqueHandle &&src) noexcept 27 | :value(std::exchange(src.value, nullptr)) 28 | { 29 | } 30 | 31 | /* this overload allows casting a specialized handle to a 32 | std::coroutine_handle */ 33 | template 34 | requires(std::is_void_v && !std::is_void_v

) 35 | UniqueHandle(UniqueHandle

&&src) noexcept 36 | :value(src.release()) 37 | { 38 | } 39 | 40 | ~UniqueHandle() noexcept { 41 | if (value) 42 | value.destroy(); 43 | } 44 | 45 | auto &operator=(UniqueHandle &&src) noexcept { 46 | using std::swap; 47 | swap(value, src.value); 48 | return *this; 49 | } 50 | 51 | operator bool() const noexcept { 52 | return (bool)value; 53 | } 54 | 55 | const auto &get() const noexcept { 56 | return value; 57 | } 58 | 59 | const auto *operator->() const noexcept { 60 | return &value; 61 | } 62 | 63 | #ifdef __clang__ 64 | /* the non-const overload is only needed for clang, because in 65 | libc++11, some methods are not "const" */ 66 | auto *operator->() noexcept { 67 | return &value; 68 | } 69 | #endif 70 | 71 | [[nodiscard]] 72 | auto release() noexcept { 73 | return std::exchange(value, nullptr); 74 | } 75 | }; 76 | 77 | } // namespace Co 78 | -------------------------------------------------------------------------------- /src/Queue.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "Queue.hxx" 5 | #include "util/StringAPI.hxx" 6 | 7 | #include 8 | 9 | void 10 | MpdQueue::clear() noexcept 11 | { 12 | version = 0; 13 | items.clear(); 14 | } 15 | 16 | const struct mpd_song * 17 | MpdQueue::GetChecked(int idx) const noexcept 18 | { 19 | if (idx < 0 || (size_type)idx >= size()) 20 | return nullptr; 21 | 22 | return &(*this)[idx]; 23 | } 24 | 25 | void 26 | MpdQueue::Move(unsigned dest, unsigned src) noexcept 27 | { 28 | assert(src < size()); 29 | assert(dest < size()); 30 | assert(src != dest); 31 | 32 | auto song = std::move(items[src]); 33 | 34 | if (src < dest) { 35 | std::move(std::next(items.begin(), src + 1), 36 | std::next(items.begin(), dest + 1), 37 | std::next(items.begin(), src)); 38 | } else { 39 | std::move_backward(std::next(items.begin(), dest), 40 | std::next(items.begin(), src), 41 | std::next(items.begin(), src + 1)); 42 | } 43 | 44 | assert(!items[dest]); 45 | items[dest] = std::move(song); 46 | } 47 | 48 | MpdQueue::size_type 49 | MpdQueue::FindByReference(const struct mpd_song &song) const noexcept 50 | { 51 | for (size_type i = 0;; ++i) { 52 | assert(i < size()); 53 | 54 | if (&(*this)[i] == &song) 55 | return i; 56 | } 57 | } 58 | 59 | int 60 | MpdQueue::FindById(unsigned id) const noexcept 61 | { 62 | for (size_type i = 0; i < size(); ++i) { 63 | const auto &song = (*this)[i]; 64 | if (mpd_song_get_id(&song) == id) 65 | return i; 66 | } 67 | 68 | return -1; 69 | } 70 | 71 | int 72 | MpdQueue::FindByUri(const char *filename) const noexcept 73 | { 74 | for (size_type i = 0; i < size(); ++i) { 75 | const auto &song = (*this)[i]; 76 | if (StringIsEqual(mpd_song_get_uri(&song), filename)) 77 | return i; 78 | } 79 | 80 | return -1; 81 | } 82 | -------------------------------------------------------------------------------- /src/ui/ListWindow.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "ListCursor.hxx" 7 | #include "Size.hxx" 8 | #include "Window.hxx" 9 | #include "config.h" 10 | 11 | enum class Command : unsigned; 12 | class ListText; 13 | class ListRenderer; 14 | 15 | class ListWindow : public ListCursor { 16 | const Window window; 17 | 18 | unsigned width; 19 | 20 | public: 21 | ListWindow(Window _window, Size _size) noexcept 22 | :ListCursor(_size.height), window(_window), width(_size.width) {} 23 | 24 | unsigned GetWidth() const noexcept { 25 | return width; 26 | } 27 | 28 | void Resize(Size _size) noexcept { 29 | SetHeight(_size.height); 30 | width = _size.width; 31 | } 32 | 33 | void Refresh() const noexcept { 34 | window.Refresh(); 35 | } 36 | 37 | void Paint(const ListRenderer &renderer) const noexcept; 38 | 39 | /** perform basic list window commands (movement) */ 40 | bool HandleCommand(Command cmd) noexcept; 41 | 42 | /** 43 | * Scroll the window. Returns true if the command has been 44 | * consumed. 45 | */ 46 | bool HandleScrollCommand(Command cmd) noexcept; 47 | 48 | #ifdef HAVE_GETMOUSE 49 | /** 50 | * The mouse was clicked. Check if the list should be scrolled 51 | * Returns non-zero if the mouse event has been handled. 52 | */ 53 | bool HandleMouse(mmask_t bstate, int y) noexcept; 54 | #endif 55 | 56 | /** 57 | * Find a string in a list window. 58 | */ 59 | bool Find(const ListText &text, 60 | std::string_view str) noexcept; 61 | 62 | /** 63 | * Find a string in a list window (reversed). 64 | */ 65 | bool ReverseFind(const ListText &text, 66 | std::string_view str) noexcept; 67 | 68 | /** 69 | * Find a string in a list window which begins with the given 70 | * characters in *str. 71 | */ 72 | bool Jump(const ListText &text, std::string_view str) noexcept; 73 | }; 74 | -------------------------------------------------------------------------------- /src/util/ReturnValue.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /** 13 | * Stores the return value of a function. It does not keep track of 14 | * whether a value has been set already. 15 | */ 16 | template 17 | class ReturnValue { 18 | std::optional value; 19 | 20 | public: 21 | /** 22 | * Set the value. May be called at most once. 23 | */ 24 | template 25 | void Set(U &&_value) noexcept { 26 | assert(!value); 27 | 28 | value.emplace(std::forward(_value)); 29 | } 30 | 31 | /** 32 | * Get (and consume) the value. May be called at most once, 33 | * but only if Set() has been called. 34 | */ 35 | [[nodiscard]] 36 | decltype(auto) Get() && noexcept { 37 | assert(value); 38 | 39 | return std::move(*value); 40 | } 41 | }; 42 | 43 | /** 44 | * Specialization for certain types to eliminate the std::optional 45 | * overhead. 46 | */ 47 | template 48 | requires std::default_initializable && std::movable && std::destructible 49 | class ReturnValue { 50 | T value; 51 | 52 | public: 53 | template 54 | void Set(U &&_value) noexcept { 55 | value = std::forward(_value); 56 | } 57 | 58 | [[nodiscard]] 59 | T &&Get() && noexcept { 60 | return std::move(value); 61 | } 62 | }; 63 | 64 | /** 65 | * This specialization supports returning references. 66 | */ 67 | template 68 | class ReturnValue { 69 | T *value; 70 | 71 | public: 72 | void Set(T &_value) noexcept { 73 | value = &_value; 74 | } 75 | 76 | [[nodiscard]] 77 | T &Get() && noexcept { 78 | return *value; 79 | } 80 | }; 81 | 82 | template<> 83 | class ReturnValue { 84 | public: 85 | void Set() noexcept {} 86 | void Get() && noexcept {} 87 | }; 88 | -------------------------------------------------------------------------------- /src/event/DeferEvent.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "util/BindMethod.hxx" 7 | #include "util/IntrusiveList.hxx" 8 | 9 | class EventLoop; 10 | 11 | /** 12 | * Defer execution until the next event loop iteration. Use this to 13 | * move calls out of the current stack frame, to avoid surprising side 14 | * effects for callers up in the call chain. 15 | * 16 | * This class is not thread-safe, all methods must be called from the 17 | * thread that runs the #EventLoop. 18 | */ 19 | class DeferEvent final : AutoUnlinkIntrusiveListHook 20 | { 21 | friend class EventLoop; 22 | friend struct IntrusiveListBaseHookTraits; 23 | 24 | EventLoop &loop; 25 | 26 | using Callback = BoundMethod; 27 | const Callback callback; 28 | 29 | public: 30 | DeferEvent(EventLoop &_loop, Callback _callback) noexcept 31 | :loop(_loop), callback(_callback) {} 32 | 33 | DeferEvent(const DeferEvent &) = delete; 34 | DeferEvent &operator=(const DeferEvent &) = delete; 35 | 36 | auto &GetEventLoop() const noexcept { 37 | return loop; 38 | } 39 | 40 | bool IsPending() const noexcept { 41 | return is_linked(); 42 | } 43 | 44 | void Schedule() noexcept; 45 | 46 | /** 47 | * Schedule this event, but only after the #EventLoop is idle, 48 | * i.e. before going to sleep. 49 | */ 50 | void ScheduleIdle() noexcept; 51 | 52 | /** 53 | * Schedule this event, but only after the next #EventLoop 54 | * iteration (i.e. after the epoll_wait() call, after handling 55 | * all pending I/O events). This is useful for repeated I/O 56 | * operations that should not occupy the whole #EventLoop, 57 | * starving all other I/O events. 58 | */ 59 | void ScheduleNext() noexcept; 60 | 61 | void Cancel() noexcept { 62 | if (IsPending()) 63 | unlink(); 64 | } 65 | 66 | private: 67 | void Run() noexcept { 68 | callback(); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/TableLayout.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "TableLayout.hxx" 5 | #include "TableStructure.hxx" 6 | 7 | #include // for std::fill() 8 | 9 | #include 10 | 11 | void 12 | TableLayout::Calculate(unsigned screen_width) noexcept 13 | { 14 | if (structure.columns.empty()) 15 | /* shouldn't happen */ 16 | return; 17 | 18 | std::fill(columns.begin(), columns.end(), TableColumnLayout{}); 19 | 20 | if (screen_width <= structure.columns.front().min_width) { 21 | /* very narrow window, there's only space for one 22 | column */ 23 | columns.front().width = screen_width; 24 | return; 25 | } 26 | 27 | /* check how many columns fit on the screen */ 28 | 29 | unsigned remaining_width = screen_width - structure.columns.front().min_width - 1; 30 | size_t n_visible = 1; 31 | float fraction_sum = structure.columns.front().fraction_width; 32 | 33 | for (size_t i = 1; i < structure.columns.size(); ++i) { 34 | auto &c = structure.columns[i]; 35 | 36 | if (remaining_width < c.min_width) 37 | /* this column doesn't fit, stop here */ 38 | break; 39 | 40 | ++n_visible; 41 | fraction_sum += c.fraction_width; 42 | remaining_width -= c.min_width; 43 | 44 | if (remaining_width == 0) 45 | /* no room for the vertical line */ 46 | break; 47 | 48 | /* subtract the vertical line */ 49 | --remaining_width; 50 | } 51 | 52 | /* distribute the remaining width */ 53 | 54 | for (size_t i = n_visible; i > 0;) { 55 | auto &c = structure.columns[--i]; 56 | 57 | unsigned width = c.min_width; 58 | if (fraction_sum > 0 && c.fraction_width > 0) { 59 | unsigned add = lrint(remaining_width * c.fraction_width 60 | / fraction_sum); 61 | if (add > remaining_width) 62 | add = remaining_width; 63 | 64 | width += add; 65 | remaining_width -= add; 66 | fraction_sum -= c.fraction_width; 67 | } 68 | 69 | columns[i].width = width; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/dialogs/KeyDialog.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "ModalDialog.hxx" 7 | #include "co/AwaitableHelper.hxx" 8 | 9 | #include 10 | #include 11 | 12 | /** 13 | * A #ModalDialog that asks the user to press a key. 14 | * 15 | * This dialog is supposed to be awaited from a coroutine using 16 | * co_await. It suspends the caller while waiting for user input. 17 | */ 18 | class KeyDialog final : public ModalDialog { 19 | const std::string_view prompt; 20 | 21 | std::coroutine_handle<> continuation; 22 | 23 | int result = -2; 24 | 25 | using Awaitable = Co::AwaitableHelper; 26 | friend Awaitable; 27 | 28 | public: 29 | /** 30 | * @param _prompt the human-readable prompt to be displayed 31 | * (including question mark if desired); the pointed-by memory 32 | * is owned by the caller and must remain valid during the 33 | * lifetime of this dialog 34 | */ 35 | KeyDialog(ModalDock &_dock, std::string_view _prompt) noexcept 36 | :ModalDialog(_dock), prompt(_prompt) 37 | { 38 | Show(); 39 | } 40 | 41 | ~KeyDialog() noexcept { 42 | Hide(); 43 | } 44 | 45 | /** 46 | * Await completion of this dialog. 47 | * 48 | * @return an ncurses key code or -1 if the dialog was 49 | * canceled 50 | */ 51 | Awaitable operator co_await() noexcept { 52 | return *this; 53 | } 54 | 55 | private: 56 | void SetResult(int _result) noexcept { 57 | result = _result; 58 | 59 | if (continuation) 60 | continuation.resume(); 61 | } 62 | 63 | bool IsReady() const noexcept { 64 | return result != -2; 65 | } 66 | 67 | int TakeValue() noexcept { 68 | return result; 69 | } 70 | 71 | public: 72 | /* virtual methodds from Modal */ 73 | void OnLeave(Window window) noexcept override; 74 | void OnCancel() noexcept override; 75 | void Paint(Window window) const noexcept override; 76 | bool OnKey(Window window, int key) override; 77 | }; 78 | -------------------------------------------------------------------------------- /src/util/StringStrip.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #include "StringStrip.hxx" 5 | #include "CharUtil.hxx" 6 | 7 | #include 8 | #include 9 | 10 | const char * 11 | StripLeft(const char *p) noexcept 12 | { 13 | while (IsWhitespaceNotNull(*p)) 14 | ++p; 15 | 16 | return p; 17 | } 18 | 19 | const char * 20 | StripLeft(const char *p, const char *end) noexcept 21 | { 22 | while (p < end && IsWhitespaceOrNull(*p)) 23 | ++p; 24 | 25 | return p; 26 | } 27 | 28 | std::string_view 29 | StripLeft(const std::string_view s) noexcept 30 | { 31 | auto i = std::find_if_not(s.begin(), s.end(), 32 | [](auto ch){ return IsWhitespaceOrNull(ch); }); 33 | 34 | #ifdef __clang__ 35 | // libc++ doesn't yet support the C++20 constructor 36 | return s.substr(std::distance(s.begin(), i)); 37 | #else 38 | return { 39 | i, 40 | s.end(), 41 | }; 42 | #endif 43 | } 44 | 45 | const char * 46 | StripRight(const char *p, const char *end) noexcept 47 | { 48 | while (end > p && IsWhitespaceOrNull(end[-1])) 49 | --end; 50 | 51 | return end; 52 | } 53 | 54 | std::size_t 55 | StripRight(const char *p, std::size_t length) noexcept 56 | { 57 | while (length > 0 && IsWhitespaceOrNull(p[length - 1])) 58 | --length; 59 | 60 | return length; 61 | } 62 | 63 | void 64 | StripRight(char *p) noexcept 65 | { 66 | std::size_t old_length = std::strlen(p); 67 | std::size_t new_length = StripRight(p, old_length); 68 | p[new_length] = 0; 69 | } 70 | 71 | std::string_view 72 | StripRight(std::string_view s) noexcept 73 | { 74 | auto i = std::find_if_not(s.rbegin(), s.rend(), 75 | [](auto ch){ return IsWhitespaceOrNull(ch); }); 76 | 77 | return s.substr(0, std::distance(i, s.rend())); 78 | } 79 | 80 | char * 81 | Strip(char *p) noexcept 82 | { 83 | p = StripLeft(p); 84 | StripRight(p); 85 | return p; 86 | } 87 | 88 | std::string_view 89 | Strip(std::string_view s) noexcept 90 | { 91 | return StripRight(StripLeft(s)); 92 | } 93 | -------------------------------------------------------------------------------- /src/event/net/ConnectSocket.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include "event/SocketEvent.hxx" 8 | #include "event/CoarseTimerEvent.hxx" 9 | #include "util/Cancellable.hxx" 10 | 11 | #include 12 | 13 | class UniqueSocketDescriptor; 14 | class SocketAddress; 15 | class AddressInfo; 16 | 17 | class ConnectSocketHandler { 18 | public: 19 | virtual void OnSocketConnectSuccess(UniqueSocketDescriptor fd) noexcept = 0; 20 | virtual void OnSocketConnectTimeout() noexcept; 21 | virtual void OnSocketConnectError(std::exception_ptr ep) noexcept = 0; 22 | }; 23 | 24 | /** 25 | * A class that connects to a SocketAddress. 26 | */ 27 | class ConnectSocket final : public Cancellable { 28 | ConnectSocketHandler &handler; 29 | 30 | SocketEvent event; 31 | CoarseTimerEvent timeout_event; 32 | 33 | public: 34 | ConnectSocket(EventLoop &_event_loop, 35 | ConnectSocketHandler &_handler) noexcept; 36 | ~ConnectSocket() noexcept; 37 | 38 | auto &GetEventLoop() const noexcept { 39 | return event.GetEventLoop(); 40 | } 41 | 42 | bool IsPending() const noexcept { 43 | return event.IsDefined(); 44 | } 45 | 46 | /** 47 | * @param timeout a timeout or a negative value to disable 48 | * timeouts 49 | */ 50 | bool Connect(const SocketAddress address, 51 | Event::Duration timeout) noexcept; 52 | 53 | bool Connect(const AddressInfo &address, 54 | Event::Duration timeout) noexcept; 55 | 56 | /** 57 | * Wait until the given socket is connected (this method returns 58 | * immediately and invokes the #ConnectSocketHandler on completion 59 | * or error). 60 | */ 61 | void WaitConnected(UniqueSocketDescriptor _fd, 62 | Event::Duration timeout=Event::Duration(-1)) noexcept; 63 | 64 | /* virtual methods from Cancellable */ 65 | void Cancel() noexcept override; 66 | 67 | private: 68 | void OnEvent(unsigned events) noexcept; 69 | void OnTimeout() noexcept; 70 | }; 71 | -------------------------------------------------------------------------------- /src/net/MsgHdr.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright CM4all GmbH 3 | // author: Max Kellermann 4 | 5 | #pragma once 6 | 7 | #include "SocketAddress.hxx" 8 | #include "StaticSocketAddress.hxx" 9 | 10 | #include 11 | 12 | #include 13 | 14 | inline constexpr struct msghdr 15 | MakeMsgHdr(std::span iov) noexcept 16 | { 17 | struct msghdr mh{}; 18 | mh.msg_iov = const_cast(iov.data()); 19 | mh.msg_iovlen = iov.size(); 20 | return mh; 21 | } 22 | 23 | /** 24 | * Construct a struct msghdr. The parameters are `const` because that 25 | * is needed for sending; but for receiving, these buffers must 26 | * actually be writable. 27 | */ 28 | inline constexpr struct msghdr 29 | MakeMsgHdr(SocketAddress name, std::span iov, 30 | std::span control) noexcept 31 | { 32 | auto mh = MakeMsgHdr(iov); 33 | mh.msg_name = const_cast(name.GetAddress()); 34 | mh.msg_namelen = name.GetSize(); 35 | mh.msg_control = const_cast(control.data()); 36 | mh.msg_controllen = control.size(); 37 | return mh; 38 | } 39 | 40 | inline constexpr struct msghdr 41 | MakeMsgHdr(StaticSocketAddress &name, std::span iov, 42 | std::span control) noexcept 43 | { 44 | auto mh = MakeMsgHdr(iov); 45 | mh.msg_name = name; 46 | mh.msg_namelen = name.GetCapacity(); 47 | mh.msg_control = const_cast(control.data()); 48 | mh.msg_controllen = control.size(); 49 | return mh; 50 | } 51 | 52 | inline constexpr struct msghdr 53 | MakeMsgHdr(struct sockaddr_storage &name, std::span iov, 54 | std::span control) noexcept 55 | { 56 | auto mh = MakeMsgHdr(iov); 57 | mh.msg_name = static_cast(static_cast(&name)); 58 | mh.msg_namelen = sizeof(name); 59 | mh.msg_control = const_cast(control.data()); 60 | mh.msg_controllen = control.size(); 61 | return mh; 62 | } 63 | -------------------------------------------------------------------------------- /src/client/gidle.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "event/SocketEvent.hxx" 7 | 8 | #include 9 | 10 | class MpdIdleHandler { 11 | public: 12 | virtual void OnIdle(unsigned events) noexcept = 0; 13 | virtual void OnIdleError(enum mpd_error error, 14 | enum mpd_server_error server_error, 15 | const char *message) noexcept = 0; 16 | }; 17 | 18 | class MpdIdleSource final { 19 | struct mpd_connection *connection; 20 | struct mpd_async *async; 21 | struct mpd_parser *parser; 22 | 23 | SocketEvent event; 24 | 25 | MpdIdleHandler &handler; 26 | 27 | unsigned io_events = 0; 28 | 29 | unsigned idle_events; 30 | 31 | public: 32 | MpdIdleSource(EventLoop &event_loop, 33 | struct mpd_connection &_connection, 34 | MpdIdleHandler &_handler) noexcept; 35 | ~MpdIdleSource() noexcept; 36 | 37 | /** 38 | * Enters idle mode. 39 | * 40 | * @return true if idle mode has been entered, false if not 41 | * (e.g. I/O error) 42 | */ 43 | bool Enter() noexcept; 44 | 45 | /** 46 | * Leaves idle mode and invokes the callback if there were events. 47 | */ 48 | void Leave() noexcept; 49 | 50 | private: 51 | void InvokeCallback() noexcept { 52 | if (idle_events != 0) 53 | handler.OnIdle(idle_events); 54 | } 55 | 56 | void InvokeError(enum mpd_error error, 57 | enum mpd_server_error server_error, 58 | const char *message) noexcept { 59 | handler.OnIdleError(error, server_error, message); 60 | } 61 | 62 | void InvokeAsyncError() noexcept; 63 | 64 | /** 65 | * Parses a response line from MPD. 66 | * 67 | * @return true on success, false on error 68 | */ 69 | bool Feed(char *line) noexcept; 70 | 71 | /** 72 | * Receives and evaluates a portion of the MPD response. 73 | * 74 | * @return true on success, false on error 75 | */ 76 | bool Receive() noexcept; 77 | 78 | void OnSocketReady(unsigned flags) noexcept; 79 | 80 | void UpdateSocket() noexcept; 81 | }; 82 | -------------------------------------------------------------------------------- /src/util/ScopeExit.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * Internal class. Do not use directly. 10 | */ 11 | template 12 | class ScopeExitGuard { 13 | [[no_unique_address]] 14 | F function; 15 | 16 | bool enabled = true; 17 | 18 | public: 19 | explicit ScopeExitGuard(F &&f) noexcept 20 | :function(std::forward(f)) {} 21 | 22 | ScopeExitGuard(ScopeExitGuard &&src) noexcept 23 | :function(std::move(src.function)), 24 | enabled(std::exchange(src.enabled, false)) {} 25 | 26 | /* destructors are "noexcept" by default; this explicit 27 | "noexcept" declaration allows the destructor to throw if 28 | the function can throw; without this, a throwing function 29 | would std::terminate() */ 30 | ~ScopeExitGuard() noexcept(noexcept(std::declval()())) { 31 | if (enabled) 32 | function(); 33 | } 34 | 35 | ScopeExitGuard(const ScopeExitGuard &) = delete; 36 | ScopeExitGuard &operator=(const ScopeExitGuard &) = delete; 37 | }; 38 | 39 | /** 40 | * Internal class. Do not use directly. 41 | */ 42 | struct ScopeExitTag { 43 | /* this operator is a trick so we don't need to close 44 | parantheses at the end of the expression AtScopeExit() 45 | call */ 46 | template 47 | ScopeExitGuard operator+(F &&f) noexcept { 48 | return ScopeExitGuard(std::forward(f)); 49 | } 50 | }; 51 | 52 | #define ScopeExitCat(a, b) a ## b 53 | #define ScopeExitName(line) ScopeExitCat(at_scope_exit_, line) 54 | 55 | /** 56 | * Call the block after this macro at the end of the current scope. 57 | * Parameters are lambda captures. 58 | * 59 | * This is exception-safe, however the given code block must not throw 60 | * exceptions. 61 | * 62 | * This attempts to be a better boost/scope_exit.hpp, without all of 63 | * Boost's compile-time and runtime bloat. 64 | */ 65 | #define AtScopeExit(...) auto ScopeExitName(__LINE__) = ScopeExitTag() + [__VA_ARGS__]() 66 | -------------------------------------------------------------------------------- /src/net/PeerCredentials.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "net/Features.hxx" // for HAVE_STRUCT_UCRED 7 | 8 | #include // for std::is_trivial_v 9 | 10 | #ifdef HAVE_STRUCT_UCRED 11 | #include // for struct ucred 12 | #endif 13 | 14 | /** 15 | * Portable wrapper for credentials of the process on the other side 16 | * of a (local) socket. 17 | */ 18 | class SocketPeerCredentials { 19 | friend class SocketDescriptor; 20 | 21 | #ifdef HAVE_STRUCT_UCRED 22 | struct ucred cred; 23 | #elif defined(HAVE_GETPEEREID) 24 | uid_t uid; 25 | gid_t gid; 26 | #endif 27 | 28 | public: 29 | constexpr SocketPeerCredentials() noexcept = default; 30 | 31 | static constexpr SocketPeerCredentials Undefined() noexcept { 32 | SocketPeerCredentials c; 33 | #ifdef HAVE_STRUCT_UCRED 34 | c.cred.pid = 0; 35 | c.cred.uid = -1; 36 | c.cred.gid = -1; 37 | #elif defined(HAVE_GETPEEREID) 38 | c.uid = static_cast(-1); 39 | c.gid = static_cast(-1); 40 | #endif 41 | return c; 42 | } 43 | 44 | constexpr bool IsDefined() const noexcept { 45 | #ifdef HAVE_STRUCT_UCRED 46 | return cred.pid > 0; 47 | #elif defined(HAVE_GETPEEREID) 48 | return uid != static_cast(-1) || 49 | gid != static_cast(-1); 50 | #else 51 | return false; 52 | #endif 53 | } 54 | 55 | constexpr auto GetPid() const noexcept { 56 | #ifdef HAVE_STRUCT_UCRED 57 | return cred.pid; 58 | #else 59 | return 0; 60 | #endif 61 | } 62 | 63 | constexpr auto GetUid() const noexcept { 64 | #ifdef HAVE_STRUCT_UCRED 65 | return cred.uid; 66 | #elif defined(HAVE_GETPEEREID) 67 | return uid; 68 | #else 69 | return -1; 70 | #endif 71 | } 72 | 73 | constexpr auto GetGid() const noexcept { 74 | #ifdef HAVE_STRUCT_UCRED 75 | return cred.gid; 76 | #elif defined(HAVE_GETPEEREID) 77 | return gid; 78 | #else 79 | return -1; 80 | #endif 81 | } 82 | }; 83 | 84 | static_assert(std::is_trivial_v); 85 | -------------------------------------------------------------------------------- /src/screen_list.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "screen_list.hxx" 5 | #include "PageMeta.hxx" 6 | #include "HelpPage.hxx" 7 | #include "QueuePage.hxx" 8 | #include "FileBrowserPage.hxx" 9 | #include "LibraryPage.hxx" 10 | #include "SearchPage.hxx" 11 | #include "SongPage.hxx" 12 | #include "KeyDefPage.hxx" 13 | #include "EditPlaylistPage.hxx" 14 | #include "LyricsPage.hxx" 15 | #include "OutputsPage.hxx" 16 | #include "ChatPage.hxx" 17 | #include "config.h" 18 | #include "util/StringAPI.hxx" 19 | 20 | #include 21 | 22 | #include 23 | 24 | static const PageMeta *const screens[] = { 25 | #ifdef ENABLE_HELP_SCREEN 26 | &screen_help, 27 | #endif 28 | &screen_queue, 29 | &screen_browse, 30 | #ifdef ENABLE_LIBRARY_PAGE 31 | &library_page, 32 | #endif 33 | #ifdef ENABLE_SEARCH_SCREEN 34 | &screen_search, 35 | #endif 36 | #ifdef ENABLE_LYRICS_SCREEN 37 | &screen_lyrics, 38 | #endif 39 | #ifdef ENABLE_OUTPUTS_SCREEN 40 | &screen_outputs, 41 | #endif 42 | #ifdef ENABLE_CHAT_SCREEN 43 | &screen_chat, 44 | #endif 45 | #ifdef ENABLE_SONG_SCREEN 46 | &screen_song, 47 | #endif 48 | #ifdef ENABLE_KEYDEF_SCREEN 49 | &screen_keydef, 50 | #endif 51 | #ifdef ENABLE_PLAYLIST_EDITOR 52 | &edit_playlist_page, 53 | #endif 54 | }; 55 | 56 | const PageMeta * 57 | GetPageMeta(unsigned i) noexcept 58 | { 59 | return i < std::size(screens) 60 | ? screens[i] 61 | : nullptr; 62 | } 63 | 64 | const PageMeta * 65 | screen_lookup_name(const char *name) noexcept 66 | { 67 | for (const auto *i : screens) 68 | if (StringIsEqual(name, i->name)) 69 | return i; 70 | 71 | #ifdef ENABLE_LIBRARY_PAGE 72 | /* compatibility with 0.32 and older */ 73 | if (StringIsEqual(name, "artist")) 74 | return &library_page; 75 | #endif 76 | 77 | return nullptr; 78 | } 79 | 80 | const PageMeta * 81 | PageByCommand(Command cmd) noexcept 82 | { 83 | for (const auto *i : screens) 84 | if (i->command == cmd) 85 | return i; 86 | 87 | return nullptr; 88 | } 89 | -------------------------------------------------------------------------------- /src/plugin.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #ifndef PLUGIN_H 5 | #define PLUGIN_H 6 | 7 | #include 8 | #include 9 | 10 | class EventLoop; 11 | 12 | /** 13 | * When a plugin cycle is finished, one method of this class is called. In any 14 | * case, plugin_stop() has to be called to free all memory. 15 | */ 16 | class PluginResponseHandler { 17 | public: 18 | /** 19 | * @param plugin_name the name of the plugin which succeeded 20 | * @param result the plugin's output (stdout) 21 | */ 22 | virtual void OnPluginSuccess(const char *plugin_name, 23 | std::string result) noexcept = 0; 24 | 25 | virtual void OnPluginError(std::string error) noexcept = 0; 26 | }; 27 | 28 | /** 29 | * A list of registered plugins. 30 | */ 31 | struct PluginList { 32 | std::vector plugins; 33 | }; 34 | 35 | /** 36 | * This object represents a cycle through all available plugins, until 37 | * a plugin returns a positive result. 38 | */ 39 | struct PluginCycle; 40 | 41 | /** 42 | * Load all plugins (executables) in a directory. 43 | */ 44 | PluginList 45 | plugin_list_load_directory(const char *path) noexcept; 46 | 47 | /** 48 | * Run plugins in this list, until one returns success (or until the 49 | * plugin list is exhausted). 50 | * 51 | * @param list the plugin list 52 | * @param args nullptr terminated command line arguments passed to the 53 | * plugin programs; they must remain valid while the plugin runs 54 | * @param handler the handler which will be called when a result is 55 | * available 56 | */ 57 | PluginCycle * 58 | plugin_run(EventLoop &event_loop, 59 | const PluginList &list, const char *const*args, 60 | PluginResponseHandler &handler) noexcept; 61 | 62 | /** 63 | * Stops the plugin cycle and frees resources. This can be called to 64 | * abort the current cycle, or after the plugin_callback_t has been 65 | * invoked. 66 | */ 67 | void 68 | plugin_stop(PluginCycle &invocation) noexcept; 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/time_format.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "time_format.hxx" 5 | #include "i18n.h" 6 | #include "lib/fmt/ToSpan.hxx" 7 | 8 | #include 9 | 10 | using std::string_view_literals::operator""sv; 11 | 12 | std::string_view 13 | format_duration_short(std::span buffer, unsigned duration) noexcept 14 | { 15 | if (duration < 3600) 16 | return FmtTruncate(buffer, "{}:{:02}"sv, duration / 60, duration % 60); 17 | else 18 | return FmtTruncate(buffer, "{}:{:02}:{:02}"sv, 19 | duration / 3600, 20 | (duration % 3600) / 60, 21 | duration % 60); 22 | } 23 | 24 | void 25 | format_duration_long(std::span buffer, unsigned long duration) noexcept 26 | { 27 | unsigned bytes_written = 0; 28 | 29 | if (duration / 31536000 > 0) { 30 | if (duration / 31536000 == 1) 31 | bytes_written = snprintf(buffer.data(), buffer.size(), "%d %s, ", 1, _("year")); 32 | else 33 | bytes_written = snprintf(buffer.data(), buffer.size(), "%lu %s, ", duration / 31536000, _("years")); 34 | duration %= 31536000; 35 | buffer = buffer.subspan(bytes_written); 36 | } 37 | if (duration / 604800 > 0) { 38 | if (duration / 604800 == 1) 39 | bytes_written = snprintf(buffer.data(), buffer.size(), "%d %s, ", 40 | 1, _("week")); 41 | else 42 | bytes_written = snprintf(buffer.data(), buffer.size(), "%lu %s, ", 43 | duration / 604800, _("weeks")); 44 | duration %= 604800; 45 | buffer = buffer.subspan(bytes_written); 46 | } 47 | if (duration / 86400 > 0) { 48 | if (duration / 86400 == 1) 49 | bytes_written = snprintf(buffer.data(), buffer.size(), "%d %s, ", 50 | 1, _("day")); 51 | else 52 | bytes_written = snprintf(buffer.data(), buffer.size(), "%lu %s, ", 53 | duration / 86400, _("days")); 54 | duration %= 86400; 55 | buffer = buffer.subspan(bytes_written); 56 | } 57 | 58 | snprintf(buffer.data(), buffer.size(), "%02lu:%02lu:%02lu", duration / 3600, 59 | duration % 3600 / 60, duration % 3600 % 60); 60 | } 61 | -------------------------------------------------------------------------------- /src/dialogs/YesNoDialog.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "YesNoDialog.hxx" 5 | #include "ui/Bell.hxx" 6 | #include "ui/Keys.hxx" 7 | #include "ui/Options.hxx" 8 | #include "ui/Window.hxx" 9 | #include "Styles.hxx" 10 | #include "i18n.h" 11 | 12 | #include 13 | 14 | using std::string_view_literals::operator""sv; 15 | 16 | void 17 | YesNoDialog::OnLeave(const Window window) noexcept 18 | { 19 | noecho(); 20 | curs_set(0); 21 | 22 | if (ui_options.enable_colors) 23 | window.SetBackgroundStyle(Style::STATUS); 24 | } 25 | 26 | void 27 | YesNoDialog::OnCancel() noexcept 28 | { 29 | SetResult(YesNoResult::CANCEL); 30 | } 31 | 32 | void 33 | YesNoDialog::Paint(const Window window) const noexcept 34 | { 35 | if (ui_options.enable_colors) 36 | window.SetBackgroundStyle(Style::INPUT); 37 | 38 | SelectStyle(window, Style::STATUS_ALERT); 39 | window.String({0, 0}, prompt); 40 | window.String(" ["sv); 41 | window.String(YES_TRANSLATION); 42 | window.Char('/'); 43 | window.String(NO_TRANSLATION); 44 | window.String("] "sv); 45 | 46 | SelectStyle(window, Style::INPUT); 47 | window.ClearToEol(); 48 | 49 | echo(); 50 | curs_set(1); 51 | } 52 | 53 | static constexpr bool 54 | IsCancelKey(int key) noexcept 55 | { 56 | return key == KEY_CANCEL || key == KEY_SCANCEL || 57 | key == KEY_CLOSE || 58 | key == KEY_UNDO || 59 | key == KEY_CTL('C') || 60 | key == KEY_CTL('G') || 61 | key == KEY_ESCAPE; 62 | } 63 | 64 | bool 65 | YesNoDialog::OnKey(Window, int key) 66 | { 67 | /* NOTE: if one day a translator decides to use a multi-byte character 68 | for one of the yes/no keys, we'll have to parse it properly */ 69 | 70 | key = tolower(key); 71 | if (key == YES_TRANSLATION[0]) { 72 | Hide(); 73 | SetResult(YesNoResult::YES); 74 | } else if (key == NO_TRANSLATION[0]) { 75 | Hide(); 76 | SetResult(YesNoResult::NO); 77 | } else if (IsCancelKey(key)) { 78 | Cancel(); 79 | } else { 80 | Bell(); 81 | } 82 | 83 | return true; 84 | } 85 | -------------------------------------------------------------------------------- /src/io/UniqueFileDescriptor.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include "FileDescriptor.hxx" // IWYU pragma: export 7 | #include "util/TagStructs.hxx" 8 | 9 | #include 10 | #include 11 | 12 | /** 13 | * An OO wrapper for a UNIX file descriptor. 14 | */ 15 | class UniqueFileDescriptor : public FileDescriptor { 16 | public: 17 | UniqueFileDescriptor() noexcept 18 | :FileDescriptor(FileDescriptor::Undefined()) {} 19 | 20 | explicit UniqueFileDescriptor(AdoptTag, int _fd) noexcept 21 | :FileDescriptor(_fd) {} 22 | 23 | explicit UniqueFileDescriptor(AdoptTag, FileDescriptor _fd) noexcept 24 | :FileDescriptor(_fd) {} 25 | 26 | UniqueFileDescriptor(const UniqueFileDescriptor &) = delete; 27 | 28 | UniqueFileDescriptor(UniqueFileDescriptor &&other) noexcept 29 | :FileDescriptor(other.Steal()) {} 30 | 31 | ~UniqueFileDescriptor() noexcept { 32 | Close(); 33 | } 34 | 35 | UniqueFileDescriptor &operator=(UniqueFileDescriptor &&other) noexcept { 36 | using std::swap; 37 | swap(fd, other.fd); 38 | return *this; 39 | } 40 | 41 | /** 42 | * Release ownership and return the descriptor as an unmanaged 43 | * #FileDescriptor instance. 44 | */ 45 | FileDescriptor Release() noexcept { 46 | return std::exchange(*(FileDescriptor *)this, Undefined()); 47 | } 48 | 49 | protected: 50 | void Set(int _fd) noexcept { 51 | assert(!IsDefined()); 52 | assert(_fd >= 0); 53 | 54 | FileDescriptor::Set(_fd); 55 | } 56 | 57 | public: 58 | #ifndef _WIN32 59 | static bool CreatePipe(UniqueFileDescriptor &r, UniqueFileDescriptor &w) noexcept { 60 | return FileDescriptor::CreatePipe(r, w); 61 | } 62 | 63 | static bool CreatePipeNonBlock(UniqueFileDescriptor &r, 64 | UniqueFileDescriptor &w) noexcept { 65 | return FileDescriptor::CreatePipeNonBlock(r, w); 66 | } 67 | 68 | static bool CreatePipe(FileDescriptor &r, FileDescriptor &w) noexcept; 69 | #endif 70 | 71 | bool Close() noexcept { 72 | return IsDefined() && FileDescriptor::Close(); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /src/dialogs/YesNoDialog.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "ModalDialog.hxx" 7 | #include "co/AwaitableHelper.hxx" 8 | 9 | #include 10 | #include 11 | 12 | enum class YesNoResult : int8_t { 13 | CANCEL = -1, 14 | NO = false, 15 | YES = true, 16 | }; 17 | 18 | /** 19 | * A #ModalDialog that asks the user a yes/no question. 20 | * 21 | * This dialog is supposed to be awaited from a coroutine using 22 | * co_await. It suspends the caller while waiting for user input. 23 | */ 24 | class YesNoDialog final : public ModalDialog { 25 | const std::string_view prompt; 26 | 27 | std::coroutine_handle<> continuation; 28 | 29 | bool ready = false; 30 | YesNoResult result; 31 | 32 | using Awaitable = Co::AwaitableHelper; 33 | friend Awaitable; 34 | 35 | public: 36 | /** 37 | * @param _prompt the human-readable prompt to be displayed 38 | * (including question mark if desired); the pointed-by memory 39 | * is owned by the caller and must remain valid during the 40 | * lifetime of this dialog 41 | */ 42 | YesNoDialog(ModalDock &_dock, std::string_view _prompt) noexcept 43 | :ModalDialog(_dock), prompt(_prompt) 44 | { 45 | Show(); 46 | } 47 | 48 | ~YesNoDialog() noexcept { 49 | Hide(); 50 | } 51 | 52 | /** 53 | * Await completion of this dialog. 54 | * 55 | * @return a YesNoResult 56 | */ 57 | Awaitable operator co_await() noexcept { 58 | return *this; 59 | } 60 | 61 | private: 62 | void SetResult(YesNoResult _result) noexcept { 63 | result = _result; 64 | ready = true; 65 | 66 | if (continuation) 67 | continuation.resume(); 68 | } 69 | 70 | bool IsReady() const noexcept { 71 | return ready; 72 | } 73 | 74 | YesNoResult TakeValue() noexcept { 75 | return result; 76 | } 77 | 78 | public: 79 | /* virtual methodds from Modal */ 80 | void OnLeave(Window window) noexcept override; 81 | void OnCancel() noexcept override; 82 | void Paint(Window window) const noexcept override; 83 | bool OnKey(Window window, int key) override; 84 | }; 85 | -------------------------------------------------------------------------------- /src/ProgressBar.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #include "ProgressBar.hxx" 5 | #include "Styles.hxx" 6 | #include "ui/Options.hxx" 7 | #include "config.h" 8 | 9 | #include 10 | 11 | ProgressBar::ProgressBar(Point p, unsigned _width) noexcept 12 | :window(p, {_width, 1u}) 13 | { 14 | leaveok(window.w, true); 15 | 16 | if (ui_options.enable_colors) 17 | window.SetBackgroundStyle(Style::PROGRESSBAR); 18 | } 19 | 20 | void 21 | ProgressBar::Paint() const noexcept 22 | { 23 | const unsigned window_width = window.GetWidth(); 24 | 25 | if (max > 0) { 26 | assert(width < window_width); 27 | 28 | SelectStyle(window, Style::PROGRESSBAR); 29 | 30 | if (width > 0) { 31 | #if NCURSES_WIDECHAR 32 | window.HLine({0, 0}, width, WACS_D_HLINE); 33 | #else 34 | window.HLine({0, 0}, width, '='); 35 | #endif 36 | } 37 | 38 | window.Char({(int)width, 0}, '>'); 39 | unsigned x = width + 1; 40 | 41 | if (x < window_width) { 42 | SelectStyle(window, Style::PROGRESSBAR_BACKGROUND); 43 | window.HLine({(int)x, 0}, window_width - x, ACS_HLINE); 44 | } 45 | } else { 46 | /* no progress bar, just a simple horizontal line */ 47 | SelectStyle(window, Style::LINE); 48 | window.HLine({0, 0}, window_width, ACS_HLINE); 49 | } 50 | 51 | window.RefreshNoOut(); 52 | } 53 | 54 | bool 55 | ProgressBar::Calculate() noexcept 56 | { 57 | if (max == 0) 58 | return false; 59 | 60 | const unsigned window_width = window.GetWidth(); 61 | unsigned old_width = width; 62 | width = (window_width * current) / (max + 1); 63 | assert(width < window_width); 64 | 65 | return width != old_width; 66 | } 67 | 68 | void 69 | ProgressBar::OnResize(Point p, unsigned _width) noexcept 70 | { 71 | window.Resize({_width, 1u}); 72 | window.Move(p); 73 | 74 | Calculate(); 75 | } 76 | 77 | bool 78 | ProgressBar::Set(unsigned _current, unsigned _max) noexcept 79 | { 80 | if (_current > _max) 81 | _current = _max; 82 | 83 | bool modified = (_max == 0) != (max == 0); 84 | 85 | max = _max; 86 | current = _current; 87 | 88 | return Calculate() || modified; 89 | } 90 | -------------------------------------------------------------------------------- /src/hscroll.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | // Copyright The Music Player Daemon Project 3 | 4 | #pragma once 5 | 6 | #include "BasicMarquee.hxx" 7 | #include "ui/Point.hxx" 8 | #include "ui/Window.hxx" 9 | #include "event/FineTimerEvent.hxx" 10 | 11 | enum class Style : unsigned; 12 | 13 | /** 14 | * This class is used to auto-scroll text which does not fit on the 15 | * screen. Call hscroll_init() to initialize the object, 16 | * hscroll_clear() to free resources, and hscroll_set() to begin 17 | * scrolling. 18 | */ 19 | class hscroll { 20 | const Window window; 21 | 22 | BasicMarquee basic; 23 | 24 | /** 25 | * The postion on the screen. 26 | */ 27 | Point position; 28 | 29 | /** 30 | * Style for drawing the text. 31 | */ 32 | Style style; 33 | 34 | attr_t attr; 35 | 36 | /** 37 | * A timer which updates the scrolled area every second. 38 | */ 39 | FineTimerEvent timer; 40 | 41 | public: 42 | hscroll(EventLoop &event_loop, 43 | const Window _window, std::string_view _separator) noexcept 44 | :window(_window), basic(_separator), 45 | timer(event_loop, BIND_THIS_METHOD(OnTimer)) 46 | { 47 | } 48 | 49 | bool IsDefined() const noexcept { 50 | return basic.IsDefined(); 51 | } 52 | 53 | /** 54 | * Sets a text to scroll. This installs a timer which redraws 55 | * every second with the current window attributes. Call 56 | * hscroll_clear() to disable it. 57 | */ 58 | void Set(Point _position, unsigned width, std::string_view text, 59 | Style style, attr_t attr=0) noexcept; 60 | 61 | /** 62 | * Removes the text and the timer. It may be reused with 63 | * Set(). 64 | */ 65 | void Clear() noexcept; 66 | 67 | void Rewind() noexcept { 68 | basic.Rewind(); 69 | } 70 | 71 | void Step() noexcept { 72 | basic.Step(); 73 | } 74 | 75 | /** 76 | * Explicitly draws the scrolled text. Calling this function 77 | * is only allowed if there is a text currently. 78 | */ 79 | void Paint() const noexcept; 80 | 81 | private: 82 | void OnTimer() noexcept; 83 | 84 | void ScheduleTimer() noexcept { 85 | timer.Schedule(std::chrono::seconds(1)); 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The current ncmpc development team 2 | ================================== 3 | 4 | Max Kellermann 5 | Thomas Jansen 6 | Romain Bignon 7 | Patrick Hallen 8 | Jeffrey Middleton 9 | Andy Spencer 10 | Jonathan Neuschäfer 11 | 12 | 13 | Translators 14 | =========== 15 | 16 | Johám-Luís Miguéns Vila 17 | Galician and Spanish 18 | 19 | Jozef Riha 20 | Slovak 21 | 22 | Björn Pettersson 23 | Swedish 24 | 25 | László Áshin 26 | Hungarian 27 | 28 | Max Arnold 29 | Russian 30 | 31 | Mikkel Kirkgaard Nielsen 32 | Danish 33 | 34 | Monika Brinkert 35 | German 36 | 37 | Jon Bergli Heier 38 | Norwegian 39 | 40 | Yann Cézard 41 | François Blondel 42 | French 43 | 44 | Jay Whang , Atie 45 | Korean 46 | 47 | Gao Jie 48 | Simplified Chinese 49 | 50 | Niels Anker 51 | Danish (plus some Norwegian and Swedish) 52 | 53 | Vojtěch Trefný 54 | Czech 55 | 56 | Oleksandr Kovalenko 57 | Ukrainian 58 | 59 | Thomas Casteleyn 60 | Dutch 61 | 62 | Itai Kloog 63 | Hebrew 64 | 65 | Luis Miguel Domínguez Peinado 66 | Italian 67 | 68 | Krzysztof Krakowiak , Author and maintainer (2004-2006) 76 | Jonathan Fors 77 | Andreas Obergrusberger , Maintainer (2006-2008) 78 | J. Alexander Treuman 79 | Mikael Svantesson 80 | 81 | Other authors who may have gone unmentioned are listed in the repository logs. 82 | -------------------------------------------------------------------------------- /src/util/Manual.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-2-Clause 2 | // author: Max Kellermann 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * Container for an object that gets constructed and destructed 13 | * manually. The object is constructed in-place, and therefore 14 | * without allocation overhead. It can be constructed and destructed 15 | * repeatedly. 16 | */ 17 | template 18 | class Manual { 19 | alignas(T) std::byte data[sizeof(T)]; 20 | 21 | #ifndef NDEBUG 22 | bool initialized = false; 23 | #endif 24 | 25 | public: 26 | using value_type = T; 27 | using reference = T &; 28 | using const_reference = const T &; 29 | using pointer = T *; 30 | using const_pointer = const T *; 31 | 32 | #ifndef NDEBUG 33 | ~Manual() noexcept { 34 | assert(!initialized); 35 | } 36 | #endif 37 | 38 | /** 39 | * Cast a value reference to the containing Manual instance. 40 | */ 41 | static constexpr Manual &Cast(reference value) noexcept { 42 | return reinterpret_cast &>(value); 43 | } 44 | 45 | template 46 | void Construct(Args&&... args) { 47 | assert(!initialized); 48 | 49 | ::new(data) T(std::forward(args)...); 50 | 51 | #ifndef NDEBUG 52 | initialized = true; 53 | #endif 54 | } 55 | 56 | void Destruct() noexcept { 57 | assert(initialized); 58 | 59 | reference t = Get(); 60 | t.T::~T(); 61 | 62 | #ifndef NDEBUG 63 | initialized = false; 64 | #endif 65 | } 66 | 67 | reference Get() noexcept { 68 | assert(initialized); 69 | 70 | return *std::launder(reinterpret_cast(data)); 71 | } 72 | 73 | const_reference Get() const noexcept { 74 | assert(initialized); 75 | 76 | return *std::launder(reinterpret_cast(data)); 77 | } 78 | 79 | operator reference() noexcept { 80 | return Get(); 81 | } 82 | 83 | operator const_reference() const noexcept { 84 | return Get(); 85 | } 86 | 87 | pointer operator->() noexcept { 88 | return &Get(); 89 | } 90 | 91 | const_pointer operator->() const noexcept { 92 | return &Get(); 93 | } 94 | }; 95 | --------------------------------------------------------------------------------