25 Commits

Author SHA1 Message Date
d0d248e44c Fix crash when scrolling too far up 2023-06-15 14:45:26 +02:00
ebda305e2d Change default active tab bar attributes
It mostly just looked weird when the underline wasn't on the last row.
2023-06-15 14:38:47 +02:00
5d5f73f22f Don't layout during rendering 2023-06-15 12:35:43 +02:00
4a9e621d92 Slightly clean up layouting 2023-06-15 11:07:47 +02:00
9cd511a2e2 Implement font fallbacks
The editor doesn't support this so far, and it could be faster.
2023-06-10 21:28:49 +02:00
b11f5d0e3c Factor out app_widget_by_id() 2023-06-09 17:44:43 +02:00
13cf0da8c4 Fix build without optional dependencies 2023-06-09 17:44:43 +02:00
f05be01fba Change volume adjustment key bindings
Use the much more obvious ones.
2023-05-30 15:17:04 +02:00
14dba91dd1 Add a go-to-playing action and binding 2023-05-30 14:41:44 +02:00
dcb2829e9b Separate the actions of aborting and quitting
The user should not be afraid of pressing Escape too many times.
2023-03-25 11:39:34 +01:00
349c907cbf X11: act on DestroyNotify rather than UnmapNotify
This makes the program survive i3 restarts, which cause a sequence
of: UnmapNotify, ReparentNotify, MapNotify.
2023-03-25 11:10:26 +01:00
0b62b2a788 Update NEWS 2023-03-07 01:56:26 +01:00
d58856571d Improve display of files lacking proper metadata 2023-03-07 01:53:50 +01:00
61fac878ad X11: fix rendering of overflowing last list items 2022-10-30 18:49:10 +01:00
da83dbee1f Bump liberty 2022-10-09 01:10:50 +02:00
41fda4e317 Bump liberty, improve fallback manual page output 2022-09-30 18:22:28 +02:00
d4d2259825 Bump liberty, make use of its new asciiman.awk 2022-09-25 21:14:36 +02:00
568abc896c 10-azlyrics.pl: fix "the" stripping 2022-09-20 12:45:45 +02:00
8aac4ae0a8 Update documentation 2022-09-20 12:24:00 +02:00
e72ed71f53 X11: support italic fonts as well
The bold + italic combination isn't supported thus far,
because it seems unnecessary.
2022-09-20 11:15:20 +02:00
28ed7a85a8 Implement lyrics lookup
There is now a generic mechanism for loading lyrics,
or any other arbitrary content related to songs.
2022-09-20 11:04:39 +02:00
b6dd940720 Implement M-u, M-l, M-c from Readline 2022-09-18 04:24:58 +02:00
d8e0d1b2fe Make M-f behave like it does in Readline 2022-09-18 01:07:47 +02:00
5cda848f94 Don't depend on a standalone C preprocessor
And get rid of the sed insanity.
2022-09-13 01:01:35 +02:00
a167ae40b3 Document configuration file key binding 2022-09-12 20:02:57 +02:00
12 changed files with 1172 additions and 256 deletions

View File

@@ -10,6 +10,14 @@ endif ()
# For custom modules
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake)
# Collect important build toggles for our simple preprocessor
# (cpp(1) isn't part of POSIX, otherwise we could reuse config.h)
set (options)
macro (add_option variable help value)
option (${ARGV})
list (APPEND options "${variable}=$<BOOL:${${variable}}>")
endmacro ()
# Dependencies
find_package (Ncursesw REQUIRED)
find_package (PkgConfig REQUIRED)
@@ -19,8 +27,8 @@ pkg_check_modules (curl REQUIRED libcurl)
include (AddThreads)
find_package (Termo QUIET NO_MODULE)
option (USE_SYSTEM_TERMO
"Don't compile our own termo library, use the system one" ${Termo_FOUND})
add_option (USE_SYSTEM_TERMO
"Don't compile our own termo library, use the system one" "${Termo_FOUND}")
if (USE_SYSTEM_TERMO)
if (NOT Termo_FOUND)
message (FATAL_ERROR "System termo library not found")
@@ -41,7 +49,8 @@ else ()
endif ()
pkg_check_modules (fftw fftw3 fftw3f)
option (WITH_FFTW "Use FFTW to enable spectrum visualisation" ${fftw_FOUND})
add_option (WITH_FFTW
"Use FFTW to enable spectrum visualisation" "${fftw_FOUND}")
if (WITH_FFTW)
if (NOT fftw_FOUND)
message (FATAL_ERROR "FFTW not found")
@@ -50,7 +59,8 @@ if (WITH_FFTW)
endif ()
pkg_check_modules (libpulse libpulse)
option (WITH_PULSE "Enable control of PulseAudio sink volume" ${libpulse_FOUND})
add_option (WITH_PULSE
"Enable PulseAudio sink volume control" "${libpulse_FOUND}")
if (WITH_PULSE)
if (NOT libpulse_FOUND)
message (FATAL_ERROR "libpulse not found")
@@ -59,7 +69,7 @@ if (WITH_PULSE)
endif ()
pkg_check_modules (x11 x11 xrender xft fontconfig)
option (WITH_X11 "Use FFTW to enable spectrum visualisation" ${x11_FOUND})
add_option (WITH_X11 "Use FFTW to enable spectrum visualisation" "${x11_FOUND}")
if (WITH_X11)
if (NOT x11_FOUND)
message (FATAL_ERROR "Some X11 libraries were not found")
@@ -95,24 +105,18 @@ foreach (extra m)
endforeach ()
# Generate a configuration file
include (GNUInstallDirs)
configure_file (${PROJECT_SOURCE_DIR}/config.h.in
${PROJECT_BINARY_DIR}/config.h)
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_awk ${PROJECT_SOURCE_DIR}/nncmpp.actions.awk)
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)
COMMAND env LC_ALL=C ${options}
awk -f ${actions_awk} ${actions_list} > ${actions}
DEPENDS ${actions_awk} ${actions_list} VERBATIM)
# Build the main executable and link it
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${actions})
@@ -121,16 +125,17 @@ target_link_libraries (${PROJECT_NAME} ${Unistring_LIBRARIES}
add_threads (${PROJECT_NAME})
# Installation
include (GNUInstallDirs)
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
install (DIRECTORY contrib DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME})
install (DIRECTORY info DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME})
# Generate documentation from text markup
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
find_program (A2X_EXECUTABLE a2x)
if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
message (FATAL_ERROR "Neither asciidoctor nor a2x were found")
message (WARNING "Neither asciidoctor nor a2x were found, "
"falling back to a substandard manual page generator")
endif ()
foreach (page ${PROJECT_NAME})
@@ -152,6 +157,14 @@ foreach (page ${PROJECT_NAME})
"${PROJECT_SOURCE_DIR}/${page}.adoc"
DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
else ()
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk)
add_custom_command (OUTPUT ${page_output}
COMMAND env LC_ALL=C asciidoc-release-version=${PROJECT_VERSION}
awk -f ${ASCIIMAN} "${PROJECT_SOURCE_DIR}/${page}.adoc"
> ${page_output}
DEPENDS ${page}.adoc ${ASCIIMAN}
COMMENT "Generating man page for ${page}" VERBATIM)
endif ()
endforeach ()

View File

@@ -1,4 +1,4 @@
Copyright (c) 2016 - 2022, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2016 - 2023, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

20
NEWS
View File

@@ -1,3 +1,23 @@
Unreleased
* Added ability to look up song lyrics,
using a new scriptable extension interface for the Info tab
* Improved song information shown in the window header
* Escape no longer quits the program
* X11: added support for font fallbacks and italic fonts
* X11: fixed rendering of overflowing, partially visible list items
* Added a "o" binding to select the currently playing song
* Added Readline-like M-u, M-l, M-c editor bindings
* Changed volume adjustment bindings to use +/- keys
2.0.0 (2022-09-03)
* Added an optional X11 user interface

View File

@@ -18,6 +18,7 @@ Features
Most stuff is there. I've been using the program exclusively for many years.
Among other things, it can display and change PulseAudio volume directly
to cover the use case of remote control, it has a fast spectrum visualiser,
it can be extended with plugins to fetch lyrics or other song-related info,
and both its appearance and key bindings can be customized.
Note that currently only the filesystem browsing mode is implemented,
@@ -37,10 +38,10 @@ The rest of this README will concern itself with externalities.
Building
--------
Build dependencies: CMake, pkg-config, asciidoctor or asciidoc,
liberty (included), termo (included) +
Build dependencies: CMake, pkg-config, awk, liberty (included),
termo (included), asciidoctor or asciidoc (recommended but optional) +
Runtime dependencies: ncursesw, libunistring, cURL +
Optional runtime dependencies: fftw3, libpulse, x11, xft
Optional runtime dependencies: fftw3, libpulse, x11, xft, Perl + cURL (lyrics)
$ git clone --recursive https://git.janouch.name/p/nncmpp.git
$ mkdir nncmpp/build

View File

@@ -4,6 +4,9 @@
#define PROGRAM_NAME "${PROJECT_NAME}"
#define PROGRAM_VERSION "${PROJECT_VERSION}"
// We use the XDG Base Directory Specification, but may be installed anywhere.
#define PROJECT_DATADIR "${CMAKE_INSTALL_FULL_DATADIR}"
#cmakedefine HAVE_RESIZETERM
#cmakedefine WITH_FFTW
#cmakedefine WITH_PULSE

43
info/10-azlyrics.pl Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env perl
# 10-azlyrics.pl: nncmpp info plugin to fetch song lyrics on AZLyrics
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Inspired by a similar ncmpc plugin.
use warnings;
use strict;
use utf8;
use open ':std', ':utf8';
unless (@ARGV) {
print "Look up on AZLyrics\n";
exit;
}
use Encode;
my ($title, $artist, $album) = map {decode_utf8($_)} @ARGV;
# TODO: An upgrade would be transliteration with, e.g., Text::Unidecode.
use Unicode::Normalize;
$artist = lc(NFD($artist)) =~ s/^the\s+//ir =~ s/[^a-z0-9]//gr;
$title = lc(NFD($title)) =~ s/\(.*?\)//gr =~ s/[^a-z0-9]//gr;
# TODO: Consider caching the results in a location like
# $XDG_CACHE_HOME/nncmpp/info/azlyrics/$artist-$title
my $found = 0;
if ($title ne '') {
open(my $curl, '-|', 'curl', '-sA', 'nncmpp/2.0',
"https://www.azlyrics.com/lyrics/$artist/$title.html") or die $!;
while (<$curl>) {
next unless /^<div>/ .. /^<\/div>/; s/<!--.*?-->//g; s/\s+$//gs;
$found = 1;
s/<\/?b>/\x01/g; s/<\/?i>/\x02/g; s/<br>/\n/; s/<.+?>//g;
s/&lt;/</g; s/&gt;/>/g; s/&quot;/"/g; s/&apos;/'/g; s/&amp;/&/g;
print;
}
close($curl) or die $?;
}
print "No lyrics have been found.\n" unless $found;

Submodule liberty updated: 63aed8f0fd...0e86ffe7c3

View File

@@ -1,7 +1,7 @@
/*
* line-editor.c: a line editor component for the TUI part of liberty
*
* Copyright (c) 2017 - 2018, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2017 - 2022, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -48,6 +48,10 @@ enum line_editor_action
LINE_EDITOR_HOME, ///< Go to start of line
LINE_EDITOR_END, ///< Go to end of line
LINE_EDITOR_UPCASE_WORD, ///< Convert word to uppercase
LINE_EDITOR_DOWNCASE_WORD, ///< Convert word to lowercase
LINE_EDITOR_CAPITALIZE_WORD, ///< Capitalize word
LINE_EDITOR_B_DELETE, ///< Delete last character
LINE_EDITOR_F_DELETE, ///< Delete next character
LINE_EDITOR_B_KILL_WORD, ///< Delete last word
@@ -185,8 +189,8 @@ line_editor_action (struct line_editor *self, enum line_editor_action action)
if (self->point + 1 > (int) self->len)
return false;
int i = self->point;
while (i < (int) self->len && self->line[i] != ' ') i++;
while (i < (int) self->len && self->line[i] == ' ') i++;
while (i < (int) self->len && self->line[i] != ' ') i++;
self->point = i;
return true;
}
@@ -197,6 +201,41 @@ line_editor_action (struct line_editor *self, enum line_editor_action action)
self->point = self->len;
return true;
case LINE_EDITOR_UPCASE_WORD:
{
int i = self->point;
for (; i < (int) self->len && self->line[i] == ' '; i++);
for (; i < (int) self->len && self->line[i] != ' '; i++)
self->line[i] = uc_toupper (self->line[i]);
self->point = i;
line_editor_changed (self);
return true;
}
case LINE_EDITOR_DOWNCASE_WORD:
{
int i = self->point;
for (; i < (int) self->len && self->line[i] == ' '; i++);
for (; i < (int) self->len && self->line[i] != ' '; i++)
self->line[i] = uc_tolower (self->line[i]);
self->point = i;
line_editor_changed (self);
return true;
}
case LINE_EDITOR_CAPITALIZE_WORD:
{
int i = self->point;
ucs4_t (*converter) (ucs4_t) = uc_totitle;
for (; i < (int) self->len && self->line[i] == ' '; i++);
for (; i < (int) self->len && self->line[i] != ' '; i++)
{
self->line[i] = converter (self->line[i]);
converter = uc_tolower;
}
self->point = i;
line_editor_changed (self);
return true;
}
case LINE_EDITOR_B_DELETE:
{
if (self->point < 1)

View File

@@ -1,9 +1,8 @@
#include "config.h"
NONE, Do nothing
QUIT, Quit
REDRAW, Redraw screen
ABORT, Abort
TAB_HELP, Switch to help tab
TAB_LAST, Switch to last tab
TAB_PREVIOUS, Switch to previous tab
@@ -28,11 +27,11 @@ MPD_CONSUME, Toggle consume
MPD_UPDATE_DB, Update MPD database
MPD_COMMAND, Send raw command to MPD
#ifdef WITH_PULSE
.ifdef WITH_PULSE
PULSE_VOLUME_UP, Increase PulseAudio volume
PULSE_VOLUME_DOWN, Decrease PulseAudio volume
PULSE_MUTE, Toggle PulseAudio sink mute
#endif
.endif
CHOOSE, Choose item
DELETE, Delete item
@@ -47,6 +46,8 @@ CENTER_CURSOR, Center the cursor
MOVE_UP, Move selection up
MOVE_DOWN, Move selection down
GOTO_PLAYING, Go to playing song
GOTO_TOP, Go to top
GOTO_BOTTOM, Go to bottom
GOTO_ITEM_PREVIOUS, Go to previous item
@@ -67,6 +68,10 @@ EDITOR_F_WORD, Go forward a word
EDITOR_HOME, Go to start of line
EDITOR_END, Go to end of line
EDITOR_UPCASE_WORD, Convert word to uppercase
EDITOR_DOWNCASE_WORD, Convert word to lowercase
EDITOR_CAPITALIZE_WORD, Capitalize word
EDITOR_B_DELETE, Delete last character
EDITOR_F_DELETE, Delete next character
EDITOR_B_KILL_WORD, Delete last word

106
nncmpp.actions.awk Normal file
View File

@@ -0,0 +1,106 @@
# nncmpp.actions.awk: produce C code for a list of user actions
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Usage: env LC_ALL=C A=0 B=1 awk -f nncmpp.actions.awk \
# nncmpp.actions > nncmpp-actions.h
# --- Preprocessor -------------------------------------------------------------
function fatal(message) {
print "// " FILENAME ":" FNR ": fatal error: " message
print FILENAME ":" FNR ": fatal error: " message > "/dev/stderr"
exit 1
}
function condition(pass, passing, a, i) {
split(substr($0, RSTART + RLENGTH), a, /[[:space:]]+/)
if (!(1 in a))
fatal("missing condition")
passing = 0
for (i in a)
if (a[i] && !pass == !ENVIRON[a[i]])
passing = 1
while (getline > 0) {
if (match($0, /^[[:space:]]*[.]endif[[:space:]]*$/))
return 1
if (match($0, /^[[:space:]]*[.]else[[:space:]]*$/))
passing = !passing
else if (!directive() && passing)
process()
}
fatal("unterminated condition body")
}
# Multiple arguments mean logical OR, multiple directives mean logical AND.
# Similar syntax is also used by Exim, BSD make, or various assemblers.
#
# Looking at what others have picked for their preprocessor syntax:
# {OpenGL, FreeBASIC} reuse #ifdef, which would be confusing with C code around,
# {Mental Ray, RapidQ and UniVerse BASIC} use $ifdef, NSIS has !ifdef,
# and Verilog went for `ifdef. Not much more can be easily found.
function directive() {
sub(/#.*/, "")
if (match($0, /^[[:space:]]*[.]ifdef[[:space:]]+/))
return condition(1)
if (match($0, /^[[:space:]]*[.]ifndef[[:space:]]+/))
return condition(0)
if (/^[[:space:]]*[.]/)
fatal("unexpected or unsupported directive")
return 0
}
!directive() {
process()
}
# --- Postprocessor ------------------------------------------------------------
function strip(string) {
gsub(/^[[:space:]]*|[[:space:]]*$/, "", string)
return string
}
function process( constant, name, description) {
if (match($0, /,/)) {
constant = name = strip(substr($0, 1, RSTART - 1))
description = strip(substr($0, RSTART + RLENGTH))
gsub(/_/, "-", name)
N++
Constants[N] = constant
Names[N] = tolower(name)
Descriptions[N] = description
} else if (/[^[:space:]]/) {
fatal("invalid action definition syntax")
}
}
function tocstring(string) {
gsub(/\\/, "\\\\", string)
gsub(/"/, "\\\"", string)
return "\"" string "\""
}
END {
print "enum action {"
for (i in Constants)
print "\t" "ACTION_" Constants[i] ","
print "\t" "ACTION_COUNT"
print "};"
print ""
print "static const char *g_action_names[] = {"
for (i in Names)
print "\t" tocstring(Names[i]) ","
print "};"
print ""
print "static const char *g_action_descriptions[] = {"
for (i in Descriptions)
print "\t" tocstring(Descriptions[i]) ","
print "};"
}

View File

@@ -81,6 +81,10 @@ The distribution contains example colour schemes in the _contrib_ directory.
// TODO: it seems like liberty should contain an includable snippet about
// the format, which could form a part of nncmpp.conf(5).
To adjust key bindings, put them within a *normal* or *editor* object.
Run *nncmpp* with the *--debug* option to find out key combinations names.
Press *?* in the help tab to learn the action identifiers to use.
Spectrum visualiser
-------------------
When built against the FFTW library, *nncmpp* can make use of MPD's "fifo"
@@ -113,8 +117,8 @@ as in the snippet above. To replace the default volume control bindings, use:
....
normal = {
"M-PageUp" = "pulse-volume-up"
"M-PageDown" = "pulse-volume-down"
"+" = "pulse-volume-up"
"-" = "pulse-volume-down"
}
....
@@ -124,6 +128,19 @@ 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.
Info plugins
------------
You can invoke various plugins from the Info tab, for example to look up
song lyrics.
Plugins can be arbitrary scripts or binaries. When run without command line
arguments, a plugin outputs a user interface description of what it does.
When invoked by a user, it receives the following self-explanatory arguments:
_TITLE_ _ARTIST_ [_ALBUM_], and anything it writes to its standard output
or standard error stream is presented back to the user. Here, bold and italic
formatting can be toggled with ASCII control characters 1 (SOH) and 2 (STX),
respectively. Otherwise, all input and output makes use of the UTF-8 encoding.
Files
-----
*nncmpp* follows the XDG Base Directory Specification.
@@ -131,6 +148,14 @@ Files
_~/.config/nncmpp/nncmpp.conf_::
The configuration file.
_~/.local/share/nncmpp/info/_::
_/usr/local/share/nncmpp/info/_::
_/usr/share/nncmpp/info/_::
Info plugins are loaded from these directories, in order,
then listed lexicographically.
Only the first occurence of a particular filename is used,
and empty files act as silent disablers.
Reporting bugs
--------------
Use https://git.janouch.name/p/nncmpp to report bugs, request features,

1081
nncmpp.c

File diff suppressed because it is too large Load Diff