New Upstream Release - ncmpc

Ready changes

Summary

Merged new upstream version: 0.49 (was: 0.48).

Resulting package

Built on 2023-08-05T15:45 (took 7m44s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ncmpc-dbgsymapt install -t fresh-releases ncmpc-lyricsapt install -t fresh-releases ncmpc

Lintian Result

Diff

diff --git a/NEWS b/NEWS
index e8c2176b..81fb9bf7 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,8 @@
+ncmpc 0.49 - (2023-08-04)
+* fix UI freeze if lyrics plugin is stuck
+* fix missing tags with libmpdclient 2.21
+* fix build failure on macOS
+
 ncmpc 0.48 - (2023-04-06)
 * drop support for ~/.ncmpc/; using only ~/.config/ncmpc/ (XDG)
 * improve scroll-offset handling
diff --git a/debian/changelog b/debian/changelog
index 680fafdd..f795c9b4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,10 +1,14 @@
-ncmpc (0.48-1) UNRELEASED; urgency=medium
+ncmpc (0.49-1) UNRELEASED; urgency=medium
 
+  [ Geoffroy Youri Berret ]
   * New upstream release
   * Update copyright (upstream switched to spdx headers)
   * Add NEWS.Debian regarding the new config location
 
- -- Geoffroy Youri Berret <kaliko@debian.org>  Sun, 23 Apr 2023 15:40:46 +0200
+  [ Debian Janitor ]
+  * New upstream release.
+
+ -- Geoffroy Youri Berret <kaliko@debian.org>  Sat, 05 Aug 2023 15:39:09 -0000
 
 ncmpc (0.47-2) unstable; urgency=medium
 
diff --git a/doc/conf.py b/doc/conf.py
index a83b4016..65032693 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
 # built documents.
 #
 # The short X.Y version.
-version = '0.48'
+version = '0.49'
 # The full version, including alpha/beta/rc tags.
 release = version
 
diff --git a/meson.build b/meson.build
index fa59fe92..fa0eff94 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('ncmpc', 'cpp',
-  version: '0.48',
+  version: '0.49',
   meson_version: '>= 0.49',
   default_options: [
     'cpp_std=c++2a',
diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx
index c00ec50a..31180d7c 100644
--- a/src/ConfigFile.cxx
+++ b/src/ConfigFile.cxx
@@ -34,17 +34,22 @@ IsFile(const char *path) noexcept
 std::string
 MakeKeysPath()
 {
-	return MakeUserConfigPath(KEYS_FILENAME);
+	const auto dir = GetUserConfigDirectory(PACKAGE);
+	if (dir.empty())
+		return {};
+
+	mkdir(dir.c_str(), 0777);
+	return BuildPath(dir, KEYS_FILENAME);
 }
 
 std::string
 GetUserConfigPath() noexcept
 {
-	const auto dir = GetHomeConfigDirectory();
+	const auto dir = GetUserConfigDirectory(PACKAGE);
 	if (dir.empty())
 		return {};
 
-	return BuildPath(dir, PACKAGE, CONFIG_FILENAME);
+	return BuildPath(dir, CONFIG_FILENAME);
 }
 
 std::string
@@ -65,30 +70,15 @@ GetSystemConfigPath() noexcept
 #endif
 }
 
-#ifndef _WIN32
-
-[[gnu::pure]]
-static std::string
-GetHomeKeysPath() noexcept
-{
-	const char *home = GetHomeDirectory();
-	if (home == nullptr)
-		return {};
-
-	return BuildPath(home, "." PACKAGE, KEYS_FILENAME);
-}
-
-#endif
-
 [[gnu::pure]]
 static std::string
 GetUserKeysPath() noexcept
 {
-	const auto dir = GetHomeConfigDirectory();
+	const auto dir = GetUserConfigDirectory(PACKAGE);
 	if (dir.empty())
 		return {};
 
-	return BuildPath(dir, PACKAGE, KEYS_FILENAME);
+	return BuildPath(dir, KEYS_FILENAME);
 }
 
 [[gnu::pure]]
@@ -142,13 +132,6 @@ find_keys_file() noexcept
 	if (!filename.empty() && IsFile(filename.c_str()))
 		return filename;
 
-#ifndef _WIN32
-	/* check for  user key bindings ~/.ncmpc/keys */
-	filename = GetHomeKeysPath();
-	if (!filename.empty() && IsFile(filename.c_str()))
-		return filename;
-#endif
-
 	/* check for  global key bindings SYSCONFDIR/ncmpc/keys */
 	filename = GetSystemKeysPath();
 	if (IsFile(filename.c_str()))
diff --git a/src/KeyDefPage.cxx b/src/KeyDefPage.cxx
index 583016f2..be638940 100644
--- a/src/KeyDefPage.cxx
+++ b/src/KeyDefPage.cxx
@@ -284,8 +284,6 @@ CommandKeysPage::OnCommand(struct mpdclient &c, Command cmd)
 	default:
 		return false;
 	}
-
-	return false;
 }
 
 class CommandListPage final : public ListPage, ListText {
diff --git a/src/LyricsCache.cxx b/src/LyricsCache.cxx
index 77fae25a..d98a214f 100644
--- a/src/LyricsCache.cxx
+++ b/src/LyricsCache.cxx
@@ -13,7 +13,7 @@
 static std::string
 GetLyricsCacheDirectory() noexcept
 {
-	const auto ncmpc_cache_directory = GetHomeCacheDirectory(PACKAGE);
+	const auto ncmpc_cache_directory = GetUserCacheDirectory(PACKAGE);
 	if (ncmpc_cache_directory.empty())
 		return {};
 
diff --git a/src/TagMask.hxx b/src/TagMask.hxx
index e4fd9864..94da63b8 100644
--- a/src/TagMask.hxx
+++ b/src/TagMask.hxx
@@ -11,7 +11,11 @@
 #include <stdint.h>
 
 class TagMask {
-	typedef uint_least32_t mask_t;
+	using mask_t = uint_least64_t;
+
+	static_assert(sizeof(mask_t) * 8 >= MPD_TAG_COUNT,
+		      "The mask does not have enough bits for the tags supported by MPD");
+
 	mask_t value;
 
 	explicit constexpr TagMask(uint_least32_t _value) noexcept
diff --git a/src/XdgBaseDirectory.cxx b/src/XdgBaseDirectory.cxx
index 3eef4b7d..2d404793 100644
--- a/src/XdgBaseDirectory.cxx
+++ b/src/XdgBaseDirectory.cxx
@@ -2,34 +2,26 @@
 // Copyright The Music Player Daemon Project
 
 #include "XdgBaseDirectory.hxx"
-#include "config.h"
 #include "io/Path.hxx"
 
 #include <stdlib.h>
-#include <sys/stat.h>
 
-[[gnu::pure]]
-static bool
-IsDirectory(const char *path) noexcept
-{
-	struct stat st;
-	return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
-}
-
-const char *
-GetHomeDirectory() noexcept
+[[gnu::const]]
+static const char *
+GetUserDirectory() noexcept
 {
 	return getenv("HOME");
 }
 
-std::string
-GetHomeConfigDirectory() noexcept
+[[gnu::const]]
+static std::string
+GetUserConfigDirectory() noexcept
 {
 	const char *config_home = getenv("XDG_CONFIG_HOME");
 	if (config_home != nullptr && *config_home != 0)
 		return config_home;
 
-	const char *home = GetHomeDirectory();
+	const char *home = GetUserDirectory();
 	if (home != nullptr)
 		return BuildPath(home, ".config");
 
@@ -37,36 +29,24 @@ GetHomeConfigDirectory() noexcept
 }
 
 std::string
-GetHomeConfigDirectory(const char *package) noexcept
+GetUserConfigDirectory(std::string_view package) noexcept
 {
-	const auto dir = GetHomeConfigDirectory();
+	const auto dir = GetUserConfigDirectory();
 	if (dir.empty())
 		return {};
 
 	return BuildPath(dir, package);
 }
 
-std::string
-MakeUserConfigPath(const char *filename) noexcept
-{
-	const auto directory = GetHomeConfigDirectory(PACKAGE);
-	if (directory.empty())
-		return {};
-
-	return IsDirectory(directory.c_str()) ||
-		mkdir(directory.c_str(), 0755) == 0
-		? BuildPath(directory, filename)
-		: std::string();
-}
-
-std::string
-GetHomeCacheDirectory() noexcept
+[[gnu::const]]
+static std::string
+GetUserCacheDirectory() noexcept
 {
 	const char *cache_home = getenv("XDG_CACHE_HOME");
 	if (cache_home != nullptr && *cache_home != 0)
 		return cache_home;
 
-	const char *home = GetHomeDirectory();
+	const char *home = GetUserDirectory();
 	if (home != nullptr)
 		return BuildPath(home, ".cache");
 
@@ -74,9 +54,9 @@ GetHomeCacheDirectory() noexcept
 }
 
 std::string
-GetHomeCacheDirectory(const char *package) noexcept
+GetUserCacheDirectory(std::string_view package) noexcept
 {
-	const auto dir = GetHomeCacheDirectory();
+	const auto dir = GetUserCacheDirectory();
 	if (dir.empty())
 		return {};
 
diff --git a/src/XdgBaseDirectory.hxx b/src/XdgBaseDirectory.hxx
index 7f775233..3888c2cf 100644
--- a/src/XdgBaseDirectory.hxx
+++ b/src/XdgBaseDirectory.hxx
@@ -1,38 +1,23 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 // Copyright The Music Player Daemon Project
 
-#ifndef XDG_BASE_DIRECTORY_HXX
-#define XDG_BASE_DIRECTORY_HXX
+#pragma once
 
 #include <string>
+#include <string_view>
 
-[[gnu::const]]
-const char *
-GetHomeDirectory() noexcept;
-
-[[gnu::const]]
-std::string
-GetHomeConfigDirectory() noexcept;
-
+/**
+ * Returns the absolute path of the XDG config directory for the
+ * specified package (or an empty string on error).
+ */
 [[gnu::pure]]
 std::string
-GetHomeConfigDirectory(const char *package) noexcept;
+GetUserConfigDirectory(std::string_view package) noexcept;
 
 /**
- * Find or create the directory for writing configuration files.
- *
- * @return the absolute path; an empty string indicates that no
- * directory could be created
+ * Returns the absolute path of the XDG cache directory for the
+ * specified package (or an empty string on error).
  */
-std::string
-MakeUserConfigPath(const char *filename) noexcept;
-
-[[gnu::const]]
-std::string
-GetHomeCacheDirectory() noexcept;
-
 [[gnu::pure]]
 std::string
-GetHomeCacheDirectory(const char *package) noexcept;
-
-#endif
+GetUserCacheDirectory(std::string_view package) noexcept;
diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index a5e54c98..12b9fdba 100644
--- a/src/event/Loop.cxx
+++ b/src/event/Loop.cxx
@@ -275,10 +275,8 @@ EventLoop::Run() noexcept
 #endif
 
 	assert(IsInside());
-	assert(!quit);
 #ifdef HAVE_THREADED_EVENT_LOOP
-	assert(!quit_injected);
-	assert(alive);
+	assert(alive || quit_injected);
 	assert(busy);
 
 	wake_event.Schedule(SocketEvent::READ);
@@ -303,7 +301,7 @@ EventLoop::Run() noexcept
 
 	FlushClockCaches();
 
-	do {
+	while (!quit) {
 		again = false;
 
 		/* invoke timers */
@@ -365,7 +363,7 @@ EventLoop::Run() noexcept
 
 			socket_event.Dispatch();
 		}
-	} while (!quit);
+	}
 
 #ifdef HAVE_THREADED_EVENT_LOOP
 #ifndef NDEBUG
diff --git a/src/io/Path.hxx b/src/io/Path.hxx
index e8bbf367..4d042459 100644
--- a/src/io/Path.hxx
+++ b/src/io/Path.hxx
@@ -5,8 +5,7 @@
 #define IO_PATH_HXX
 
 #include <string>
-
-#include <string.h>
+#include <string_view>
 
 namespace PathDetail {
 
@@ -16,93 +15,37 @@ static constexpr char SEPARATOR = '\\';
 static constexpr char SEPARATOR = '/';
 #endif
 
-[[gnu::pure]]
-inline size_t
-GetLength(const char *s) noexcept
-{
-	return strlen(s);
-}
-
-[[gnu::pure]]
-inline size_t
-GetLength(const std::string &s) noexcept
+inline void
+AppendWithSeparator(std::string &dest, std::string_view src) noexcept
 {
-	return s.length();
+	dest.push_back(SEPARATOR);
+	dest.append(src);
 }
 
 template<typename... Args>
-inline size_t
-FillLengths(size_t *lengths, Args&&... args) noexcept;
-
-template<typename First, typename... Args>
-inline size_t
-FillLengths(size_t *lengths, First &&first, Args&&... args) noexcept
-{
-	size_t length = GetLength(std::forward<First>(first));
-	*lengths++ = length;
-	return length + FillLengths(lengths, std::forward<Args>(args)...);
-}
-
-template<>
-inline size_t
-FillLengths(size_t *) noexcept
-{
-	return 0;
-}
-
-inline std::string &
-Append(std::string &dest, const std::string &value, size_t length) noexcept
+std::string
+BuildPath(std::string_view first, Args&&... args) noexcept
 {
-	return dest.append(value, 0, length);
-}
+	constexpr size_t n = sizeof...(args);
 
-inline std::string &
-Append(std::string &dest, const char *value, size_t length) noexcept
-{
-	return dest.append(value, length);
-}
+	const std::size_t total = first.size() + (args.size() + ...);
 
-template<typename... Args>
-inline std::string &
-AppendWithSeparators(std::string &dest, const size_t *lengths,
-		     Args&&... args) noexcept;
+	std::string result;
+	result.reserve(total + n);
 
-template<typename First, typename... Args>
-inline std::string &
-AppendWithSeparators(std::string &dest, const size_t *lengths,
-		     First &&first, Args&&... args) noexcept
-{
-	dest.push_back(SEPARATOR);
-	return AppendWithSeparators(Append(dest, std::forward<First>(first),
-					   *lengths),
-				    lengths + 1,
-				    std::forward<Args>(args)...);
-}
+	result.append(first);
+	(AppendWithSeparator(result, args), ...);
 
-template<>
-inline std::string &
-AppendWithSeparators(std::string &dest, const size_t *) noexcept
-{
-	return dest;
+	return result;
 }
 
 } // namespace PathDetail
 
-template<typename First, typename... Args>
+template<typename... Args>
 std::string
-BuildPath(First &&first, Args&&... args) noexcept
+BuildPath(std::string_view first, Args&&... args) noexcept
 {
-	constexpr size_t n = sizeof...(args);
-
-	size_t lengths[n + 1];
-	const size_t total = PathDetail::FillLengths(lengths, first, args...);
-
-	std::string result;
-	result.reserve(total + n);
-	PathDetail::Append(result, std::forward<First>(first), lengths[0]);
-	PathDetail::AppendWithSeparators(result, lengths + 1,
-					 std::forward<Args>(args)...);
-	return result;
+	return PathDetail::BuildPath(first, static_cast<std::string_view>(args)...);
 }
 
 #endif
diff --git a/src/plugin.cxx b/src/plugin.cxx
index f1ee25db..719aeab0 100644
--- a/src/plugin.cxx
+++ b/src/plugin.cxx
@@ -6,6 +6,7 @@
 #include "io/UniqueFileDescriptor.hxx"
 #include "event/PipeEvent.hxx"
 #include "event/CoarseTimerEvent.hxx"
+#include "event/Features.h" // for USE_SIGNALFD
 #include "util/ScopeExit.hxx"
 #include "util/UriUtil.hxx"
 
@@ -21,6 +22,10 @@
 #include <sys/stat.h>
 #include <sys/wait.h>
 
+#ifdef __APPLE__
+extern char **environ;
+#endif
+
 struct PluginCycle;
 
 class PluginPipe {
@@ -259,7 +264,15 @@ PluginCycle::LaunchPlugin(const char *plugin_path) noexcept
 	posix_spawn_file_actions_adddup2(&file_actions, stderr_w.Get(),
 					 STDERR_FILENO);
 
-	if (posix_spawn(&pid, plugin_path, &file_actions, nullptr,
+	posix_spawnattr_t attr;
+	posix_spawnattr_init(&attr);
+
+#ifdef USE_SIGNALFD
+	/* unblock all signals which may be blocked for signalfd */
+	posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK);
+#endif
+
+	if (posix_spawn(&pid, plugin_path, &file_actions, &attr,
 			argv.get(), environ) != 0)
 		return -1;
 
diff --git a/src/system/Error.hxx b/src/system/Error.hxx
index bce69f99..706c1dd8 100644
--- a/src/system/Error.hxx
+++ b/src/system/Error.hxx
@@ -6,23 +6,10 @@
 #include <system_error> // IWYU pragma: export
 #include <utility>
 
-#include <stdio.h>
-
-template<typename... Args>
-static inline std::system_error
-FormatSystemError(std::error_code code, const char *fmt,
-		  Args&&... args) noexcept
-{
-	char buffer[1024];
-	snprintf(buffer, sizeof(buffer), fmt, std::forward<Args>(args)...);
-	return std::system_error(code, buffer);
-}
-
 #ifdef _WIN32
 
 #include <errhandlingapi.h> // for GetLastError()
-#include <windef.h> // for HWND (needed by winbase.h)
-#include <winbase.h> // for FormatMessageA()
+#include <winerror.h>
 
 /**
  * Returns the error_category to be used to wrap WIN32 GetLastError()
@@ -59,38 +46,10 @@ MakeLastError(const char *msg) noexcept
 	return MakeLastError(GetLastError(), msg);
 }
 
-template<typename... Args>
-static inline std::system_error
-FormatLastError(DWORD code, const char *fmt, Args&&... args) noexcept
-{
-	char buffer[512];
-	const auto end = buffer + sizeof(buffer);
-	size_t length = snprintf(buffer, sizeof(buffer) - 128,
-				 fmt, std::forward<Args>(args)...);
-	char *p = buffer + length;
-	*p++ = ':';
-	*p++ = ' ';
-
-	FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
-		       FORMAT_MESSAGE_IGNORE_INSERTS,
-		       nullptr, code, 0, p, end - p, nullptr);
-	return MakeLastError(code, buffer);
-}
-
-template<typename... Args>
-static inline std::system_error
-FormatLastError(const char *fmt, Args&&... args) noexcept
-{
-	return FormatLastError(GetLastError(), fmt,
-			       std::forward<Args>(args)...);
-}
-
 #endif /* _WIN32 */
 
 #include <cerrno> // IWYU pragma: export
 
-#include <string.h>
-
 /**
  * Returns the error_category to be used to wrap errno values.  The
  * C++ standard does not define this well, so this code is based on
@@ -127,35 +86,6 @@ MakeErrno(const char *msg) noexcept
 	return MakeErrno(errno, msg);
 }
 
-template<typename... Args>
-static inline std::system_error
-FormatErrno(int code, const char *fmt, Args&&... args) noexcept
-{
-	char buffer[512];
-	snprintf(buffer, sizeof(buffer),
-		 fmt, std::forward<Args>(args)...);
-	return MakeErrno(code, buffer);
-}
-
-template<typename... Args>
-static inline std::system_error
-FormatErrno(const char *fmt, Args&&... args) noexcept
-{
-	return FormatErrno(errno, fmt, std::forward<Args>(args)...);
-}
-
-template<typename... Args>
-static inline std::system_error
-FormatFileNotFound(const char *fmt, Args&&... args) noexcept
-{
-#ifdef _WIN32
-	return FormatLastError(ERROR_FILE_NOT_FOUND, fmt,
-			       std::forward<Args>(args)...);
-#else
-	return FormatErrno(ENOENT, fmt, std::forward<Args>(args)...);
-#endif
-}
-
 [[gnu::pure]]
 inline bool
 IsErrno(const std::system_error &e, int code) noexcept
diff --git a/src/util/Concepts.hxx b/src/util/Concepts.hxx
index 6439dd92..a741d690 100644
--- a/src/util/Concepts.hxx
+++ b/src/util/Concepts.hxx
@@ -5,33 +5,5 @@
 
 #include <concepts>
 
-/**
- * Compatibility wrapper for std::invocable which is unavailable in
- * the Android NDK r25b and Apple Xcode.
- */
-#if !defined(ANDROID) && !defined(__APPLE__)
-template<typename F, typename... Args>
-concept Invocable = std::invocable<F, Args...>;
-#else
-template<typename F, typename... Args>
-concept Invocable = requires(F f, Args... args) {
-	{ f(args...) };
-};
-#endif
-
-/**
- * Compatibility wrapper for std::predicate which is unavailable in
- * the Android NDK r25b and Apple Xcode.
- */
-#if !defined(ANDROID) && !defined(__APPLE__)
-template<typename F, typename... Args>
-concept Predicate = std::predicate<F, Args...>;
-#else
-template<typename F, typename... Args>
-concept Predicate = requires(F f, Args... args) {
-	{ f(args...) } -> std::same_as<bool>;
-};
-#endif
-
 template<typename F, typename T>
-concept Disposer = Invocable<F, T *>;
+concept Disposer = std::invocable<F, T *>;
diff --git a/src/util/IntrusiveList.hxx b/src/util/IntrusiveList.hxx
index f8b5116e..aa1c67e5 100644
--- a/src/util/IntrusiveList.hxx
+++ b/src/util/IntrusiveList.hxx
@@ -77,31 +77,26 @@ using SafeLinkIntrusiveListHook =
 using AutoUnlinkIntrusiveListHook =
 	IntrusiveListHook<IntrusiveHookMode::AUTO_UNLINK>;
 
-/**
- * Detect the hook type which is embedded in the given type as a base
- * class.  This is a template to postpone the type checks, to allow
- * forward-declared types.
- */
-template<typename U>
-struct IntrusiveListHookDetection {
-	/* TODO can this be simplified somehow, without checking for
-	   all possible enum values? */
-	using type = std::conditional_t<std::is_base_of_v<IntrusiveListHook<IntrusiveHookMode::NORMAL>, U>,
-					IntrusiveListHook<IntrusiveHookMode::NORMAL>,
-					std::conditional_t<std::is_base_of_v<IntrusiveListHook<IntrusiveHookMode::TRACK>, U>,
-							   IntrusiveListHook<IntrusiveHookMode::TRACK>,
-							   std::conditional_t<std::is_base_of_v<IntrusiveListHook<IntrusiveHookMode::AUTO_UNLINK>, U>,
-									      IntrusiveListHook<IntrusiveHookMode::AUTO_UNLINK>,
-									      void>>>;
-};
-
 /**
  * For classes which embed #IntrusiveListHook as base class.
  */
 template<typename T>
 struct IntrusiveListBaseHookTraits {
+	/* a never-called helper function which is used by _Cast() */
+	template<IntrusiveHookMode mode>
+	static constexpr IntrusiveListHook<mode> _Identity(const IntrusiveListHook<mode> &) noexcept;
+
+	/* another never-called helper function which "calls"
+	   _Identity(), implicitly casting the item to the
+	   IntrusiveListHook specialization; we use this to detect
+	   which IntrusiveListHook specialization is used */
+	template<typename U>
+	static constexpr auto _Cast(const U &u) noexcept {
+		return decltype(_Identity(u))();
+	}
+
 	template<typename U>
-	using Hook = typename IntrusiveListHookDetection<U>::type;
+	using Hook = decltype(_Cast(std::declval<U>()));
 
 	static constexpr T *Cast(IntrusiveListNode *node) noexcept {
 		auto *hook = &Hook<T>::Cast(*node);
@@ -264,16 +259,14 @@ public:
 
 	void clear_and_dispose(Disposer<value_type> auto disposer) noexcept {
 		while (!empty()) {
-			auto *item = &front();
-			pop_front();
-			disposer(item);
+			disposer(&pop_front());
 		}
 	}
 
 	/**
 	 * @return the number of removed items
 	 */
-	std::size_t remove_and_dispose_if(Predicate<const_reference> auto pred,
+	std::size_t remove_and_dispose_if(std::predicate<const_reference> auto pred,
 					  Disposer<value_type> auto dispose) noexcept {
 		std::size_t result = 0;
 
@@ -302,15 +295,15 @@ public:
 		return *Cast(head.next);
 	}
 
-	void pop_front() noexcept {
-		ToHook(front()).unlink();
+	reference pop_front() noexcept {
+		auto &i = front();
+		ToHook(i).unlink();
 		--counter;
+		return i;
 	}
 
 	void pop_front_and_dispose(Disposer<value_type> auto disposer) noexcept {
-		auto &i = front();
-		ToHook(i).unlink();
-		--counter;
+		auto &i = pop_front();
 		disposer(&i);
 	}
 
@@ -319,7 +312,8 @@ public:
 	}
 
 	void pop_back() noexcept {
-		ToHook(back()).unlink();
+		auto &i = back();
+		ToHook(i).unlink();
 		--counter;
 	}
 
@@ -487,12 +481,18 @@ public:
 		insert(end(), t);
 	}
 
+	/**
+	 * Insert a new item before the given position.
+	 *
+	 * @param p a valid iterator (end() is allowed)for this list
+	 * describing the position where to insert
+	 */
 	void insert(iterator p, reference t) noexcept {
 		static_assert(!constant_time_size ||
 			      GetHookMode() < IntrusiveHookMode::AUTO_UNLINK,
 			      "Can't use auto-unlink hooks with constant_time_size");
 
-		auto &existing_node = ToNode(*p);
+		auto &existing_node = *p.cursor;
 		auto &new_node = ToNode(t);
 
 		IntrusiveListNode::Connect(*existing_node.prev,
@@ -502,6 +502,13 @@ public:
 		++counter;
 	}
 
+	/**
+	 * Like insert(), but insert after the given position.
+	 */
+	void insert_after(iterator p, reference t) noexcept {
+		insert(std::next(p), t);
+	}
+
 	/**
 	 * Move one item of the given list to this one before the
 	 * given position.
@@ -522,13 +529,13 @@ public:
 		if (_begin == _end)
 			return;
 
-		auto &next_node = ToNode(*position);
-		auto &prev_node = ToNode(*std::prev(position));
+		auto &next_node = *position.cursor;
+		auto &prev_node = *std::prev(position).cursor;
 
-		auto &first_node = ToNode(*_begin);
-		auto &before_first_node = ToNode(*std::prev(_begin));
-		auto &last_node = ToNode(*std::prev(_end));
-		auto &after_last_node = ToNode(*_end);
+		auto &first_node = *_begin.cursor;
+		auto &before_first_node = *std::prev(_begin).cursor;
+		auto &last_node = *std::prev(_end).cursor;
+		auto &after_last_node = *_end.cursor;
 
 		/* remove from the other list */
 		IntrusiveListNode::Connect(before_first_node, after_last_node);
diff --git a/src/util/ScopeExit.hxx b/src/util/ScopeExit.hxx
index 5c6fc9a7..9e1cc5dd 100644
--- a/src/util/ScopeExit.hxx
+++ b/src/util/ScopeExit.hxx
@@ -10,20 +10,27 @@
  * Internal class.  Do not use directly.
  */
 template<typename F>
-class ScopeExitGuard : F {
+class ScopeExitGuard {
+	[[no_unique_address]]
+	F function;
+
 	bool enabled = true;
 
 public:
-	explicit ScopeExitGuard(F &&f):F(std::forward<F>(f)) {}
+	explicit ScopeExitGuard(F &&f) noexcept
+		:function(std::forward<F>(f)) {}
 
-	ScopeExitGuard(ScopeExitGuard &&src)
-		:F(std::move(src)), enabled(src.enabled) {
-		src.enabled = false;
-	}
+	ScopeExitGuard(ScopeExitGuard &&src) noexcept
+		:function(std::move(src.function)),
+		 enabled(std::exchange(src.enabled, false)) {}
 
-	~ScopeExitGuard() {
+	/* destructors are "noexcept" by default; this explicit
+	   "noexcept" declaration allows the destructor to throw if
+	   the function can throw; without this, a throwing function
+	   would std::terminate() */
+	~ScopeExitGuard() noexcept(noexcept(std::declval<F>()())) {
 		if (enabled)
-			F::operator()();
+			function();
 	}
 
 	ScopeExitGuard(const ScopeExitGuard &) = delete;
@@ -38,7 +45,7 @@ struct ScopeExitTag {
 	   parantheses at the end of the expression AtScopeExit()
 	   call */
 	template<typename F>
-	ScopeExitGuard<F> operator+(F &&f) {
+	ScopeExitGuard<F> operator+(F &&f) noexcept {
 		return ScopeExitGuard<F>(std::forward<F>(f));
 	}
 };
diff --git a/src/util/SortList.hxx b/src/util/SortList.hxx
index 6856872b..54a43745 100644
--- a/src/util/SortList.hxx
+++ b/src/util/SortList.hxx
@@ -3,10 +3,10 @@
 
 #pragma once
 
-#include "Concepts.hxx"
 #include "StaticVector.hxx"
 
 #include <algorithm> // for std::find_if()
+#include <concepts>
 
 /**
  * Move all items from #src to #dest, keeping both sorted.
@@ -17,7 +17,7 @@
 template<typename List>
 constexpr void
 MergeList(List &dest, List &src,
-	  Predicate<typename List::const_reference, typename List::const_reference> auto p) noexcept
+	  std::predicate<typename List::const_reference, typename List::const_reference> auto p) noexcept
 {
 	const auto dest_end = dest.end(), src_end = src.end();
 
@@ -59,7 +59,7 @@ MergeList(List &dest, List &src,
 template<typename List>
 constexpr void
 SortList(List &list,
-	 Predicate <typename List::const_reference, typename List::const_reference> auto p) noexcept
+	 std::predicate <typename List::const_reference, typename List::const_reference> auto p) noexcept
 {
 	using std::swap;
 

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/debug/.build-id/14/81cdff33f82d51ea07abe638a0fde3818bd3f7.debug

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/debug/.build-id/58/71c31f6f8235a672262faa946908f60a6ba22d.debug

Control files of package ncmpc: lines which differ (wdiff format)

  • Depends: libc6 (>= 2.34), libgcc-s1 (>= 3.0), liblirc-client0, libmpdclient2 (>= 2.19), libncursesw6 (>= 6), libpcre2-8-0 (>= 10.22), libstdc++6 (>= 11), 13), libtinfo6 (>= 6)

Control files of package ncmpc-dbgsym: lines which differ (wdiff format)

  • Build-Ids: 5871c31f6f8235a672262faa946908f60a6ba22d 1481cdff33f82d51ea07abe638a0fde3818bd3f7

No differences were encountered between the control files of package ncmpc-lyrics

More details

Full run details