Compare commits
	
		
			18 Commits
		
	
	
		
			v1.1.0
			...
			1a671dfad5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						1a671dfad5
	
				 | 
					
					
						|||
| 
						
						
							
						
						587a02fa15
	
				 | 
					
					
						|||
| 
						
						
							
						
						227b8e0fa2
	
				 | 
					
					
						|||
| 
						
						
							
						
						e66e9f249a
	
				 | 
					
					
						|||
| 
						
						
							
						
						32203f8117
	
				 | 
					
					
						|||
| 
						
						
							
						
						6b871898d8
	
				 | 
					
					
						|||
| 
						
						
							
						
						4598c45d2f
	
				 | 
					
					
						|||
| 
						
						
							
						
						66c77c3f8d
	
				 | 
					
					
						|||
| 
						
						
							
						
						7165a8eb02
	
				 | 
					
					
						|||
| 
						
						
							
						
						87b57bb24c
	
				 | 
					
					
						|||
| 
						
						
							
						
						ba86961ba5
	
				 | 
					
					
						|||
| 
						
						
							
						
						0cdb4989e5
	
				 | 
					
					
						|||
| 
						
						
							
						
						6de940fe96
	
				 | 
					
					
						|||
| 
						
						
							
						
						6bd8c1db2f
	
				 | 
					
					
						|||
| 
						
						
							
						
						56efe9c6a9
	
				 | 
					
					
						|||
| 
						
						
							
						
						8a17e674f8
	
				 | 
					
					
						|||
| 
						
						
							
						
						bd0ee66c19
	
				 | 
					
					
						|||
| 
						
						
							
						
						6f6efe077b
	
				 | 
					
					
						
							
								
								
									
										32
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					# clang-format is fairly limited, and these rules are approximate:
 | 
				
			||||||
 | 
					#  - array initializers can get terribly mangled with clang-format 12.0,
 | 
				
			||||||
 | 
					#  - sometimes it still aligns with space characters,
 | 
				
			||||||
 | 
					#  - struct name NL { NL ... NL } NL name; is unachievable.
 | 
				
			||||||
 | 
					BasedOnStyle: GNU
 | 
				
			||||||
 | 
					ColumnLimit: 80
 | 
				
			||||||
 | 
					IndentWidth: 4
 | 
				
			||||||
 | 
					TabWidth: 4
 | 
				
			||||||
 | 
					UseTab: ForContinuationAndIndentation
 | 
				
			||||||
 | 
					BreakBeforeBraces: Allman
 | 
				
			||||||
 | 
					SpaceAfterCStyleCast: true
 | 
				
			||||||
 | 
					AlignAfterOpenBracket: DontAlign
 | 
				
			||||||
 | 
					AlignOperands: DontAlign
 | 
				
			||||||
 | 
					AlignConsecutiveMacros: Consecutive
 | 
				
			||||||
 | 
					AllowAllArgumentsOnNextLine: false
 | 
				
			||||||
 | 
					AllowAllParametersOfDeclarationOnNextLine: false
 | 
				
			||||||
 | 
					IndentGotoLabels: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IncludeCategories has some potential, but it may also break the build.
 | 
				
			||||||
 | 
					# Note that the documentation says the value should be "Never".
 | 
				
			||||||
 | 
					SortIncludes: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This is a compromise, it generally works out aesthetically better.
 | 
				
			||||||
 | 
					BinPackArguments: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Unfortunately, this can't be told to align to column 40 or so.
 | 
				
			||||||
 | 
					SpacesBeforeTrailingComments: 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# liberty-specific macro body wrappers.
 | 
				
			||||||
 | 
					MacroBlockBegin: "BLOCK_START"
 | 
				
			||||||
 | 
					MacroBlockEnd: "BLOCK_END"
 | 
				
			||||||
 | 
					ForEachMacros: ["LIST_FOR_EACH"]
 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -7,3 +7,5 @@
 | 
				
			|||||||
/nncmpp.files
 | 
					/nncmpp.files
 | 
				
			||||||
/nncmpp.creator*
 | 
					/nncmpp.creator*
 | 
				
			||||||
/nncmpp.includes
 | 
					/nncmpp.includes
 | 
				
			||||||
 | 
					/nncmpp.cflags
 | 
				
			||||||
 | 
					/nncmpp.cxxflags
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
cmake_minimum_required (VERSION 3.0)
 | 
					cmake_minimum_required (VERSION 3.0)
 | 
				
			||||||
project (nncmpp VERSION 1.1.0 LANGUAGES C)
 | 
					project (nncmpp VERSION 1.1.1 LANGUAGES C)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Moar warnings
 | 
					# Moar warnings
 | 
				
			||||||
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
 | 
					if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
 | 
				
			||||||
@@ -24,14 +24,17 @@ option (USE_SYSTEM_TERMO
 | 
				
			|||||||
if (USE_SYSTEM_TERMO)
 | 
					if (USE_SYSTEM_TERMO)
 | 
				
			||||||
	if (NOT Termo_FOUND)
 | 
						if (NOT Termo_FOUND)
 | 
				
			||||||
		message (FATAL_ERROR "System termo library not found")
 | 
							message (FATAL_ERROR "System termo library not found")
 | 
				
			||||||
	endif (NOT Termo_FOUND)
 | 
						endif ()
 | 
				
			||||||
else ()
 | 
					else ()
 | 
				
			||||||
 | 
						# We don't want the library to install, but EXCLUDE_FROM_ALL ignores tests
 | 
				
			||||||
	add_subdirectory (termo EXCLUDE_FROM_ALL)
 | 
						add_subdirectory (termo EXCLUDE_FROM_ALL)
 | 
				
			||||||
	# We don't have many good choices when we don't want to install it and want
 | 
						file (WRITE ${PROJECT_BINARY_DIR}/CTestCustom.cmake
 | 
				
			||||||
	# to support older versions of CMake; this is a relatively clean approach
 | 
							"execute_process (COMMAND ${CMAKE_COMMAND} --build termo)")
 | 
				
			||||||
	# (other possibilities: setting a variable in the parent scope, using a
 | 
					
 | 
				
			||||||
	# cache variable, writing a special config file with build paths in it and
 | 
						# We don't have many good choices; this is a relatively clean approach
 | 
				
			||||||
	# including it here, or setting a custom property on the targets).
 | 
						# (other possibilities: setting a variable in the parent scope, using
 | 
				
			||||||
 | 
						# a cache variable, writing a special config file with build paths in it
 | 
				
			||||||
 | 
						# and including it here, or setting a custom property on the targets)
 | 
				
			||||||
	get_directory_property (Termo_INCLUDE_DIRS
 | 
						get_directory_property (Termo_INCLUDE_DIRS
 | 
				
			||||||
		DIRECTORY termo INCLUDE_DIRECTORIES)
 | 
							DIRECTORY termo INCLUDE_DIRECTORIES)
 | 
				
			||||||
	set (Termo_LIBRARIES termo-static)
 | 
						set (Termo_LIBRARIES termo-static)
 | 
				
			||||||
@@ -42,25 +45,38 @@ option (WITH_FFTW "Use FFTW to enable spectrum visualisation" ${fftw_FOUND})
 | 
				
			|||||||
if (WITH_FFTW)
 | 
					if (WITH_FFTW)
 | 
				
			||||||
	if (NOT fftw_FOUND)
 | 
						if (NOT fftw_FOUND)
 | 
				
			||||||
		message (FATAL_ERROR "FFTW not found")
 | 
							message (FATAL_ERROR "FFTW not found")
 | 
				
			||||||
	endif()
 | 
						endif ()
 | 
				
			||||||
 | 
						list (APPEND extra_libraries ${fftw_LIBRARIES})
 | 
				
			||||||
 | 
					endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pkg_check_modules (libpulse libpulse)
 | 
				
			||||||
 | 
					option (WITH_PULSE "Enable control of PulseAudio sink volume" ${libpulse_FOUND})
 | 
				
			||||||
 | 
					if (WITH_PULSE)
 | 
				
			||||||
 | 
						if (NOT libpulse_FOUND)
 | 
				
			||||||
 | 
							message (FATAL_ERROR "libpulse not found")
 | 
				
			||||||
 | 
						endif ()
 | 
				
			||||||
 | 
						list (APPEND extra_libraries ${libpulse_LIBRARIES})
 | 
				
			||||||
endif ()
 | 
					endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
include_directories (${Unistring_INCLUDE_DIRS}
 | 
					include_directories (${Unistring_INCLUDE_DIRS}
 | 
				
			||||||
	${Ncursesw_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS} ${curl_INCLUDE_DIRS}
 | 
						${Ncursesw_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS} ${curl_INCLUDE_DIRS}
 | 
				
			||||||
	${fftw_INCLUDE_DIRS})
 | 
						${fftw_INCLUDE_DIRS} ${libpulse_INCLUDE_DIRS})
 | 
				
			||||||
link_directories (${curl_LIBRARY_DIRS} ${fftw_LIBRARY_DIRS})
 | 
					link_directories (${curl_LIBRARY_DIRS}
 | 
				
			||||||
 | 
						${fftw_LIBRARY_DIRS} ${libpulse_LIBRARY_DIRS})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Configuration
 | 
					# Configuration
 | 
				
			||||||
include (CheckFunctionExists)
 | 
					 | 
				
			||||||
set (CMAKE_REQUIRED_LIBRARIES ${Ncursesw_LIBRARIES})
 | 
					 | 
				
			||||||
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
 | 
					if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
 | 
				
			||||||
	# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
 | 
						# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
 | 
				
			||||||
	# our POSIX version macros make it undefined
 | 
						# our POSIX version macros make it undefined
 | 
				
			||||||
	add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
 | 
						add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
 | 
				
			||||||
 | 
					elseif (APPLE)
 | 
				
			||||||
 | 
						add_definitions (-D_DARWIN_C_SOURCE)
 | 
				
			||||||
endif ()
 | 
					endif ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include (CheckFunctionExists)
 | 
				
			||||||
 | 
					set (CMAKE_REQUIRED_LIBRARIES ${Ncursesw_LIBRARIES})
 | 
				
			||||||
 | 
					CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# -lm may or may not be a part of libc
 | 
					# -lm may or may not be a part of libc
 | 
				
			||||||
foreach (extra m)
 | 
					foreach (extra m)
 | 
				
			||||||
	find_library (extra_lib_${extra} ${extra})
 | 
						find_library (extra_lib_${extra} ${extra})
 | 
				
			||||||
@@ -74,11 +90,25 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in
 | 
				
			|||||||
	${PROJECT_BINARY_DIR}/config.h)
 | 
						${PROJECT_BINARY_DIR}/config.h)
 | 
				
			||||||
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
 | 
					include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Assuming a Unix-compatible system with a standalone preprocessor
 | 
				
			||||||
 | 
					set (actions_list ${PROJECT_SOURCE_DIR}/nncmpp.actions)
 | 
				
			||||||
 | 
					set (actions ${PROJECT_BINARY_DIR}/nncmpp-actions.h)
 | 
				
			||||||
 | 
					add_custom_command (OUTPUT ${actions}
 | 
				
			||||||
 | 
						COMMAND cpp -I${PROJECT_BINARY_DIR} -P ${actions_list}
 | 
				
			||||||
 | 
							| grep . | tr [[\n]] ^ | sed -ne [[h; s/,[^^]*/,/g]] -e [[s/$/COUNT/]]
 | 
				
			||||||
 | 
								-e [[s/[^^]*/\tACTION_&/g]] -e [[s/.*/enum action {\n&\n};\n/p]]
 | 
				
			||||||
 | 
								-e [[g; s/,[^^]*//g; y/_/-/]] -e [[s/[^^]\{1,\}/\t"&",/g]]
 | 
				
			||||||
 | 
								-e [[s/.*/static const char *g_action_names[] = {\n&};\n/p]]
 | 
				
			||||||
 | 
								-e [[g; s/[^^]*, *//g;]] -e [[s/[^^]\{1,\}/\t"&",/g]]
 | 
				
			||||||
 | 
								-e [[s/.*/static const char *g_action_descriptions[] = {\n&};/p]]
 | 
				
			||||||
 | 
							| tr ^ [[\n]] > ${actions}
 | 
				
			||||||
 | 
						COMMAND test -s ${actions}
 | 
				
			||||||
 | 
						DEPENDS ${actions_list} ${PROJECT_BINARY_DIR}/config.h VERBATIM)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Build the main executable and link it
 | 
					# Build the main executable and link it
 | 
				
			||||||
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c)
 | 
					add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${actions})
 | 
				
			||||||
target_link_libraries (${PROJECT_NAME} ${Unistring_LIBRARIES}
 | 
					target_link_libraries (${PROJECT_NAME} ${Unistring_LIBRARIES}
 | 
				
			||||||
	${Ncursesw_LIBRARIES} termo-static ${curl_LIBRARIES}
 | 
						${Ncursesw_LIBRARIES} termo-static ${curl_LIBRARIES} ${extra_libraries})
 | 
				
			||||||
	${fftw_LIBRARIES} ${extra_libraries})
 | 
					 | 
				
			||||||
add_threads (${PROJECT_NAME})
 | 
					add_threads (${PROJECT_NAME})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Installation
 | 
					# Installation
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								NEWS
									
									
									
									
									
								
							@@ -1,3 +1,10 @@
 | 
				
			|||||||
 | 
					1.1.1 (2021-11-04)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * Terminal focus in/out events no longer ring the terminall bell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * Made mouse work in non-rxvt terminals with recent xterm terminfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1.1.0 (2021-10-21)
 | 
					1.1.0 (2021-10-21)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 * Now requesting and processing terminal de/focus events,
 | 
					 * Now requesting and processing terminal de/focus events,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.adoc
									
									
									
									
									
								
							@@ -11,9 +11,14 @@ names, and should be pronounced as "nincompoop".
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Features
 | 
					Features
 | 
				
			||||||
--------
 | 
					--------
 | 
				
			||||||
Most things are there.  Enough for me to use it exclusively.  Note that since I
 | 
					Most stuff is there.  Enough for me to use the program exclusively.  Among other
 | 
				
			||||||
only use the filesystem browsing mode, that's also the only thing I care to
 | 
					things, it can display and change PulseAudio volume directly to cover the use
 | 
				
			||||||
implement for the time being.
 | 
					case of remote control, it has a fast spectrum visualiser, and both
 | 
				
			||||||
 | 
					the appearance and key bindings can be customized.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that since I only use the filesystem browsing mode, that's also the only
 | 
				
			||||||
 | 
					thing I care to implement for the time being.  Similarly, the search feature is
 | 
				
			||||||
 | 
					known to be clumsy.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
image::nncmpp.png[align="center"]
 | 
					image::nncmpp.png[align="center"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,9 +34,10 @@ The rest of this README will concern itself with externalities.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Building
 | 
					Building
 | 
				
			||||||
--------
 | 
					--------
 | 
				
			||||||
Build dependencies: CMake, pkg-config, asciidoctor, liberty (included),
 | 
					Build dependencies: CMake, pkg-config, asciidoctor,
 | 
				
			||||||
                    termo (included) +
 | 
					                    liberty (included), termo (included) +
 | 
				
			||||||
Runtime dependencies: ncursesw, libunistring, cURL, fftw3 (optional)
 | 
					Runtime dependencies: ncursesw, libunistring, cURL,
 | 
				
			||||||
 | 
					                      fftw3 (optional), libpulse (optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 $ git clone --recursive https://git.janouch.name/p/nncmpp.git
 | 
					 $ git clone --recursive https://git.janouch.name/p/nncmpp.git
 | 
				
			||||||
 $ mkdir nncmpp/build
 | 
					 $ mkdir nncmpp/build
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
#ifndef CONFIG_H
 | 
					#ifndef CONFIG_H
 | 
				
			||||||
#define CONFIG_H
 | 
					#define CONFIG_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define PROGRAM_NAME "${CMAKE_PROJECT_NAME}"
 | 
					#define PROGRAM_NAME "${PROJECT_NAME}"
 | 
				
			||||||
#define PROGRAM_VERSION "${PROJECT_VERSION}"
 | 
					#define PROGRAM_VERSION "${PROJECT_VERSION}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#cmakedefine HAVE_RESIZETERM
 | 
					#cmakedefine HAVE_RESIZETERM
 | 
				
			||||||
#cmakedefine WITH_FFTW
 | 
					#cmakedefine WITH_FFTW
 | 
				
			||||||
 | 
					#cmakedefine WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif  // ! CONFIG_H
 | 
					#endif  /* ! CONFIG_H */
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								liberty
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								liberty
									
									
									
									
									
								
							 Submodule liberty updated: d71c47f8ce...782a9a5977
									
								
							
							
								
								
									
										71
									
								
								nncmpp.actions
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								nncmpp.actions
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					#include "config.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NONE,               Do nothing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QUIT,               Quit
 | 
				
			||||||
 | 
					REDRAW,             Redraw screen
 | 
				
			||||||
 | 
					TAB_HELP,           Switch to help tab
 | 
				
			||||||
 | 
					TAB_LAST,           Switch to last tab
 | 
				
			||||||
 | 
					TAB_PREVIOUS,       Switch to previous tab
 | 
				
			||||||
 | 
					TAB_NEXT,           Switch to next tab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MPD_TOGGLE,         Toggle play/pause
 | 
				
			||||||
 | 
					MPD_STOP,           Stop playback
 | 
				
			||||||
 | 
					MPD_PREVIOUS,       Previous song
 | 
				
			||||||
 | 
					MPD_NEXT,           Next song
 | 
				
			||||||
 | 
					MPD_BACKWARD,       Seek backwards
 | 
				
			||||||
 | 
					MPD_FORWARD,        Seek forwards
 | 
				
			||||||
 | 
					MPD_VOLUME_UP,      Increase MPD volume
 | 
				
			||||||
 | 
					MPD_VOLUME_DOWN,    Decrease MPD volume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MPD_SEARCH,         Global search
 | 
				
			||||||
 | 
					MPD_ADD,            Add selection to playlist
 | 
				
			||||||
 | 
					MPD_REPLACE,        Replace playlist
 | 
				
			||||||
 | 
					MPD_REPEAT,         Toggle repeat
 | 
				
			||||||
 | 
					MPD_RANDOM,         Toggle random playback
 | 
				
			||||||
 | 
					MPD_SINGLE,         Toggle single song playback
 | 
				
			||||||
 | 
					MPD_CONSUME,        Toggle consume
 | 
				
			||||||
 | 
					MPD_UPDATE_DB,      Update MPD database
 | 
				
			||||||
 | 
					MPD_COMMAND,        Send raw command to MPD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
					PULSE_VOLUME_UP,    Increase PulseAudio volume
 | 
				
			||||||
 | 
					PULSE_VOLUME_DOWN,  Decrease PulseAudio volume
 | 
				
			||||||
 | 
					PULSE_MUTE,         Toggle PulseAudio sink mute
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CHOOSE,             Choose item
 | 
				
			||||||
 | 
					DELETE,             Delete item
 | 
				
			||||||
 | 
					UP,                 Go up a level
 | 
				
			||||||
 | 
					MULTISELECT,        Toggle multiselect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SCROLL_UP,          Scroll up
 | 
				
			||||||
 | 
					SCROLL_DOWN,        Scroll down
 | 
				
			||||||
 | 
					MOVE_UP,            Move selection up
 | 
				
			||||||
 | 
					MOVE_DOWN,          Move selection down
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GOTO_TOP,           Go to top
 | 
				
			||||||
 | 
					GOTO_BOTTOM,        Go to bottom
 | 
				
			||||||
 | 
					GOTO_ITEM_PREVIOUS, Go to previous item
 | 
				
			||||||
 | 
					GOTO_ITEM_NEXT,     Go to next item
 | 
				
			||||||
 | 
					GOTO_PAGE_PREVIOUS, Go to previous page
 | 
				
			||||||
 | 
					GOTO_PAGE_NEXT,     Go to next page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GOTO_VIEW_TOP,      Select top item
 | 
				
			||||||
 | 
					GOTO_VIEW_CENTER,   Select center item
 | 
				
			||||||
 | 
					GOTO_VIEW_BOTTOM,   Select bottom item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EDITOR_CONFIRM,     Confirm input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EDITOR_B_CHAR,      Go back a character
 | 
				
			||||||
 | 
					EDITOR_F_CHAR,      Go forward a character
 | 
				
			||||||
 | 
					EDITOR_B_WORD,      Go back a word
 | 
				
			||||||
 | 
					EDITOR_F_WORD,      Go forward a word
 | 
				
			||||||
 | 
					EDITOR_HOME,        Go to start of line
 | 
				
			||||||
 | 
					EDITOR_END,         Go to end of line
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EDITOR_B_DELETE,    Delete last character
 | 
				
			||||||
 | 
					EDITOR_F_DELETE,    Delete next character
 | 
				
			||||||
 | 
					EDITOR_B_KILL_WORD, Delete last word
 | 
				
			||||||
 | 
					EDITOR_B_KILL_LINE, Delete everything up to BOL
 | 
				
			||||||
 | 
					EDITOR_F_KILL_LINE, Delete everything up to EOL
 | 
				
			||||||
							
								
								
									
										77
									
								
								nncmpp.adoc
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								nncmpp.adoc
									
									
									
									
									
								
							@@ -37,34 +37,35 @@ Options
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Configuration
 | 
					Configuration
 | 
				
			||||||
-------------
 | 
					-------------
 | 
				
			||||||
Unless you run MPD on a remote machine, on an unusual port, or protected by
 | 
					Unless you run MPD on a remote machine, on an unusual port, protected by
 | 
				
			||||||
a password, the client doesn't need a configuration file to work.  It is,
 | 
					a password, or only accessible through a Unix socket, the client doesn't need
 | 
				
			||||||
however, likely that you'll want to customize the looks or add some streams.
 | 
					a configuration file to work.  It is, however, likely that you'll want to
 | 
				
			||||||
You can start off with the following snippet:
 | 
					customize the looks or add some streams.  You can start off with the following
 | 
				
			||||||
 | 
					snippet:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
....
 | 
					....
 | 
				
			||||||
settings = {
 | 
					settings = {
 | 
				
			||||||
	address    = "localhost:6600"
 | 
					  address    = "~/.mpd/mpd.socket"
 | 
				
			||||||
	password   = "<your password>"
 | 
					  password   = "<your password>"
 | 
				
			||||||
	root       = "~/Music"
 | 
					  pulseaudio = on
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
colors = {
 | 
					colors = {
 | 
				
			||||||
	normal      = ""
 | 
					  normal      = ""
 | 
				
			||||||
	highlight   = "bold"
 | 
					  highlight   = "bold"
 | 
				
			||||||
	elapsed     = "reverse"
 | 
					  elapsed     = "reverse"
 | 
				
			||||||
	remains     = "ul"
 | 
					  remains     = "ul"
 | 
				
			||||||
	tab_bar     = "reverse"
 | 
					  tab_bar     = "reverse"
 | 
				
			||||||
	tab_active  = "ul"
 | 
					  tab_active  = "ul"
 | 
				
			||||||
	even        = ""
 | 
					  even        = ""
 | 
				
			||||||
	odd         = ""
 | 
					  odd         = ""
 | 
				
			||||||
	selection   = "reverse"
 | 
					  selection   = "reverse"
 | 
				
			||||||
	multiselect = "-1 6"
 | 
					  multiselect = "-1 6"
 | 
				
			||||||
	defocused   = "ul"
 | 
					  defocused   = "ul"
 | 
				
			||||||
	scrollbar   = ""
 | 
					  scrollbar   = ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
streams = {
 | 
					streams = {
 | 
				
			||||||
	"dnbradio.com" = "http://www.dnbradio.com/hi.m3u"
 | 
					  "dnbradio.com" = "http://www.dnbradio.com/hi.m3u"
 | 
				
			||||||
	"BassDrive.com" = "http://bassdrive.com/v2/streams/BassDrive.pls"
 | 
					  "BassDrive.com" = "http://bassdrive.com/v2/streams/BassDrive.pls"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
....
 | 
					....
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,17 +86,39 @@ need to set it up manually to match your MPD configuration, e.g.:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
....
 | 
					....
 | 
				
			||||||
settings = {
 | 
					settings = {
 | 
				
			||||||
	...
 | 
					  ...
 | 
				
			||||||
	spectrum_path = "~/.mpd/mpd.fifo"  # "path"
 | 
					  spectrum_path = "~/.mpd/mpd.fifo"  # "path"
 | 
				
			||||||
	spectrum_format = "44100:16:2"     # "format" (samplerate:bits:channels)
 | 
					  spectrum_format = "44100:16:2"     # "format" (samplerate:bits:channels)
 | 
				
			||||||
	spectrum_bars = 8                  # beware of exponential complexity
 | 
					  spectrum_bars = 8                  # beware of exponential complexity
 | 
				
			||||||
	...
 | 
					  ...
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
....
 | 
					....
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The sample rate should be greater than 40 kHz, the number of bits 8 or 16,
 | 
					The sample rate should be greater than 40 kHz, the number of bits 8 or 16,
 | 
				
			||||||
and the number of channels doesn't matter, as they're simply averaged together.
 | 
					and the number of channels doesn't matter, as they're simply averaged together.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PulseAudio
 | 
				
			||||||
 | 
					----------
 | 
				
			||||||
 | 
					If you find standard MPD volume control useless, you may instead configure
 | 
				
			||||||
 | 
					*nncmpp* to show and control the volume of any PulseAudio sink MPD is currently
 | 
				
			||||||
 | 
					connected to.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This feature may be enabled with the *settings.pulseaudio* configuration option,
 | 
				
			||||||
 | 
					as in the snippet above.  To replace the default volume control bindings, use:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					....
 | 
				
			||||||
 | 
					normal = {
 | 
				
			||||||
 | 
					  "M-PageUp" = "pulse-volume-up"
 | 
				
			||||||
 | 
					  "M-PageDown" = "pulse-volume-down"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					....
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The respective actions may also be invoked from the help tab directly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For this to work, *nncmpp* needs to access the right PulseAudio daemon--in case
 | 
				
			||||||
 | 
					your setup is unusual, consult the list of environment variables in
 | 
				
			||||||
 | 
					*pulseaudio*(1).  MPD-compatibles are currently unsupported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Files
 | 
					Files
 | 
				
			||||||
-----
 | 
					-----
 | 
				
			||||||
*nncmpp* follows the XDG Base Directory Specification.
 | 
					*nncmpp* follows the XDG Base Directory Specification.
 | 
				
			||||||
@@ -110,4 +133,4 @@ or submit pull requests.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
See also
 | 
					See also
 | 
				
			||||||
--------
 | 
					--------
 | 
				
			||||||
*mpd*(1)
 | 
					*mpd*(1), *pulseaudio*(1)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										534
									
								
								nncmpp.c
									
									
									
									
									
								
							
							
						
						
									
										534
									
								
								nncmpp.c
									
									
									
									
									
								
							@@ -78,30 +78,35 @@ enum
 | 
				
			|||||||
#include <math.h>
 | 
					#include <math.h>
 | 
				
			||||||
#include <locale.h>
 | 
					#include <locale.h>
 | 
				
			||||||
#include <termios.h>
 | 
					#include <termios.h>
 | 
				
			||||||
#ifndef TIOCGWINSZ
 | 
					 | 
				
			||||||
#include <sys/ioctl.h>
 | 
					#include <sys/ioctl.h>
 | 
				
			||||||
#endif  // ! TIOCGWINSZ
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ncurses is notoriously retarded for input handling, we need something
 | 
					// ncurses is notoriously retarded for input handling, we need something
 | 
				
			||||||
// different if only to receive mouse events reliably.
 | 
					// different if only to receive mouse events reliably.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// 2021 update: ncurses is mostly reliable now, though rxvt-unicode only
 | 
					// 2021 update: ncurses is mostly reliable now, though rxvt-unicode only
 | 
				
			||||||
// supports the 1006 mode that ncurses also supports mode starting with 9.25.
 | 
					// supports the 1006 mode that ncurses also supports mode starting with 9.25.
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "termo.h"
 | 
					#include "termo.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// We need cURL to extract links from Internet stream playlists.  It'd be way
 | 
					// We need cURL to extract links from Internet stream playlists.  It'd be way
 | 
				
			||||||
// too much code to do this all by ourselves, and there's nothing better around.
 | 
					// too much code to do this all by ourselves, and there's nothing better around.
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <curl/curl.h>
 | 
					#include <curl/curl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The spectrum analyser requires a DFT transform.  The FFTW library is fairly
 | 
					// The spectrum analyser requires a DFT transform.  The FFTW library is fairly
 | 
				
			||||||
// efficient, and doesn't have a requirement on the number of bins.
 | 
					// efficient, and doesn't have a requirement on the number of bins.
 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef WITH_FFTW
 | 
					#ifdef WITH_FFTW
 | 
				
			||||||
#include <fftw3.h>
 | 
					#include <fftw3.h>
 | 
				
			||||||
#endif  // WITH_FFTW
 | 
					#endif  // WITH_FFTW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Remote MPD control needs appropriate volume controls.
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
					#include "liberty/liberty-pulse.c"
 | 
				
			||||||
 | 
					#include <pulse/context.h>
 | 
				
			||||||
 | 
					#include <pulse/error.h>
 | 
				
			||||||
 | 
					#include <pulse/introspect.h>
 | 
				
			||||||
 | 
					#include <pulse/subscribe.h>
 | 
				
			||||||
 | 
					#include <pulse/sample.h>
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define APP_TITLE  PROGRAM_NAME         ///< Left top corner
 | 
					#define APP_TITLE  PROGRAM_NAME         ///< Left top corner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Utilities ---------------------------------------------------------------
 | 
					// --- Utilities ---------------------------------------------------------------
 | 
				
			||||||
@@ -110,7 +115,7 @@ enum
 | 
				
			|||||||
static void
 | 
					static void
 | 
				
			||||||
update_curses_terminal_size (void)
 | 
					update_curses_terminal_size (void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
 | 
					#if defined HAVE_RESIZETERM && defined TIOCGWINSZ
 | 
				
			||||||
	struct winsize size;
 | 
						struct winsize size;
 | 
				
			||||||
	if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
 | 
						if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@@ -862,6 +867,256 @@ spectrum_free (struct spectrum *s)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#endif  // WITH_FFTW
 | 
					#endif  // WITH_FFTW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- PulseAudio --------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct pulse
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct poller_timer make_context;   ///< Event to establish connection
 | 
				
			||||||
 | 
						pa_mainloop_api *api;               ///< PulseAudio event loop proxy
 | 
				
			||||||
 | 
						pa_context *context;                ///< PulseAudio connection context
 | 
				
			||||||
 | 
						uint32_t sink_candidate;            ///< Used while searching for MPD
 | 
				
			||||||
 | 
						uint32_t sink;                      ///< The relevant sink or -1
 | 
				
			||||||
 | 
						pa_cvolume sink_volume;             ///< Current volume
 | 
				
			||||||
 | 
						bool sink_muted;                    ///< Currently muted?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void (*on_update) (void);           ///< Update callback
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_on_sink_info (pa_context *context, const pa_sink_info *info, int eol,
 | 
				
			||||||
 | 
						void *userdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) context;
 | 
				
			||||||
 | 
						(void) eol;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct pulse *self = userdata;
 | 
				
			||||||
 | 
						if (info)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							self->sink_volume = info->volume;
 | 
				
			||||||
 | 
							self->sink_muted = !!info->mute;
 | 
				
			||||||
 | 
							self->on_update ();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_update_from_sink (struct pulse *self)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (self->sink == PA_INVALID_INDEX)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pa_operation_unref (pa_context_get_sink_info_by_index
 | 
				
			||||||
 | 
							(self->context, self->sink, pulse_on_sink_info, self));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_on_sink_input_info (pa_context *context,
 | 
				
			||||||
 | 
						const struct pa_sink_input_info *info, int eol, void *userdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) context;
 | 
				
			||||||
 | 
						(void) eol;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct pulse *self = userdata;
 | 
				
			||||||
 | 
						if (!info)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if ((self->sink = self->sink_candidate) != PA_INVALID_INDEX)
 | 
				
			||||||
 | 
								pulse_update_from_sink (self);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								self->on_update ();
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: also save info->mute as a different mute level,
 | 
				
			||||||
 | 
						//   and perhaps info->index (they can appear and disappear)
 | 
				
			||||||
 | 
						const char *name =
 | 
				
			||||||
 | 
							pa_proplist_gets (info->proplist, PA_PROP_APPLICATION_NAME);
 | 
				
			||||||
 | 
						if (name && !strcmp (name, "Music Player Daemon"))
 | 
				
			||||||
 | 
							self->sink_candidate = info->sink;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_read_sink_inputs (struct pulse *self)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						self->sink_candidate = PA_INVALID_INDEX;
 | 
				
			||||||
 | 
						pa_operation_unref (pa_context_get_sink_input_info_list
 | 
				
			||||||
 | 
							(self->context, pulse_on_sink_input_info, self));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_on_event (pa_context *context, pa_subscription_event_type_t event,
 | 
				
			||||||
 | 
						uint32_t index, void *userdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct pulse *self = userdata;
 | 
				
			||||||
 | 
						switch (event & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
 | 
				
			||||||
 | 
							pulse_read_sink_inputs (self);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PA_SUBSCRIPTION_EVENT_SINK:
 | 
				
			||||||
 | 
							if (index == self->sink)
 | 
				
			||||||
 | 
								pulse_update_from_sink (self);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_on_subscribe_finish (pa_context *context, int success, void *userdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct pulse *self = userdata;
 | 
				
			||||||
 | 
						if (success)
 | 
				
			||||||
 | 
							pulse_read_sink_inputs (self);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							print_debug ("PulseAudio failed to subscribe for events");
 | 
				
			||||||
 | 
							self->on_update ();
 | 
				
			||||||
 | 
							pa_context_disconnect (context);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_on_context_state_change (pa_context *context, void *userdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pulse *self = userdata;
 | 
				
			||||||
 | 
						switch (pa_context_get_state (context))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						case PA_CONTEXT_FAILED:
 | 
				
			||||||
 | 
						case PA_CONTEXT_TERMINATED:
 | 
				
			||||||
 | 
							print_debug ("PulseAudio context failed or has been terminated");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pa_context_unref (context);
 | 
				
			||||||
 | 
							self->context = NULL;
 | 
				
			||||||
 | 
							self->sink = PA_INVALID_INDEX;
 | 
				
			||||||
 | 
							self->on_update ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Retry after an arbitrary delay of 5 seconds
 | 
				
			||||||
 | 
							poller_timer_set (&self->make_context, 5000);
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case PA_CONTEXT_READY:
 | 
				
			||||||
 | 
							pa_context_set_subscribe_callback (context, pulse_on_event, userdata);
 | 
				
			||||||
 | 
							pa_operation_unref (pa_context_subscribe (context,
 | 
				
			||||||
 | 
								PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SINK_INPUT,
 | 
				
			||||||
 | 
								pulse_on_subscribe_finish, userdata));
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_make_context (void *user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct pulse *self = user_data;
 | 
				
			||||||
 | 
						self->context = pa_context_new (self->api, PROGRAM_NAME);
 | 
				
			||||||
 | 
						pa_context_set_state_callback (self->context,
 | 
				
			||||||
 | 
							pulse_on_context_state_change, self);
 | 
				
			||||||
 | 
						pa_context_connect (self->context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_on_finish (pa_context *context, int success, void *userdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) context;
 | 
				
			||||||
 | 
						(void) success;
 | 
				
			||||||
 | 
						(void) userdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Just like... whatever, man
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					pulse_volume_mute (struct pulse *self)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!self->context || self->sink == PA_INVALID_INDEX)
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pa_operation_unref (pa_context_set_sink_mute_by_index (self->context,
 | 
				
			||||||
 | 
							self->sink, !self->sink_muted, pulse_on_finish, self));
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					pulse_volume_set (struct pulse *self, int arg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!self->context || self->sink == PA_INVALID_INDEX)
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pa_cvolume volume = self->sink_volume;
 | 
				
			||||||
 | 
						if (arg > 0)
 | 
				
			||||||
 | 
							pa_cvolume_inc (&volume, (pa_volume_t)  arg * PA_VOLUME_NORM / 100);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							pa_cvolume_dec (&volume, (pa_volume_t) -arg * PA_VOLUME_NORM / 100);
 | 
				
			||||||
 | 
						pa_operation_unref (pa_context_set_sink_volume_by_index (self->context,
 | 
				
			||||||
 | 
							self->sink, &volume, pulse_on_finish, self));
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_init (struct pulse *self, struct poller *poller)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						memset (self, 0, sizeof *self);
 | 
				
			||||||
 | 
						self->sink = PA_INVALID_INDEX;
 | 
				
			||||||
 | 
						if (!poller)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						self->api = poller_pa_new (poller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						self->make_context = poller_timer_make (poller);
 | 
				
			||||||
 | 
						self->make_context.dispatcher = pulse_make_context;
 | 
				
			||||||
 | 
						self->make_context.user_data = self;
 | 
				
			||||||
 | 
						poller_timer_set (&self->make_context, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_free (struct pulse *self)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (self->context)
 | 
				
			||||||
 | 
							pa_context_unref (self->context);
 | 
				
			||||||
 | 
						if (self->api)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							poller_pa_destroy (self->api);
 | 
				
			||||||
 | 
							poller_timer_reset (&self->make_context);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pulse_init (self, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define VOLUME_PERCENT(x) (((x) * 100 + PA_VOLUME_NORM / 2) / PA_VOLUME_NORM)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					pulse_volume_status (struct pulse *self, struct str *s)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!self->context || self->sink == PA_INVALID_INDEX
 | 
				
			||||||
 | 
						 || !self->sink_volume.channels)
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (self->sink_muted)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							str_append (s, "Muted");
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						str_append_printf (s,
 | 
				
			||||||
 | 
							"%u%%", VOLUME_PERCENT (self->sink_volume.values[0]));
 | 
				
			||||||
 | 
						if (!pa_cvolume_channels_equal_to (&self->sink_volume,
 | 
				
			||||||
 | 
								self->sink_volume.values[0]))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (size_t i = 1; i < self->sink_volume.channels; i++)
 | 
				
			||||||
 | 
								str_append_printf (s, " / %u%%",
 | 
				
			||||||
 | 
									VOLUME_PERCENT (self->sink_volume.values[i]));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Application -------------------------------------------------------------
 | 
					// --- Application -------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Function names are prefixed mostly because of curses which clutters the
 | 
					// Function names are prefixed mostly because of curses which clutters the
 | 
				
			||||||
@@ -985,6 +1240,11 @@ static struct app_context
 | 
				
			|||||||
	struct poller_fd spectrum_event;    ///< FIFO watcher
 | 
						struct poller_fd spectrum_event;    ///< FIFO watcher
 | 
				
			||||||
#endif  // WITH_FFTW
 | 
					#endif  // WITH_FFTW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
						struct pulse pulse;                 ///< PulseAudio control
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
						bool pulse_control_requested;       ///< PulseAudio control desired by user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct line_editor editor;          ///< Line editor
 | 
						struct line_editor editor;          ///< Line editor
 | 
				
			||||||
	struct poller_idle refresh_event;   ///< Refresh the screen
 | 
						struct poller_idle refresh_event;   ///< Refresh the screen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1045,6 +1305,13 @@ on_poll_elapsed_time_changed (struct config_item *item)
 | 
				
			|||||||
	g.elapsed_poll = item->value.boolean;
 | 
						g.elapsed_poll = item->value.boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					on_pulseaudio_changed (struct config_item *item)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// This is only set once, on application startup
 | 
				
			||||||
 | 
						g.pulse_control_requested = item->value.boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct config_schema g_config_settings[] =
 | 
					static struct config_schema g_config_settings[] =
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	{ .name      = "address",
 | 
						{ .name      = "address",
 | 
				
			||||||
@@ -1056,6 +1323,7 @@ static struct config_schema g_config_settings[] =
 | 
				
			|||||||
	  .type      = CONFIG_ITEM_STRING },
 | 
						  .type      = CONFIG_ITEM_STRING },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// NOTE: this is unused--in theory we could allow manual metadata adjustment
 | 
						// NOTE: this is unused--in theory we could allow manual metadata adjustment
 | 
				
			||||||
 | 
						// NOTE: the "config" command may return "music_directory" for local clients
 | 
				
			||||||
	{ .name      = "root",
 | 
						{ .name      = "root",
 | 
				
			||||||
	  .comment   = "Where all the files MPD is playing are located",
 | 
						  .comment   = "Where all the files MPD is playing are located",
 | 
				
			||||||
	  .type      = CONFIG_ITEM_STRING },
 | 
						  .type      = CONFIG_ITEM_STRING },
 | 
				
			||||||
@@ -1076,6 +1344,14 @@ static struct config_schema g_config_settings[] =
 | 
				
			|||||||
	  .default_  = "8" },
 | 
						  .default_  = "8" },
 | 
				
			||||||
#endif  // WITH_FFTW
 | 
					#endif  // WITH_FFTW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
						{ .name      = "pulseaudio",
 | 
				
			||||||
 | 
						  .comment   = "Look up MPD in PulseAudio for improved volume controls",
 | 
				
			||||||
 | 
						  .type      = CONFIG_ITEM_BOOLEAN,
 | 
				
			||||||
 | 
						  .on_change = on_pulseaudio_changed,
 | 
				
			||||||
 | 
						  .default_  = "off" },
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Disabling this minimises MPD traffic and has the following caveats:
 | 
						// Disabling this minimises MPD traffic and has the following caveats:
 | 
				
			||||||
	//  - when MPD stalls on retrieving audio data, we keep ticking
 | 
						//  - when MPD stalls on retrieving audio data, we keep ticking
 | 
				
			||||||
	//  - when the "play" succeeds in ACTION_MPD_REPLACE for the same item as
 | 
						//  - when the "play" succeeds in ACTION_MPD_REPLACE for the same item as
 | 
				
			||||||
@@ -1236,6 +1512,10 @@ app_init_context (void)
 | 
				
			|||||||
	g.spectrum_row = g.spectrum_column = -1;
 | 
						g.spectrum_row = g.spectrum_column = -1;
 | 
				
			||||||
#endif  // WITH_FFTW
 | 
					#endif  // WITH_FFTW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
						pulse_init (&g.pulse, NULL);
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// This is also approximately what libunistring does internally,
 | 
						// This is also approximately what libunistring does internally,
 | 
				
			||||||
	// since the locale name is canonicalized by locale_charset().
 | 
						// since the locale name is canonicalized by locale_charset().
 | 
				
			||||||
	// Note that non-Unicode locales are handled pretty inefficiently.
 | 
						// Note that non-Unicode locales are handled pretty inefficiently.
 | 
				
			||||||
@@ -1299,6 +1579,10 @@ app_free_context (void)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
#endif  // WITH_FFTW
 | 
					#endif  // WITH_FFTW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
						pulse_free (&g.pulse);
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	line_editor_free (&g.editor);
 | 
						line_editor_free (&g.editor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	config_free (&g.config);
 | 
						config_free (&g.config);
 | 
				
			||||||
@@ -1498,14 +1782,32 @@ app_draw_status (void)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// It gets a bit complicated due to the only right-aligned item on the row
 | 
						// It gets a bit complicated due to the only right-aligned item on the row
 | 
				
			||||||
	char *volume = NULL;
 | 
						struct str volume = str_make ();
 | 
				
			||||||
	int remaining = COLS - buf.total_width;
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
	if (g.volume >= 0)
 | 
						if (g.pulse_control_requested)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		volume = xstrdup_printf ("  %3d%%", g.volume);
 | 
							struct str buf = str_make ();
 | 
				
			||||||
		remaining -= strlen (volume);
 | 
							if (pulse_volume_status (&g.pulse, &buf))
 | 
				
			||||||
	}
 | 
							{
 | 
				
			||||||
 | 
								if (g.volume >= 0 && g.volume != 100)
 | 
				
			||||||
 | 
									str_append_printf (&buf, " (%d%%)", g.volume);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (g.volume >= 0)
 | 
				
			||||||
 | 
									str_append_printf (&buf, "(%d%%)", g.volume);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (buf.len)
 | 
				
			||||||
 | 
								str_append_printf (&volume, "  %s", buf.str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							str_free (&buf);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
						if (g.volume >= 0)
 | 
				
			||||||
 | 
							str_append_printf (&volume, "  %3d%%", g.volume);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int remaining = COLS - buf.total_width - volume.len;
 | 
				
			||||||
	if (!stopped && g.song_elapsed >= 0 && g.song_duration >= 1
 | 
						if (!stopped && g.song_elapsed >= 0 && g.song_duration >= 1
 | 
				
			||||||
	 && remaining > 0)
 | 
						 && remaining > 0)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@@ -1517,11 +1819,10 @@ app_draw_status (void)
 | 
				
			|||||||
	else
 | 
						else
 | 
				
			||||||
		row_buffer_space (&buf, remaining, attr_normal);
 | 
							row_buffer_space (&buf, remaining, attr_normal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (volume)
 | 
						if (volume.len)
 | 
				
			||||||
	{
 | 
							row_buffer_append (&buf, volume.str, attr_normal);
 | 
				
			||||||
		row_buffer_append (&buf, volume, attr_normal);
 | 
						str_free (&volume);
 | 
				
			||||||
		free (volume);
 | 
					
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	g.controls_offset = g.header_height;
 | 
						g.controls_offset = g.header_height;
 | 
				
			||||||
	app_flush_header (&buf, attr_normal);
 | 
						app_flush_header (&buf, attr_normal);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1948,105 +2249,14 @@ app_goto_tab (int tab_index)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// --- Actions -----------------------------------------------------------------
 | 
					// --- Actions -----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define ACTIONS(XX) \
 | 
					#include "nncmpp-actions.h"
 | 
				
			||||||
	XX( NONE,               Do nothing                  ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( QUIT,               Quit                        ) \
 | 
					 | 
				
			||||||
	XX( REDRAW,             Redraw screen               ) \
 | 
					 | 
				
			||||||
	XX( TAB_HELP,           Switch to help tab          ) \
 | 
					 | 
				
			||||||
	XX( TAB_LAST,           Switch to last tab          ) \
 | 
					 | 
				
			||||||
	XX( TAB_PREVIOUS,       Switch to previous tab      ) \
 | 
					 | 
				
			||||||
	XX( TAB_NEXT,           Switch to next tab          ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( MPD_TOGGLE,         Toggle play/pause           ) \
 | 
					 | 
				
			||||||
	XX( MPD_STOP,           Stop playback               ) \
 | 
					 | 
				
			||||||
	XX( MPD_PREVIOUS,       Previous song               ) \
 | 
					 | 
				
			||||||
	XX( MPD_NEXT,           Next song                   ) \
 | 
					 | 
				
			||||||
	XX( MPD_BACKWARD,       Seek backwards              ) \
 | 
					 | 
				
			||||||
	XX( MPD_FORWARD,        Seek forwards               ) \
 | 
					 | 
				
			||||||
	XX( MPD_VOLUME_UP,      Increase volume             ) \
 | 
					 | 
				
			||||||
	XX( MPD_VOLUME_DOWN,    Decrease volume             ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( MPD_SEARCH,         Global search               ) \
 | 
					 | 
				
			||||||
	XX( MPD_ADD,            Add selection to playlist   ) \
 | 
					 | 
				
			||||||
	XX( MPD_REPLACE,        Replace playlist            ) \
 | 
					 | 
				
			||||||
	XX( MPD_REPEAT,         Toggle repeat               ) \
 | 
					 | 
				
			||||||
	XX( MPD_RANDOM,         Toggle random playback      ) \
 | 
					 | 
				
			||||||
	XX( MPD_SINGLE,         Toggle single song playback ) \
 | 
					 | 
				
			||||||
	XX( MPD_CONSUME,        Toggle consume              ) \
 | 
					 | 
				
			||||||
	XX( MPD_UPDATE_DB,      Update MPD database         ) \
 | 
					 | 
				
			||||||
	XX( MPD_COMMAND,        Send raw command to MPD     ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( CHOOSE,             Choose item                 ) \
 | 
					 | 
				
			||||||
	XX( DELETE,             Delete item                 ) \
 | 
					 | 
				
			||||||
	XX( UP,                 Go up a level               ) \
 | 
					 | 
				
			||||||
	XX( MULTISELECT,        Toggle multiselect          ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( SCROLL_UP,          Scroll up                   ) \
 | 
					 | 
				
			||||||
	XX( SCROLL_DOWN,        Scroll down                 ) \
 | 
					 | 
				
			||||||
	XX( MOVE_UP,            Move selection up           ) \
 | 
					 | 
				
			||||||
	XX( MOVE_DOWN,          Move selection down         ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( GOTO_TOP,           Go to top                   ) \
 | 
					 | 
				
			||||||
	XX( GOTO_BOTTOM,        Go to bottom                ) \
 | 
					 | 
				
			||||||
	XX( GOTO_ITEM_PREVIOUS, Go to previous item         ) \
 | 
					 | 
				
			||||||
	XX( GOTO_ITEM_NEXT,     Go to next item             ) \
 | 
					 | 
				
			||||||
	XX( GOTO_PAGE_PREVIOUS, Go to previous page         ) \
 | 
					 | 
				
			||||||
	XX( GOTO_PAGE_NEXT,     Go to next page             ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( GOTO_VIEW_TOP,      Select top item             ) \
 | 
					 | 
				
			||||||
	XX( GOTO_VIEW_CENTER,   Select center item          ) \
 | 
					 | 
				
			||||||
	XX( GOTO_VIEW_BOTTOM,   Select bottom item          ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( EDITOR_CONFIRM,     Confirm input               ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( EDITOR_B_CHAR,      Go back a character         ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_F_CHAR,      Go forward a character      ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_B_WORD,      Go back a word              ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_F_WORD,      Go forward a word           ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_HOME,        Go to start of line         ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_END,         Go to end of line           ) \
 | 
					 | 
				
			||||||
	\
 | 
					 | 
				
			||||||
	XX( EDITOR_B_DELETE,    Delete last character       ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_F_DELETE,    Delete next character       ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_B_KILL_WORD, Delete last word            ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_B_KILL_LINE, Delete everything up to BOL ) \
 | 
					 | 
				
			||||||
	XX( EDITOR_F_KILL_LINE, Delete everything up to EOL )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum action
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
#define XX(name, description) ACTION_ ## name,
 | 
					 | 
				
			||||||
	ACTIONS (XX)
 | 
					 | 
				
			||||||
#undef XX
 | 
					 | 
				
			||||||
	ACTION_COUNT
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static struct action_info
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	const char *name;                   ///< Name for user bindings
 | 
					 | 
				
			||||||
	const char *description;            ///< Human-readable description
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
g_actions[] =
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
#define XX(name, description) { #name, #description },
 | 
					 | 
				
			||||||
	ACTIONS (XX)
 | 
					 | 
				
			||||||
#undef XX
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Accept a more human format of action-name instead of ACTION_NAME
 | 
					 | 
				
			||||||
static int action_toupper (int c) { return c == '-' ? '_' : toupper_ascii (c); }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int
 | 
					static int
 | 
				
			||||||
action_resolve (const char *name)
 | 
					action_resolve (const char *name)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	const unsigned char *s = (const unsigned char *) name;
 | 
					 | 
				
			||||||
	for (int i = 0; i < ACTION_COUNT; i++)
 | 
						for (int i = 0; i < ACTION_COUNT; i++)
 | 
				
			||||||
	{
 | 
							if (!strcasecmp_ascii (g_action_names[i], name))
 | 
				
			||||||
		const char *target = g_actions[i].name;
 | 
								return i;
 | 
				
			||||||
		for (size_t k = 0; action_toupper (s[k]) == target[k]; k++)
 | 
					 | 
				
			||||||
			if (!s[k] && !target[k])
 | 
					 | 
				
			||||||
				return i;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return -1;
 | 
						return -1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2225,6 +2435,12 @@ app_process_action (enum action action)
 | 
				
			|||||||
	case ACTION_MPD_VOLUME_UP:          return app_setvol (g.volume + 10);
 | 
						case ACTION_MPD_VOLUME_UP:          return app_setvol (g.volume + 10);
 | 
				
			||||||
	case ACTION_MPD_VOLUME_DOWN:        return app_setvol (g.volume - 10);
 | 
						case ACTION_MPD_VOLUME_DOWN:        return app_setvol (g.volume - 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
						case ACTION_PULSE_VOLUME_UP:        return pulse_volume_set (&g.pulse, +10);
 | 
				
			||||||
 | 
						case ACTION_PULSE_VOLUME_DOWN:      return pulse_volume_set (&g.pulse, -10);
 | 
				
			||||||
 | 
						case ACTION_PULSE_MUTE:             return pulse_volume_mute (&g.pulse);
 | 
				
			||||||
 | 
					#endif  // WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// XXX: these should rather be parametrized
 | 
							// XXX: these should rather be parametrized
 | 
				
			||||||
	case ACTION_SCROLL_UP:              return app_scroll (-3);
 | 
						case ACTION_SCROLL_UP:              return app_scroll (-3);
 | 
				
			||||||
	case ACTION_SCROLL_DOWN:            return app_scroll  (3);
 | 
						case ACTION_SCROLL_DOWN:            return app_scroll  (3);
 | 
				
			||||||
@@ -2588,10 +2804,12 @@ app_init_bindings (const char *keymap,
 | 
				
			|||||||
static bool
 | 
					static bool
 | 
				
			||||||
app_process_termo_event (termo_key_t *event)
 | 
					app_process_termo_event (termo_key_t *event)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if (event->type == TERMO_TYPE_FOCUS)
 | 
						bool handled = false;
 | 
				
			||||||
 | 
						if ((handled = event->type == TERMO_TYPE_FOCUS))
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		g.focused = !!event->code.focused;
 | 
							g.focused = !!event->code.focused;
 | 
				
			||||||
		app_invalidate ();
 | 
							app_invalidate ();
 | 
				
			||||||
 | 
							// Senseless fall-through
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct binding dummy = { *event, 0, 0 }, *binding;
 | 
						struct binding dummy = { *event, 0, 0 }, *binding;
 | 
				
			||||||
@@ -2601,7 +2819,7 @@ app_process_termo_event (termo_key_t *event)
 | 
				
			|||||||
			sizeof *binding, app_binding_cmp)))
 | 
								sizeof *binding, app_binding_cmp)))
 | 
				
			||||||
			return app_editor_process_action (binding->action);
 | 
								return app_editor_process_action (binding->action);
 | 
				
			||||||
		if (event->type != TERMO_TYPE_KEY || event->modifiers != 0)
 | 
							if (event->type != TERMO_TYPE_KEY || event->modifiers != 0)
 | 
				
			||||||
			return false;
 | 
								return handled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		line_editor_insert (&g.editor, event->code.codepoint);
 | 
							line_editor_insert (&g.editor, event->code.codepoint);
 | 
				
			||||||
		app_invalidate ();
 | 
							app_invalidate ();
 | 
				
			||||||
@@ -2620,7 +2838,7 @@ app_process_termo_event (termo_key_t *event)
 | 
				
			|||||||
		if (app_goto_tab ((n == 0 ? 10 : n) - 1))
 | 
							if (app_goto_tab ((n == 0 ? 10 : n) - 1))
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return false;
 | 
						return handled;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Current tab -------------------------------------------------------------
 | 
					// --- Current tab -------------------------------------------------------------
 | 
				
			||||||
@@ -3644,7 +3862,7 @@ help_tab_group (struct binding *keys, size_t len, struct strv *out,
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			char *joined = strv_join (&ass, ", ");
 | 
								char *joined = strv_join (&ass, ", ");
 | 
				
			||||||
			strv_append_owned (out, xstrdup_printf
 | 
								strv_append_owned (out, xstrdup_printf
 | 
				
			||||||
				("  %-30s %s", g_actions[i].description, joined));
 | 
									("  %-30s %s", g_action_descriptions[i], joined));
 | 
				
			||||||
			free (joined);
 | 
								free (joined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			bound[i] = true;
 | 
								bound[i] = true;
 | 
				
			||||||
@@ -3661,7 +3879,7 @@ help_tab_unbound (struct strv *out, bool bound[ACTION_COUNT])
 | 
				
			|||||||
		if (!bound[i])
 | 
							if (!bound[i])
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			strv_append_owned (out,
 | 
								strv_append_owned (out,
 | 
				
			||||||
				xstrdup_printf ("  %-30s", g_actions[i].description));
 | 
									xstrdup_printf ("  %-30s", g_action_descriptions[i]));
 | 
				
			||||||
			help_tab_assign_action (i);
 | 
								help_tab_assign_action (i);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -3910,11 +4128,85 @@ spectrum_setup_fifo (void)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#else  // ! WITH_FFTW
 | 
					#else  // ! WITH_FFTW
 | 
				
			||||||
#define spectrum_setup_fifo()
 | 
					#define spectrum_setup_fifo()   BLOCK_START BLOCK_END
 | 
				
			||||||
#define spectrum_clear()
 | 
					#define spectrum_clear()        BLOCK_START BLOCK_END
 | 
				
			||||||
#define spectrum_discard_fifo()
 | 
					#define spectrum_discard_fifo() BLOCK_START BLOCK_END
 | 
				
			||||||
#endif  // ! WITH_FFTW
 | 
					#endif  // ! WITH_FFTW
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- PulseAudio --------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					mpd_find_output (const struct strv *data, const char *wanted)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// The plugin field is new in MPD 0.21, by default take any output
 | 
				
			||||||
 | 
						unsigned long n, accept = 1;
 | 
				
			||||||
 | 
						for (size_t i = data->len; i--; )
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							char *key, *value;
 | 
				
			||||||
 | 
							if (!(key = mpd_parse_kv (data->vector[i], &value)))
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!strcasecmp_ascii (key, "outputid"))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (accept)
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								accept = 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if (!strcasecmp_ascii (key, "plugin"))
 | 
				
			||||||
 | 
								accept &= !strcmp (value, wanted);
 | 
				
			||||||
 | 
							else if (!strcasecmp_ascii (key, "outputenabled")
 | 
				
			||||||
 | 
								&& xstrtoul (&n, value, 10))
 | 
				
			||||||
 | 
								accept &= n == 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					mpd_on_outputs_response (const struct mpd_response *response,
 | 
				
			||||||
 | 
						const struct strv *data, void *user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						(void) user_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: check whether an action is actually necessary
 | 
				
			||||||
 | 
						pulse_free (&g.pulse);
 | 
				
			||||||
 | 
						if (response->success && !mpd_find_output (data, "pulse"))
 | 
				
			||||||
 | 
							print_debug ("MPD has no PulseAudio output to control");
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							pulse_init (&g.pulse, &g.poller);
 | 
				
			||||||
 | 
							g.pulse.on_update = app_invalidate;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app_invalidate ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_update (void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct mpd_client *c = &g.client;
 | 
				
			||||||
 | 
						if (!g.pulse_control_requested)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The read permission is sufficient for this command
 | 
				
			||||||
 | 
						mpd_client_send_command (c, "outputs", NULL);
 | 
				
			||||||
 | 
						mpd_client_add_task (c, mpd_on_outputs_response, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					pulse_disable (void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						pulse_free (&g.pulse);
 | 
				
			||||||
 | 
						app_invalidate ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#else  // ! WITH_PULSE
 | 
				
			||||||
 | 
					#define pulse_update()  BLOCK_START BLOCK_END
 | 
				
			||||||
 | 
					#define pulse_disable() BLOCK_START BLOCK_END
 | 
				
			||||||
 | 
					#endif  // ! WITH_PULSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- MPD interface -----------------------------------------------------------
 | 
					// --- MPD interface -----------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
@@ -4179,6 +4471,8 @@ mpd_on_events (unsigned subsystems, void *user_data)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if (subsystems & MPD_SUBSYSTEM_DATABASE)
 | 
						if (subsystems & MPD_SUBSYSTEM_DATABASE)
 | 
				
			||||||
		library_tab_reload (NULL);
 | 
							library_tab_reload (NULL);
 | 
				
			||||||
 | 
						if (subsystems & MPD_SUBSYSTEM_OUTPUT)
 | 
				
			||||||
 | 
							pulse_update ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (subsystems & (MPD_SUBSYSTEM_PLAYER | MPD_SUBSYSTEM_OPTIONS
 | 
						if (subsystems & (MPD_SUBSYSTEM_PLAYER | MPD_SUBSYSTEM_OPTIONS
 | 
				
			||||||
		| MPD_SUBSYSTEM_PLAYLIST | MPD_SUBSYSTEM_MIXER | MPD_SUBSYSTEM_UPDATE))
 | 
							| MPD_SUBSYSTEM_PLAYLIST | MPD_SUBSYSTEM_MIXER | MPD_SUBSYSTEM_UPDATE))
 | 
				
			||||||
@@ -4243,6 +4537,7 @@ mpd_on_ready (void)
 | 
				
			|||||||
	mpd_request_info ();
 | 
						mpd_request_info ();
 | 
				
			||||||
	library_tab_reload (NULL);
 | 
						library_tab_reload (NULL);
 | 
				
			||||||
	spectrum_setup_fifo ();
 | 
						spectrum_setup_fifo ();
 | 
				
			||||||
 | 
						pulse_update ();
 | 
				
			||||||
	mpd_enqueue_step (0);
 | 
						mpd_enqueue_step (0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -4297,6 +4592,7 @@ mpd_on_failure (void *user_data)
 | 
				
			|||||||
	info_tab_update ();
 | 
						info_tab_update ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	spectrum_discard_fifo ();
 | 
						spectrum_discard_fifo ();
 | 
				
			||||||
 | 
						pulse_disable ();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								termo
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								termo
									
									
									
									
									
								
							 Submodule termo updated: 94a77a10d8...8265f075b1
									
								
							
		Reference in New Issue
	
	Block a user