diff --git a/INSTALL b/INSTALL index a9cec3f..bede127 100644 --- a/INSTALL +++ b/INSTALL @@ -5,24 +5,27 @@ mpg123 install hints You really need: -- a C compiler; we try to keep the code ANSI C89/ISO C90 compatible - gcc from 2.95 on should work, others, too - please report any issues - Actually, we have a confirmed working build (svn trunk leading to - release 0.67) on SunOS 4.1.4 with gcc-2.7.0 . +- a C99 compiler (moderate C99 support) - an (UNIX-like) operating system with standard tools; MinGW32 and Cygwin are working for Microsoft Windows, too. We also have users happily on OS/2. -- For the library only, you may get lucky with MSVC++ using the project - files under ports/ + +- For the library only, you may get lucky with MSVC++ using CMake and + ports/cmake as source directory. You can also use CMake on other + platforms for a full build, but its main purpose is for portability + where autotools don't do the trick. + - For other exotic platforms, also see ports/ + - If building from direct SCM checkout, you need GNU autotools installed - (see below). + (see developer build below). You want: - working assembler (recent GNU binutils) if using certain CPU optimizations + - headers and lib for certain audio output drivers (libasound for alsa, sdl for sdl...) @@ -31,10 +34,11 @@ You want: There is one main supported way to get your mpg123 installation consisting of -a) the mpg123, mpg123-strip, out123 program binaries +a) the mpg123, mpg123-strip, mpg123-id3dump, out123 program binaries - with libmpg123, libout123, and libsyn123 as shared libraries or statically linked - with audio output plugins for libout123, or one statically linked + b) man pages (you may want to copy some of the documentation - README, etc - to diff --git a/Makefile.in b/Makefile.in index 38728f2..fc42a4e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -372,14 +372,23 @@ check_PROGRAMS = src/tests/decode_fixed$(EXEEXT) \ @HAVE_MODULES_TRUE@am__append_96 = src/libout123/module.c @HAVE_MODULES_FALSE@am__append_97 = src/libout123/legacy_module.c -@WIN32_CODES_TRUE@am__append_98 = \ -@WIN32_CODES_TRUE@ src/win32_support.c \ -@WIN32_CODES_TRUE@ src/win32_net.c - -@WIN32_CODES_TRUE@am__append_99 = \ +@TERM_POSIX_TRUE@am__append_98 = src/term_posix.c +@TERM_WIN32_TRUE@am__append_99 = src/term_win32.c +@TERM_NONE_TRUE@am__append_100 = src/term_none.c +@NET123_EXEC_TRUE@am__append_101 = src/net123.h src/net123_exec.c +@NET123_WINHTTP_TRUE@am__append_102 = src/net123.h src/net123_winhttp.c +@NET123_WINHTTP_TRUE@am__append_103 = -lwinhttp +@NET123_WININET_TRUE@am__append_104 = src/net123.h src/net123_wininet.c +@NET123_WININET_TRUE@am__append_105 = -lwininet +@WIN32_CODES_TRUE@am__append_106 = \ @WIN32_CODES_TRUE@ src/win32_support.c -@WIN32_CODES_TRUE@am__append_100 = \ +@NETWORK_WINSOCK_TRUE@@WIN32_CODES_TRUE@am__append_107 = src/win32_net.c +@NETWORK_WINSOCK_TRUE@@WIN32_CODES_TRUE@am__append_108 = -lws2_32 +@WIN32_CODES_TRUE@am__append_109 = \ +@WIN32_CODES_TRUE@ src/win32_support.c + +@WIN32_CODES_TRUE@am__append_110 = \ @WIN32_CODES_TRUE@ src/win32_support.c subdir = . @@ -1112,10 +1121,18 @@ am__src_mpg123_SOURCES_DIST = src/audio.c src/audio.h src/common.c \ src/genre.c src/mpg123.c src/mpg123app.h src/metaprint.c \ src/metaprint.h src/local.h src/local.c src/playlist.c \ src/playlist.h src/streamdump.h src/streamdump.c src/term.c \ - src/term.h src/win32_support.h src/win32_support.c \ - src/win32_net.c -@WIN32_CODES_TRUE@am__objects_70 = src/win32_support.$(OBJEXT) \ -@WIN32_CODES_TRUE@ src/win32_net.$(OBJEXT) + src/term.h src/terms.h src/win32_support.h src/term_posix.c \ + src/term_win32.c src/term_none.c src/net123.h \ + src/net123_exec.c src/net123_winhttp.c src/net123_wininet.c \ + src/win32_support.c src/win32_net.c +@TERM_POSIX_TRUE@am__objects_70 = src/term_posix.$(OBJEXT) +@TERM_WIN32_TRUE@am__objects_71 = src/term_win32.$(OBJEXT) +@TERM_NONE_TRUE@am__objects_72 = src/term_none.$(OBJEXT) +@NET123_EXEC_TRUE@am__objects_73 = src/net123_exec.$(OBJEXT) +@NET123_WINHTTP_TRUE@am__objects_74 = src/net123_winhttp.$(OBJEXT) +@NET123_WININET_TRUE@am__objects_75 = src/net123_wininet.$(OBJEXT) +@WIN32_CODES_TRUE@am__objects_76 = src/win32_support.$(OBJEXT) +@NETWORK_WINSOCK_TRUE@@WIN32_CODES_TRUE@am__objects_77 = src/win32_net.$(OBJEXT) am_src_mpg123_OBJECTS = src/audio.$(OBJEXT) src/common.$(OBJEXT) \ src/sysutil.$(OBJEXT) src/control_generic.$(OBJEXT) \ src/equalizer.$(OBJEXT) src/getlopt.$(OBJEXT) \ @@ -1123,19 +1140,22 @@ am_src_mpg123_OBJECTS = src/audio.$(OBJEXT) src/common.$(OBJEXT) \ src/genre.$(OBJEXT) src/mpg123.$(OBJEXT) \ src/metaprint.$(OBJEXT) src/local.$(OBJEXT) \ src/playlist.$(OBJEXT) src/streamdump.$(OBJEXT) \ - src/term.$(OBJEXT) $(am__objects_70) + src/term.$(OBJEXT) $(am__objects_70) $(am__objects_71) \ + $(am__objects_72) $(am__objects_73) $(am__objects_74) \ + $(am__objects_75) $(am__objects_76) $(am__objects_77) src_mpg123_OBJECTS = $(am_src_mpg123_OBJECTS) +am__DEPENDENCIES_1 = src_mpg123_DEPENDENCIES = src/compat/libcompat.la \ src/libmpg123/libmpg123.la src/libout123/libout123.la \ - src/libsyn123/libsyn123.la + src/libsyn123/libsyn123.la $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) src_mpg123_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(src_mpg123_LDFLAGS) $(LDFLAGS) -o $@ am__src_mpg123_id3dump_SOURCES_DIST = src/mpg123-id3dump.c \ src/getlopt.c src/getlopt.h src/win32_support.c -@WIN32_CODES_TRUE@am__objects_71 = src/win32_support.$(OBJEXT) am_src_mpg123_id3dump_OBJECTS = src/mpg123-id3dump.$(OBJEXT) \ - src/getlopt.$(OBJEXT) $(am__objects_71) + src/getlopt.$(OBJEXT) $(am__objects_76) src_mpg123_id3dump_OBJECTS = $(am_src_mpg123_id3dump_OBJECTS) src_mpg123_id3dump_DEPENDENCIES = src/compat/libcompat.la \ src/libmpg123/libmpg123.la @@ -1150,7 +1170,7 @@ am__src_out123_SOURCES_DIST = src/sysutil.c src/sysutil.h src/common.h \ src/win32_support.h src/win32_support.c am_src_out123_OBJECTS = src/sysutil.$(OBJEXT) src/getlopt.$(OBJEXT) \ src/local.$(OBJEXT) src/filters.$(OBJEXT) src/out123.$(OBJEXT) \ - $(am__objects_71) + $(am__objects_76) src_out123_OBJECTS = $(am_src_out123_OBJECTS) src_out123_DEPENDENCIES = src/compat/libcompat.la \ src/libsyn123/libsyn123.la src/libout123/libout123.la @@ -1220,11 +1240,15 @@ am__depfiles_remade = src/$(DEPDIR)/audio.Po src/$(DEPDIR)/common.Po \ src/$(DEPDIR)/getlopt.Po src/$(DEPDIR)/httpget.Po \ src/$(DEPDIR)/local.Po src/$(DEPDIR)/metaprint.Po \ src/$(DEPDIR)/mpg123-id3dump.Po src/$(DEPDIR)/mpg123-strip.Po \ - src/$(DEPDIR)/mpg123.Po src/$(DEPDIR)/out123.Po \ + src/$(DEPDIR)/mpg123.Po src/$(DEPDIR)/net123_exec.Po \ + src/$(DEPDIR)/net123_winhttp.Po \ + src/$(DEPDIR)/net123_wininet.Po src/$(DEPDIR)/out123.Po \ src/$(DEPDIR)/playlist.Po src/$(DEPDIR)/resolver.Po \ src/$(DEPDIR)/streamdump.Po src/$(DEPDIR)/sysutil.Po \ - src/$(DEPDIR)/term.Po src/$(DEPDIR)/win32_net.Po \ - src/$(DEPDIR)/win32_support.Po src/compat/$(DEPDIR)/compat.Plo \ + src/$(DEPDIR)/term.Po src/$(DEPDIR)/term_none.Po \ + src/$(DEPDIR)/term_posix.Po src/$(DEPDIR)/term_win32.Po \ + src/$(DEPDIR)/win32_net.Po src/$(DEPDIR)/win32_support.Po \ + src/compat/$(DEPDIR)/compat.Plo \ src/compat/$(DEPDIR)/compat_dl.Plo \ src/compat/$(DEPDIR)/compat_str.Plo \ src/libmpg123/$(DEPDIR)/calctables.Po \ @@ -2001,12 +2025,12 @@ EXTRA_DIST = src/tests/testtext.sh src/tests/decode_fixed.sh \ doc/ROAD_TO_LGPL doc/TODO doc/LICENSE doc/THANKS doc/ACCURACY \ doc/LARGEFILE doc/libmpg123_speed.txt doc/doxyhead.xhtml \ doc/doxyfoot.xhtml doc/doxy_examples.c doc/doxygen.conf \ - doc/examples/mpg123_to_out123.c doc/examples/scan.c \ - doc/examples/mpglib.c doc/examples/id3dump.c \ - doc/examples/feedseek.c doc/examples/dump_seekindex.c \ - doc/examples/extract_frames.c doc/examples/Makefile \ - mpg123.spec makedll.sh windows-builds.sh equalize.dat \ - NEWS.libmpg123 NEWS.libout123 NEWS.libsyn123 \ + doc/windows-notes.html doc/examples/mpg123_to_out123.c \ + doc/examples/scan.c doc/examples/mpglib.c \ + doc/examples/id3dump.c doc/examples/feedseek.c \ + doc/examples/dump_seekindex.c doc/examples/extract_frames.c \ + doc/examples/Makefile mpg123.spec makedll.sh windows-builds.sh \ + equalize.dat NEWS.libmpg123 NEWS.libout123 NEWS.libsyn123 \ ports/cmake/CMakeLists.txt ports/cmake/mpg123-config.cmake.in \ ports/cmake/cmake/search_libs.cmake \ ports/cmake/cmake/read_api_version.cmake \ @@ -2854,13 +2878,10 @@ src_libsyn123_libsyn123_la_SOURCES = \ src/libsyn123/filter.c \ src/libsyn123/sampleconv.c -src_mpg123_LDADD = \ - src/compat/libcompat.la \ - src/libmpg123/libmpg123.la \ - src/libout123/libout123.la \ - src/libsyn123/libsyn123.la \ - @PROG_LIBS@ - +src_mpg123_LDADD = src/compat/libcompat.la src/libmpg123/libmpg123.la \ + src/libout123/libout123.la src/libsyn123/libsyn123.la \ + @PROG_LIBS@ $(am__append_103) $(am__append_105) \ + $(am__append_108) src_mpg123_LDFLAGS = @EXEC_LT_LDFLAGS@ src_out123_LDADD = \ src/compat/libcompat.la \ @@ -2888,15 +2909,18 @@ src_mpg123_SOURCES = src/audio.c src/audio.h src/common.c src/common.h \ src/genre.c src/mpg123.c src/mpg123app.h src/metaprint.c \ src/metaprint.h src/local.h src/local.c src/playlist.c \ src/playlist.h src/streamdump.h src/streamdump.c src/term.c \ - src/term.h src/win32_support.h $(am__append_98) + src/term.h src/terms.h src/win32_support.h $(am__append_98) \ + $(am__append_99) $(am__append_100) $(am__append_101) \ + $(am__append_102) $(am__append_104) $(am__append_106) \ + $(am__append_107) # Replace common.h by sysutil.h! src_out123_SOURCES = src/sysutil.c src/sysutil.h src/common.h \ src/getlopt.c src/getlopt.h src/local.h src/local.c \ src/filters.h src/filters.c src/out123.c src/mpg123app.h \ - src/win32_support.h $(am__append_99) + src/win32_support.h $(am__append_109) src_mpg123_id3dump_SOURCES = src/mpg123-id3dump.c src/getlopt.c \ - src/getlopt.h $(am__append_100) + src/getlopt.h $(am__append_110) src_mpg123_strip_SOURCES = \ src/mpg123-strip.c \ src/getlopt.c \ @@ -3689,6 +3713,18 @@ src/playlist.$(OBJEXT): src/$(am__dirstamp) \ src/streamdump.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/term.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/term_posix.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/term_win32.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/term_none.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/net123_exec.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/net123_winhttp.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/net123_wininet.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) src/win32_support.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/win32_net.$(OBJEXT): src/$(am__dirstamp) \ @@ -3815,12 +3851,18 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/mpg123-id3dump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/mpg123-strip.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/mpg123.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/net123_exec.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/net123_winhttp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/net123_wininet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/out123.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/playlist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/resolver.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/streamdump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sysutil.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/term.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/term_none.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/term_posix.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/term_win32.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/win32_net.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/win32_support.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/compat/$(DEPDIR)/compat.Plo@am__quote@ # am--include-marker @@ -5342,12 +5384,18 @@ distclean: distclean-am -rm -f src/$(DEPDIR)/mpg123-id3dump.Po -rm -f src/$(DEPDIR)/mpg123-strip.Po -rm -f src/$(DEPDIR)/mpg123.Po + -rm -f src/$(DEPDIR)/net123_exec.Po + -rm -f src/$(DEPDIR)/net123_winhttp.Po + -rm -f src/$(DEPDIR)/net123_wininet.Po -rm -f src/$(DEPDIR)/out123.Po -rm -f src/$(DEPDIR)/playlist.Po -rm -f src/$(DEPDIR)/resolver.Po -rm -f src/$(DEPDIR)/streamdump.Po -rm -f src/$(DEPDIR)/sysutil.Po -rm -f src/$(DEPDIR)/term.Po + -rm -f src/$(DEPDIR)/term_none.Po + -rm -f src/$(DEPDIR)/term_posix.Po + -rm -f src/$(DEPDIR)/term_win32.Po -rm -f src/$(DEPDIR)/win32_net.Po -rm -f src/$(DEPDIR)/win32_support.Po -rm -f src/compat/$(DEPDIR)/compat.Plo @@ -5590,12 +5638,18 @@ maintainer-clean: maintainer-clean-am -rm -f src/$(DEPDIR)/mpg123-id3dump.Po -rm -f src/$(DEPDIR)/mpg123-strip.Po -rm -f src/$(DEPDIR)/mpg123.Po + -rm -f src/$(DEPDIR)/net123_exec.Po + -rm -f src/$(DEPDIR)/net123_winhttp.Po + -rm -f src/$(DEPDIR)/net123_wininet.Po -rm -f src/$(DEPDIR)/out123.Po -rm -f src/$(DEPDIR)/playlist.Po -rm -f src/$(DEPDIR)/resolver.Po -rm -f src/$(DEPDIR)/streamdump.Po -rm -f src/$(DEPDIR)/sysutil.Po -rm -f src/$(DEPDIR)/term.Po + -rm -f src/$(DEPDIR)/term_none.Po + -rm -f src/$(DEPDIR)/term_posix.Po + -rm -f src/$(DEPDIR)/term_win32.Po -rm -f src/$(DEPDIR)/win32_net.Po -rm -f src/$(DEPDIR)/win32_support.Po -rm -f src/compat/$(DEPDIR)/compat.Plo diff --git a/NEWS b/NEWS index 93ccde0..dd0e302 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,49 @@ +1.30.0 +------ +- build: +-- Use dummy as default module when no other outputs are enabled. This also + fixes a non-module build with just the dummy (bug 333). +-- Use CMAKE_CURRENT_SOURCE_DIR in CMake build to help nested use (bug 335). +-- some updates for OS/2 support (fixing up stdin playing, for example) +- mpg123: +-- new network backend using external tools/libraries to also support HTTPS +-- old network backend changed to use h_addr_list[0] instead of h_addr +-- terminal control keys now case-sensitive (fixing smal/big pitch controls) +-- additional terminal control keys for simple equalizer control (A/a for bass, + J/j for mids, N/n for treble, e for reset, E for printout) +-- terminal volume control now in decibel steps and bounded to +/- 60 dB +-- terminal control now also with audio from stdin (bug 338) via + /dev/tty or ctermid() +-- terminal control also available for OS/2 and Windows platforms +-- re-print tag info on decrease of terminal width for a bit less mess +-- always print an empty line after tag info for cleaner appearance +-- print lyrics also to stderr +-- remote control API v10 with "@P 3" as additonal message on track end +-- also added PROGRESS command as opposite of SILENCE +-- fix some verbosity, tweak help for --icy-interval +-- added --auth-file +-- also obscure argument to --auth for others +-- Cygwin/MinGW: Provide _win32_utf8_wide and _win32_wide_utf8 unconditionally. + It is needed by the WASAPI plugins, the underlying conversion functions + should be present since Windows 2000. Fixes WASAPI support on Cygwin. + Also needed for new network code. +- libout123: +-- pulse: initialize more error codes to avoid bogus error messages +-- os2: considerable fixup for proper writes of full buffers avoiding + nasty effects from the ... special audio system, more cleanup still + nice-to-have, but still lacking + +1.29.4 +------ +- libmpg123: +-- Saturate reader file position at off_t limit to satisfy + undefined behaviour checkers. +-- Avoid harmless unitialized value in ID3v1 check (filepos, later being + set before actual use). +- libout123: +-- Build fix for win32_wasapi output for predefined _WIN32_WINNT (bug 329), + thanks to Vincent Torri. + 1.29.3 ------ - libmpg123: Catch more NULL pointer arguments in LFS wrappers diff --git a/NEWS.libmpg123 b/NEWS.libmpg123 index dd66905..32ddf68 100644 --- a/NEWS.libmpg123 +++ b/NEWS.libmpg123 @@ -1,5 +1,7 @@ Changes in libmpg123 libtool interface versions... +47.0.47 - Added mpg123_eq_bands(), mpg123_eq_change() and mpg123_volume_change_db(). + 46.0.46 - Functions mpg123_init() and mpg123_exit() are really no-ops now. There is no need to call them, and no harm done calling them in diff --git a/configure b/configure index d3404be..db3d7f4 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for mpg123 1.29.3. +# Generated by GNU Autoconf 2.69 for mpg123 1.30.0. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='mpg123' PACKAGE_TARNAME='mpg123' -PACKAGE_VERSION='1.29.3' -PACKAGE_STRING='mpg123 1.29.3' +PACKAGE_VERSION='1.30.0' +PACKAGE_STRING='mpg123 1.30.0' PACKAGE_BUGREPORT='maintainer@mpg123.org' PACKAGE_URL='' @@ -641,6 +641,20 @@ LIBOUT123_LIBS LIBSYN123_LIBS LIBMPG123_LIBS PROG_LIBS +TERM_WIN32_FALSE +TERM_WIN32_TRUE +TERM_NONE_FALSE +TERM_NONE_TRUE +TERM_POSIX_FALSE +TERM_POSIX_TRUE +NET123_WININET_FALSE +NET123_WININET_TRUE +NET123_WINHTTP_FALSE +NET123_WINHTTP_TRUE +NET123_EXEC_FALSE +NET123_EXEC_TRUE +NETWORK_WINSOCK_FALSE +NETWORK_WINSOCK_TRUE WIN32_CODES_FALSE WIN32_CODES_TRUE BUILD_BUFFER_FALSE @@ -1060,8 +1074,6 @@ enable_xdebug enable_nagging enable_gapless enable_fifo -enable_ipv6 -enable_network enable_id3v2 enable_string enable_icy @@ -1094,6 +1106,9 @@ with_seektable enable_largefile enable_lfs_alias enable_feature_report +enable_ipv6 +enable_network +with_network ' ac_precious_vars='build_alias host_alias @@ -1668,7 +1683,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures mpg123 1.29.3 to adapt to many kinds of systems. +\`configure' configures mpg123 1.30.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1739,7 +1754,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of mpg123 1.29.3:";; + short | recursive ) echo "Configuration of mpg123 1.30.0:";; esac cat <<\_ACEOF @@ -1764,8 +1779,6 @@ Optional Features: --enable-nagging=no/yes turn on GCC's pedantic nagging with error on warnings, does not include --enable-debug anymore --enable-gapless=no/yes turn on gapless (enabled per default) --enable-fifo=no/yes FIFO support for control interface (auto-enabled on linux) - --enable-ipv6=no/yes IPv6 support (actually any protocol your libc does with getaddrinfo) - --enable-network=no/yes network support (http streams / webradio) --disable-id3v2=no/yes no ID3v2 parsing --disable-string=no/yes no string API (this will disable ID3v2; main mpg123 won't build anymore) --disable-icy=no/yes no ICY metainfo parsing/conversion (main mpg123 won't build!) @@ -1793,6 +1806,8 @@ Optional Features: --disable-largefile omit support for large files --disable-lfs-alias disable alias wrappers for largefile bitness (mpg123_seek_32 or mpg123_seek_64 in addition to mpg123_seek, or the other way around; It is a mess, do not play with this!) --disable-feature_report Disable feature report function + --enable-ipv6=no/yes IPv6 support in internal network stack + --enable-network=no/yes network support (http streams / webradio), if available (overruled by --with-network!) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1875,6 +1890,9 @@ Optional Packages: --with-seektable= choose size of seek index table (0 disables it), default 1000 + --with-network= Available options, depending on platform, are auto, + none, internal, winhttp, wininet, and exec (wget or + curl binaries). Some influential environment variables: CC C compiler command @@ -1970,7 +1988,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -mpg123 configure 1.29.3 +mpg123 configure 1.30.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2576,7 +2594,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by mpg123 $as_me 1.29.3, which was +It was created by mpg123 $as_me 1.30.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2925,11 +2943,11 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -API_VERSION=46 -LIB_PATCHLEVEL=7 +API_VERSION=47 +LIB_PATCHLEVEL=0 OUTAPI_VERSION=4 -OUTLIB_PATCHLEVEL=3 +OUTLIB_PATCHLEVEL=4 SYNAPI_VERSION=1 SYNLIB_PATCHLEVEL=4 @@ -3535,7 +3553,7 @@ fi # Define the identity of the package. PACKAGE='mpg123' - VERSION='1.29.3' + VERSION='1.30.0' cat >>confdefs.h <<_ACEOF @@ -14401,42 +14419,6 @@ else fi -# Check whether --enable-ipv6 was given. -if test "${enable_ipv6+set}" = set; then : - enableval=$enable_ipv6; - if test "x$enableval" = xyes - then - ipv6="enabled" - else - ipv6="disabled" - fi - -else - - ipv6="auto" - - -fi - - -# Check whether --enable-network was given. -if test "${enable_network+set}" = set; then : - enableval=$enable_network; - if test "x$enableval" = xyes - then - network="enabled" - else - network="disabled" - fi - -else - - network="auto" - - -fi - - id3v2=enabled @@ -16039,6 +16021,7 @@ done fi +term_type=none # Check if system supports termios { $as_echo "$as_me:${as_lineno-$LINENO}: checking POSIX termios" >&5 $as_echo_n "checking POSIX termios... " >&6; } @@ -16077,6 +16060,7 @@ cat >>confdefs.h <<_ACEOF #define HAVE_TERMIOS 1 _ACEOF + term_type=posix fi for ac_func in random @@ -16173,6 +16157,77 @@ fi done +for ac_func in fork execvp +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + have_fork=yes +else + have_fork=no; break +fi +done + + +for ac_func in ctermid +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + +#AC_CHECK_FUNCS( _setmode setmode ) +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include + +int main() +{ + _setmode(STDIN_FILENO, _O_BINARY); + _setmode(STDOUT_FILENO, _O_TEXT); + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + +$as_echo "#define HAVE__SETMODE 1" >>confdefs.h + +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include + +int main() +{ + setmode(STDIN_FILENO, O_BINARY); + setmode(STDOUT_FILENO, O_TEXT); + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + +$as_echo "#define HAVE_SETMODE 1" >>confdefs.h + +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + # locale headers for ac_header in locale.h langinfo.h wchar.h wctype.h @@ -16189,7 +16244,7 @@ fi done # Headers for network (http) stuff -network_type=Unknown +network_internal=unknown for ac_header in netdb.h sys/param.h sys/socket.h netinet/in.h arpa/inet.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` @@ -16209,7 +16264,7 @@ if test "x$ac_cv_header_netdb_h" = "xyes" && test "x$ac_cv_header_netinet_in_h" = "xyes" && test "x$ac_cv_header_arpa_inet_h" = "xyes"; then have_network=yes - network_type=Posix + network_internal=posix else have_network=no fi @@ -16494,54 +16549,20 @@ ADD_CPPFLAGS="$sys_cppflags" ADD_LDFLAGS="" LIBS="$LIBS" -# Consider moving that stuff. -ac_fn_c_check_header_mongrel "$LINENO" "os2.h" "ac_cv_header_os2_h" "$ac_includes_default" +# Remove that if it is settled that tcsetattr on OS/2 is unsalvageable anyway. +# The only user of os2.h is the output module. +for ac_header in os2.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "os2.h" "ac_cv_header_os2_h" "$ac_includes_default" if test "x$ac_cv_header_os2_h" = xyes; then : - ADD_CPPFLAGS="$ADD_CPPFLAGS -DOS2" -fi - - -# On OS/2, we need to link to os2term to make terminal control actually work. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for tcsetattr in -los2term" >&5 -$as_echo_n "checking for tcsetattr in -los2term... " >&6; } -if ${ac_cv_lib_os2term_tcsetattr+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-los2term $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char tcsetattr (); -int -main () -{ -return tcsetattr (); - ; - return 0; -} + cat >>confdefs.h <<_ACEOF +#define HAVE_OS2_H 1 _ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_os2term_tcsetattr=yes -else - ac_cv_lib_os2term_tcsetattr=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_os2term_tcsetattr" >&5 -$as_echo "$ac_cv_lib_os2term_tcsetattr" >&6; } -if test "x$ac_cv_lib_os2term_tcsetattr" = xyes; then : - ADD_LDFLAGS="$ADD_LDFLAGS -los2term" + fi +done + # If debugging is enabled, just enable debugging symbols. # All other stuff enters nagging territory. @@ -17047,7 +17068,10 @@ output_modules= check_forced=no check_failed=no if test "x$with_audio" != "x"; then - check_modules="`echo $with_audio|tr , ' '` dummy" + check_modules=`echo $with_audio|tr , ' '` + if ! echo "$check_modules" | grep -qw dummy; then + check_modules="$check_modules dummy" + fi echo "Limiting outputs to build according to your preference: $check_modules" check_forced=yes fi @@ -18298,6 +18322,9 @@ done ;; os2) OS2_LIBS="-lcx -lmmpm2" + OS2_CFLAGS="-idirafter /@unixroot/usr/include/os2tk45" + oldcflags="$CFLAGS" + CFLAGS="$CFLAGS $OS2_CFLAGS" for ac_header in os2.h do : ac_fn_c_check_header_mongrel "$LINENO" "os2.h" "ac_cv_header_os2_h" "$ac_includes_default" @@ -18319,6 +18346,7 @@ done for ac_header in os2me.h do : ac_fn_c_check_header_compile "$LINENO" "os2me.h" "ac_cv_header_os2me_h" "#define INCL_OS2MM +#undef VERSION #define INCL_DOS #define INCL_VIO #define INCL_KBD @@ -18343,6 +18371,7 @@ done else check_failed=yes fi + CFLAGS="$oldcflags" ;; # from here on only forced tests, untested code hp) @@ -18760,6 +18789,15 @@ fi default_output_module=`first_word $default_output_modules` +# Without actual modules, there is only the builtin one. +if test x"$modules" = xdisabled; then + # Module-less build needs _some_ default module. + if test -z "$default_output_module"; then + default_output_module=dummy + fi + default_output_modules=$default_output_module +fi + # Setup the static build. # The conditionals always need to be defined by configure, even if # HAVE_MODULES is FALSE! @@ -18964,10 +19002,6 @@ else fi -# Without actual modules, there is only the builtin one. -if test x"$modules" = xdisabled; then - default_output_modules=$default_output_module -fi default_modstring=`echo "$default_output_modules"|tr ' ' ,` cat >>confdefs.h <<_ACEOF @@ -19060,21 +19094,14 @@ _ACEOF # Check if we want Unicode for Win32. Cygwin and Midipix does not need _wopen -if test "x$ac_cv_header_windows_h" = xyes && - test "x$host_os" != xcygwin && - test "x$host_os" != xmidipix; then - win32_specific_codes=enabled -else - win32_specific_codes=disabled -fi +COMPAT_LIBS= + win32_unicode=unneeded win32_sockets=disabled win32_sockets_working=no win32_wide_working=no win32_winver_bump=no -COMPAT_LIBS= - ### mingw.org may not work properly for newer APIs case $host_os in *mingw*) @@ -19096,6 +19123,11 @@ fi ;; esac +# enable win32 code +if test "x$ac_cv_header_windows_h" = xyes -a "x$host_os" != xcygwin -a "x$host_os" != xmidipix; then + win32_specific_codes=enabled +fi + if test "x$win32_specific_codes" = xenabled; then #### Check for Wide functions ac_fn_c_check_func "$LINENO" "_wopen" "ac_cv_func__wopen" @@ -19202,7 +19234,7 @@ done { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we want Win32 sockets" >&5 $as_echo_n "checking if we want Win32 sockets... " >&6; } - if test "x$win32_sockets" = "xenabled" && test "x$network" != "xdisabled"; then + if test "x$win32_sockets" = "xenabled"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking if winsock2 API is available" >&5 @@ -19261,17 +19293,14 @@ rm -f core conftest.err conftest.$ac_objext \ if test "x$win32_sockets_working" = "xyes"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - -$as_echo "#define WANT_WIN32_SOCKETS 1" >>confdefs.h - - network_type=Winsock2 + network_internal=winsock2 have_network=yes have_ipv6=yes else - LIBS="$wsoldlibs" { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Please update your headers to support winsock 2.2." >&5 $as_echo "$as_me: WARNING: Please update your headers to support winsock 2.2." >&2;} fi + LIBS="$wsoldlibs" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } @@ -19436,6 +19465,123 @@ $as_echo "#define EOVERFLOW EFBIG" >>confdefs.h fi +# old network choice + +# Check whether --enable-ipv6 was given. +if test "${enable_ipv6+set}" = set; then : + enableval=$enable_ipv6; + if test "x$enableval" = xyes + then + ipv6="enabled" + else + ipv6="disabled" + fi + +else + + ipv6="auto" + + +fi + + +network_type=auto + +# Check whether --enable-network was given. +if test "${enable_network+set}" = set; then : + enableval=$enable_network; + if test "x$enableval" = xyes + then + network_type=auto + else + network_type=disabled + fi + +fi + + +# new network choice + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for preferred network support" >&5 +$as_echo_n "checking for preferred network support... " >&6; } + +# Check whether --with-network was given. +if test "${with_network+set}" = set; then : + withval=$with_network; + case "$withval" in + exec) + network_type="exec" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: exec" >&5 +$as_echo "exec" >&6; } + ;; + winhttp) + network_type="winhttp" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: winhttp" >&5 +$as_echo "winhttp" >&6; } + ;; + wininet) + network_type="wininet" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: wininet" >&5 +$as_echo "wininet" >&6; } + ;; + internal) + network_type="internal" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: internal" >&5 +$as_echo "internal" >&6; } + ;; + yes|auto) + network_type="auto" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: auto" >&5 +$as_echo "auto" >&6; } + ;; + no|none) + network_type="disabled" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } + ;; + *) + as_fn_error $? "Unknown network option \"$withval\"" "$LINENO" 5 + ;; + esac + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $network_type" >&5 +$as_echo "$network_type" >&6; } + + +fi + + +if test x$network_type = xauto; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for automatic network support" >&5 +$as_echo_n "checking for automatic network support... " >&6; } + if test "x$have_fork" = "xyes"; then + network_type=exec + elif test "x$win32_specific_codes" = "xenabled"; then + network_type=winhttp + elif test "x$have_network" = "xyes"; then + network_type="internal" + else + network_type="disabled" + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $network_type" >&5 +$as_echo "$network_type" >&6; } +fi + +# new net123 +if test x$network_type != xinternal -a x$network_type != xdisabled; then + +$as_echo "#define NET123 1" >>confdefs.h + +fi +if test x$network_type = xexec -a x$have_fork != xyes; then + as_fn_error $? "exec network support selected but fork not available" "$LINENO" 5 +fi +if test x$network_type = xwininet -o x$network_type = xwinhttp && test x$win32_specific_codes != xenabled; then + as_fn_error $? "wininet or winhttp is currently only for Windows" "$LINENO" 5 +fi + #### Use Win32 support codes if test "x$win32_specific_codes" = xenabled ; then WIN32_CODES_TRUE= @@ -19446,21 +19592,83 @@ else fi -if test x"$network" = xauto; then - if test x"$have_network" = xyes; then - network=enabled - else - network=disabled - fi -fi if test x"$ipv6" = xauto; then - if test x"$have_ipv6" = xyes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking IPv6 use" >&5 +$as_echo_n "checking IPv6 use... " >&6; } + if test x"$have_ipv6" = xyes -a x"$network_type" = xinternal; then ipv6=enabled else ipv6=disabled fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ipv6" >&5 +$as_echo "$ipv6" >&6; } fi +if test x$network_type = xinternal -a x$network_internal = xwinsock2; then + +$as_echo "#define WANT_WIN32_SOCKETS 1" >>confdefs.h + +fi + + if test x$network_type = xinternal -a x$network_internal = xwinsock2 ; then + NETWORK_WINSOCK_TRUE= + NETWORK_WINSOCK_FALSE='#' +else + NETWORK_WINSOCK_TRUE='#' + NETWORK_WINSOCK_FALSE= +fi + + if test x$network_type = xexec ; then + NET123_EXEC_TRUE= + NET123_EXEC_FALSE='#' +else + NET123_EXEC_TRUE='#' + NET123_EXEC_FALSE= +fi + + if test x$network_type = xwinhttp ; then + NET123_WINHTTP_TRUE= + NET123_WINHTTP_FALSE='#' +else + NET123_WINHTTP_TRUE='#' + NET123_WINHTTP_FALSE= +fi + + if test x$network_type = xwininet ; then + NET123_WININET_TRUE= + NET123_WININET_FALSE='#' +else + NET123_WININET_TRUE='#' + NET123_WININET_FALSE= +fi + + + + if test "x$term_type" = xposix ; then + TERM_POSIX_TRUE= + TERM_POSIX_FALSE='#' +else + TERM_POSIX_TRUE='#' + TERM_POSIX_FALSE= +fi + + if test "x$term_type" = xnone ; then + TERM_NONE_TRUE= + TERM_NONE_FALSE='#' +else + TERM_NONE_TRUE='#' + TERM_NONE_FALSE= +fi + + if test "x$term_type" = xwin32 ; then + TERM_WIN32_TRUE= + TERM_WIN32_FALSE='#' +else + TERM_WIN32_TRUE='#' + TERM_WIN32_FALSE= +fi + + if test x"$fifo" = xauto; then if test x"$have_mkfifo" = xyes; then @@ -19480,7 +19688,7 @@ $as_echo "$as_me: WARNING: You forced FIFO code while I think there is no mkfif fi fi -if test x"$network" = xenabled; then +if test x"$network_type" = xinternal; then $as_echo "#define NETWORK 1" >>confdefs.h @@ -20023,6 +20231,34 @@ if test -z "${WIN32_CODES_TRUE}" && test -z "${WIN32_CODES_FALSE}"; then as_fn_error $? "conditional \"WIN32_CODES\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${NETWORK_WINSOCK_TRUE}" && test -z "${NETWORK_WINSOCK_FALSE}"; then + as_fn_error $? "conditional \"NETWORK_WINSOCK\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${NET123_EXEC_TRUE}" && test -z "${NET123_EXEC_FALSE}"; then + as_fn_error $? "conditional \"NET123_EXEC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${NET123_WINHTTP_TRUE}" && test -z "${NET123_WINHTTP_FALSE}"; then + as_fn_error $? "conditional \"NET123_WINHTTP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${NET123_WININET_TRUE}" && test -z "${NET123_WININET_FALSE}"; then + as_fn_error $? "conditional \"NET123_WININET\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${TERM_POSIX_TRUE}" && test -z "${TERM_POSIX_FALSE}"; then + as_fn_error $? "conditional \"TERM_POSIX\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${TERM_NONE_TRUE}" && test -z "${TERM_NONE_FALSE}"; then + as_fn_error $? "conditional \"TERM_NONE\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${TERM_WIN32_TRUE}" && test -z "${TERM_WIN32_FALSE}"; then + as_fn_error $? "conditional \"TERM_WIN32\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 @@ -20420,7 +20656,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by mpg123 $as_me 1.29.3, which was +This file was extended by mpg123 $as_me 1.30.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -20486,7 +20722,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -mpg123 config.status 1.29.3 +mpg123 config.status 1.30.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" @@ -22185,8 +22421,6 @@ fi - - echo " $PACKAGE_NAME $PACKAGE_VERSION @@ -22195,13 +22429,16 @@ echo " Compiler Optimization ... $with_optimization Gapless Support ......... $gapless Debugging ............... $debugging + Terminal control ........ $term_type Extreme debugging ....... $xdebugging Seek table size ......... $seektable FIFO support ............ $fifo Buffer .................. $buffer - Network (http streams) .. $network - Network Sockets ......... $network_type + Network (http streams) .. $network_type" +if test x$network_type = xinternal; then + echo " Internal network type ... $network_internal IPv6 (getaddrinfo) ...... $ipv6" +fi if test x"$LARGEFILE_BITS" = x; then echo " File offsets ............ default" else diff --git a/configure.ac b/configure.ac index 3ca8766..9eb685d 100644 --- a/configure.ac +++ b/configure.ac @@ -9,16 +9,16 @@ dnl 2.69 at least. AC_PREREQ([2.69]) dnl ############# Initialisation -AC_INIT([mpg123], [1.29.3], [maintainer@mpg123.org]) +AC_INIT([mpg123], [1.30.0], [maintainer@mpg123.org]) dnl Increment API_VERSION when the API gets changes (new functions). dnl libmpg123 -API_VERSION=46 -LIB_PATCHLEVEL=7 +API_VERSION=47 +LIB_PATCHLEVEL=0 dnl libout123 OUTAPI_VERSION=4 -OUTLIB_PATCHLEVEL=3 +OUTLIB_PATCHLEVEL=4 dnl libsyn123 SYNAPI_VERSION=1 @@ -370,36 +370,6 @@ AC_ARG_ENABLE(fifo, ] ) -AC_ARG_ENABLE(ipv6, - [ --enable-ipv6=[no/yes] IPv6 support (actually any protocol your libc does with getaddrinfo) ], - [ - if test "x$enableval" = xyes - then - ipv6="enabled" - else - ipv6="disabled" - fi - ], - [ - ipv6="auto" - ] -) - -AC_ARG_ENABLE(network, - [ --enable-network=[no/yes] network support (http streams / webradio) ], - [ - if test "x$enableval" = xyes - then - network="enabled" - else - network="disabled" - fi - ], - [ - network="auto" - ] -) - dnl Optional objects list, depends on decoder choice and core feature selection. dnl Not just for specific decoders anymore... @@ -1239,11 +1209,13 @@ if test "x$have_mmap" = "xno"; then AC_CHECK_FUNCS([shmget shmat shmdt shmctl],[], [buffer=disabled]) fi +term_type=none # Check if system supports termios AC_SYS_POSIX_TERMIOS if test "x$ac_cv_sys_posix_termios" = "xyes"; then AC_DEFINE_UNQUOTED([HAVE_TERMIOS], 1, [Define this if you have the POSIX termios library]) + term_type=posix fi AC_CHECK_FUNCS( random ) @@ -1262,12 +1234,45 @@ AC_CHECK_FUNCS( atoll ) AC_CHECK_FUNCS( mkfifo, [ have_mkfifo=yes ], [ have_mkfifo=no ] ) +AC_CHECK_FUNCS( fork execvp, [ have_fork=yes ], [have_fork=no; break] ) + +AC_CHECK_FUNCS( ctermid ) + +dnl For Windows and OS/2. +dnl Checking for both the functions as well as the flag symbols. +dnl Taking care with _setmode() being preferred on Windows, and +dnl OS/2 providing _setmode, but not the underscored flags. +#AC_CHECK_FUNCS( _setmode setmode ) +AC_LINK_IFELSE( [AC_LANG_SOURCE( +#include +#include +#include + +int main() +{ + _setmode(STDIN_FILENO, _O_BINARY); + _setmode(STDOUT_FILENO, _O_TEXT); + return 0; +} )], [AC_DEFINE( HAVE__SETMODE, 1, [for Win/DOS system with _setmode()] )], [] ) + +AC_LINK_IFELSE( [AC_LANG_SOURCE( +#include +#include +#include + +int main() +{ + setmode(STDIN_FILENO, O_BINARY); + setmode(STDOUT_FILENO, O_TEXT); + return 0; +} )], [AC_DEFINE( HAVE_SETMODE, 1, [for Win/DOS system with setmode()] )], [] ) + dnl ############## Header and Library Checks # locale headers AC_CHECK_HEADERS([locale.h langinfo.h wchar.h wctype.h]) # Headers for network (http) stuff -network_type=Unknown +network_internal=unknown AC_CHECK_HEADERS([netdb.h sys/param.h sys/socket.h netinet/in.h arpa/inet.h]) if test "x$ac_cv_header_netdb_h" = "xyes" && test "x$ac_cv_header_sys_param_h" = "xyes" && @@ -1275,7 +1280,7 @@ if test "x$ac_cv_header_netdb_h" = "xyes" && test "x$ac_cv_header_netinet_in_h" = "xyes" && test "x$ac_cv_header_arpa_inet_h" = "xyes"; then have_network=yes - network_type=Posix + network_internal=posix else have_network=no fi @@ -1342,10 +1347,9 @@ ADD_CPPFLAGS="$sys_cppflags" ADD_LDFLAGS="" LIBS="$LIBS" -# Consider moving that stuff. -AC_CHECK_HEADER([os2.h], [ADD_CPPFLAGS="$ADD_CPPFLAGS -DOS2"]) -# On OS/2, we need to link to os2term to make terminal control actually work. -AC_CHECK_LIB([os2term], [tcsetattr], [ADD_LDFLAGS="$ADD_LDFLAGS -los2term"]) +# Remove that if it is settled that tcsetattr on OS/2 is unsalvageable anyway. +# The only user of os2.h is the output module. +AC_CHECK_HEADERS([os2.h]) # If debugging is enabled, just enable debugging symbols. # All other stuff enters nagging territory. @@ -1675,7 +1679,10 @@ output_modules= check_forced=no check_failed=no if test "x$with_audio" != "x"; then - check_modules="`echo $with_audio|tr , ' '` dummy" + check_modules=`echo $with_audio|tr , ' '` + if ! echo "$check_modules" | grep -qw dummy; then + check_modules="$check_modules dummy" + fi echo "Limiting outputs to build according to your preference: $check_modules" check_forced=yes fi @@ -1968,6 +1975,9 @@ int main(){ ;; os2) OS2_LIBS="-lcx -lmmpm2" + OS2_CFLAGS="-idirafter /@unixroot/usr/include/os2tk45" + oldcflags="$CFLAGS" + CFLAGS="$CFLAGS $OS2_CFLAGS" AC_CHECK_HEADERS([os2.h]) # os2me.h depends on os2.h # Yes, that way of coding it is ugly. @@ -1976,6 +1986,7 @@ int main(){ # We mimick exactly the way how the header will be used. # It seems to be picky... AC_CHECK_HEADERS([os2me.h], [], [], [#define INCL_OS2MM +#undef VERSION #define INCL_DOS #define INCL_VIO #define INCL_KBD @@ -1990,6 +2001,7 @@ int main(){ else check_failed=yes fi + CFLAGS="$oldcflags" ;; # from here on only forced tests, untested code hp) @@ -2186,6 +2198,15 @@ fi default_output_module=`first_word $default_output_modules` +# Without actual modules, there is only the builtin one. +if test x"$modules" = xdisabled; then + # Module-less build needs _some_ default module. + if test -z "$default_output_module"; then + default_output_module=dummy + fi + default_output_modules=$default_output_module +fi + # Setup the static build. # The conditionals always need to be defined by configure, even if # HAVE_MODULES is FALSE! @@ -2222,10 +2243,6 @@ AM_CONDITIONAL([BUILD_SGI], [ test "sgi" = $default_output_module ]) AM_CONDITIONAL([BUILD_MINT], [ test "mint" = $default_output_module ]) AM_CONDITIONAL([BUILD_OPENAL], [ test "openal" = $default_output_module ]) -# Without actual modules, there is only the builtin one. -if test x"$modules" = xdisabled; then - default_output_modules=$default_output_module -fi default_modstring=`echo "$default_output_modules"|tr ' ' ,` AC_DEFINE_UNQUOTED( DEFAULT_OUTPUT_MODULE, "$default_modstring", [The default audio output module(s) to use] ) @@ -2305,21 +2322,14 @@ AC_DEFINE_UNQUOTED(INDEX_SIZE, $seektable, [size of the frame index seek table]) dnl ############## Win32 function checks # Check if we want Unicode for Win32. Cygwin and Midipix does not need _wopen -if test "x$ac_cv_header_windows_h" = xyes && - test "x$host_os" != xcygwin && - test "x$host_os" != xmidipix; then - win32_specific_codes=enabled -else - win32_specific_codes=disabled -fi +COMPAT_LIBS= + win32_unicode=unneeded win32_sockets=disabled win32_sockets_working=no win32_wide_working=no win32_winver_bump=no -COMPAT_LIBS= - ### mingw.org may not work properly for newer APIs case $host_os in *mingw*) @@ -2331,6 +2341,11 @@ case $host_os in ;; esac +# enable win32 code +if test "x$ac_cv_header_windows_h" = xyes -a "x$host_os" != xcygwin -a "x$host_os" != xmidipix; then + win32_specific_codes=enabled +fi + dnl We do not support non-unicode Windows. if test "x$win32_specific_codes" = xenabled; then #### Check for Wide functions @@ -2390,7 +2405,7 @@ if test "x$win32_specific_codes" = xenabled; then #### Check for Network functions AC_CHECK_HEADERS([ws2tcpip.h], [win32_sockets=enabled], [AC_MSG_WARN([Please update your headers to support winsock 2.2.])]) AC_MSG_CHECKING([if we want Win32 sockets]) - if test "x$win32_sockets" = "xenabled" && test "x$network" != "xdisabled"; then + if test "x$win32_sockets" = "xenabled"; then AC_MSG_RESULT([yes]) AC_MSG_CHECKING([if winsock2 API is available]) wsoldlibs="$LIBS" @@ -2424,14 +2439,13 @@ if test "x$win32_specific_codes" = xenabled; then fi if test "x$win32_sockets_working" = "xyes"; then AC_MSG_RESULT([yes]) - AC_DEFINE([WANT_WIN32_SOCKETS], [1], [ Define to use Win32 sockets ]) - network_type=Winsock2 + network_internal=winsock2 have_network=yes have_ipv6=yes else - LIBS="$wsoldlibs" AC_MSG_WARN([Please update your headers to support winsock 2.2.]) fi + LIBS="$wsoldlibs" else AC_MSG_RESULT([no]) fi @@ -2522,24 +2536,137 @@ int i = (EOVERFLOW) + 0; AC_MSG_RESULT([$eoverflow_present]) AS_IF([test "x$eoverflow_present" = "xyes"],[],[AC_DEFINE([EOVERFLOW],[EFBIG],[Use EFBIG as substitude for EOVERFLOW, mingw.org may lack the latter])]) +# old network choice + +AC_ARG_ENABLE(ipv6, + [ --enable-ipv6=[no/yes] IPv6 support in internal network stack ], + [ + if test "x$enableval" = xyes + then + ipv6="enabled" + else + ipv6="disabled" + fi + ], + [ + ipv6="auto" + ] +) + +dnl make it clear that auto is the default for both enable and with options. +network_type=auto + +dnl This is to be kept in sync with --with-network, it is a shortcut that +dnl keep the old semantics of being able to --disable-network support altogether. +dnl The default should match the --with-network default. +AC_ARG_ENABLE(network, + [ --enable-network=[no/yes] network support (http streams / webradio), if available (overruled by --with-network!) ], + [ + if test "x$enableval" = xyes + then + network_type=auto + else + network_type=disabled + fi + ], + [ + ] +) + +# new network choice + +AC_MSG_CHECKING([for preferred network support]) +AC_ARG_WITH([network], + [AS_HELP_STRING([--with-network=], [Available options, depending on platform, are auto, none, internal, winhttp, wininet, and exec (wget or curl binaries).])], + [ + case "$withval" in + exec) + network_type="exec" + AC_MSG_RESULT([exec]) + ;; + winhttp) + network_type="winhttp" + AC_MSG_RESULT([winhttp]) + ;; + wininet) + network_type="wininet" + AC_MSG_RESULT([wininet]) + ;; + internal) + network_type="internal" + AC_MSG_RESULT([internal]) + ;; + yes|auto) + network_type="auto" + AC_MSG_RESULT([auto]) + ;; + no|none) + network_type="disabled" + AC_MSG_RESULT([none]) + ;; + *) + AC_MSG_ERROR([Unknown network option "$withval"]) + ;; + esac + ], + [ + AC_MSG_RESULT([$network_type]) + ] +) + +if test x$network_type = xauto; then + AC_MSG_CHECKING([for automatic network support]) + if test "x$have_fork" = "xyes"; then + network_type=exec + elif test "x$win32_specific_codes" = "xenabled"; then + network_type=winhttp + elif test "x$have_network" = "xyes"; then + network_type="internal" + else + network_type="disabled" + fi + AC_MSG_RESULT([$network_type]) +fi + +# new net123 +if test x$network_type != xinternal -a x$network_type != xdisabled; then + AC_DEFINE([NET123], [1], [ Define to for new net123 network stack. ]) +fi +if test x$network_type = xexec -a x$have_fork != xyes; then + AC_MSG_ERROR([exec network support selected but fork not available]) +fi +if test x$network_type = xwininet -o x$network_type = xwinhttp && test x$win32_specific_codes != xenabled; then + AC_MSG_ERROR([wininet or winhttp is currently only for Windows]) +fi + #### Use Win32 support codes AM_CONDITIONAL([WIN32_CODES], [ test "x$win32_specific_codes" = xenabled ]) -if test x"$network" = xauto; then - if test x"$have_network" = xyes; then - network=enabled - else - network=disabled - fi -fi if test x"$ipv6" = xauto; then - if test x"$have_ipv6" = xyes; then + AC_MSG_CHECKING([IPv6 use]) + if test x"$have_ipv6" = xyes -a x"$network_type" = xinternal; then ipv6=enabled else ipv6=disabled fi + AC_MSG_RESULT([$ipv6]) fi +if test x$network_type = xinternal -a x$network_internal = xwinsock2; then + AC_DEFINE([WANT_WIN32_SOCKETS], [1], [ Define to use Win32 sockets ]) +fi + +AM_CONDITIONAL([NETWORK_WINSOCK], [ test x$network_type = xinternal -a x$network_internal = xwinsock2 ]) +AM_CONDITIONAL([NET123_EXEC], [ test x$network_type = xexec ]) +AM_CONDITIONAL([NET123_WINHTTP], [ test x$network_type = xwinhttp ]) +AM_CONDITIONAL([NET123_WININET], [ test x$network_type = xwininet ]) + +dnl ############## Terminal choice + +AM_CONDITIONAL( [TERM_POSIX], [test "x$term_type" = xposix] ) +AM_CONDITIONAL( [TERM_NONE], [test "x$term_type" = xnone] ) +AM_CONDITIONAL( [TERM_WIN32], [test "x$term_type" = xwin32] ) + dnl ############## FIFO enable if test x"$fifo" = xauto; then @@ -2558,7 +2685,7 @@ if test x"$fifo" = xenabled; then fi dnl ############## Network enable -if test x"$network" = xenabled; then +if test x"$network_type" = xinternal; then AC_DEFINE(NETWORK, 1, [ Define if network support is enabled. ]) if test x"$have_network" = xno; then AC_MSG_WARN( [ You forced network code while I think there is support missing! ] ) @@ -2601,8 +2728,6 @@ AC_CONFIG_FILES([ AC_OUTPUT - - dnl ############## Display Message echo " @@ -2613,13 +2738,16 @@ echo " Compiler Optimization ... $with_optimization Gapless Support ......... $gapless Debugging ............... $debugging + Terminal control ........ $term_type Extreme debugging ....... $xdebugging Seek table size ......... $seektable FIFO support ............ $fifo Buffer .................. $buffer - Network (http streams) .. $network - Network Sockets ......... $network_type + Network (http streams) .. $network_type" +if test x$network_type = xinternal; then + echo " Internal network type ... $network_internal IPv6 (getaddrinfo) ...... $ipv6" +fi if test x"$LARGEFILE_BITS" = x; then echo " File offsets ............ default" else diff --git a/doc/Makemodule.am b/doc/Makemodule.am index 4bb91c8..565ff76 100644 --- a/doc/Makemodule.am +++ b/doc/Makemodule.am @@ -19,6 +19,7 @@ EXTRA_DIST += \ doc/doxyfoot.xhtml \ doc/doxy_examples.c \ doc/doxygen.conf \ + doc/windows-notes.html \ doc/examples/mpg123_to_out123.c \ doc/examples/scan.c \ doc/examples/mpglib.c \ diff --git a/doc/README.remote b/doc/README.remote index 0b0e74f..c436a1d 100644 --- a/doc/README.remote +++ b/doc/README.remote @@ -63,7 +63,9 @@ SEQ : simple eq setting... PITCH <[+|-]value>: adjust playback speed (+0.01 is 1 % faster) -SILENCE: be silent during playback (meaning silence in text form) +SILENCE: be silent during playback (no progress info, opposite of PROGRESS) + +PROGRESS: turn on progress display (opposite of SILENCE) STATE: Print auxiliary state info in several lines (just try it to see what info is there). @@ -102,7 +104,7 @@ Note: mpg123 returns errors on stderr, so your frontend should look not only at stdout but also at stderr for responses. It is a good idea to use --remote-err and just look at stderr. -@R MPG123 (ThOr) v7 +@R MPG123 (ThOr) v10 Startup version message. Everything after MPG123 is auxilliary information about behaviour and command support, ID3v2 tag support is new in v3. @I ID3: @@ -175,6 +177,7 @@ to help parsers decide if they got the whole list. a = 0: playing stopped a = 1: playing paused a = 2: playing unpaused + a = 3: end of track reached (followed by another status response) @E An error occured diff --git a/doc/windows-notes.html b/doc/windows-notes.html new file mode 100644 index 0000000..df47570 --- /dev/null +++ b/doc/windows-notes.html @@ -0,0 +1,243 @@ + + + + + + + + + + +

+
+ +

+

+
+ +

+

+GENERAL INFORMATION ON RUNNING MPG123 IN A WINDOWS ENVIRONMENT

+

+
+ +

+

+(Caution- This is a brief guide, based on my experience running +MPG123 under Windows. This is NOT a sanctioned document from the +MPG123 team. It has not been reviewed by MPG123 for accuracy or +completeness. Use this information at your own risk. DON'T submit +tickets to MPG123 based on anything you read in this document, and +there is NO guarantee as to the validity of information contained +herein. -- J. Amick - 2021-02-13 ) +

+

+
+ +

+

+A portable (no installation required) MPG123.EXE can be downloaded +from “Win32 +and Win64 binaries” at WWW.MPG123.DE. + The +file you want is mpg123-v-static-x86-bb.zip, +where +v (version) is currently “1.26.4” and +bb (your system architecture) is either “32” or “64” +bit.. Extract the entire downloaded zip. Consider creating a new +target directory named MPG123 in “Program Files”. Make +sure the target directory is included in your PATH system environment +variable- add if necessary. +

+

+
+ +

+

+Command +Line usage: mpg123 +[ +options +] +file +... | +URL ... +| - + +

+

+
+ +

+

+file +…

+

+
+ +

+

+Windows +full path file name(s), surrounded +by double quotes. + The * wildcard is OK. For example: “F:\Music\*.mp3”. +Surrounding +double +quotes allow file/folder names containing +blanks.

+

+
+ +

+

+Option +[-a dev] + +

+

+
+ +

+

+Specify +the audio device to use. If not present on the command line, MPG123 +will use the Windows default audio device- sharing the same audio +channel with normal Windows sounds. If you want MPG123 to play +through a separate/dedicated audio channel, you have to provide +another audio device (sound card, USB sound device, etc). To see +what sound devices are installed +and available +in your Windows system, access and run the “Device Manager” +(devmgmt.msc). Click to expand “Sound, video and game +controllers”. Identify the desired sound device and +double-click or right click to “Properties”. Select the +“Details” tab, and look for Property “Device +instance path” (Win 10) or “Device instance id” +(Win XP). The “Value” box will contain one or more +lines- the first line is the one you want- and looks something like +this: USB\VID_0D8C&PID_0014&MI_00\6&175BA917&0&0000. + If you type this line on the command line, surround it with double +quotes. Consider saving this (including +the surrounding double quotes) as +a System environment variable, to +eliminate a lot of future typing if debugging a BAT file: System +Properties > Environment Variables > System Variables.

+

+
+ +

+

+If +you have multiple sound devices in your computer, confirm the Windows +Default Sound Device is set to the one you want for any +audio other +than that +generated +by MPG123.

+

+
+ +

+

+
+ +

+

+
+ +

+

+
+ +

+

+
+ +

+

+CASE +STUDY- SOUND SOURCE FOR TELEPHONE SYSTEM MUSIC ON HOLD

+

+
+ +

+

+I +needed a music source to implement “Music on Hold” in our +office phone system. In the past, personal CD/MP3 players have +sufficed, but none would last more than a year playing continuously. +All the inexpensive I-Pod type MP3 players I found couldn’t +play and charge at the same time. The solution came in the form of +MPG123 playing a folder of MP3 files on an old XP computer running +unattended in the phone closet, still doing batch file document +backups. A channel of MOH audio exclusively for the phone system was +added in the form of a Sabrent USB-SBCV plug-in audio generator +device capable of continuous +play +for less than $10 (Amazon). This device requires NO external +drivers- truly plug and play.

+

+
+ +

+

+This +application required MPG123 to play a continuous loop of every MP3 +file found in a folder named “Music On Hold”. MPG123 has +the -Z option which does this directly, but files are played in +random order. I preferred to have the files looped in alphabetical +order, which MPG123 will not directly do. A short batch file +(created in the same directory as MPG123.EXE) +takes care of this problem. As a template, copy and paste the +following lines into Notepad, and save as a BAT file:

+

+
+ +

+

+@echo off

+

+rem Start up an infinite loop

+

+for /L %%n in () do (

+

+rem Play mp3 files in alphabetical order from specified directory

+

+for %%f in ("G:\music on hold\*.mp3") do (

+

+ mpg123 -m -a %Sabrent% ^"%%f^" ))

+

+
+ +

+

+This batch file presumes a Sabrent system environment variable set +from the sound device property details as described earlier. +Substitute the correct location of your MP3 files. Remove the -m +option if you want stereo. +

+

+
+ +

+

+This batch file can be initiated directly from a scheduled task +created to launch when windows starts (or other trigger). Each +version of Windows has its own idiosyncrasies starting a batch file +from a scheduled task, and I recommend a Google search if you have +difficulties. Ditto for having the batch file run in a minimized CMD +window, or no window at all. Consider flushing all console output +(stdout and stderr) when you invoke the batch file from a scheduled +task. For this batch file saved as PLAYEM.BAT- enter “PLAYEM > +nul 2>&1” on the command line. +

+ + \ No newline at end of file diff --git a/man1/mpg123.1 b/man1/mpg123.1 index f721bea..c4a3a38 100644 --- a/man1/mpg123.1 +++ b/man1/mpg123.1 @@ -91,7 +91,11 @@ from the respective servers. See also the .TP \fB\-u \fIauth\fR, \fB\-\^\-auth \fIauth HTTP authentication to use when receiving files via HTTP. -The format used is user:password. +The format used is user:password. Mpg123 will clear this quickly, but it may still appear +in sight of other users or even just in your shell history. +.TP +\fB\-\^\-auth-file \fIauthfile +Provide the authentication info via given file instead of command line directly. .TP \fB\-\^\-ignore\-mime Ignore MIME types given by HTTP server. If you know better and want mpg123 @@ -126,7 +130,13 @@ or an URL pointing to a an appropriate list file. Note: only one .B \-@ option can be used (if more than one is specified, only the -last one will be recognized). +last one will be recognized). Furthermore, for HTTP resources, the +MIME type information will be used to re-open an actual MPEG stream as +such instead of treating it as playlist file. So you could just always +use +.B \-@ +for web resources without bothering if it is a playlist or already the resolved +stream address. .TP \fB\-l \fIn\fR, \fB\-\^\-listentry \fIn Of the playlist, play specified entry only. diff --git a/mpg123.spec b/mpg123.spec index f012155..e3b9105 100644 --- a/mpg123.spec +++ b/mpg123.spec @@ -3,7 +3,7 @@ # - devel packages for alsa, sdl, etc... to build the respective output modules. Summary: The fast console mpeg audio decoder/player. Name: mpg123 -Version: 1.29.3 +Version: 1.30.0 Release: 1 URL: http://www.mpg123.org/ License: GPL diff --git a/ports/cmake/cmake/read_api_version.cmake b/ports/cmake/cmake/read_api_version.cmake index a2b12b4..131d973 100644 --- a/ports/cmake/cmake/read_api_version.cmake +++ b/ports/cmake/cmake/read_api_version.cmake @@ -1,6 +1,6 @@ function(read_api_version project_version api_version outapi_version synapi_version ) - file( READ "${CMAKE_SOURCE_DIR}/../../configure.ac" configure_ac ) + file( READ "${CMAKE_CURRENT_SOURCE_DIR}/../../configure.ac" configure_ac ) string( REGEX MATCH "AC_INIT\\(\\[mpg123\\], \\[([0-9\\.]+)" result ${configure_ac} ) set( ${project_version} ${CMAKE_MATCH_1} PARENT_SCOPE ) diff --git a/ports/cmake/src/CMakeLists.txt b/ports/cmake/src/CMakeLists.txt index ee9a1ba..44f99b9 100644 --- a/ports/cmake/src/CMakeLists.txt +++ b/ports/cmake/src/CMakeLists.txt @@ -8,15 +8,12 @@ include(CheckSymbolExists) include(CMakeDependentOption) include(TestBigEndian) -check_include_file("arpa/inet.h" HAVE_ARPA_INET_H) check_include_file("dirent.h" HAVE_DIRENT_H) check_include_file("dlfcn.h" HAVE_DLFCN_H) check_include_file("inttypes.h" HAVE_INTTYPES_H) check_include_file("langinfo.h" HAVE_LANGINFO_H) check_include_file("limits.h" HAVE_LIMITS_H) check_include_file("locale.h" HAVE_LOCALE_H) -check_include_file("netdb.h" HAVE_NETDB_H) -check_include_file("netinet/in.h" HAVE_NETINET_IN_H) check_include_file("sched.h" HAVE_SCHED_H) check_include_file("signal.h" HAVE_SIGNAL_H) check_include_file("stdio.h" HAVE_STDIO_H) @@ -25,7 +22,6 @@ check_include_file("stdlib.h" HAVE_STDLIB_H) check_include_file("string.h" HAVE_STRING_H) check_include_file("sys/ioctl.h" HAVE_SYS_IOCTL_H) check_include_file("sys/ipc.h" HAVE_SYS_IPC_H) -check_include_file("sys/param.h" HAVE_SYS_PARAM_H) check_include_file("sys/resource.h" HAVE_SYS_RESOURCE_H) check_include_file("sys/select.h" HAVE_SYS_SELECT_H) check_include_file("sys/shm.h" HAVE_SYS_SHM_H) @@ -38,7 +34,6 @@ check_include_file("sys/wait.h" HAVE_SYS_WAIT_H) check_include_file("termios.h" HAVE_TERMIOS) check_include_file("unistd.h" HAVE_UNISTD_H) check_include_file("windows.h" HAVE_WINDOWS_H) - function(check_m) set(CMAKE_REQUIRED_LIBRARIES m) check_function_exists(sin HAVE_M) @@ -58,6 +53,9 @@ check_function_exists(shmat HAVE_SHMAT) check_function_exists(shmdt HAVE_SHMDT) check_function_exists(shmctl HAVE_SHMCTL) check_function_exists(strerror HAVE_STRERROR) +check_function_exists(fork HAVE_FORK) +check_function_exists(execvp HAVE_EXECVP) +check_function_exists(ctermid HAVE_CTERMID) search_libs(gethostbyname GETHOSTBYNAME_LIB nsl socket network) search_libs(socket SOCKET_LIB socket) @@ -175,10 +173,6 @@ if(WIN32) set(HAVE_MKFIFO ON) endif() -if(HAVE_NETDB_H AND HAVE_SYS_PARAM_H AND HAVE_SYS_SOCKET_H AND HAVE_NETINET_IN_H AND HAVE_ARPA_INET_H) - set(HAVE_NETWORK ON) -endif() - if(NO_MESSAGES) set(NO_WARNING ON) set(NO_ERRORMSG ON) @@ -319,7 +313,6 @@ if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -pedantic") endif() endif() -cmake_dependent_option(NETWORK "network support (http streams / webradio)" ON "HAVE_NETWORK" OFF) option(NEWOLD_WRITE_SAMPLE "enable new/old WRITE_SAMPLE macro for non-accurate 16 bit output, faster on certain CPUs (default on on x86-32)" OFF) cmake_dependent_option(NO_BUFFER "enable audio buffer code (default uses system whitelist... proper checks later)" OFF "HAVE_MMAP OR HAVE_SYS_IPC_H AND HAVE_SYS_SHM_H AND HAVE_SHMGET AND HAVE_SHMAT AND HAVE_SHMDT AND HAVE_SHMCTL" ON) option(NO_DOWNSAMPLE "no downsampled decoding" OFF) @@ -376,6 +369,18 @@ if(UNIX) option(BUILD_PROGRAMS "Build programs" ON) if(BUILD_PROGRAMS) + + if(HAVE_FORK AND HAVE_EXECVP) + set(NET123 ON) + set(NET123_EXEC ON) + endif() + + if(HAVE_TERMIOS) + set(TERM_POSIX ON) + else() + set(TERM_NONE ON) + endif() + add_subdirectory("tests") if(BUILD_LIBOUT123) @@ -388,12 +393,15 @@ if(UNIX) "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/getlopt.c" "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/httpget.c" "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/resolver.c" + $<$:${CMAKE_CURRENT_SOURCE_DIR}/../../../src/net123_exec.c> "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/genre.c" "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/mpg123.c" "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/metaprint.c" "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/local.c" "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/playlist.c" "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/streamdump.c" + $<$:${CMAKE_CURRENT_SOURCE_DIR}/../../../src/term_posix.c> + $<$:${CMAKE_CURRENT_SOURCE_DIR}/../../../src/term_none.c> "${CMAKE_CURRENT_SOURCE_DIR}/../../../src/term.c") target_link_libraries(${PROJECT_NAME} PRIVATE compat diff --git a/ports/cmake/src/config.cmake.h.in b/ports/cmake/src/config.cmake.h.in index d90ff71..27dc92a 100644 --- a/ports/cmake/src/config.cmake.h.in +++ b/ports/cmake/src/config.cmake.h.in @@ -69,8 +69,9 @@ #define LFS_ALIAS_BITS @LFS_ALIAS_BITS@ #define LT_MODULE_EXT "@CMAKE_SHARED_MODULE_SUFFIX@" -// Define if network support is enabled. -#cmakedefine NETWORK 1 +// Define for new-style network code. +#cmakedefine NET123 1 +#cmakedefine // Define to disable downsampled decoding. #cmakedefine NO_DOWNSAMPLE 1 diff --git a/src/Makemodule.am b/src/Makemodule.am index 3653327..7b30217 100644 --- a/src/Makemodule.am +++ b/src/Makemodule.am @@ -75,6 +75,7 @@ src_mpg123_SOURCES = \ src/streamdump.c \ src/term.c \ src/term.h \ + src/terms.h \ src/win32_support.h # Does that finally work to build/link the correct object file? @@ -105,10 +106,40 @@ src_mpg123_strip_SOURCES = \ src/getlopt.c \ src/getlopt.h +if TERM_POSIX +src_mpg123_SOURCES += src/term_posix.c +endif + +if TERM_WIN32 +src_mpg123_SOURCES += src/term_win32.c +endif + +if TERM_NONE +src_mpg123_SOURCES += src/term_none.c +endif + +if NET123_EXEC +src_mpg123_SOURCES += src/net123.h src/net123_exec.c +endif + +if NET123_WINHTTP +src_mpg123_SOURCES += src/net123.h src/net123_winhttp.c +src_mpg123_LDADD += -lwinhttp +endif + +if NET123_WININET +src_mpg123_SOURCES += src/net123.h src/net123_wininet.c +src_mpg123_LDADD += -lwininet +endif + if WIN32_CODES src_mpg123_SOURCES += \ - src/win32_support.c \ - src/win32_net.c + src/win32_support.c + +if NETWORK_WINSOCK +src_mpg123_SOURCES += src/win32_net.c +src_mpg123_LDADD += -lws2_32 +endif src_out123_SOURCES+= \ src/win32_support.c diff --git a/src/common.c b/src/common.c index 164943a..ce7f438 100644 --- a/src/common.c +++ b/src/common.c @@ -1,7 +1,7 @@ /* common: misc stuff... audio flush, status display... - copyright ?-2020 by the mpg123 project + copyright ?-2022 by the mpg123 project free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org @@ -15,6 +15,8 @@ #include "out123.h" #include #include "common.h" +#include "terms.h" +#include "metaprint.h" #include "debug.h" @@ -166,6 +168,7 @@ void print_buf(const char* prefix, out123_handle *ao) void print_stat(mpg123_handle *fr, long offset, out123_handle *ao, int draw_bar , struct parameter *param) { + static int old_term_width = -1; size_t buffered; off_t decoded; off_t elapsed; @@ -183,6 +186,7 @@ void print_stat(mpg123_handle *fr, long offset, out123_handle *ao, int draw_bar char linebuf[256]; char *line = NULL; +#ifndef __OS2__ #ifndef WIN32 #ifndef GENERIC /* Only generate new stat line when stderr is ready... don't overfill... */ @@ -199,6 +203,7 @@ void print_stat(mpg123_handle *fr, long offset, out123_handle *ao, int draw_bar if(n <= 0) return; } #endif +#endif #endif if(out123_getformat(ao, &rate, NULL, NULL, &framesize)) return; @@ -237,6 +242,22 @@ void print_stat(mpg123_handle *fr, long offset, out123_handle *ao, int draw_bar /* 255 is enough for the data I prepare, if there is no terminal width to fill */ maxlen = term_width(STDERR_FILENO); + if(draw_bar && maxlen > 0 && maxlen < old_term_width) + { + // Hack about draw_bar: That's the normal print_stat that is not followed by + // metadata anyway. No need to double things. + print_stat(fr, offset, ao, 0, param); + if(param->verbose > 2) + fprintf(stderr,"Note: readjusting for smaller terminal (%d to %d)\n", old_term_width, maxlen); + fprintf(stderr, "\n\n\n"); + if(param->verbose > 1) + print_header(fr); + else + print_header_compact(fr); + print_id3_tag(fr, param->long_id3, stderr, maxlen); + } + if(draw_bar) + old_term_width = maxlen; linelen = maxlen > 0 ? maxlen : (sizeof(linebuf)-1); line = linelen >= sizeof(linebuf) ? malloc(linelen+1) /* Only malloc if it is a really long line. */ @@ -354,7 +375,6 @@ void print_stat(mpg123_handle *fr, long offset, out123_handle *ao, int draw_bar Shouldn't we always fill to maxlen? */ if(maxlen > 0) memset(line+len, ' ', linelen-len); -#ifdef HAVE_TERMIOS draw_bar = draw_bar && term_have_fun(STDERR_FILENO,param->term_visual); /* Use inverse color to draw a progress bar. */ if(maxlen > 0 && draw_bar) @@ -377,17 +397,9 @@ void print_stat(mpg123_handle *fr, long offset, out123_handle *ao, int draw_bar fprintf(stderr, "%s", line+barlen); } else -#endif fprintf(stderr, "\r%s", line); } } - /* Check for changed tags here too? */ - if( mpg123_meta_check(fr) & MPG123_NEW_ICY && MPG123_OK == mpg123_icy(fr, &icy) ) - { - if(line) /* Clear the inverse video. */ - fprintf(stderr, "\r%s", line); - fprintf(stderr, "\nICY-META: %s\n", icy); - } if(line && line != linebuf) free(line); } diff --git a/src/compat/compat.c b/src/compat/compat.c index a810b5f..6e724c2 100644 --- a/src/compat/compat.c +++ b/src/compat/compat.c @@ -155,6 +155,15 @@ int compat_fclose(FILE *stream) return fclose(stream); } +void compat_binmode(int fd, int enable) +{ +#if defined(HAVE__SETMODE) + _setmode(fd, enable ? _O_BINARY : _O_TEXT); +#elif defined(HAVE_SETMODE) + setmode(fd, enable ? O_BINARY : O_TEXT); +#endif +} + #ifndef WINDOWS_UWP /* @@ -441,7 +450,12 @@ size_t unintr_write(int fd, void const *buffer, size_t bytes) { bytes -= part; written += part; - } else if(errno != EINTR) + } else if(errno != EINTR && errno != EAGAIN +#ifndef __KLIBC__ + // OS/2 is funny with POSIX. + && errno != EWOULDBLOCK +#endif + ) break; } return written; @@ -456,11 +470,16 @@ size_t unintr_read(int fd, void *buffer, size_t bytes) { errno = 0; ssize_t part = read(fd, (char*)buffer+got, bytes); - if(part >= 0) + if(part > 0) // == 0 is end of file { bytes -= part; got += part; - } else if(errno != EINTR) + } else if(errno != EINTR && errno != EAGAIN +#ifndef __KLIBC__ + // OS/2 is funny with POSIX. + && errno != EWOULDBLOCK +#endif + ) break; } return got; diff --git a/src/compat/compat.h b/src/compat/compat.h index 3e81919..f2b7af2 100644 --- a/src/compat/compat.h +++ b/src/compat/compat.h @@ -67,6 +67,19 @@ #define ULONG_MAX ((unsigned long)-1) #endif +#ifndef OFF_MAX +#if SIZEOF_OFF_T == 4 +#define OFF_MAX ((uint32_t)-1/2) +#elif SIZEOF_OFF_T == 8 +#define OFF_MAX ((uint64_t)-1/2) +#else +#error "Unexpected width of off_t." +#endif +#endif + +// Add two values (themselves assumed to be < limit), saturating to given limit. +#define SATURATE_ADD(inout, add, limit) inout = (limit-add >= inout) ? inout+add : limit; + #ifdef HAVE_STRING_H #include #endif @@ -74,7 +87,7 @@ #include #endif -#ifdef OS2 +#ifdef __OS2__ #include #endif @@ -182,9 +195,17 @@ FILE* compat_fdopen(int fd, const char *mode); int compat_close(int infd); int compat_fclose(FILE* stream); + +/** + * Setting binary mode on a descriptor, where necessary. + * We do not bother with errors. This has to work. + * You can enable or disable binary mode. + */ +void compat_binmode(int fd, int enable); + /* Those do make sense in a separate file, but I chose to include them in compat.c because that's the one source whose object is shared between mpg123 and libmpg123 -- and both need the functionality internally. */ -#ifdef WANT_WIN32_UNICODE +#if defined (_WIN32) || defined (__CYGWIN__) /** * win32_uni2mbc * Converts a null terminated UCS-2 string to a multibyte (UTF-8) equivalent. @@ -198,6 +219,19 @@ int compat_fclose(FILE* stream); */ int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen); +/** + * win32_uni2mbc + * Converts a null terminated UCS-2 string to a multibyte (UTF-7) equivalent. + * Caller is supposed to free allocated buffer. + * @param[in] wptr Pointer to wide string. + * @param[out] mbptr Pointer to multibyte string. + * @param[out] buflen Optional parameter for length of allocated buffer. + * @return status of WideCharToMultiByte conversion. + * + * WideCharToMultiByte - http://msdn.microsoft.com/en-us/library/dd374130(VS.85).aspx + */ +int win32_wide_utf7(const wchar_t * const wptr, char **mbptr, size_t * buflen); + /** * win32_mbc2uni * Converts a null terminated UTF-8 string to a UCS-2 equivalent. @@ -281,11 +315,6 @@ size_t unintr_write(int fd, void const *buffer, size_t bytes); size_t unintr_read (int fd, void *buffer, size_t bytes); size_t unintr_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); -/* That one comes from Tellie on OS/2, needed in resolver. */ -#ifdef __KLIBC__ -typedef int socklen_t; -#endif - /* OSX SDK defines an enum with "normal" as value. That clashes with optimize.h */ #ifdef __APPLE__ diff --git a/src/compat/compat_str.c b/src/compat/compat_str.c index 735f7b6..2bda813 100644 --- a/src/compat/compat_str.c +++ b/src/compat/compat_str.c @@ -14,7 +14,7 @@ /* Win32 is only supported with unicode now. These headers also cover module stuff. The WANT_WIN32_UNICODE macro is synonymous with "want windows-specific API, and only the unicode variants of which". */ -#ifdef WANT_WIN32_UNICODE +#if defined (_WIN32) || defined (__CYGWIN__) #include #include #include @@ -63,20 +63,21 @@ char* compat_strdup(const char *src) } /* Windows Unicode stuff */ - -#ifdef WANT_WIN32_UNICODE -int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen) +/* Provided unconditionally, since WASAPI needs it to configure the audio device */ +#if defined (_WIN32) || defined (__CYGWIN__) +static +int win32_wide_common(const wchar_t * const wptr, char **mbptr, size_t * buflen, UINT cp) { size_t len; char *buf; int ret = 0; - len = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, NULL, 0, NULL, NULL); /* Get utf-8 string length */ + len = WideCharToMultiByte(cp, 0, wptr, -1, NULL, 0, NULL, NULL); /* Get utf-8 string length */ buf = calloc(len + 1, sizeof (char)); /* Can we assume sizeof char always = 1? */ if(!buf) len = 0; else { - if (len != 0) ret = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, buf, len, NULL, NULL); /*Do actual conversion*/ + if (len != 0) ret = WideCharToMultiByte(cp, 0, wptr, -1, buf, len, NULL, NULL); /*Do actual conversion*/ buf[len] = '0'; /* Must terminate */ } *mbptr = buf; /* Set string pointer to allocated buffer */ @@ -84,6 +85,16 @@ int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen) return ret; } +int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen) +{ + return win32_wide_common(wptr, mbptr, buflen, CP_UTF8); +} + +int win32_wide_utf7(const wchar_t * const wptr, char **mbptr, size_t * buflen) +{ + return win32_wide_common(wptr, mbptr, buflen, CP_UTF7); +} + int win32_utf8_wide(const char *const mbptr, wchar_t **wptr, size_t *buflen) { size_t len; diff --git a/src/config.h.in b/src/config.h.in index 464514d..3fed0dc 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -82,6 +82,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_CORESERVICES_CORESERVICES_H +/* Define to 1 if you have the `ctermid' function. */ +#undef HAVE_CTERMID + /* Define to 1 if you have the header file. */ #undef HAVE_CULIB_H @@ -100,6 +103,12 @@ /* Define to 1 if you have the `dlsym' function. */ #undef HAVE_DLSYM +/* Define to 1 if you have the `execvp' function. */ +#undef HAVE_EXECVP + +/* Define to 1 if you have the `fork' function. */ +#undef HAVE_FORK + /* Define to 1 if you have the `getaddrinfo' function. */ #undef HAVE_GETADDRINFO @@ -181,6 +190,9 @@ /* Define to 1 if you have the `setlocale' function. */ #undef HAVE_SETLOCALE +/* for Win/DOS system with setmode() */ +#undef HAVE_SETMODE + /* Define to 1 if you have the `setpriority' function. */ #undef HAVE_SETPRIORITY @@ -295,6 +307,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_WS2TCPIP_H +/* for Win/DOS system with _setmode() */ +#undef HAVE__SETMODE + /* Define to indicate that float storage follows IEEE754. */ #undef IEEE_FLOAT @@ -320,6 +335,9 @@ /* Define to the shared archive member specification, say "(shr.o)". */ #undef LT_SHARED_LIB_MEMBER +/* Define to for new net123 network stack. */ +#undef NET123 + /* Define if network support is enabled. */ #undef NETWORK diff --git a/src/control_generic.c b/src/control_generic.c index d90c953..5a75921 100644 --- a/src/control_generic.c +++ b/src/control_generic.c @@ -319,8 +319,8 @@ static void generic_load(mpg123_handle *fr, char *arg, int state) } else generic_sendinfo(arg); - if(htd.icy_name.fill) generic_sendstr(1, "I ICY-NAME: %s", htd.icy_name.p); - if(htd.icy_url.fill) generic_sendstr(1, "I ICY-URL: %s", htd.icy_url.p); + if(filept->htd.icy_name.fill) generic_sendstr(1, "I ICY-NAME: %s", filept->htd.icy_name.p); + if(filept->htd.icy_url.fill) generic_sendstr(1, "I ICY-URL: %s", filept->htd.icy_url.p); mode = state; init = 1; @@ -408,7 +408,7 @@ int control_generic (mpg123_handle *fr) #endif /* the command behaviour is different, so is the ID */ /* now also with version for command availability */ - fprintf(outstream, "@R MPG123 (ThOr) v9\n"); + fprintf(outstream, "@R MPG123 (ThOr) v10\n"); #ifdef FIFO if(param.fifo) { @@ -451,6 +451,7 @@ int control_generic (mpg123_handle *fr) if (n == 0) { if (!play_frame()) { + generic_sendmsg("P 3"); out123_pause(ao); /* When the track ended, user may want to keep it open (to seek back), so there is a decision between stopping and pausing at the end. */ @@ -598,6 +599,13 @@ int control_generic (mpg123_handle *fr) continue; } + /* PROGRESS, opposite of silence */ + if(!strcasecmp(comstr, "PROGRESS")) { + silent = 0; + generic_sendmsg("progress"); + continue; + } + if(!strcasecmp(comstr, "MUTE")) { set_mute(ao, muted=TRUE); generic_sendmsg("mute"); @@ -703,7 +711,8 @@ int control_generic (mpg123_handle *fr) generic_sendmsg("H FORMAT: print out sampling rate in Hz and channel count"); generic_sendmsg("H SEQ : simple eq setting..."); generic_sendmsg("H PITCH <[+|-]value>: adjust playback speed (+0.01 is 1 %% faster)"); - generic_sendmsg("H SILENCE: be silent during playback (meaning silence in text form)"); + generic_sendmsg("H SILENCE: be silent during playback (no progress info, opposite of PROGRESS)"); + generic_sendmsg("H PROGRESS: turn on progress display (opposite of SILENCE)"); generic_sendmsg("H STATE: Print auxiliary state info in several lines (just try it to see what info is there)."); generic_sendmsg("H TAG/T: Print all available (ID3) tag info, for ID3v2 that gives output of all collected text fields, using the ID3v2.3/4 4-character names. NOTE: ID3v2 data will be deleted on non-forward seeks."); generic_sendmsg("H The output is multiple lines, begin marked by \"@T {\", end by \"@T }\"."); @@ -740,13 +749,9 @@ int control_generic (mpg123_handle *fr) int cn; if(sscanf(arg, "%lf %lf %lf", &b, &m, &t) == 3) { - /* Consider adding mpg123_seq()... but also, on could define a nicer courve for that. */ - for(cn=0; cn < 1; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, b); - - for(cn=1; cn < 2; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, m); - - for(cn=2; cn < 32; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, t); - + mpg123_eq_bands(fr, MPG123_LR, 0, 0, b); + mpg123_eq_bands(fr, MPG123_LR, 1, 1, m); + mpg123_eq_bands(fr, MPG123_LR, 2, 31, t); generic_sendmsg("bass: %f mid: %f treble: %f", b, m, t); } else generic_sendmsg("E invalid arguments for SEQ: %s", arg); diff --git a/src/httpget.c b/src/httpget.c index e1226f0..6026fdf 100644 --- a/src/httpget.c +++ b/src/httpget.c @@ -93,6 +93,25 @@ static const char* mime_pls[] = }; static const char** mimes[] = { mime_file, mime_m3u, mime_pls, NULL }; +int append_accept(mpg123_string *s) +{ + size_t i,j; + if(!mpg123_add_string(s, "Accept: ")) return FALSE; + + /* We prefer what we know. */ + for(i=0; mimes[i] != NULL; ++i) + for(j=0; mimes[i][j] != NULL; ++j) + { + if( !mpg123_add_string(s, mimes[i][j]) + || !mpg123_add_string(s, ", ") ) + return FALSE; + } + /* Well... in the end, we accept everything, trying to make sense with reality. */ + if(!mpg123_add_string(s, "*/*")) return FALSE; + + return TRUE; +} + int debunk_mime(const char* mime) { int i,j; @@ -268,8 +287,6 @@ void get_header_string(mpg123_string *response, const char *fieldname, mpg123_st /* shoutcsast meta data: 1=on, 0=off */ -char *httpauth = NULL; - size_t accept_length(void) { int i,j; @@ -320,26 +337,6 @@ int proxy_init(struct httpdata *hd) return ret; } -static int append_accept(mpg123_string *s) -{ - size_t i,j; - if(!mpg123_add_string(s, "Accept: ")) return FALSE; - - /* We prefer what we know. */ - for(i=0; mimes[i] != NULL; ++i) - for(j=0; mimes[i][j] != NULL; ++j) - { - if( !mpg123_add_string(s, mimes[i][j]) - || !mpg123_add_string(s, ", ") ) - return FALSE; - } - /* Well... in the end, we accept everything, trying to make sense with reality. */ - if(!mpg123_add_string(s, "*/*\r\n")) return FALSE; - - return TRUE; -} - - /* Converts spaces to "%20" ... actually, I have to ask myself why. What about converting them to "+" instead? Would make things a lot easier. @@ -421,13 +418,15 @@ int fill_request(mpg123_string *request, mpg123_string *host, mpg123_string *por } /* Acceptance, stream setup. */ - if( !append_accept(request) + if( !append_accept(request) + || !mpg123_add_string(request, "\r\n") || !mpg123_add_string(request, CONN_HEAD) - || !mpg123_add_string(request, icy) ) + || !mpg123_add_string(request, icy) + || !mpg123_add_string(request, "\r\n") ) return FALSE; /* Authorization. */ - if (httpauth1->fill || httpauth) { + if (httpauth1->fill || param.httpauth) { char *buf; if(!mpg123_add_string(request,"Authorization: Basic ")) return FALSE; if(httpauth1->fill) { @@ -441,15 +440,15 @@ int fill_request(mpg123_string *request, mpg123_string *host, mpg123_string *por } encode64(httpauth1->p,buf); } else { - if(strlen(httpauth) > SIZE_MAX / 4 - 4 ) return FALSE; + if(strlen(param.httpauth) > SIZE_MAX / 4 - 4 ) return FALSE; - buf=(char *)malloc((strlen(httpauth) + 1) * 4); + buf=(char *)malloc((strlen(param.httpauth) + 1) * 4); if(!buf) { error("malloc() for http auth failed, out of memory."); return FALSE; } - encode64(httpauth,buf); + encode64(param.httpauth,buf); } if( !mpg123_add_string(request, buf) || !mpg123_add_string(request, "\r\n")) @@ -502,7 +501,7 @@ static int resolve_redirect(mpg123_string *response, mpg123_string *request_url, return TRUE; } -int http_open(char* url, struct httpdata *hd) +int http_open(const char* url, struct httpdata *hd) { mpg123_string purl, host, port, path; mpg123_string request, response, request_url; @@ -719,7 +718,7 @@ exit: /* The end as well as the exception handling point... */ #else /* NETWORK */ /* stub */ -int http_open (char* url, struct httpdata *hd) +int http_open (const char* url, struct httpdata *hd) { if(!param.quiet) error("HTTP support not built in."); diff --git a/src/httpget.h b/src/httpget.h index 2fccfaf..ecdb80b 100644 --- a/src/httpget.h +++ b/src/httpget.h @@ -14,6 +14,8 @@ #define _HTTPGET_H_ #include "mpg123.h" +#include "net123.h" + /* Pulled in by mpg123app.h! */ struct httpdata @@ -54,13 +56,13 @@ char *get_header_val(const char *hname, mpg123_string *response); /* needed for HTTP/1.1 non-pipelining mode */ /* #define CONN_HEAD "Connection: close\r\n" */ #define CONN_HEAD "" -#define icy_yes "Icy-MetaData: 1\r\n" -#define icy_no "Icy-MetaData: 0\r\n" +#define icy_yes "Icy-MetaData: 1" +#define icy_no "Icy-MetaData: 0" + +// Append an accept header line to the string, without line end. +int append_accept(mpg123_string *s); -extern char *proxyurl; -extern unsigned long proxyip; /* takes url and content type string address, opens resource, returns fd for data, allocates and sets content type */ -extern int http_open (char* url, struct httpdata *hd); -extern char *httpauth; +extern int http_open (const char* url, struct httpdata *hd); #endif diff --git a/src/intsym.h b/src/intsym.h index 7b9b0fb..7c817e3 100644 --- a/src/intsym.h +++ b/src/intsym.h @@ -17,6 +17,7 @@ #define compat_close INT123_compat_close #define compat_fclose INT123_compat_fclose #define win32_wide_utf8 INT123_win32_wide_utf8 +#define win32_wide_utf7 INT123_win32_wide_utf7 #define win32_utf8_wide INT123_win32_utf8_wide #define compat_catpath INT123_compat_catpath #define compat_isdir INT123_compat_isdir diff --git a/src/libmpg123/frame.c b/src/libmpg123/frame.c index b14908f..f818bc5 100644 --- a/src/libmpg123/frame.c +++ b/src/libmpg123/frame.c @@ -984,6 +984,12 @@ int attribute_align_arg mpg123_volume_change(mpg123_handle *mh, double change) return mpg123_volume(mh, change + (double) mh->p.outscale); } +int attribute_align_arg mpg123_volume_change_db(mpg123_handle *mh, double change) +{ + if(mh == NULL) return MPG123_ERR; + return mpg123_volume(mh, dbchange(mh->p.outscale, change)); +} + int attribute_align_arg mpg123_volume(mpg123_handle *mh, double vol) { if(mh == NULL) return MPG123_ERR; diff --git a/src/libmpg123/libmpg123.c b/src/libmpg123/libmpg123.c index f175a5c..068524f 100644 --- a/src/libmpg123/libmpg123.c +++ b/src/libmpg123/libmpg123.c @@ -454,8 +454,8 @@ int attribute_align_arg mpg123_getstate2(mpg123_handle *mh, int key, long *val, int attribute_align_arg mpg123_eq(mpg123_handle *mh, enum mpg123_channels channel, int band, double val) { -#ifndef NO_EQUALIZER if(mh == NULL) return MPG123_BAD_HANDLE; +#ifndef NO_EQUALIZER if(band < 0 || band > 31){ mh->err = MPG123_BAD_BAND; return MPG123_ERR; } switch(channel) { @@ -478,9 +478,42 @@ int attribute_align_arg mpg123_eq2(mpg123_handle *mh, int channel, int band, dou return mpg123_eq(mh, channel, band, val); } +int attribute_align_arg mpg123_eq_bands(mpg123_handle *mh, int channel, int a, int b, double factor) +{ + if(mh == NULL) return MPG123_BAD_HANDLE; +#ifndef NO_EQUALIZER + int ret; + // Always count up. + if(a>b){ int s=a; a=b; b=s; } + for(int n=a; n<=b; ++n) + if( (ret=mpg123_eq(mh, channel, n, factor)) != MPG123_OK ) + return ret; +#endif + return MPG123_OK; +} + +int attribute_align_arg mpg123_eq_change(mpg123_handle *mh, int channel, int a, int b, double db) +{ + if(mh == NULL) return MPG123_BAD_HANDLE; +#ifndef NO_EQUALIZER + // Always count up. + if(a>b){ int s=a; a=b; b=s; } + for(int band=a; band<=b; ++band) + { + if(band < 0 || band > 31){ mh->err = MPG123_BAD_BAND; return MPG123_ERR; } + if(channel & MPG123_LEFT) + mh->equalizer[0][band] = DOUBLE_TO_REAL(dbchange(REAL_TO_DOUBLE(mh->equalizer[0][band]), db)); + if(channel & MPG123_RIGHT) + mh->equalizer[1][band] = DOUBLE_TO_REAL(dbchange(REAL_TO_DOUBLE(mh->equalizer[1][band]), db));; + mh->have_eq_settings = TRUE; + } +#endif + return MPG123_OK; +} + double attribute_align_arg mpg123_geteq(mpg123_handle *mh, enum mpg123_channels channel, int band) { - double ret = 0.; + double ret = 1.; #ifndef NO_EQUALIZER /* Handle this gracefully. When there is no band, it has no volume. */ diff --git a/src/libmpg123/mpg123.h.in b/src/libmpg123/mpg123.h.in index 8cb1fec..75e0ced 100644 --- a/src/libmpg123/mpg123.h.in +++ b/src/libmpg123/mpg123.h.in @@ -1093,6 +1093,28 @@ MPG123_EXPORT int mpg123_eq( mpg123_handle *mh MPG123_EXPORT int mpg123_eq2( mpg123_handle *mh , int channel, int band, double val ); +/** Set a range of equalizer bands + * \param channel Can be #MPG123_LEFT, #MPG123_RIGHT or + * #MPG123_LEFT|#MPG123_RIGHT for both. + * \param a The first equalizer band to set (from 0 to 31) + * \param b The last equalizer band to set (from 0 to 31) + * \param factor The (linear) adjustment factor, 1 being neutral. + * \return MPG123_OK on success + */ +MPG123_EXPORT int mpg123_eq_bands( mpg123_handle *mh +, int channel, int a, int b, double factor ); + +/** Change a range of equalizer bands + * \param channel Can be #MPG123_LEFT, #MPG123_RIGHT or + * #MPG123_LEFT|#MPG123_RIGHT for both. + * \param a The first equalizer band to change (from 0 to 31) + * \param b The last equalizer band to change (from 0 to 31) + * \param db The adjustment in dB (limited to +/- 60 dB). + * \return MPG123_OK on success + */ +MPG123_EXPORT int mpg123_eq_change( mpg123_handle *mh +, int channel, int a, int b, double db ); + #ifdef MPG123_ENUM_API /** Get the 32 Band Audio Equalizer settings. * @@ -1141,6 +1163,13 @@ MPG123_EXPORT int mpg123_volume(mpg123_handle *mh, double vol); */ MPG123_EXPORT int mpg123_volume_change(mpg123_handle *mh, double change); +/** Adjust output volume including the RVA setting by chosen amount + * \param mh handle + * \param change volume adjustment in decibels (limited to +/- 60 dB) + * \return MPG123_OK on success + */ +MPG123_EXPORT int mpg123_volume_change_db(mpg123_handle *mh, double db); + /** Return current volume setting, the actual value due to RVA, and the RVA * adjustment itself. It's all as double float value to abstract the sample * format. The volume values are linear factors / amplitudes (not percent) @@ -1741,7 +1770,7 @@ MPG123_EXPORT int mpg123_id3( mpg123_handle *mh , mpg123_id3v1 **v1, mpg123_id3v2 **v2 ); /** Return pointers to and size of stored raw ID3 data if storage has - * been configured with MPG123_RAW_ID3 and stream parsing passed the + * been configured with MPG123_STORE_RAW_ID3 and stream parsing passed the * metadata already. Null value with zero size is a possibility! * The storage can change at any next API call. * diff --git a/src/libmpg123/mpg123lib_intern.h b/src/libmpg123/mpg123lib_intern.h index 49f0b78..e89af70 100644 --- a/src/libmpg123/mpg123lib_intern.h +++ b/src/libmpg123/mpg123lib_intern.h @@ -339,4 +339,16 @@ int open_fixed_post(mpg123_handle *mh, int channels, int encoding); #define TIMEOUT_READ #endif +// Change a given linear factor by the given dB value, bounded +// to +/- 60 dB. +static inline double dbchange(double base_factor, double db) +{ + double nscale = base_factor * pow(10, db/20); + if(nscale < 0.001) // -60 dB + nscale = 0.001; + if(nscale > 1000) + nscale = 1000; // +60 dB + return nscale; +} + #endif diff --git a/src/libmpg123/readers.c b/src/libmpg123/readers.c index a360c0d..fbaa4d9 100644 --- a/src/libmpg123/readers.c +++ b/src/libmpg123/readers.c @@ -129,7 +129,8 @@ static ssize_t icy_fullread(mpg123_handle *fr, unsigned char *buf, ssize_t count return READER_ERROR; } - if(!(fr->rdat.flags & READER_BUFFERED)) fr->rdat.filepos += ret; + if(!(fr->rdat.flags & READER_BUFFERED)) + SATURATE_ADD(fr->rdat.filepos, ret, OFF_MAX); cnt += ret; fr->icy.next -= ret; if(fr->icy.next > 0) @@ -147,7 +148,8 @@ static ssize_t icy_fullread(mpg123_handle *fr, unsigned char *buf, ssize_t count if(ret == 0) break; debug2("got meta-size byte: %u, at filepos %li", temp_buff, (long)fr->rdat.filepos ); - if(!(fr->rdat.flags & READER_BUFFERED)) fr->rdat.filepos += ret; /* 1... */ + if(!(fr->rdat.flags & READER_BUFFERED)) + SATURATE_ADD(fr->rdat.filepos, ret, OFF_MAX); /* 1... */ if((meta_size = ((size_t) temp_buff) * 16)) { @@ -166,7 +168,8 @@ static ssize_t icy_fullread(mpg123_handle *fr, unsigned char *buf, ssize_t count left -= ret; } meta_buff[meta_size] = 0; /* string paranoia */ - if(!(fr->rdat.flags & READER_BUFFERED)) fr->rdat.filepos += ret; + if(!(fr->rdat.flags & READER_BUFFERED)) + SATURATE_ADD(fr->rdat.filepos, ret, OFF_MAX); if(fr->icy.data) free(fr->icy.data); fr->icy.data = meta_buff; @@ -218,7 +221,8 @@ static ssize_t plain_fullread(mpg123_handle *fr,unsigned char *buf, ssize_t coun ret = fr->rdat.fdread(fr,buf+cnt,count-cnt); if(ret < 0) return READER_ERROR; if(ret == 0) break; - if(!(fr->rdat.flags & READER_BUFFERED)) fr->rdat.filepos += ret; + if(!(fr->rdat.flags & READER_BUFFERED)) + SATURATE_ADD(fr->rdat.filepos, ret, OFF_MAX); cnt += ret; } return cnt; @@ -396,7 +400,10 @@ static off_t generic_tell(mpg123_handle *fr) { #ifndef NO_FEEDER if(fr->rdat.flags & READER_BUFFERED) - fr->rdat.filepos = fr->rdat.buffer.fileoff+fr->rdat.buffer.pos; + { + fr->rdat.filepos = fr->rdat.buffer.fileoff; + SATURATE_ADD(fr->rdat.filepos, fr->rdat.buffer.pos, OFF_MAX); + } #endif return fr->rdat.filepos; @@ -458,6 +465,7 @@ static off_t get_fileinfo(mpg123_handle *fr) debug("cannot seek back"); return -1; } + fr->rdat.filepos = 0; // un-do our seeking here debug1("returning length: %"OFF_P, (off_p)len); return len; @@ -816,7 +824,8 @@ static int feed_seek_frame(mpg123_handle *fr, off_t num){ return READER_ERROR; } static void buffered_forget(mpg123_handle *fr) { bc_forget(&fr->rdat.buffer); - fr->rdat.filepos = fr->rdat.buffer.fileoff + fr->rdat.buffer.pos; + fr->rdat.filepos = fr->rdat.buffer.fileoff; + SATURATE_ADD(fr->rdat.filepos, fr->rdat.buffer.pos, OFF_MAX); } off_t feed_set_pos(mpg123_handle *fr, off_t pos) @@ -1064,8 +1073,8 @@ static int default_init(mpg123_handle *fr) if(fr->p.icy_interval > 0) fr->rdat.lseek = nix_lseek; #endif - fr->rdat.filelen = fr->p.flags & MPG123_NO_PEEK_END ? -1 : get_fileinfo(fr); fr->rdat.filepos = 0; + fr->rdat.filelen = fr->p.flags & MPG123_NO_PEEK_END ? -1 : get_fileinfo(fr); if(fr->p.flags & MPG123_FORCE_SEEKABLE) fr->rdat.flags |= READER_SEEKABLE; /* diff --git a/src/libout123/libout123.c b/src/libout123/libout123.c index daf29ff..289be42 100644 --- a/src/libout123/libout123.c +++ b/src/libout123/libout123.c @@ -724,6 +724,8 @@ out123_play(out123_handle *ao, void *bytes, size_t count) , ao->flags & OUT123_KEEP_PLAYING ); if(written > 0) { + if(written > block) + written = block; // Safeguard against sloppy output modules. bytes = (char*)bytes+written; sum += written; count -= written; diff --git a/src/libout123/modules/os2.c b/src/libout123/modules/os2.c index 520edfb..d6f246d 100644 --- a/src/libout123/modules/os2.c +++ b/src/libout123/modules/os2.c @@ -22,17 +22,20 @@ #include "debug.h" +// Only one instance at a time! This all needs to go into userptr! +static BOOL opened = FALSE; + /* complementary audio parameters */ -int numbuffers = 5; /* total audio buffers, _bare_ minimum = 4 (cuz of prio boost check) */ -int audiobufsize = 4884; -int lockdevice = FALSE; -USHORT volume = 100; -char *boostprio = NULL; -char *normalprio = NULL; -unsigned char boostclass = 3, normalclass = 2; -signed char boostdelta = 0, normaldelta = 31; -unsigned char mmerror[160] = {0}; -int playingframe; +static int numbuffers = 8; /* total audio buffers, _bare_ minimum = 4 (cuz of prio boost check) */ +#define audiobufsize 4096 +static int lockdevice = FALSE; +static USHORT volume = 100; +static char *boostprio = NULL; +static char *normalprio = NULL; +static unsigned char boostclass = 3, normalclass = 2; +static signed char boostdelta = 0, normaldelta = 31; +static unsigned char mmerror[160] = {0}; +static int playingframe; /* audio buffers */ static ULONG ulMCIBuffers; @@ -45,13 +48,24 @@ static MCI_SET_PARMS msp = {0}; static MCI_STATUS_PARMS mstatp = {0}; static MCI_MIX_BUFFER *MixBuffers = NULL; +struct prebuf +{ + unsigned char d[audiobufsize]; // data area + int fill; // number of bytes in there +}; +// Being lazy for draining. +static unsigned char zbuf[audiobufsize] = {0}; + typedef struct { MCI_MIX_BUFFER *NextBuffer; int frameNum; } BUFFERINFO; -BUFFERINFO *bufferinfo = NULL; +// This static business is EVIL! +// All this needs to go into userptr to allow multiple instances. + +static BUFFERINFO *bufferinfo = NULL; static HEV dataplayed = 0; @@ -65,7 +79,7 @@ static BOOL nomoredata,nobuffermode,justflushed; static TIB *mainthread; /* thread info to set thread priority */ -ULONG keyboardtid; +static ULONG keyboardtid; static LONG APIENTRY DARTEvent(ULONG ulStatus, MCI_MIX_BUFFER *PlayedBuffer, ULONG ulFlags) @@ -158,12 +172,16 @@ static int set_volume(out123_handle *ao, USHORT setvolume) int open_os2(out123_handle *ao) { + if(opened) + return -1; + ULONG rc,i; char *temp; ULONG openflags; PPIB ppib; USHORT bits; const char *dev = ao->device; + struct prebuf *pb = NULL; if(maop.usDeviceID) return (maop.usDeviceID); @@ -180,7 +198,13 @@ int open_os2(out123_handle *ao) else if(ao->format == MPG123_ENC_UNSIGNED_8) bits = 8; else return -1; - + + ao->userptr = malloc(sizeof(struct prebuf)); + if(!ao->userptr) + return -1; + pb = (struct prebuf*)ao->userptr; + pb->fill = 0; + /* open the mixer device */ memset (&maop, 0, sizeof(maop)); maop.usDeviceID = 0; @@ -324,54 +348,88 @@ int open_os2(out123_handle *ao) rc = mmp.pmixWrite( mmp.ulMixHandle, MixBuffers, ulMCIBuffers ); - + + opened = TRUE; return maop.usDeviceID; } +// Always write full blocks of audiobufsize. The engine does not like +// things otherwise. Using a local prebuffer for the leftovers. static int write_os2(out123_handle *ao,unsigned char *buf,int len) { - /* if we're too quick, let's wait */ - if(nobuffermode) + int written = 0; + struct prebuf *pb = ao->userptr; + mdebug("write_os2(%p, %p, %d)", ao, buf, len); + while(len > 0) { - MCI_MIX_BUFFER *temp = playingbuffer; - - while( - (tobefilled != (temp = ((BUFFERINFO *) temp->ulUserParm)->NextBuffer)) && - (tobefilled != (temp = ((BUFFERINFO *) temp->ulUserParm)->NextBuffer)) && - (tobefilled != (temp = ((BUFFERINFO *) temp->ulUserParm)->NextBuffer)) ) + if(len + pb->fill < audiobufsize) { - DosResetEventSem(dataplayed,&resetcount); - DosWaitEventSem(dataplayed, -1); - temp = playingbuffer; + mdebug("storing into pb %p @ %p, %d on top of %d", pb->d, pb->d+pb->fill, len, pb->fill); + // just collect until we got a full buffer + memcpy(pb->d + pb->fill, buf, len); + pb->fill += len; + written += len; + break; } - - } else { - while(tobefilled == playingbuffer) + // Now, we at least got one full buffer to serve. + + /* if we're too quick, let's wait */ + if(nobuffermode) { - DosResetEventSem(dataplayed,&resetcount); - DosWaitEventSem(dataplayed, -1); + MCI_MIX_BUFFER *temp = playingbuffer; + + while( + (tobefilled != (temp = ((BUFFERINFO *) temp->ulUserParm)->NextBuffer)) && + (tobefilled != (temp = ((BUFFERINFO *) temp->ulUserParm)->NextBuffer)) && + (tobefilled != (temp = ((BUFFERINFO *) temp->ulUserParm)->NextBuffer)) ) + { + DosResetEventSem(dataplayed,&resetcount); + DosWaitEventSem(dataplayed, -1); + temp = playingbuffer; + } + + } else { + while(tobefilled == playingbuffer) + { + DosResetEventSem(dataplayed,&resetcount); + DosWaitEventSem(dataplayed, -1); + } + } + + if (justflushed) { + justflushed = FALSE; + } else { + nomoredata = FALSE; + int got = 0; + // First the rest from the prebuffer, then the remaining part from the new stuff. + if(pb->fill) + { + mdebug("copy all %d bytes of prebuffer from %p to %p", pb->fill, pb->d, tobefilled->pBuffer); + memcpy(tobefilled->pBuffer, pb->d, pb->fill); + got = pb->fill; + pb->fill = 0; + } + int therest = audiobufsize - got; + // len + got >= audiobufsize!! + mdebug("copy therest %d from %p to %p", therest, buf, ((unsigned char*)tobefilled->pBuffer)+got); + memcpy(((unsigned char*)tobefilled->pBuffer)+got, buf, therest); + debug("done with buffer writes"); + buf += therest; + len -= therest; + written += therest; + tobefilled->ulBufferLength = got+therest; // always == audiobufsize! + // ((BUFFERINFO *) tobefilled->ulUserParm)->frameNum = fr->frameNum; + + /* if we're out of the water (3rd ahead buffer filled), + let's reduce our priority */ + if(tobefilled == ( (BUFFERINFO *) ( (BUFFERINFO *) ((BUFFERINFO *) playingbuffer->ulUserParm)->NextBuffer->ulUserParm)->NextBuffer->ulUserParm)->NextBuffer) + DosSetPriority(PRTYS_THREAD,normalclass,normaldelta,mainthread->tib_ptib2->tib2_ultid); + + tobefilled = ((BUFFERINFO *) tobefilled->ulUserParm)->NextBuffer; } } - - if (justflushed) { - justflushed = FALSE; - } else { - nomoredata = FALSE; - - memcpy(tobefilled->pBuffer, buf, len); - tobefilled->ulBufferLength = len; - // ((BUFFERINFO *) tobefilled->ulUserParm)->frameNum = fr->frameNum; - - /* if we're out of the water (3rd ahead buffer filled), - let's reduce our priority */ - if(tobefilled == ( (BUFFERINFO *) ( (BUFFERINFO *) ((BUFFERINFO *) playingbuffer->ulUserParm)->NextBuffer->ulUserParm)->NextBuffer->ulUserParm)->NextBuffer) - DosSetPriority(PRTYS_THREAD,normalclass,normaldelta,mainthread->tib_ptib2->tib2_ultid); - - tobefilled = ((BUFFERINFO *) tobefilled->ulUserParm)->NextBuffer; - } - - return len; + return written; } #if 0 @@ -457,6 +515,12 @@ int audio_trash_buffers(out123_handle *ao) static int close_os2(out123_handle *ao) { + opened = FALSE; + if(ao && ao->userptr) + { + free(ao->userptr); + ao->userptr = NULL; + } ULONG rc; if(!maop.usDeviceID) @@ -626,9 +690,26 @@ static int get_devices_os2(char *info, int deviceid) } } +// at least drain out our prebuffer, proper draining +// should be (re?)enabled, too +static void drain_os2(out123_handle *ao) +{ + if(!ao) + return; + ao->write(ao, zbuf, audiobufsize); + if(ao->userptr) + ((struct prebuf*)ao->userptr)->fill = 0; + while(!nomoredata) + { + DosResetEventSem(dataplayed,&resetcount); + DosWaitEventSem(dataplayed, -1); + } +} static void flush_os2(out123_handle *ao) { + if(ao && ao->userptr) + ((struct prebuf*)ao->userptr)->fill = 0; } @@ -642,7 +723,8 @@ static int init_os2(out123_handle* ao) ao->write = write_os2; ao->get_formats = get_formats_os2; ao->close = close_os2; - + ao->drain = drain_os2; + /* Success */ return 0; } diff --git a/src/libout123/modules/pulse.c b/src/libout123/modules/pulse.c index ed5989c..b090767 100644 --- a/src/libout123/modules/pulse.c +++ b/src/libout123/modules/pulse.c @@ -89,7 +89,7 @@ static int check_for_server() static int open_pulse(out123_handle *ao) { - int err; + int err = 0; pa_simple* pas = NULL; pa_sample_spec ss; /* Check if already open ? */ @@ -225,7 +225,7 @@ static int close_pulse(out123_handle *ao) pa_simple *pas = (pa_simple*)ao->userptr; if (pas) { - int err; /* Do we really want to handle errors here? End is the end. */ + int err = 0; /* Do we really want to handle errors here? End is the end. */ pa_simple_drain(pas, &err); pa_simple_free(pas); ao->userptr = NULL; @@ -239,7 +239,7 @@ static void flush_pulse(out123_handle *ao) pa_simple *pas = (pa_simple*)ao->userptr; if (pas) { - int err; + int err = 0; pa_simple_flush( pas, &err ); if(err && !AOQUIET) error1("Failed to flush audio: %s", pa_strerror(err)); diff --git a/src/libout123/modules/win32_wasapi.c b/src/libout123/modules/win32_wasapi.c index 487b430..9764689 100644 --- a/src/libout123/modules/win32_wasapi.c +++ b/src/libout123/modules/win32_wasapi.c @@ -6,7 +6,13 @@ based on win32.c */ -#define _WIN32_WINNT 0x601 +#if defined(_WIN32_WINNT) +# if _WIN32_WINNT < 0x0601 +# error "wrong _WIN32_WINNT value" +# endif +#else +# define _WIN32_WINNT 0x0601 +#endif #define COBJMACROS 1 #include "out123_int.h" #include diff --git a/src/libout123/wav.c b/src/libout123/wav.c index dfd8919..7c4ff13 100644 --- a/src/libout123/wav.c +++ b/src/libout123/wav.c @@ -170,9 +170,7 @@ static int open_file(struct wavdata *wdat, char *filename) if(!filename || !strcmp("-",filename) || !strcmp("", filename)) { wdat->wavfp = stdout; -#ifdef WIN32 - _setmode(STDOUT_FILENO, _O_BINARY); -#endif + compat_binmode(STDOUT_FILENO, TRUE); /* If stdout is redirected to a file, seeks suddenly can work. Doing one here to ensure that such a file has the same output it had when opening directly as such. */ diff --git a/src/local.c b/src/local.c index fcac628..9a1dc4e 100644 --- a/src/local.c +++ b/src/local.c @@ -24,17 +24,6 @@ #endif #include "compat.h" -#ifdef __EMX__ -/* Special ways for OS/2 EMX */ -#include -#else -/* POSIX stuff */ -#ifdef HAVE_TERMIOS -#include -#include -#endif -#endif - #include "local.h" #ifdef HAVE_WCHAR_H @@ -118,76 +107,6 @@ static int is_utf8(const char *lang) return 0; } -int term_have_fun(int fd, int want_visuals) -{ - if(term_is_fun > -1) - return term_is_fun; - else - term_is_fun = 0; -#ifdef HAVE_TERMIOS - if(term_width(fd) > 0 && want_visuals) - { - /* Only play with non-dumb terminals. */ - char *tname = compat_getenv("TERM"); - if(tname) - { - if(strcmp(tname, "") && strcmp(tname, "dumb")) - term_is_fun = 1; - free(tname); - } - } -#endif - return term_is_fun; -} - -/* Also serves as a way to detect if we have an interactive terminal. */ -int term_width(int fd) -{ -#ifdef __EMX__ -/* OS/2 */ - int s[2]; - _scrsize (s); - if (s[0] >= 0) - return s[0]; -#else -#ifdef HAVE_TERMIOS -/* POSIX */ - struct winsize geometry; - geometry.ws_col = 0; - if(ioctl(fd, TIOCGWINSZ, &geometry) >= 0) - return (int)geometry.ws_col; -#else -#ifdef WIN32 - CONSOLE_SCREEN_BUFFER_INFO pinfo; - HANDLE hStdout; - DWORD handle; - - switch(fd){ - case STDIN_FILENO: - handle = STD_INPUT_HANDLE; - break; - case STDOUT_FILENO: - handle = STD_OUTPUT_HANDLE; - break; - case STDERR_FILENO: - handle = STD_ERROR_HANDLE; - break; - default: - return -1; - } - - hStdout = GetStdHandle(handle); - if(hStdout == INVALID_HANDLE_VALUE || hStdout == NULL) - return -1; - if(GetConsoleScreenBufferInfo(hStdout, &pinfo)){ - return pinfo.dwMaximumWindowSize.X; - } -#endif -#endif -#endif - return -1; -} - // Moved encoding stuff over from metaprint.c and removed references to libmpg123, // meaning no mpg123_string for you! diff --git a/src/metaprint.c b/src/metaprint.c index 54192d4..5f5d6a4 100644 --- a/src/metaprint.c +++ b/src/metaprint.c @@ -358,7 +358,7 @@ void print_id3_tag(mpg123_handle *mh, int long_id3, FILE *out, int linelimit) memcpy(lang, &v2->text[i].lang, 3); lang[3] = 0; - printf("Lyrics begin, language: %s; %s\n\n", lang, v2->text[i].description.fill ? v2->text[i].description.p : ""); + fprintf(out, "Lyrics begin, language: %s; %s\n\n", lang, v2->text[i].description.fill ? v2->text[i].description.p : ""); mpg123_init_string(&innline); mpg123_init_string(&outline); @@ -369,7 +369,7 @@ void print_id3_tag(mpg123_handle *mh, int long_id3, FILE *out, int linelimit) /* Either found end of a line or end of the string (null byte) */ mpg123_set_substring(&innline, uslt->p, a, b-a); mpg_utf8outstr(&outline, &innline, is_term); - printf(" %s\n", outline.p); + fprintf(out, " %s\n", outline.p); if(uslt->p[b] == uslt->fill) break; /* nothing more */ @@ -381,10 +381,12 @@ void print_id3_tag(mpg123_handle *mh, int long_id3, FILE *out, int linelimit) mpg123_free_string(&innline); mpg123_free_string(&outline); - printf("\nLyrics end.\n"); + fprintf(out, "\nLyrics end.\n"); } } } + // A separator line just looks nicer. + fprintf(out, "\n"); } void print_icy(mpg123_handle *mh, FILE *outstream) diff --git a/src/mpg123-strip.c b/src/mpg123-strip.c index 330c5ef..39c1522 100644 --- a/src/mpg123-strip.c +++ b/src/mpg123-strip.c @@ -133,6 +133,8 @@ int do_work(mpg123_handle *m) { int ret; size_t count = 0; + compat_binmode(STDIN_FILENO, TRUE); + compat_binmode(STDOUT_FILENO, TRUE); ret = mpg123_open_fd(m, STDIN_FILENO); if(ret != MPG123_OK) return ret; diff --git a/src/mpg123.c b/src/mpg123.c index cb90fcb..c47f0f0 100644 --- a/src/mpg123.c +++ b/src/mpg123.c @@ -36,8 +36,8 @@ #include "sysutil.h" #include "getlopt.h" #include "term.h" +#include "terms.h" #include "playlist.h" -#include "httpget.h" #include "metaprint.h" #include "httpget.h" #include "streamdump.h" @@ -63,12 +63,10 @@ struct parameter param = { DEFAULT_OUTPUT_MODULE, /* output module */ NULL, /* output device */ 0, /* destination (headphones, ...) */ -#ifdef HAVE_TERMIOS FALSE, /* term control */ TRUE, /* term visuals */ MPG123_TERM_USR1, MPG123_TERM_USR2, -#endif FALSE , /* checkrange */ 0 , /* force_reopen, always (re)opens audio device for next song */ FALSE, /* try to run process in 'realtime mode' */ @@ -118,6 +116,12 @@ struct parameter param = { ,0 /* ICY interval */ ,"mpg123" /* name */ ,0. /* device buffer */ +#if defined(NETWORK) || defined(NET123) + ,NULL /* auth */ +#endif +#ifdef NET123 + ,"auto" /* network_backend */ +#endif }; mpg123_handle *mh = NULL; @@ -129,7 +133,6 @@ static long output_propflags = 0; char *prgName = NULL; /* ThOr: pointers are not TRUE or FALSE */ char *equalfile = NULL; -struct httpdata htd; int fresh = TRUE; FILE* aux_out = NULL; /* Output for interesting information, normally on stdout to be parseable. */ @@ -144,6 +147,7 @@ enum runmodes static int runmode = RUN_MAIN; +int stdin_is_term = FALSE; // It's an interactive terminal. int stdout_is_term = FALSE; // It's an interactive terminal. int stderr_is_term = FALSE; // It's an interactive terminal. int got_played = 0; // State of attempted playback and success: 0, -1, 1 @@ -151,9 +155,7 @@ int intflag = FALSE; int deathflag = FALSE; static int skip_tracks = 0; -static int filept = -1; - -static int network_sockets_used = 0; /* Win32 socket open/close Support */ +struct stream *filept = NULL; char *fullprogname = NULL; /* Copy of argv[0]. */ char *binpath; /* Path to myself. */ @@ -275,10 +277,8 @@ static void controlled_drain(void) out123_ndrain(ao, drain_block); if(param.verbose) print_buf("Draining buffer: ", ao); -#ifdef HAVE_TERMIOS if(param.term_ctrl) term_control(mh, ao); -#endif } while(!intflag && out123_buffered(ao)); if(param.verbose) @@ -305,7 +305,8 @@ void safe_exit(int code) if(cleanup_mpg123) mpg123_exit(); - httpdata_free(&htd); + stream_close(filept); + filept = NULL; #ifdef WANT_WIN32_UNICODE win32_cmdline_free(argc, argv); /* This handles the premature argv == NULL, too. */ @@ -317,9 +318,10 @@ void safe_exit(int code) split_dir_file("", &dummy, &dammy); if(fullprogname) free(fullprogname); -#ifdef HAVE_TERMIOS - term_exit(); +#if defined(NETWORK) || defined(NET123) + if(param.httpauth) free(param.httpauth); #endif + term_exit(); exit(code); } @@ -478,6 +480,46 @@ static void set_appflag(char *arg, topt *opts) { param.appflags |= appflag; } +#if defined(NETWORK) || defined(NET123) +static void set_httpauth(char *arg, topt *opts) +{ + param.httpauth = strdup(arg); + // Do not advertise the password for all system users. + memset(arg, 'x', strlen(arg)); +} +static void set_httpauth_file(char *arg, topt *opts) +{ + char buf[4096]; + int fd = open(arg, 0); + int good = 0; + if(fd >= 0) + { + ssize_t rdb = read(fd, buf, sizeof(buf)); + // If the file filled the whole buffer, this is too much. + // realistic limits for aith are 255:255. + if(rdb > 0 && rdb < sizeof(buf)) + { + buf[sizeof(buf)-1] = 0; + for(size_t i=0; i= 0) && (htd.content_type.p != NULL) - && !APPFLAG(MPG123APP_IGNORE_MIME) && !(debunk_mime(htd.content_type.p) & IS_FILE) ) - { - error1("Unknown mpeg MIME type %s - is it perhaps a playlist (use -@)?", htd.content_type.p == NULL ? "" : htd.content_type.p); - error("If you know the stream is mpeg1/2 audio, then please report this as "PACKAGE_NAME" bug"); - return 0; - } - if(filept < 0) - { - error1("Access to http resource %s failed.", fname); - return 0; - } - if(MPG123_OK != mpg123_param(mh, MPG123_ICY_INTERVAL, htd.icy_interval, 0)) - error1("Cannot set ICY interval: %s", mpg123_strerror(mh)); - if(param.verbose > 1) fprintf(stderr, "Info: ICY interval %li\n", (long)htd.icy_interval); - } + if(filept) // total paranoia + stream_close(filept); + filept = stream_open(fname); + if(!filept) + return 0; - if(param.icy_interval > 0) + /* now check if we got sth. and if we got sth. good */ + if( filept->htd.content_type.p != NULL + && !APPFLAG(MPG123APP_IGNORE_MIME) && !(debunk_mime(filept->htd.content_type.p) & IS_FILE) ) { - if(MPG123_OK != mpg123_param(mh, MPG123_ICY_INTERVAL, param.icy_interval, 0)) - error1("Cannot set ICY interval: %s", mpg123_strerror(mh)); - if(param.verbose > 1) fprintf(stderr, "Info: Forced ICY interval %li\n", param.icy_interval); - } - - debug("OK... going to finally open."); - /* Now hook up the decoder on the opened stream or the file. */ - if(filept > -1) - { - return open_track_fd(); - } - else if(mpg123_open(mh, fname) != MPG123_OK) - { - error2("Cannot open %s: %s", fname, mpg123_strerror(mh)); + error1("Unknown mpeg MIME type %s - is it perhaps a playlist (use -@)?", filept->htd.content_type.p == NULL ? "" : filept->htd.content_type.p); + error("If you know the stream is mpeg1/2 audio, then please report this as "PACKAGE_NAME" bug"); return 0; } - debug("Track successfully opened."); + debug("OK... going to hook up libmpg123 and dump."); + if(dump_setup(filept, mh)) + return 0; fresh = TRUE; return 1; } @@ -786,15 +766,8 @@ int open_track(char *fname) void close_track(void) { mpg123_close(mh); -#if defined (WANT_WIN32_SOCKETS) - if (network_sockets_used) - win32_net_close(filept); - filept = -1; - return; -#endif - network_sockets_used = 0; - if(filept > -1) close(filept); - filept = -1; + stream_close(filept); + filept = NULL; } /* return 1 on success, 0 on failure */ @@ -957,9 +930,7 @@ int skip_or_die(struct timeval *start_time) * ThOr: Yep, I deactivated the Ctrl+C hack for active control modes. * Though, some sort of hack remains, still using intflag for track skip. */ -#ifdef HAVE_TERMIOS if(!param.term_ctrl) -#endif { struct timeval now; unsigned long secdiff; @@ -980,13 +951,11 @@ int skip_or_die(struct timeval *start_time) ++skip_tracks; } } -#ifdef HAVE_TERMIOS else if(skip_tracks == 0) { debug("breaking up"); return FALSE; } -#endif return TRUE; /* Track advancement... no instant kill on generic/windows... */ } #else @@ -1100,21 +1069,18 @@ int main(int sys_argc, char ** sys_argv) out123_del(paro); } -#ifdef OS2 +#ifdef __OS2__ _wildcard(&argc,&argv); #endif -#ifdef HAVE_TERMIOS - // Detect terminal, enable control by default. - // Only if both input and output are connected, though. You got strange effects - // otherwise, for example mpg123 messing up settings if piping debugging output - // to another interactive program. - // Also, the playlist better not reference stdin, fighting with term_control(). - int term_ctrl_default = !(term_width(STDIN_FILENO) < 0) && - !(term_width(STDERR_FILENO) < 0); - param.term_ctrl = MAYBE; -#endif - stderr_is_term = term_width(STDERR_FILENO) >= 0; + // If either stdin or stderr look like a terminal, we enable things. + // Actually checking for terminal properties is safer than calling ctermid() here. + int stderr_width = term_width(STDERR_FILENO); + int stdin_width = term_width(STDIN_FILENO); + stderr_is_term = stderr_width >= 0; + stdin_is_term = stdin_width >= 0; stdout_is_term = term_width(STDOUT_FILENO) >= 0; + int term_ctrl_default = stdin_is_term || stdout_is_term || stderr_is_term; + param.term_ctrl = MAYBE; while ((result = getlopt(argc, argv, opts))) switch (result) { case GLO_UNKNOWN: @@ -1177,8 +1143,6 @@ int main(int sys_argc, char ** sys_argv) /* Don't just exit() or return out... */ /* ========================================================================================================= */ - httpdata_init(&htd); - #if !defined(WIN32) && !defined(GENERIC) if(param.remote && !param.verbose) param.quiet = 1; @@ -1245,9 +1209,6 @@ int main(int sys_argc, char ** sys_argv) } mpg123_delete_pars(mp); /* Don't need the parameters anymore ,they're in the handle now. */ - /* Prepare stream dumping, possibly replacing mpg123 reader. */ - if(dump_open(mh) != 0) safe_exit(78); - load_equalizer(mh); #ifdef HAVE_SETPRIORITY @@ -1312,6 +1273,9 @@ int main(int sys_argc, char ** sys_argv) if(!param.remote) prepare_playlist(argc, argv, args_utf8, &pl_utf8); + if(param.verbose > 2) + fprintf(stderr, "Note: stderr terminal width: %i\n", stderr_width); + #if !defined(WIN32) && !defined(GENERIC) /* Remote mode is special... but normal console and terminal-controlled operation needs to catch the SIGINT. For one it serves for track skip when not in terminal control mode. @@ -1334,16 +1298,9 @@ int main(int sys_argc, char ** sys_argv) ret = control_generic(mh); safe_exit(ret); } -#ifdef HAVE_TERMIOS if(param.term_ctrl == MAYBE) - param.term_ctrl = (term_ctrl_default && !playlist_stdin()); - if(param.term_ctrl && playlist_stdin()) - { - error("no terminal control because standard input is being played"); - param.term_ctrl = FALSE; - } + param.term_ctrl = term_ctrl_default; term_init(); -#endif if(APPFLAG(MPG123APP_CONTINUE)) frames_left = param.frame_number; while ((fname = get_next_file())) @@ -1380,11 +1337,9 @@ int main(int sys_argc, char ** sys_argv) got_played = -1; if(intflag || !open_track_ret) { -#ifdef HAVE_TERMIOS /* We need the opportunity to cancel in case of --loop -1 . */ if(param.term_ctrl) term_control(mh, ao); else -#endif /* No wait for a second interrupt before we started playing. */ if(intflag) break; @@ -1440,10 +1395,8 @@ int main(int sys_argc, char ** sys_argv) fprintf(stderr, "\n"); } -#ifdef HAVE_TERMIOS /* Reminder about terminal usage. */ if(param.term_ctrl) term_hint(); -#endif if(param.verbose) { @@ -1457,8 +1410,10 @@ int main(int sys_argc, char ** sys_argv) , (size_p)plpos, (size_p)plfill ); print_outstr(stderr, filename, 0, stderr_is_term); fprintf(stderr, " ...\n"); - if(htd.icy_name.fill) fprintf(stderr, "ICY-NAME: %s\n", htd.icy_name.p); - if(htd.icy_url.fill) fprintf(stderr, "ICY-URL: %s\n", htd.icy_url.p); + if(filept->htd.icy_name.fill) + fprintf(stderr, "ICY-NAME: %s\n", filept->htd.icy_name.p); + if(filept->htd.icy_url.fill) + fprintf(stderr, "ICY-URL: %s\n", filept->htd.icy_url.p); } #if !defined(GENERIC) { @@ -1485,9 +1440,7 @@ int main(int sys_argc, char ** sys_argv) /* Rethink that SIGINT logic... */ #if !defined(WIN32) && !defined(GENERIC) -#ifdef HAVE_TERMIOS if(!param.term_ctrl) -#endif gettimeofday (&start_time, NULL); #endif @@ -1519,21 +1472,14 @@ int main(int sys_argc, char ** sys_argv) if(meta & MPG123_NEW_ID3) print_id3_tag( mh, param.long_id3 , stderr, term_width(STDERR_FILENO) ); if(meta & MPG123_NEW_ICY) print_icy(mh, stderr); - -#ifdef HAVE_TERMIOS - if(!param.term_ctrl) /* Terminal user can query meta data again. */ -#endif - mpg123_meta_free(mh); /* Do not waste memory after delivering. */ } } if(!fresh && param.verbose) { if(param.verbose > 1 || !(framenum & 0x7)) print_stat(mh,0,ao,1,¶m); } -#ifdef HAVE_TERMIOS if(!param.term_ctrl) continue; else term_control(mh, ao); -#endif } if(!param.smooth && !intflag) @@ -1621,11 +1567,7 @@ static void usage(int err) /* print syntax & exit */ #endif fprintf(o," -z shuffle play (with wildcards) -Z random play\n"); fprintf(o," -u a HTTP authentication string -E f Equalizer, data from file\n"); -#ifdef HAVE_TERMIOS fprintf(o," -C enable control keys --no-gapless not skip junk/padding in mp3s\n"); -#else - fprintf(o," --no-gapless not skip junk/padding in mp3s\n"); -#endif fprintf(o," -? this help --version print name + version\n"); fprintf(o,"See the manpage "PACKAGE_NAME"(1) or call %s with --longhelp for more parameters and information.\n", prgName); safe_exit(err); @@ -1656,9 +1598,17 @@ static void long_usage(int err) fprintf(o," --fuzzy Enable fuzzy seeks (guessing byte offsets or using approximate seek points from Xing TOC)\n"); fprintf(o," -y --no-resync DISABLES resync on error (--resync is deprecated)\n"); fprintf(o," -F --no-frankenstein disable support for Frankenstein streams\n"); +#if defined(NETWORK) || defined(NET123) #ifdef NETWORK fprintf(o," -p --proxy set WWW proxy\n"); +#else + fprintf(o," --network select network backend, available: auto"); + const char **nb = net123_backends; + while(*nb){ fprintf(o, " %s", *nb++); } + fprintf(o, "\n"); +#endif fprintf(o," -u --auth set auth values for HTTP access\n"); + fprintf(o," --auth-file set auth values for HTTP access from given file\n"); fprintf(o," --ignore-mime ignore HTTP MIME types (content-type)\n"); #endif fprintf(o," --no-seekbuffer disable seek buffer\n"); @@ -1676,7 +1626,8 @@ static void long_usage(int err) fprintf(o," --preframes number of frames to decode in advance after seeking (to keep layer 3 bit reservoir happy)\n"); fprintf(o," --resync-limit Set number of bytes to search for valid MPEG data; <0 means search whole stream.\n"); fprintf(o," --streamdump Dump a copy of input data (as read by libmpg123) to given file.\n"); - fprintf(o," --icy-interval Enforce ICY interval in bytes (for playing a stream dump.\n"); + fprintf(o," --icy-interval Enforce ICY interval in bytes\n"); + fprintf(o," (if > 0, for playing a stream dump).\n"); fprintf(o," --ignore-streamlength Ignore header info about length of MPEG streams.\n"); fprintf(o,"\noutput/processing options\n\n"); fprintf(o," -o --output select audio output module\n"); @@ -1741,7 +1692,6 @@ static void long_usage(int err) fprintf(o," -c --check count and display clipped samples\n"); fprintf(o," -v[*] --verbose increase verboselevel\n"); fprintf(o," -q --quiet quiet mode\n"); - #ifdef HAVE_TERMIOS fprintf(o," -C --control enable terminal control keys (else auto detect)\n"); fprintf(o," --no-control disable terminal control keys (disable auto detect)\n"); fprintf(o," --no-visual disable visual enhancements in output (hide cursor,\n" @@ -1750,7 +1700,6 @@ static void long_usage(int err) fprintf(o," (default is for stop/start)\n"); fprintf(o," --ctrlusr2 control key (characer) to map to SIGUSR2\n"); fprintf(o," (default is for next track)\n"); - #endif #ifndef GENERIC fprintf(o," --title set terminal title to filename\n"); #endif diff --git a/src/mpg123app.h b/src/mpg123app.h index 5b05649..944ec4c 100644 --- a/src/mpg123app.h +++ b/src/mpg123app.h @@ -22,6 +22,8 @@ #include "win32_support.h" #endif +#include "streamdump.h" + #if defined(WIN32) && defined(DYNAMIC_BUILD) #define LINK_MPG123_DLL #endif @@ -41,6 +43,7 @@ #define VERBOSE_MAX 3 extern char* binpath; /* argv[0], actually... */ +extern int stdin_is_term; extern int stdout_is_term; extern int stderr_is_term; @@ -57,13 +60,11 @@ struct parameter const char* output_module; /* audio output module to use */ const char* output_device; /* audio output device to use */ long output_flags; /* out123 flags */ -#ifdef HAVE_TERMIOS int term_ctrl; int term_visual; /* Those are supposed to be single characters. */ char* term_usr1; char* term_usr2; -#endif int checkrange; int force_reopen; long realtime; @@ -109,6 +110,12 @@ struct parameter long icy_interval; const char* name; /* name for this player instance */ double device_buffer; /* output device buffer */ +#if defined(NETWORK) || defined(NET123) + char *httpauth; /* HTTP auth data */ +#endif +#if defined(NET123) + char *network_backend; +#endif }; enum mpg123app_flags @@ -123,8 +130,7 @@ enum mpg123app_flags extern char *equalfile; extern off_t framenum; -extern struct httpdata htd; - +extern struct stream *filept; extern int intflag; #ifdef VARMODESUPPORT diff --git a/src/net123.h b/src/net123.h new file mode 100644 index 0000000..f6c725f --- /dev/null +++ b/src/net123.h @@ -0,0 +1,69 @@ +/* + net123: network (HTTP(S)) streaming for mpg123 using external code + + copyright 2022 by the mpg123 project -- + free software under the terms of the LGPL 2.1, + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Thomas Orgis + + This is supposed to be a binding to some system facilty to + get HTTP/HTTPS streaming going. The goal is to not be responsible + for HTTP protocol handling or even TLS in mpg123 code. + Maybe this could also stream via other protocols ... maybe even + SSH, whatever. + + For POSIX platforms, this shall refer to external binaries that + do all the network stuff. For others, some system library or + other facility shall provide the minimal required functionality. + + We could support seeking using HTTP ranges. But the first step is + to be able to just make a request to a certain URL and get the + server response headers followed by the data (be it a playlist file + or the MPEG stream itself). + + We need to support: + - client headers (ICY yes or no, client name) + - HTTP auth parameters + + The idea is that this just handles the network and protocol part + of fetching data from an URL, returning + + + + + + via net123_read(). Header part is with , just passing through + what the server gives should be OK. The only HTTP thing mpg123 shall do + is to parse headers. +*/ +#ifndef _MPG123_NET123_H_ +#define _MPG123_NET123_H_ + +#include + +// The network implementation defines the struct for private use. +// The purpose is just to keep enough context to be able to +// call net123_read() and net123_close() afterwards. +struct net123_handle_struct; +typedef struct net123_handle_struct net123_handle; + +extern const char *net123_backends[]; + +// Open stream from URL, preparing output such that net123_read() +// later on gets the response header lines followed by one empty line +// and then the raw data. +// client_head contains header lines to send with the request, without +// line ending +net123_handle *net123_open(const char *url, const char * const *client_head); + +// Read data into buffer, return bytes read. +// This handles interrupts (EAGAIN, EINTR, ..) internally and only returns +// a short byte count on EOF or error. End of file or error is not distinguished: +// For the user, it only matters if there will be more bytes or not. +// Feel free to communicate errors via error() / merror() functions inside. +size_t net123_read(net123_handle *nh, void *buf, size_t bufsize); + +// Call that to free up resources, end processes. +void net123_close(net123_handle *nh); + +#endif diff --git a/src/net123_exec.c b/src/net123_exec.c new file mode 100644 index 0000000..61601e3 --- /dev/null +++ b/src/net123_exec.c @@ -0,0 +1,306 @@ +/* + net123_exec: network (HTTP(S)) streaming for mpg123 via fork+exec + + This avoids linking any network code directly into mpg123, just using external + tools at runtime. + + This calls wget with fallback to curl by default, one of those two + specifically if param.network_backend is set accordingly. +*/ + + +#include "config.h" +#include "net123.h" +// for strings +#include "mpg123.h" + +// Just for parameter struct that we use for HTTP auth and proxy info. +#include "mpg123app.h" + +#include "compat.h" + +#include "debug.h" +#include +#include +#include + +// Those are set via environment variables: +// http_proxy +// https_proxy +// If set, the http_proxy and https_proxy variables should contain the +// URLs of the proxies for HTTP and HTTPS connections respectively. +// ftp_proxy +// same +// wget --user=... --password=... +// Alternatively: Have them in .netrc. + +const char *net123_backends[] = +{ + "wget" +, "curl" +, NULL +}; + +struct net123_handle_struct +{ + int fd; + pid_t worker; +}; + +// Combine two given strings into one newly allocated one. +// Use: (--parameter=, value) -> --parameter=value +static char *catstr(const char *par, const char *value) +{ + char *res = malloc(strlen(par)+strlen(value)+1); + if(res) + { + res[0] = 0; + strcat(res, par); + strcat(res, value); + } + return res; +} + +// < 0: not checked, 0: not there, 1: present +static int got_curl = -1; +static int got_wget = -1; + +static int check_program(char **argv) +{ + pid_t pid = fork(); + if(pid == 0) + { + int outfd = open("/dev/null", O_WRONLY); + dup2(outfd, STDOUT_FILENO); + int infd = open("/dev/null", O_RDONLY); + dup2(infd, STDIN_FILENO); + int errfd = open("/dev/null", O_WRONLY); + dup2(errfd, STDERR_FILENO); + execvp(argv[0], argv); + exit(1); + } + else if(pid > 0) + { + int stat; + if( (waitpid(pid, &stat, 0) == pid) + && WIFEXITED(stat) && WEXITSTATUS(stat)==0 ) + return 1; + } + return 0; // false, not there +} + +static char **wget_argv(const char *url, const char * const * client_head) +{ + const char* base_args[] = + { + "wget" // begins with program name + , "--output-document=-" +#ifndef DEBUG + , "--quiet" +#endif + , "--save-headers" + }; + size_t cheads = 0; + while(client_head && client_head[cheads]){ ++cheads; } + // Get the count of argument strings right! + // Fixed args + agent + client headers [+ auth] + URL + NULL + int argc = sizeof(base_args)/sizeof(char*)+1+cheads+1+1; + char *httpauth = NULL; + char *user = NULL; + char *password = NULL; + if(param.httpauth && (httpauth = compat_strdup(param.httpauth))) + { + char *sep = strchr(httpauth, ':'); + if(sep) + { + argc += 2; + *sep = 0; + user = httpauth; + password = sep+1; + } + } + char ** argv = malloc(sizeof(char*)*(argc+1)); + if(!argv) + { + error("failed to allocate argv"); + return NULL; + } + int an = 0; + for(;anfd = -1; + nh->worker = 0; + errno = 0; + if(pipe(fd)) + { + merror("failed creating a pipe: %s", strerror(errno)); + free(nh); + return NULL; + } + + compat_binmode(fd[0], TRUE); + compat_binmode(fd[1], TRUE); + + nh->worker = fork(); + if(nh->worker == -1) + { + merror("fork failed: %s", strerror(errno)); + free(nh); + return NULL; + } + + if(nh->worker == 0) + { + close(fd[0]); + dup2(fd[1], STDOUT_FILENO); + int infd = open("/dev/null", O_RDONLY); + dup2(infd, STDIN_FILENO); + // child + // Proxy environment variables can just be set in the user and inherited here, right? + int argc; + + char **argv = use_curl ? curl_argv(url, client_head) : wget_argv(url, client_head); + if(!argv) + exit(1); + errno = 0; + if(param.verbose > 2) + { + char **a = argv; + fprintf(stderr, "HTTP helper command:\n"); + while(*a) + { + fprintf(stderr, " %s\n", *a); + ++a; + } + } +#ifndef DEBUG + int errfd = open("/dev/null", O_WRONLY); + dup2(errfd, STDERR_FILENO); +#endif + execvp(argv[0], argv); + merror("cannot execute %s: %s", argv[0], strerror(errno)); + exit(1); + } + // parent + if(param.verbose > 1) + fprintf(stderr, "Note: started network helper with PID %"PRIiMAX"\n", (intmax_t)nh->worker); + errno = 0; + close(fd[1]); + nh->fd = fd[0]; + return nh; +} + +size_t net123_read(net123_handle *nh, void *buf, size_t bufsize) +{ + if(!nh || (bufsize && !buf)) + return 0; + return unintr_read(nh->fd, buf, bufsize); +} + +void net123_close(net123_handle *nh) +{ + if(!nh) + return; + if(nh->worker) + { + kill(nh->worker, SIGKILL); + errno = 0; + if(waitpid(nh->worker, NULL, 0) < 0) + merror("failed to wait for worker process: %s", strerror(errno)); + else if(param.verbose > 1) + fprintf(stderr, "Note: network helper %"PRIiMAX" finished\n", (intmax_t)nh->worker); + } + if(nh->fd > -1) + close(nh->fd); + free(nh); +} + diff --git a/src/net123_winhttp.c b/src/net123_winhttp.c new file mode 100644 index 0000000..f02070f --- /dev/null +++ b/src/net123_winhttp.c @@ -0,0 +1,262 @@ +#include "config.h" +#include "net123.h" +#include "compat.h" +#include "debug.h" +#include +#include + +const char *net123_backends[] = { "(always winhttp)", NULL }; + +// The network implementation defines the struct for private use. +// The purpose is just to keep enough context to be able to +// call net123_read() and net123_close() afterwards. +#define URL_COMPONENTS_LENGTH 255 +struct net123_handle_struct { + HINTERNET session; + HINTERNET connect; + HINTERNET request; + URL_COMPONENTS comps; + wchar_t lpszHostName[URL_COMPONENTS_LENGTH]; + wchar_t lpszUserName[URL_COMPONENTS_LENGTH]; + wchar_t lpszPassword[URL_COMPONENTS_LENGTH]; + wchar_t lpszUrlPath[URL_COMPONENTS_LENGTH]; + wchar_t lpszExtraInfo[URL_COMPONENTS_LENGTH]; + DWORD supportedAuth, firstAuth, authTarget, authTried; + char *headers; + size_t headers_pos, headers_len; + DWORD internetStatus, internetStatusLength; + LPVOID additionalInfo; +}; + +#define MPG123CONCAT_(x,y) x ## y +#define MPG123CONCAT(x,y) MPG123CONCAT_(x,y) +#define MPG123STRINGIFY_(x) #x +#define MPG123STRINGIFY(x) MPG123STRINGIFY_(x) +#define MPG123WSTR(x) MPG123CONCAT(L,MPG123STRINGIFY(x)) + +static DWORD wrap_auth(net123_handle *nh){ + DWORD mode; + DWORD ret; + + if(nh->comps.dwUserNameLength) { + if(!nh->authTried) { + ret = WinHttpQueryAuthSchemes(nh->request, &nh->supportedAuth, &nh->firstAuth, &nh->authTarget); + if(!ret) return GetLastError(); + nh->authTried = 1; + } + + mode = nh->supportedAuth & WINHTTP_AUTH_SCHEME_DIGEST ? WINHTTP_AUTH_SCHEME_DIGEST : + nh->supportedAuth & WINHTTP_AUTH_SCHEME_BASIC ? WINHTTP_AUTH_SCHEME_BASIC : 0; + + /* no supported mode? */ + if(!mode) + return ERROR_WINHTTP_INTERNAL_ERROR; + + ret = WinHttpSetCredentials(nh->request, WINHTTP_AUTH_TARGET_SERVER, mode, nh->comps.lpszUserName, nh->comps.lpszPassword, NULL); + return GetLastError(); + } +} + +#if DEBUG +static void debug_crack(URL_COMPONENTS *comps){ + wprintf(L"dwStructSize: %lu\n", comps->dwStructSize); + //wprintf(L"lpszScheme: %s\n", comps->lpszScheme ? comps->lpszScheme : L"null"); + //wprintf(L"dwSchemeLength: %lu\n", comps->dwSchemeLength); + wprintf(L"nScheme: %u %s\n", comps->nScheme, comps->nScheme == 1 ? L"INTERNET_SCHEME_HTTP": comps->nScheme == 2 ? L"INTERNET_SCHEME_HTTPS" : L"UNKNOWN"); + wprintf(L"lpszHostName: %s\n", comps->lpszHostName ? comps->lpszHostName : L"null"); + wprintf(L"dwHostNameLength: %u\n", comps->dwHostNameLength); + wprintf(L"nPort: %u\n", comps->nPort); + wprintf(L"lpszUserName: %s\n", comps->lpszUserName ? comps->lpszUserName : L"null"); + wprintf(L"dwUserNameLength: %lu\n", comps->dwUserNameLength); + wprintf(L"lpszPassword: %s\n", comps->lpszPassword ? comps->lpszPassword : L"null"); + wprintf(L"dwPasswordLength: %lu\n", comps->dwPasswordLength); + wprintf(L"lpszUrlPath: %s\n", comps->lpszUrlPath ? comps->lpszUrlPath : L"null"); + wprintf(L"dwUrlPathLength: %lu\n", comps->dwUrlPathLength); + wprintf(L"lpszExtraInfo: %s\n", comps->lpszExtraInfo? comps->lpszExtraInfo : L"null"); + wprintf(L"dwExtraInfoLength: %lu\n", comps->dwExtraInfoLength); +} +#else +static void debug_crack(URL_COMPONENTS *comps){} +#endif + +static +void WINAPI net123_ssl_errors(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength){ + net123_handle *nh = (net123_handle *)dwContext; + nh->internetStatus = dwInternetStatus; + nh->additionalInfo = lpvStatusInformation; + nh->internetStatusLength = dwStatusInformationLength; +} + +net123_handle *net123_open(const char *url, const char * const *client_head){ + LPWSTR urlW = NULL, headers = NULL; + size_t ii; + WINBOOL res; + DWORD headerlen; + const LPCWSTR useragent = MPG123WSTR(PACKAGE_NAME) L"/" MPG123WSTR(PACKAGE_VERSION); + WINHTTP_STATUS_CALLBACK cb; + + if(!WinHttpCheckPlatform()) + return NULL; + + win32_utf8_wide(url, &urlW, NULL); + if(urlW == NULL) goto cleanup; + + net123_handle *ret = calloc(1, sizeof(net123_handle)); + if (!ret) return ret; + + ret->comps.dwStructSize = sizeof(ret->comps); + ret->comps.dwSchemeLength = 0; + ret->comps.dwUserNameLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwPasswordLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwHostNameLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwUrlPathLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwExtraInfoLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.lpszHostName = ret->lpszHostName; + ret->comps.lpszUserName = ret->lpszUserName; + ret->comps.lpszPassword = ret->lpszPassword; + ret->comps.lpszUrlPath = ret->lpszUrlPath; + ret->comps.lpszExtraInfo = ret->lpszExtraInfo; + + debug1("net123_open start crack %S", urlW); + + if(!(res = WinHttpCrackUrl(urlW, 0, 0, &ret->comps))) { + debug1("net123_open crack fail %lu", GetLastError()); + goto cleanup; + } + + debug("net123_open crack OK"); + debug_crack(&ret->comps); + + ret->session = WinHttpOpen(useragent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + free(urlW); + urlW = NULL; + debug("net123_open WinHttpOpen OK"); + if(!ret->session) goto cleanup; + + debug2("net123_open WinHttpConnect %S %u", ret->comps.lpszHostName, ret->comps.nPort); + ret->connect = WinHttpConnect(ret->session, ret->comps.lpszHostName, ret->comps.nPort, 0); + if(!ret->connect) goto cleanup; + debug("net123_open WinHttpConnect OK"); + + debug1("WinHttpOpenRequest GET %S", ret->comps.lpszUrlPath); + ret->request = WinHttpOpenRequest(ret->connect, L"GET", ret->comps.lpszUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, ret->comps.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0); + if(!ret->request) goto cleanup; + debug("WinHttpOpenRequest GET OK"); + + cb = WinHttpSetStatusCallback(ret->request, (WINHTTP_STATUS_CALLBACK)net123_ssl_errors, WINHTTP_CALLBACK_FLAG_SECURE_FAILURE, 0); + if(cb == WINHTTP_INVALID_STATUS_CALLBACK){ + error1("WinHttpSetStatusCallback failed to install callback, errors might not be reported properly! (%lu)", GetLastError()); + } + + wrap_auth(ret); + + for(ii = 0; client_head[ii]; ii++){ + win32_utf8_wide(client_head[ii], &headers, NULL); + if(!headers) + goto cleanup; + debug1("WinHttpAddRequestHeaders add %S", headers); + res = WinHttpAddRequestHeaders(ret->request, headers, (DWORD) -1, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE); + debug2("WinHttpAddRequestHeaders returns %u %lu", res, res ? 0 : GetLastError()); + free(headers); + headers = NULL; + } + + debug("net123_open ADD HEADERS OK"); + + res = WinHttpSendRequest(ret->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, (DWORD_PTR)ret); + + if (!res) { + res = GetLastError(); + error1("WinHttpSendRequest failed with %lu", res); + if(res == ERROR_WINHTTP_SECURE_FAILURE){ + res = *(DWORD *)ret->additionalInfo; + error("Additionally, the ERROR_WINHTTP_SECURE_FAILURE failed with:"); + if (res & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED) error(" WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED"); + if (res & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT) error(" WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT"); + if (res & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED) error(" WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED"); + if (res & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA) error(" WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA"); + if (res & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID) error(" WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID"); + if (res & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID) error(" WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID"); + if (res & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR) error(" WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR"); + } + goto cleanup; + } + + res = WinHttpReceiveResponse(ret->request, NULL); + + if (!res) { + error1("WinHttpReceiveResponse failed with %lu", GetLastError()); + goto cleanup; + } + + res = WinHttpQueryHeaders(ret->request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER, &headerlen, WINHTTP_NO_HEADER_INDEX); + + if(GetLastError() == ERROR_INSUFFICIENT_BUFFER && headerlen > 0) { + headers = calloc(1, headerlen); + if (!headers) goto cleanup; + WinHttpQueryHeaders(ret->request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, headers, &headerlen, WINHTTP_NO_HEADER_INDEX); + win32_wide_utf7(headers, &ret->headers, &ret->headers_len); + /* bytes written, skip the terminating null, we want to stop at the \r\n\r\n */ + ret->headers_len --; + free(headers); + headers = NULL; + } else { + error("WinHttpQueryHeaders did not execute as expected"); + goto cleanup; + } + debug("net123_open OK"); + + return ret; +cleanup: + debug("net123_open error"); + if (urlW) free(urlW); + net123_close(ret); + ret = NULL; + return ret; +} + +// Read data into buffer, return bytes read. +// This handles interrupts (EAGAIN, EINTR, ..) internally and only returns +// a short byte count on EOF or error. End of file or error is not distinguished: +// For the user, it only matters if there will be more bytes or not. +// Feel free to communicate errors via error() / merror() functions inside. +size_t net123_read(net123_handle *nh, void *buf, size_t bufsize){ + size_t ret; + size_t to_copy = nh->headers_len - nh->headers_pos; + DWORD bytesread = 0; + + if(to_copy){ + ret = to_copy <= bufsize ? to_copy : bufsize; + memcpy(buf, nh->headers + nh->headers_pos, ret); + nh->headers_pos += ret; + return ret; + } + + /* is this needed? */ + to_copy = bufsize > ULONG_MAX ? ULONG_MAX : bufsize; + if(!WinHttpReadData(nh->request, buf, to_copy, &bytesread)){ + return EOF; + } + return bytesread; +} + +// Call that to free up resources, end processes. +void net123_close(net123_handle *nh){ + if(nh->headers) { + free(nh->headers); + nh->headers = NULL; + } + if(nh->request) { + WinHttpCloseHandle(nh->request); + nh->request = NULL; + } + if(nh->connect) { + WinHttpCloseHandle(nh->connect); + nh->connect = NULL; + } + if(nh->session) { + WinHttpCloseHandle(nh->session); + nh->session = NULL; + } + free(nh); +} diff --git a/src/net123_wininet.c b/src/net123_wininet.c new file mode 100644 index 0000000..49907e3 --- /dev/null +++ b/src/net123_wininet.c @@ -0,0 +1,227 @@ +#include "config.h" +#include "net123.h" +#include "compat.h" +#include "debug.h" +#include +#include + +const char *net123_backends[] = { "(always wininet)", NULL }; + +// The network implementation defines the struct for private use. +// The purpose is just to keep enough context to be able to +// call net123_read() and net123_close() afterwards. +#define URL_COMPONENTS_LENGTH 255 +struct net123_handle_struct { + HINTERNET session; + HINTERNET connect; + HINTERNET request; + URL_COMPONENTSW comps; + wchar_t lpszHostName[URL_COMPONENTS_LENGTH]; + wchar_t lpszUserName[URL_COMPONENTS_LENGTH]; + wchar_t lpszPassword[URL_COMPONENTS_LENGTH]; + wchar_t lpszUrlPath[URL_COMPONENTS_LENGTH]; + wchar_t lpszExtraInfo[URL_COMPONENTS_LENGTH]; + wchar_t lpszScheme[URL_COMPONENTS_LENGTH]; + DWORD supportedAuth, firstAuth, authTarget, authTried; + char *headers; + size_t headers_pos, headers_len; + DWORD HttpQueryInfoIndex; + DWORD internetStatus, internetStatusLength; + LPVOID additionalInfo; +}; + +#define MPG123CONCAT_(x,y) x ## y +#define MPG123CONCAT(x,y) MPG123CONCAT_(x,y) +#define MPG123STRINGIFY_(x) #x +#define MPG123STRINGIFY(x) MPG123STRINGIFY_(x) +#define MPG123WSTR(x) MPG123CONCAT(L,MPG123STRINGIFY(x)) + +#if DEBUG +static void debug_crack(URL_COMPONENTSW *comps){ + wprintf(L"dwStructSize: %lu\n", comps->dwStructSize); + wprintf(L"lpszScheme: %s\n", comps->lpszScheme ? comps->lpszScheme : L"null"); + wprintf(L"dwSchemeLength: %lu\n", comps->dwSchemeLength); + wprintf(L"nScheme: %u %s\n", comps->nScheme, comps->nScheme == 1 ? L"INTERNET_SCHEME_HTTP": comps->nScheme == 2 ? L"INTERNET_SCHEME_HTTPS" : L"UNKNOWN"); + wprintf(L"lpszHostName: %s\n", comps->lpszHostName ? comps->lpszHostName : L"null"); + wprintf(L"dwHostNameLength: %u\n", comps->dwHostNameLength); + wprintf(L"nPort: %u\n", comps->nPort); + wprintf(L"lpszUserName: %s\n", comps->lpszUserName ? comps->lpszUserName : L"null"); + wprintf(L"dwUserNameLength: %lu\n", comps->dwUserNameLength); + wprintf(L"lpszPassword: %s\n", comps->lpszPassword ? comps->lpszPassword : L"null"); + wprintf(L"dwPasswordLength: %lu\n", comps->dwPasswordLength); + wprintf(L"lpszUrlPath: %s\n", comps->lpszUrlPath ? comps->lpszUrlPath : L"null"); + wprintf(L"dwUrlPathLength: %lu\n", comps->dwUrlPathLength); + wprintf(L"lpszExtraInfo: %s\n", comps->lpszExtraInfo? comps->lpszExtraInfo : L"null"); + wprintf(L"dwExtraInfoLength: %lu\n", comps->dwExtraInfoLength); +} +#else +static void debug_crack(URL_COMPONENTSW *comps){} +#endif + +static +void WINAPI net123_ssl_errors(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength){ + net123_handle *nh = (net123_handle *)dwContext; + nh->internetStatus = dwInternetStatus; + nh->additionalInfo = lpvStatusInformation; + nh->internetStatusLength = dwStatusInformationLength; +} + +net123_handle *net123_open(const char *url, const char * const *client_head){ + LPWSTR urlW = NULL, headers = NULL; + size_t ii; + WINBOOL res; + DWORD headerlen; + const LPCWSTR useragent = MPG123WSTR(PACKAGE_NAME) L"/" MPG123WSTR(PACKAGE_VERSION); + INTERNET_STATUS_CALLBACK cb; + + win32_utf8_wide(url, &urlW, NULL); + if(urlW == NULL) goto cleanup; + + net123_handle *ret = calloc(1, sizeof(net123_handle)); + if (!ret) return ret; + + ret->comps.dwStructSize = sizeof(ret->comps); + ret->comps.dwSchemeLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwUserNameLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwPasswordLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwHostNameLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwUrlPathLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.dwExtraInfoLength = URL_COMPONENTS_LENGTH - 1; + ret->comps.lpszHostName = ret->lpszHostName; + ret->comps.lpszUserName = ret->lpszUserName; + ret->comps.lpszPassword = ret->lpszPassword; + ret->comps.lpszUrlPath = ret->lpszUrlPath; + ret->comps.lpszExtraInfo = ret->lpszExtraInfo; + ret->comps.lpszScheme = ret->lpszScheme; + + debug1("net123_open start crack %S", urlW); + + if(!(res = InternetCrackUrlW(urlW, 0, 0, &ret->comps))) { + debug1("net123_open crack fail %lu", GetLastError()); + goto cleanup; + } + + debug("net123_open crack OK"); + debug_crack(&ret->comps); + + ret->session = InternetOpenW(useragent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + free(urlW); + urlW = NULL; + debug("net123_open InternetOpenW OK"); + if(!ret->session) goto cleanup; + + debug2("net123_open InternetConnectW %S %u", ret->comps.lpszHostName, ret->comps.nPort); + ret->connect = InternetConnectW(ret->session, ret->comps.lpszHostName, ret->comps.nPort, + ret->comps.dwUserNameLength ? ret->comps.lpszUserName : NULL, ret->comps.dwPasswordLength ? ret->comps.lpszPassword : NULL, + INTERNET_SERVICE_HTTP, 0, 0); + if(!ret->connect) goto cleanup; + debug("net123_open InternetConnectW OK"); + + debug1("HttpOpenRequestW GET %S", ret->comps.lpszUrlPath); + ret->request = HttpOpenRequestW(ret->connect, L"GET", ret->comps.lpszUrlPath, NULL, NULL, NULL, ret->comps.nScheme == INTERNET_SCHEME_HTTPS ? INTERNET_FLAG_SECURE : 0, (DWORD_PTR)ret); + if(!ret->request) goto cleanup; + debug("HttpOpenRequestW GET OK"); + + cb = InternetSetStatusCallback(ret->request, (INTERNET_STATUS_CALLBACK)net123_ssl_errors); + if(cb != NULL){ + error1("InternetSetStatusCallback failed to install callback, errors might not be reported properly! (%lu)", GetLastError()); + } + + for(ii = 0; client_head[ii]; ii++){ + win32_utf8_wide(client_head[ii], &headers, NULL); + if(!headers) + goto cleanup; + debug1("HttpAddRequestHeadersW add %S", headers); + res = HttpAddRequestHeadersW(ret->request, headers, (DWORD) -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); + debug2("HttpAddRequestHeadersW returns %u %lu", res, res ? 0 : GetLastError()); + free(headers); + headers = NULL; + } + + debug("net123_open ADD HEADERS OK"); + + res = HttpSendRequestW(ret->request, NULL, 0, NULL, 0); + + if (!res) { + res = GetLastError(); + error1("HttpSendRequestW failed with %lu", res); + goto cleanup; + } + + // dummy, cannot be null + headers = calloc(1,1); + headerlen = 1; + if(headers == NULL) { + error("Cannot allocate dummy buffer for HttpQueryInfoW"); + goto cleanup; + } + res = HttpQueryInfoW(ret->request, HTTP_QUERY_RAW_HEADERS_CRLF, headers, &headerlen, &ret->HttpQueryInfoIndex); + free(headers); + + if(!res && GetLastError() == ERROR_INSUFFICIENT_BUFFER && headerlen > 0) { + /* buffer size is in bytes, not including terminator */ + headers = calloc(1, headerlen + sizeof(*headers)); + if (!headers) goto cleanup; + res = HttpQueryInfoW(ret->request, HTTP_QUERY_RAW_HEADERS_CRLF, headers, &headerlen, &ret->HttpQueryInfoIndex); + debug3("HttpQueryInfoW returned %u, err %u : %S", res, GetLastError(), headers ? headers : L"null"); + win32_wide_utf7(headers, &ret->headers, &ret->headers_len); + /* bytes written, skip the terminating null, we want to stop at the \r\n\r\n */ + ret->headers_len --; + free(headers); + headers = NULL; + } else { + error("HttpQueryInfoW did not execute as expected"); + goto cleanup; + } + debug("net123_open OK"); + + return ret; +cleanup: + debug("net123_open error"); + if (urlW) free(urlW); + net123_close(ret); + ret = NULL; + return ret; +} + +size_t net123_read(net123_handle *nh, void *buf, size_t bufsize){ + size_t ret; + size_t to_copy = nh->headers_len - nh->headers_pos; + DWORD bytesread = 0; + + if(to_copy){ + ret = to_copy <= bufsize ? to_copy : bufsize; + memcpy(buf, nh->headers + nh->headers_pos, ret); + nh->headers_pos += ret; + return ret; + } + + /* is this needed? */ + to_copy = bufsize > ULONG_MAX ? ULONG_MAX : bufsize; + if(!InternetReadFile(nh->request, buf, to_copy, &bytesread)){ + error1("InternetReadFile exited with %d", GetLastError()); + return EOF; + } + return bytesread; +} + +// Call that to free up resources, end processes. +void net123_close(net123_handle *nh){ + if(nh->headers) { + free(nh->headers); + nh->headers = NULL; + } + if(nh->request) { + InternetCloseHandle(nh->request); + nh->request = NULL; + } + if(nh->connect) { + InternetCloseHandle(nh->connect); + nh->connect = NULL; + } + if(nh->session) { + InternetCloseHandle(nh->session); + nh->session = NULL; + } + free(nh); +} diff --git a/src/out123.c b/src/out123.c index a431bce..3d4170b 100644 --- a/src/out123.c +++ b/src/out123.c @@ -88,9 +88,6 @@ enum runmodes static int runmode = RUN_MAIN; -int stdout_is_term = FALSE; // It's an interactive terminal. -int stderr_is_term = FALSE; // It's an interactive terminal. - static FILE* input = NULL; static char *encoding_name = NULL; static int encoding = MPG123_ENC_SIGNED_16; @@ -1466,9 +1463,7 @@ static void setup_processing(void) int main(int sys_argc, char ** sys_argv) { int result; -#if defined(WIN32) - _setmode(STDIN_FILENO, _O_BINARY); -#endif + compat_binmode(STDIN_FILENO, TRUE); check_locale(); #if defined (WANT_WIN32_UNICODE) @@ -1509,12 +1504,10 @@ int main(int sys_argc, char ** sys_argv) out123_del(paro); } -#ifdef OS2 +#ifdef __OS2__ _wildcard(&argc,&argv); #endif - stderr_is_term = term_width(STDERR_FILENO) >= 0; - stdout_is_term = term_width(STDOUT_FILENO) >= 0; while ((result = getlopt(argc, argv, opts))) switch (result) { case GLO_UNKNOWN: diff --git a/src/playlist.c b/src/playlist.c index c8e7717..fe48f54 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -18,6 +18,7 @@ #include "term.h" /* for term_restore */ #include "playlist.h" #include "httpget.h" +#include "streamdump.h" #include "local.h" #include "metaprint.h" #include /* For srand(). */ @@ -31,9 +32,6 @@ #define SRAND srand #endif -/* increase linebuf in blocks of ... bytes */ -#define LINEBUF_STEP 100 - enum playlist_type { UNKNOWN = 0, M3U, PLS, NO_LIST }; typedef struct listitem @@ -45,7 +43,7 @@ typedef struct listitem typedef struct playlist_struct { - FILE* file; /* the current playlist stream */ + struct stream *file; /* the current playlist stream */ size_t entry; /* entry in the playlist file */ size_t playcount; /* overall track counter for playback */ long loop; /* repeat a track n times */ @@ -60,9 +58,6 @@ typedef struct playlist_struct enum playlist_type type; int is_utf8; /* if we really know the contents are UTF-8-encooded */ int hit_end; -#if defined (WANT_WIN32_SOCKETS) - int sockd; /* Is Win32 socket descriptor working? */ -#endif // If the playlist itself or an input file was '-' (stdin not usable for terminal.). int stdin_used; } playlist_struct; @@ -86,6 +81,11 @@ void prepare_playlist(int argc, char** argv, int args_utf8, int *is_utf8) */ init_playlist(); while (add_next_file(argc, argv, args_utf8)) {} + if(pl.file) + { + stream_close(pl.file); + pl.file = NULL; + } if(param.verbose > 1) { fprintf(stderr, "\nplaylist in normal order:\n"); @@ -297,9 +297,6 @@ static void init_playlist(void) pl.is_utf8 = FALSE; pl.hit_end = FALSE; pl.loop = param.loop; -#ifdef WANT_WIN32_SOCKETS - pl.sockd = -1; -#endif pl.stdin_used = FALSE; } @@ -347,50 +344,38 @@ static int add_next_file (int argc, char *argv[], int args_utf8) { size_t line_offset = 0; pl.is_utf8 = 0; // Playlist files in env encoding (HTTP lists should be ASCII-clean). -#ifndef WANT_WIN32_SOCKETS - if (!pl.file) -#else - if (!pl.file && pl.sockd == -1) -#endif + if(!pl.file) { - /* empty or "-" */ - if (!*param.listname || !strcmp(param.listname, "-")) + pl.file = stream_open(param.listname); + if(pl.file) { - pl.file = stdin; - pl.stdin_used = TRUE; - param.listname = NULL; - pl.entry = 0; + firstline = 1; /* just opened */ + if(pl.file->fd == STDIN_FILENO) + { + pl.stdin_used = TRUE; + param.listname = NULL; + } } - else if (!strncmp(param.listname, "http://", 7)) + pl.entry = 0; + if(pl.file && pl.file->network) { - int fd; - struct httpdata htd; - httpdata_init(&htd); -#ifndef WANT_WIN32_SOCKETS - fd = http_open(param.listname, &htd); -#else - fd = win32_net_http_open(param.listname, &htd); -#endif - debug1("htd.content_type.p: %p", (void*) htd.content_type.p); - if(!APPFLAG(MPG123APP_IGNORE_MIME) && htd.content_type.p != NULL) + debug1("htd.content_type.p: %p", (void*) pl.file->htd.content_type.p); + if(!APPFLAG(MPG123APP_IGNORE_MIME) && pl.file->htd.content_type.p != NULL) { int mimi; - debug1("htd.content_type.p value: %s", htd.content_type.p); - mimi = debunk_mime(htd.content_type.p); + debug1("htd.content_type.p value: %s", pl.file->htd.content_type.p); + mimi = debunk_mime(pl.file->htd.content_type.p); - if(mimi & IS_M3U) pl.type = M3U; - else if(mimi & IS_PLS) pl.type = PLS; + if(mimi & IS_M3U) + pl.type = M3U; + else if(mimi & IS_PLS) + pl.type = PLS; else { -#ifndef WANT_WIN32_SOCKETS - if(fd >= 0) close(fd); -#else - if(fd != SOCKET_ERROR) win32_net_close(fd); -#endif - fd = -1; - if(mimi & IS_FILE) { + stream_close(pl.file); + pl.file = NULL; pl.type = NO_LIST; if(param.listentry < 0) { @@ -407,257 +392,181 @@ static int add_next_file (int argc, char *argv[], int args_utf8) } } char *ptmp = NULL; - outstr(&ptmp, htd.content_type.p, 0, stderr_is_term); + outstr(&ptmp, pl.file->htd.content_type.p, 0, stderr_is_term); error1( "Unknown playlist MIME type %s; maybe "PACKAGE_NAME " can support it in future if you report this to the maintainer." , PSTR(ptmp) ); free(ptmp); + stream_close(pl.file); + pl.file = NULL; } - httpdata_free(&htd); - } - if(fd < 0) - { - param.listname = NULL; - pl.file = NULL; -#ifdef WANT_WIN32_SOCKETS - pl.sockd = -1; -#endif - error("Invalid playlist from http_open()!\n"); - } - else - { - pl.entry = 0; -#ifndef WANT_WIN32_SOCKETS - pl.file = compat_fdopen(fd,"r"); -#else - pl.sockd = fd; -#endif } } - else if (!(pl.file = compat_fopen(param.listname, "rb"))) + if(!pl.file) { - perror (param.listname); + param.listname = NULL; // why? + error("failed to open playlist file"); return 0; - } - else - { - debug("opened ordinary list file"); - pl.entry = 0; - } - if (param.verbose && pl.file) + } else if(param.verbose) { fprintf(stderr, "Using playlist from "); print_outstr( stderr, param.listname ? param.listname : "standard input" , args_utf8, stderr_is_term ); fprintf(stderr, " ...\n"); } - firstline = 1; /* just opened */ } /* reading the file line by line */ -#ifndef WANT_WIN32_SOCKETS - while (pl.file) -#else - while (pl.file || (pl.sockd) != -1) -#endif + while(pl.file && stream_getline(pl.file, &pl.linebuf) > 0) { - /* - now read a string of arbitrary size... - read a chunk, see if lineend, realloc, read another chunk - - fgets reads at most size-1 bytes and always appends the \0 - */ - size_t have = 0; - do + /* a bit of fuzzyness */ + if(firstline) { - /* have is the length of the string read, without the closing \0 */ - if(pl.linebuf.size <= have+1) + if(pl.type == UNKNOWN) { - if(!mpg123_resize_string(&pl.linebuf, pl.linebuf.size+LINEBUF_STEP)) + if(!strcmp("[playlist]", pl.linebuf.p)) { - error("cannot increase line buffer"); - break; - } - } - /* I rely on fgets writing the \0 at the end! */ -#ifndef WANT_WIN32_SOCKETS - if(fgets(pl.linebuf.p+have, pl.linebuf.size-have, pl.file)) -#else - if( (pl.file ? (fgets(pl.linebuf.p+have, pl.linebuf.size-have, pl.file)) : (win32_net_fgets(pl.linebuf.p+have, pl.linebuf.size-have, pl.sockd)))) -#endif - { - have += strlen(pl.linebuf.p+have); - debug2("have read %lu characters into linebuf: [%s]", (unsigned long)have, pl.linebuf.p); - } - else - { - debug("fgets failed to deliver something... file ended?"); - break; - } - } while(have && pl.linebuf.p[have-1] != '\r' && pl.linebuf.p[have-1] != '\n'); - if(have) - { - pl.linebuf.p[strcspn(pl.linebuf.p, "\t\n\r")] = '\0'; - /* a bit of fuzzyness */ - if(firstline) - { - if(pl.type == UNKNOWN) - { - if(!strcmp("[playlist]", pl.linebuf.p)) - { + if(param.verbose) fprintf(stderr, "Note: detected Shoutcast/Winamp PLS playlist\n"); - pl.type = PLS; - continue; - } - else if - ( - (!strncasecmp("#M3U", pl.linebuf.p ,4)) - || - (!strncasecmp("#EXTM3U", pl.linebuf.p ,7)) - || - (param.listname != NULL && (strrchr(param.listname, '.')) != NULL && !strcasecmp(".m3u", strrchr(param.listname, '.'))) - ) - { - if(param.verbose) fprintf(stderr, "Note: detected M3U playlist type\n"); - pl.type = M3U; - } - else - { - if(param.verbose) fprintf(stderr, "Note: guessed M3U playlist type\n"); - pl.type = M3U; - } + pl.type = PLS; + continue; + } + else if + ( + (!strncasecmp("#M3U", pl.linebuf.p ,4)) + || + (!strncasecmp("#EXTM3U", pl.linebuf.p ,7)) + || + (param.listname != NULL && (strrchr(param.listname, '.')) != NULL && !strcasecmp(".m3u", strrchr(param.listname, '.'))) + ) + { + if(param.verbose) fprintf(stderr, "Note: detected M3U playlist type\n"); + pl.type = M3U; } else { - if(param.verbose) - { - fprintf(stderr, "Note: Interpreting as "); - switch(pl.type) - { - case M3U: fprintf(stderr, "M3U"); break; - case PLS: fprintf(stderr, "PLS (Winamp/Shoutcast)"); break; - default: fprintf(stderr, "???"); - } - fprintf(stderr, " playlist\n"); - } + if(param.verbose) fprintf(stderr, "Note: guessed M3U playlist type\n"); + pl.type = M3U; } - firstline = 0; } - #if !defined(WIN32) + else { + if(param.verbose) + { + fprintf(stderr, "Note: Interpreting as "); + switch(pl.type) + { + case M3U: fprintf(stderr, "M3U"); break; + case PLS: fprintf(stderr, "PLS (Winamp/Shoutcast)"); break; + default: fprintf(stderr, "???"); + } + fprintf(stderr, " playlist\n"); + } + } + firstline = 0; + } +#if !defined(WIN32) + { size_t i; /* convert \ to / (from MS-like directory format) */ for (i=0;pl.linebuf.p[i]!='\0';i++) { if (pl.linebuf.p[i] == '\\') pl.linebuf.p[i] = '/'; } - } - #endif - if (pl.linebuf.p[0]=='\0') continue; /* skip empty lines... */ - - if (((pl.type == M3U) && (pl.linebuf.p[0]=='#'))) + } +#endif + if (pl.linebuf.p[0]=='\0') + continue; // skip empty lines... + if (((pl.type == M3U) && (pl.linebuf.p[0]=='#'))) + { + /* a comment line in m3u file */ + if(param.listentry < 0) { - /* a comment line in m3u file */ - if(param.listentry < 0) - { - print_outstr(stdout, pl.linebuf.p, 0, stdout_is_term); - printf("\n"); - } - continue; + print_outstr(stdout, pl.linebuf.p, 0, stdout_is_term); + printf("\n"); } + continue; + } - /* real filename may start at an offset */ - line_offset = 0; - /* extract path out of PLS */ - if(pl.type == PLS) + /* real filename may start at an offset */ + line_offset = 0; + /* extract path out of PLS */ + if(pl.type == PLS) + { + if(!strncasecmp("File", pl.linebuf.p, 4)) { - if(!strncasecmp("File", pl.linebuf.p, 4)) + /* too lazy to really check for file number... would have to change logic to support unordered file entries anyway */ + char* in_line; + if((in_line = strchr(pl.linebuf.p+4, '=')) != NULL) { - /* too lazy to really check for file number... would have to change logic to support unordered file entries anyway */ - char* in_line; - if((in_line = strchr(pl.linebuf.p+4, '=')) != NULL) + /* FileN=? */ + if(in_line[1] != 0) { - /* FileN=? */ - if(in_line[1] != 0) - { - ++in_line; - line_offset = (size_t) (in_line-pl.linebuf.p); - } - else - { - fprintf(stderr, "Warning: Invalid PLS line (empty filename) - corrupt playlist file?\n"); - continue; - } + ++in_line; + line_offset = (size_t) (in_line-pl.linebuf.p); } else { - fprintf(stderr, "Warning: Invalid PLS line (no '=' after 'File') - corrupt playlist file?\n"); + fprintf(stderr, "Warning: Invalid PLS line (empty filename) - corrupt playlist file?\n"); continue; } } else { - if(param.listentry < 0) - { - printf("#metainfo "); - print_outstr(stdout, pl.linebuf.p, 0, stdout_is_term); - printf("\n"); - } + fprintf(stderr, "Warning: Invalid PLS line (no '=' after 'File') - corrupt playlist file?\n"); continue; } } - - /* make paths absolute */ - /* Windows knows absolute paths with c: in front... should handle this if really supporting win32 again */ - if - ( - (pl.dir.p != NULL) - && (pl.linebuf.p[line_offset]!='/') - && (pl.linebuf.p[line_offset]!='\\') - && strncmp(pl.linebuf.p+line_offset, "http://", 7) - ) + else { - size_t need; - need = pl.dir.size + strlen(pl.linebuf.p+line_offset); - if(pl.linebuf.size < need) + if(param.listentry < 0) { - if(!mpg123_resize_string(&pl.linebuf, need)) - { - error("unable to enlarge linebuf for appending path! skipping"); - continue; - } + printf("#metainfo "); + print_outstr(stdout, pl.linebuf.p, 0, stdout_is_term); + printf("\n"); } - /* move to have the space at beginning */ - memmove(pl.linebuf.p+pl.dir.size-1, pl.linebuf.p+line_offset, strlen(pl.linebuf.p+line_offset)+1); - /* prepend path */ - memcpy(pl.linebuf.p, pl.dir.p, pl.dir.size-1); - line_offset = 0; - } - ++pl.entry; - if(param.listentry < 0) - { - printf("#entry %zu\n", pl.entry); - print_outstr(stdout, pl.linebuf.p+line_offset, 0, stdout_is_term); - printf("\n"); - } - else if((param.listentry == 0) || (param.listentry == pl.entry) || APPFLAG(MPG123APP_CONTINUE)) - { - add_copy_to_playlist(pl.linebuf.p+line_offset); - return 1; + continue; } } - else + + /* make paths absolute */ + /* Windows knows absolute paths with c: in front... should handle this if really supporting win32 again */ + if + ( + (pl.dir.p != NULL) + && (pl.linebuf.p[line_offset]!='/') + && (pl.linebuf.p[line_offset]!='\\') + && strncmp(pl.linebuf.p+line_offset, "http://", 7) + && strncmp(pl.linebuf.p+line_offset, "https://", 8) + && strncmp(pl.linebuf.p+line_offset, "file://", 7) + ) { - if (param.listname) - if(pl.file) fclose (pl.file); - param.listname = NULL; - pl.file = NULL; -#ifdef WANT_WIN32_SOCKETS - if( pl.sockd != -1) + size_t need; + need = pl.dir.size + strlen(pl.linebuf.p+line_offset); + if(pl.linebuf.size < need) { - win32_net_close(pl.sockd); - pl.sockd = -1; + if(!mpg123_resize_string(&pl.linebuf, need)) + { + error("unable to enlarge linebuf for appending path! skipping"); + continue; + } } -#endif + /* move to have the space at beginning */ + memmove(pl.linebuf.p+pl.dir.size-1, pl.linebuf.p+line_offset, strlen(pl.linebuf.p+line_offset)+1); + /* prepend path */ + memcpy(pl.linebuf.p, pl.dir.p, pl.dir.size-1); + line_offset = 0; + } + ++pl.entry; + if(param.listentry < 0) + { + printf("#entry %zu\n", pl.entry); + print_outstr(stdout, pl.linebuf.p+line_offset, 0, stdout_is_term); + printf("\n"); + } + else if((param.listentry == 0) || (param.listentry == pl.entry) || APPFLAG(MPG123APP_CONTINUE)) + { + add_copy_to_playlist(pl.linebuf.p+line_offset); + return 1; } } } diff --git a/src/resolver.c b/src/resolver.c index 9f0616e..87af96f 100644 --- a/src/resolver.c +++ b/src/resolver.c @@ -269,7 +269,7 @@ int open_connection(mpg123_string *host, mpg123_string *port) { /* Name lookup. */ if (!(myhostent = gethostbyname(host->p))) return -1; - memcpy (&myaddr, myhostent->h_addr, sizeof(myaddr)); + memcpy (&myaddr, myhostent->h_addr_list[0], sizeof(myaddr)); server.sin_addr.s_addr = myaddr.s_addr; } else /* Just use the IP. */ diff --git a/src/streamdump.c b/src/streamdump.c index 8d0ae36..242c88a 100644 --- a/src/streamdump.c +++ b/src/streamdump.c @@ -1,6 +1,9 @@ /* streamdump: Dumping a copy of the input data. + This evolved into the generic I/O interposer for direct file or http stream + access, with explicit buffering for getline. + copyright 2010-2019 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Michael Hipp @@ -11,13 +14,341 @@ #include #include "debug.h" +#ifndef O_BINARY +#define O_BINARY 0 +#endif + /* Stream dump descriptor. */ static int dump_fd = -1; -/* Read data from input, write copy to dump file. */ -static ssize_t dump_read(int fd, void *buf, size_t count) +// Read without the buffer. This is used to fill the buffer explicitly in getline. +// This is the function that finally wraps around all the different types of input. + +static ssize_t stream_read_raw(struct stream *sd, void *buf, size_t count) { - ssize_t ret = read(fd, buf, count); + ssize_t ret = -1; +#ifdef NET123 + if(sd->nh) + ret = net123_read(sd->nh, buf, count); +#endif +#ifdef WANT_WIN32_SOCKETS + if(sd->fd >= 0 && sd->network) + ret = win32_net_read(sd->fd, buf, count); +#endif + if(sd->fd >= 0) // plain file or network socket + ret = (ssize_t) unintr_read(sd->fd, buf, count); + return ret; +} + +static ssize_t stream_read(struct stream *sd, void *buf, size_t count) +{ + if(!sd) + return -1; + char *bbuf = buf; + ssize_t ret = 0; + if(count > SSIZE_MAX) + return -1; + while(count) + { + size_t get = 0; + if(sd->fill) + { // drain the buffer + get = sd->fill > count ? count : sd->fill; + memcpy(bbuf, sd->bufp, get); + sd->fill -= get; + sd->bufp += get; + } else + { // get it from the source + ssize_t rret = stream_read_raw(sd, bbuf, count); + if(rret < 0) + return ret > 0 ? ret : -1; + if(rret == 0) + return ret; + get = rret; + } + bbuf += get; + count -= get; + ret += get; + } + return ret; +} + +static off_t stream_seek(struct stream *sd, off_t pos, int whence) +{ + if(!sd || sd->network) + return -1; + return lseek(sd->fd, pos, whence); +} + +// Read into the stream buffer, look for line endings there, copy stuff. +// This should work with \n and \r\n sequences ... even just \r sequences? +// Yes, either \r or \n ends a line, a following \n or \r is just swallowed. +// Need to catch the case where the buffer ends with \r and the next buffer +// contents start with the matching \n, and the other way round. +ssize_t stream_getline(struct stream *sd, mpg123_string *line) +{ + if(!sd || !line) + return -1; + line->fill = 0; // this is EOF + char lend = 0; + while(1) + { + mdebug("getline loop with %d", sd->fill); + // If we got just an \r, we need to ensure that we swalloed the matching \n, + // too. This implies that we got a line stored already. + if(sd->fill && lend) + { + // finish skipping over an earlier line end + if( (*sd->bufp == '\n' || *sd->bufp == '\r') && *sd->bufp != lend) + { + ++sd->bufp; + --sd->fill; + } + // whatever happened, no half-line-end lurking anymore + return line->fill; + } + if(sd->fill) + { + // look for line end here, copy things + size_t i = 0; + while(i < sd->fill && sd->bufp[i] != '\n' && sd->bufp[i] != '\r') + ++i; + // either found an end, or hit the end + if(!mpg123_add_substring(line, sd->bufp, 0, i)) + return -1; // out of memory + // skip over a line end if found + if(i == sd->fill) + { + // not done yet, refill, please + sd->fill = 0; + } else + { + // got end and stored complete line, but need to go on to capture full line end + lend = sd->bufp[i]; // either \r or \n + sd->bufp += i+1; + sd->fill -= i+1; + } + } else + { + debug("re-filling buffer"); + // refill buffer + ssize_t ret = stream_read_raw(sd, sd->buf, sizeof(sd->buf)); + mdebug("raw read return: %zd", ret); + if(ret < 0) + return -1; + else if(ret == 0) + return line->fill; // A line ends at end of file. + else + { + sd->fill = ret; + sd->bufp = sd->buf; + } + } + } +} + +int stream_parse_headers(struct stream *sd) +{ + int ret = 0; + mpg123_string line; + mpg123_init_string(&line); + mpg123_string icyint; + mpg123_init_string(&icyint); + const char *head[] = { "content-type", "icy-name", "icy-url", "icy-metaint" }; + mpg123_string *val[] = { &sd->htd.content_type, &sd->htd.icy_name, &sd->htd.icy_url, &icyint }; + int hn = sizeof(head)/sizeof(char*); + int hi = -1; + int got_ok = 0; + debug("parsing headers"); + while(stream_getline(sd, &line) > 0) + { + mdebug("HTTP in: %s", line.p); + if(line.p[0] == 0) + { + break; // This is the content separator line. + } + // React to HTTP error codes, but do not enforce an OK being sent as Shoutcast + // only produces very minimal headers, not even a HTTP response code. + if(!strncasecmp("http/", line.p, 5)) + { + // HTTP/1.1 200 OK + char *tok = line.p; + while(*tok && *tok != ' ' && *tok != '\t') + ++tok; + while(*tok && (*tok == ' ' || *tok == '\t')) + ++tok; + if(tok && *tok != '2') + { + merror("HTTP error response: %s", line.p); + ret = -1; + break; + } else if(tok && *tok == '2') + { + if(param.verbose > 2) + fprintf(stderr, "Note: got a positive HTTP response\n"); + got_ok = 1; + } + } + if(hi >= 0 && (line.p[0] == ' ' || line.p[0] == '\t')) + { + debug("header continuation"); + // nh continuation line, appending to already stored value. + char *v = line.p+1; + while(*v == ' ' || *v == '\t'){ ++v; } + if(!mpg123_add_string(val[hi], v)) + { + merror("failed to grow header value for %s", head[hi]); + hi = -1; + continue; + } + } + char *n = line.p; + char *v = strchr(line.p, ':'); + if(!v) + continue; // No proper header line. + // Got a header line. + *v = 0; // Terminate the header name. + if(param.verbose > 2) + fprintf(stderr, "Note: got header: %s\n", n); + ++v; // Value starts after : and whitespace. + while(*v == ' ' || *v == '\t'){ ++v; } + for(hi = 0; hi 2) + fprintf(stderr, "Note: storing HTTP header %s: %s\n", head[hi], v); + got_ok = 1; // When we got some header to store, things seem fine. + if(!mpg123_set_string(val[hi], v)) + { + error("failed to allocate header value storage"); + hi = -1; + continue; + } + } + if(icyint.fill) + { + sd->htd.icy_interval = atol(icyint.p); + if(param.verbose > 1) + fprintf(stderr, "Info: ICY interval %li\n", (long)sd->htd.icy_interval); + } + if(!got_ok) + { + error("missing positive server response"); + ret = -1; + } + mpg123_free_string(&icyint); + mpg123_free_string(&line); + return ret; +} + +static void stream_init(struct stream *sd) +{ + sd->bufp = sd->buf; + sd->fill = 0; + sd->network = 0; + sd->fd = -1; +#ifdef NET123 + sd->nh = NULL; +#endif + httpdata_init(&sd->htd); +} + +struct stream *stream_open(const char *url) +{ + struct stream *sd = malloc(sizeof(struct stream)); + if(!sd) + return NULL; + stream_init(sd); + mdebug("opening resource %s", url); + if(!strcmp(url, "-")) + { + sd->fd = STDIN_FILENO; + compat_binmode(STDIN_FILENO, TRUE); + } + else if(!strncasecmp("file://", url, 7)) + url+= 7; // use local file access for files, the scheme may be useful +#ifdef NET123 + else if(!strncasecmp("http://", url, 7) || !strncasecmp("https://", url, 8)) + { + sd->network = 1; + // Network stream with header parsing. + const char *client_head[] = { NULL, NULL, NULL }; + client_head[0] = param.talk_icy ? icy_yes : icy_no; + mpg123_string accept; + mpg123_init_string(&accept); + append_accept(&accept); + client_head[1] = accept.p; + sd->nh = net123_open(url, client_head); + if(stream_parse_headers(sd)) + { + stream_close(sd); + return NULL; + } + } +#elif defined(NETWORK) + else if(!strncasecmp("http://", url, 7)) + { +#ifdef WANT_WIN32_SOCKETS + sd->fd = win32_net_http_open(url, &sd->htd); +#else + sd->fd = http_open(url, &sd->htd); +#endif + if(sd->fd < 0) + { + stream_close(sd); + return NULL; + } + } +#endif + else + { + // plain file access + errno = 0; + sd->fd = compat_open(url, O_RDONLY|O_BINARY); + if(sd->fd < 0) + { + merror("failed to open file: %s", strerror(errno)); + stream_close(sd); + return NULL; + } + } + return sd; +} + +void stream_close(struct stream *sd) +{ + if(!sd) + return; +#ifdef NET123 + if(sd->nh) + net123_close(sd->nh); +#endif +#ifdef WANT_WIN32_SOCKETS + if(sd->fd >= 0 && sd->network) + { + if(sd->fd != SOCKET_ERROR) + win32_net_close(sd->fd); + } +#endif + if(sd->fd >= 0) // plain file or network socket + close(sd->fd); + httpdata_free(&sd->htd); + free(sd); +} + +/* Read data from input, write copy to dump file. */ +static ssize_t dump_read(void *handle, void *buf, size_t count) +{ + struct stream *sd = handle; + ssize_t ret = stream_read(sd, buf, count); if(ret > 0 && dump_fd > -1) { ret = unintr_write(dump_fd, buf, ret); @@ -26,9 +357,10 @@ static ssize_t dump_read(int fd, void *buf, size_t count) } /* Also mirror seeks, to prevent messed up dumps of seekable streams. */ -static off_t dump_seek(int fd, off_t pos, int whence) +static off_t dump_seek(void *handle, off_t pos, int whence) { - off_t ret = lseek(fd, pos, whence); + struct stream *sd = handle; + off_t ret = stream_seek(sd, pos, whence); if(ret >= 0 && dump_fd > -1) { ret = lseek(dump_fd, pos, whence); @@ -37,30 +369,62 @@ static off_t dump_seek(int fd, off_t pos, int whence) } /* External API... open and close. */ -int dump_open(mpg123_handle *mh) +int dump_setup(struct stream *sd, mpg123_handle *mh) { - int ret; + int ret = MPG123_OK; + int do_replace = 0; // full replacement with handle - if(param.streamdump == NULL) return 0; - - if(!param.quiet) fprintf(stderr, "Note: Dumping stream to %s\n", param.streamdump); - - dump_fd = compat_open(param.streamdump, O_CREAT|O_TRUNC|O_RDWR); - if(dump_fd < 0) + // paranoia: if buffer active, ensure handle I/O + if(sd->fill) + do_replace = 1; +#ifdef NET123 + if(sd->nh) + do_replace = 1; +#endif + if(param.streamdump) { - error1("Failed to open dump file: %s\n", strerror(errno)); - return -1; + do_replace = 1; + // open freshly or keep open + if(dump_fd < 0) + { + if(!param.quiet) + fprintf(stderr, "Note: Dumping stream to %s\n", param.streamdump); + dump_fd = compat_open(param.streamdump, O_CREAT|O_TRUNC|O_RDWR); + } + if(dump_fd < 0) + { + error1("Failed to open dump file: %s\n", strerror(errno)); + return -1; + } +#ifdef WIN32 + _setmode(dump_fd, _O_BINARY); +#endif } -#ifdef WIN32 - _setmode(dump_fd, _O_BINARY); -#endif + if( MPG123_OK != mpg123_param(mh, MPG123_ICY_INTERVAL + , param.icy_interval ? param.icy_interval : sd->htd.icy_interval, 0) ) + error1("Cannot set ICY interval: %s", mpg123_strerror(mh)); + if(param.icy_interval > 0 && param.verbose > 1) + fprintf(stderr, "Info: Forced ICY interval %li\n", param.icy_interval); - ret = mpg123_replace_reader(mh, dump_read, dump_seek); + if(do_replace) + { + mpg123_replace_reader_handle(mh, dump_read, dump_seek, NULL); + ret = mpg123_open_handle(mh, sd); + } else + { +#ifdef WANT_WIN32_SOCKETS + if(sd->network) + win32_net_replace(mh); + else // ensure libmpg123 is using its own reader otherwise +#endif + mpg123_replace_reader(mh, NULL, NULL); + ret = mpg123_open_fd(mh, sd->fd); + } if(ret != MPG123_OK) { - error1("Unable to replace reader for stream dump: %s\n", mpg123_strerror(mh)); - dump_close(); + error1("Unable to replace reader/open track for stream dump: %s\n", mpg123_strerror(mh)); + dump_close(sd); return -1; } else return 0; diff --git a/src/streamdump.h b/src/streamdump.h index dd9f52e..c220b62 100644 --- a/src/streamdump.h +++ b/src/streamdump.h @@ -1,9 +1,13 @@ /* - streamdump: Dumping a copy of the input data. + streamdump: I/O interposing for reading input streams - copyright 2010 by the mpg123 project - free software under the terms of the LGPL 2.1 + This initially was about dumping a copy of the input data, as read + by libmpg123. Now it is a generic interposer to wrap around plain file + and network stream reading. + + copyright 2010-2022 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org - initially written by Michael Hipp + initially written by Thomas Orgis */ #ifndef STREAMDUMP_H @@ -11,10 +15,39 @@ #include "mpg123app.h" -/* Open dump stream, if requested, and replace readers. - Return value is 0 for no error, -1 when bad. */ -int dump_open(mpg123_handle *mh); -/* Just close... */ -void dump_close(void); +#ifdef NET123 +#include "net123.h" +#endif + +// The stream is either addressed via file descriptor or net123 handle. +struct stream +{ + char buf[256]; // buffer for getline + char *bufp; // read pointer in buffer + int fill; // bytes in buffer + int network; // flag to mark network streams (with httpdata) + int fd; // if > 0: plain file descriptor or win32 net socket + struct httpdata htd; +#ifdef NET123 + net123_handle *nh; // if != null: a net123 stream +#endif +}; + +// Open a stream resource, creating and returning a new handle. +struct stream * stream_open(const char *url); +// Read lines, with arbitrary line end, which is stripped. +// Return number of bytes in line (including closing zero byte) or error < 0. +// End of file returns zero, consequently. +ssize_t stream_getline(struct stream *sd, mpg123_string *line); +void stream_close(struct stream *sd); + +// Use an open stream object to optionally prepare the dump and +// link up input to the mpg123 handle. +// Can be called repeatedly to switch input methods with new +// stream objects, but keeping a stream dump file open. +// Return value is 0 for no error, -1 when bad. +int dump_setup(struct stream *sd, mpg123_handle *mh); +// Just close the dump output, not touching anything else. +void dump_close(); #endif diff --git a/src/term.c b/src/term.c index 32b3bcd..2e9594a 100644 --- a/src/term.c +++ b/src/term.c @@ -7,33 +7,20 @@ */ #include "mpg123app.h" - -#ifdef HAVE_TERMIOS - -#include #include #include "term.h" +#include "terms.h" #include "common.h" #include "playlist.h" #include "metaprint.h" #include "debug.h" static int term_enable = 0; -// We can work with the terminal either via stdin or stderr. -// It can be that only one side is hooked to an interactive terminal. -// You should be able to pipe terminal control commands (for testing) -// and still have proper display. -static int term_fd = -1; -static struct termios old_tio; int seeking = FALSE; extern out123_handle *ao; -/* Buffered key from a signal or whatnot. - We ignore the null character... */ -static char prekey = 0; - /* Hm, next step would be some system in this, plus configurability... Two keys for everything? It's just stop/pause for now... */ struct keydef { const char key; const char key2; const char* desc; }; @@ -66,50 +53,16 @@ struct keydef term_help[] = ,{ MPG123_BOOKMARK_KEY, 0, "print out current position in playlist and track, for the benefit of some external tool to store bookmarks" } ,{ MPG123_HELP_KEY, 0, "this help" } ,{ MPG123_QUIT_KEY, 0, "quit" } + ,{ MPG123_EQ_RESET_KEY, 0, "reset to a flat equalizer" } + ,{ MPG123_EQ_SHOW_KEY, 0, "show our current rough equalizer settings" } + ,{ MPG123_BASS_UP_KEY, 0, "more bass" } + ,{ MPG123_BASS_DOWN_KEY, 0, "less bass" } + ,{ MPG123_MID_UP_KEY, 0, "more mids" } + ,{ MPG123_MID_DOWN_KEY, 0, "less mids" } + ,{ MPG123_TREBLE_UP_KEY, 0, "more treble" } + ,{ MPG123_TREBLE_DOWN_KEY, 0, "less treble" } }; -void term_sigcont(int sig); -static void term_sigusr(int sig); - -/* This must call only functions safe inside a signal handler. */ -int term_setup(struct termios *pattern) -{ - mdebug("setup on fd %d", term_fd); - struct termios tio = *pattern; - - /* One might want to use sigaction instead. */ - signal(SIGCONT, term_sigcont); - signal(SIGUSR1, term_sigusr); - signal(SIGUSR2, term_sigusr); - - tio.c_lflag &= ~(ICANON|ECHO); - tio.c_cc[VMIN] = 1; - tio.c_cc[VTIME] = 0; - return tcsetattr(term_fd,TCSANOW,&tio); -} - -void term_sigcont(int sig) -{ - term_enable = 0; - - if (term_setup(&old_tio) < 0) - { - fprintf(stderr,"Can't set terminal attributes\n"); - return; - } - - term_enable = 1; -} - -static void term_sigusr(int sig) -{ - switch(sig) - { - case SIGUSR1: prekey=*param.term_usr1; break; - case SIGUSR2: prekey=*param.term_usr2; break; - } -} - /* initialze terminal */ void term_init(void) { @@ -124,19 +77,15 @@ void term_init(void) return; term_enable = 0; - - if( tcgetattr(term_fd=STDERR_FILENO,&old_tio) < 0 - && tcgetattr(term_fd=STDIN_FILENO,&old_tio) < 0 ) + errno = 0; + if(term_setup() < 0) { - fprintf(stderr,"Can't get terminal attributes\n"); + if(errno) + merror("failed to set up terminal: %s", strerror(errno)); + else + error("failed to set up terminal"); return; } - if(term_setup(&old_tio) < 0) - { - fprintf(stderr,"Can't set terminal attributes\n"); - return; - } - term_enable = 1; } @@ -255,41 +204,10 @@ static void seekmode(mpg123_handle *mh, out123_handle *ao) } } -/* Get the next pressed key, if any. - Returns 1 when there is a key, 0 if not. */ -static int get_key(int do_delay, char *val) -{ - fd_set r; - struct timeval t; - - /* Shortcut: If some other means sent a key, use it. */ - if(prekey) - { - debug1("Got prekey: %c\n", prekey); - *val = prekey; - prekey = 0; - return 1; - } - - t.tv_sec=0; - t.tv_usec=(do_delay) ? 10*1000 : 0; - - FD_ZERO(&r); - FD_SET(STDIN_FILENO,&r); - if(select(1,&r,NULL,NULL,&t) > 0 && FD_ISSET(0,&r)) - { - if(read(STDIN_FILENO,val,1) <= 0) - return 0; /* Well, we couldn't read the key, so there is none. */ - else - return 1; - } - else return 0; -} - static void term_handle_key(mpg123_handle *fr, out123_handle *ao, char val) { debug1("term_handle_key: %c", val); - switch(tolower(val)) + switch(val) { case MPG123_BACK_KEY: out123_pause(ao); @@ -314,7 +232,9 @@ static void term_handle_key(mpg123_handle *fr, out123_handle *ao, char val) case MPG123_QUIT_KEY: debug("QUIT"); if(stopped) - { + { if(param.verbose) + print_stat(fr,0,ao,0,¶m); + stopped = 0; out123_pause(ao); /* no chance for annoying underrun warnings */ out123_drop(ao); @@ -389,14 +309,48 @@ static void term_handle_key(mpg123_handle *fr, out123_handle *ao, char val) offset+=50; break; case MPG123_VOL_UP_KEY: - mpg123_volume_change(fr, 0.02); + mpg123_volume_change_db(fr, +1); break; case MPG123_VOL_DOWN_KEY: - mpg123_volume_change(fr, -0.02); + mpg123_volume_change_db(fr, -1); break; case MPG123_VOL_MUTE_KEY: set_mute(ao, muted=!muted); break; + case MPG123_EQ_RESET_KEY: + mpg123_reset_eq(fr); + break; + case MPG123_EQ_SHOW_KEY: + { + if(param.verbose) + print_stat(fr,0,ao,0,¶m); + // Assuming only changes happen via terminal control, these 3 values + // are what counts. + fprintf( stderr, "\n\nbass: %.3f\nmid: %.3f\ntreble: %.3f\n\n" + , mpg123_geteq(fr, MPG123_LEFT, 0) + , mpg123_geteq(fr, MPG123_LEFT, 1) + , mpg123_geteq(fr, MPG123_LEFT, 2) + ); + } + break; + case MPG123_BASS_UP_KEY: + mpg123_eq_change(fr, MPG123_LR, 0, 0, +1); + break; + case MPG123_BASS_DOWN_KEY: + mpg123_eq_change(fr, MPG123_LR, 0, 0, -1); + break; + case MPG123_MID_UP_KEY: + mpg123_eq_change(fr, MPG123_LR, 1, 1, +1); + break; + case MPG123_MID_DOWN_KEY: + mpg123_eq_change(fr, MPG123_LR, 1, 1, -1); + break; + case MPG123_TREBLE_UP_KEY: + mpg123_eq_change(fr, MPG123_LR, 2, 31, +1); + break; + case MPG123_TREBLE_DOWN_KEY: + mpg123_eq_change(fr, MPG123_LR, 2, 31, -1); + break; case MPG123_PITCH_UP_KEY: case MPG123_PITCH_BUP_KEY: case MPG123_PITCH_DOWN_KEY: @@ -460,7 +414,6 @@ static void term_handle_key(mpg123_handle *fr, out123_handle *ao, char val) print_stat(fr,0,ao,0,¶m); fprintf(stderr, "%s\n", param.verbose ? "\n" : ""); print_id3_tag(fr, param.long_id3, stderr, term_width(STDERR_FILENO)); - fprintf(stderr, "\n"); break; case MPG123_MPEG_KEY: if(param.verbose) @@ -555,7 +508,7 @@ static void term_handle_input(mpg123_handle *fr, out123_handle *ao, int do_delay { char val; /* Do we really want that while loop? This means possibly handling multiple inputs that come very rapidly in one go. */ - while(get_key(do_delay, &val)) + while(term_get_key(do_delay, &val)) { term_handle_key(fr, ao, val); } @@ -571,9 +524,5 @@ void term_exit(void) if(!term_enable) return; - debug("reset attrbutes"); - tcsetattr(term_fd,TCSAFLUSH,&old_tio); + term_restore(); } - -#endif - diff --git a/src/term.h b/src/term.h index 5e8e754..fc32b91 100644 --- a/src/term.h +++ b/src/term.h @@ -12,8 +12,6 @@ #include "mpg123app.h" #include "audio.h" -#ifdef HAVE_TERMIOS - #define LOOP_CYCLES 0.500000 /* Loop time in sec */ /* @@ -58,6 +56,16 @@ #define MPG123_PITCH_DOWN_KEY 'x' #define MPG123_PITCH_BDOWN_KEY 'X' #define MPG123_PITCH_ZERO_KEY 'w' + +#define MPG123_EQ_RESET_KEY 'e' +#define MPG123_EQ_SHOW_KEY 'E' +#define MPG123_BASS_UP_KEY 'A' +#define MPG123_BASS_DOWN_KEY 'a' +#define MPG123_MID_UP_KEY 'J' +#define MPG123_MID_DOWN_KEY 'j' +#define MPG123_TREBLE_UP_KEY 'N' +#define MPG123_TREBLE_DOWN_KEY 'n' + #define MPG123_BOOKMARK_KEY 'k' /* This counts as "undocumented" and can disappear */ #define MPG123_FRAME_INDEX_KEY 'i' @@ -85,5 +93,3 @@ off_t term_control(mpg123_handle *mh, out123_handle *ao); void term_hint(void); /* Print a message hinting at terminal usage. */ #endif - -#endif diff --git a/src/term_none.c b/src/term_none.c new file mode 100644 index 0000000..ec24e08 --- /dev/null +++ b/src/term_none.c @@ -0,0 +1,31 @@ +/* + term_none: no-op terminal, nothing at all + + copyright 2008-2022 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Thomas Orgis and Jonathan Yong +*/ + +int term_have_fun(int fd, int want_visuals) +{ + return 0; +} + +int term_width(int fd) +{ + return -1; +} + +int term_setup(void) +{ + return -1; +} + +void term_restore(void) +{ +} + +int term_get_key(int do_delay, char *val) +{ + return 0; +} diff --git a/src/term_posix.c b/src/term_posix.c new file mode 100644 index 0000000..ba1d161 --- /dev/null +++ b/src/term_posix.c @@ -0,0 +1,229 @@ +/* + term_posix: POSIX-specifc terminal functionality + + HAVE_TERMIOS is a prerequisite. + + copyright 2008-2022 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Thomas Orgis +*/ + +#include "config.h" + +#ifdef __OS2__ +// Hoping for properly working termios in some future (?!), but until then, +// we need keyboard access bypassing that. +#define INCL_KBD +#define INCL_DOSPROCESS +#include +#endif + +#include "compat.h" + +#ifndef HAVE_TERMIOS +#error "No TERMIOS? Here?" +#endif + +// for param struct +#include "mpg123app.h" + +#include +#include + +#include "terms.h" + +#include "debug.h" + +static int term_is_fun = -1; +// This now always refers to a ;freshly opened terminal descriptor (e.g. /dev/tty). +// Printouts to stderr are independent of this. +static int term_fd = -1; +static struct termios old_tio; + +/* Buffered key from a signal or whatnot. + We ignore the null character... */ +static char prekey = 0; + +int term_have_fun(int fd, int want_visuals) +{ + if(term_is_fun > -1) + return term_is_fun; + else + term_is_fun = 0; + if(term_width(fd) > 0 && want_visuals) + { + /* Only play with non-dumb terminals. */ + char *tname = compat_getenv("TERM"); + if(tname) + { + if(strcmp(tname, "") && strcmp(tname, "dumb")) + term_is_fun = 1; + free(tname); + } + } + return term_is_fun; +} + +/* Also serves as a way to detect if we have an interactive terminal. */ +int term_width(int fd) +{ +#ifdef __OS2__ + int s[2]; + _scrsize (s); + // It seems like we cannot really use the last character of the + // term and have to stop one short to avoid advancing a line. + if (s[0] >= 0) + return s[0] - 1; +#else + struct winsize geometry; + geometry.ws_col = 0; + if(ioctl(fd, TIOCGWINSZ, &geometry) >= 0) + return (int)geometry.ws_col; +#endif + return -1; +} + +static int term_setup_detail(struct termios *pattern); + +static void term_sigcont(int sig) +{ + if(term_setup_detail(&old_tio) < 0) + { + debug("Can't set terminal attributes"); + return; + } +} + +static void term_sigusr(int sig) +{ + switch(sig) + { + case SIGUSR1: prekey=*param.term_usr1; break; + case SIGUSR2: prekey=*param.term_usr2; break; + } +} + +/* This must call only functions safe inside a signal handler. */ +static int term_setup_detail(struct termios *pattern) +{ + mdebug("setup on fd %d", term_fd); + + /* One might want to use sigaction instead. */ + signal(SIGCONT, term_sigcont); + signal(SIGUSR1, term_sigusr); + signal(SIGUSR2, term_sigusr); + struct termios tio = *pattern; + tio.c_lflag &= ~(ICANON|ECHO); + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; +#ifdef __OS2__ + // Do not care for the error until OS/2 is known to work. + tcsetattr(term_fd,TCSANOW,&tio); + return 0; +#else + return tcsetattr(term_fd,TCSANOW,&tio); +#endif +} + +int term_setup(void) +{ + if(term_fd < 0) + { + const char *term_name; +#ifdef HAVE_CTERMID + term_name = ctermid(NULL); +#else + term_name = "/dev/tty"; +#endif + if(term_name) + mdebug("accessing terminal for control via %s", term_name); + else + { + error("no controlling terminal"); + return -1; + } + term_fd = open(term_name, O_RDONLY); + if(term_fd < 0) + { + merror("failed to open terminal: %s", strerror(errno)); + return -1; + } + } + + if(tcgetattr(term_fd, &old_tio) < 0) + { + // For now, this always fails on OS/2, but they might fix things. + // So just try to move on. +#ifndef __OS2__ + merror("failed to get terminal attributes: %s", strerror(errno)); + return -1; +#endif + } + + errno = 0; + if(term_setup_detail(&old_tio) < 0) + { + close(term_fd); + term_fd = -1; + if(errno) + merror("failure setting terminal attributes: %s", strerror(errno)); + else + error("failure setting terminal attributes"); + return -1; + } + return 0; +} + +/* Get the next pressed key, if any. + Returns 1 when there is a key, 0 if not. */ +int term_get_key(int do_delay, char *val) +{ +#ifdef __OS2__ + KBDKEYINFO key; + key.chChar = 0; + key.chScan = 0; + if(do_delay) + DosSleep(10); + if(!KbdCharIn(&key,IO_NOWAIT,0) && key.chChar) + { + *val = key.chChar; + return 1; + } +#else + fd_set r; + struct timeval t; + + /* Shortcut: If some other means sent a key, use it. */ + if(prekey) + { + debug1("Got prekey: %c\n", prekey); + *val = prekey; + prekey = 0; + return 1; + } + + t.tv_sec=0; + t.tv_usec=(do_delay) ? 10*1000 : 0; + + FD_ZERO(&r); + FD_SET(term_fd,&r); + if(select(term_fd+1,&r,NULL,NULL,&t) > 0 && FD_ISSET(term_fd,&r)) + { + if(read(term_fd,val,1) <= 0) + return 0; /* Well, we couldn't read the key, so there is none. */ + else + return 1; + } +#endif + else return 0; +} + +void term_restore(void) +{ + debug("reset attrbutes"); + tcsetattr(term_fd,TCSAFLUSH,&old_tio); + + if(term_fd > -1) + close(term_fd); + term_fd = -1; +} diff --git a/src/term_win32.c b/src/term_win32.c new file mode 100644 index 0000000..cf8e221 --- /dev/null +++ b/src/term_win32.c @@ -0,0 +1,113 @@ +/* + term_win32: Windows-specifc terminal functionality + + This is a very lightweight terminal library, just the minimum to + + - get at the width of the terminal (if there is one) + - be able to read single keys being pressed for control + - maybe also switch of echoing of input + + copyright 2008-2022 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Thomas Orgis and Jonathan Yong +*/ + + +#include "config.h" +#include "compat.h" + +#include "terms.h" + +#define WIN32_LEAN_AND_MEAN 1 +#include +#include +#include +#include + +#include "debug.h" + +static HANDLE consoleintput = INVALID_HANDLE_VALUE; +static HANDLE consoleoutput = INVALID_HANDLE_VALUE; +static HANDLE getconsoleintput(void){ + DWORD mode, r; + if(consoleintput == INVALID_HANDLE_VALUE){ + consoleintput = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if(consoleintput == INVALID_HANDLE_VALUE || consoleintput == NULL) + return consoleintput; + GetConsoleMode(consoleintput, &mode); + mode |= ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT|ENABLE_WINDOW_INPUT; + mode &= ~(ENABLE_ECHO_INPUT|ENABLE_QUICK_EDIT_MODE|ENABLE_MOUSE_INPUT); + SetConsoleMode(consoleintput, mode); + } + return consoleintput; +} + +static HANDLE getconsole(void){ + if(consoleoutput == INVALID_HANDLE_VALUE){ + consoleoutput = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + } + return consoleoutput; +} + +// No fun for windows until we reorganize control character stuff. +int term_have_fun(int fd, int want_visuals) +{ + return 0; +} + +static DWORD lastmode; +int term_setup(void) +{ + return 0; +} + +void term_restore(void){ + CloseHandle(consoleintput); + CloseHandle(consoleoutput); + consoleintput = INVALID_HANDLE_VALUE; + consoleoutput = INVALID_HANDLE_VALUE; +} + +int term_width(int fd) +{ + CONSOLE_SCREEN_BUFFER_INFO pinfo; + HANDLE h; + + h = getconsole(); + + if(h == INVALID_HANDLE_VALUE || h == NULL) + return -1; + if(GetConsoleScreenBufferInfo(h, &pinfo)) + // One less than actual width, as Terminal advances + // to next line with the last character. + return pinfo.dwMaximumWindowSize.X -1; + return -1; +} + +int term_present(void){ + return GetConsoleWindow() ? 1 : 0; +} + +/* Get the next pressed key, if any. + Returns 1 when there is a key, 0 if not. */ +int term_get_key(int do_delay, char *val){ + INPUT_RECORD record; + HANDLE input; + DWORD res; + + input = getconsoleintput(); + if(input == NULL || input == INVALID_HANDLE_VALUE) + return 0; + + while(WaitForSingleObject(input, do_delay ? 10 : 0) == WAIT_OBJECT_0){ + do_delay = 0; + if(!ReadConsoleInput(input, &record, 1, &res)) + return 0; + if(record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown){ + *val = record.Event.KeyEvent.uChar.AsciiChar; + return 1; + } + } + + return 0; +} diff --git a/src/terms.h b/src/terms.h new file mode 100644 index 0000000..1952587 --- /dev/null +++ b/src/terms.h @@ -0,0 +1,52 @@ +#ifndef H_TERMS +#define H_TERMS +/* + terms: terminal specifics + + This is a very lightweight terminal library, just the minimum to + + - get at the width of the terminal (if there is one) + - be able to read single keys being pressed for control + - maybe also switch off echoing of input + + copyright 2008-2022 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Thomas Orgis and Jonathan Yong +*/ + +/* These two functions query terminal properties based on a terminal + being connected to the specified file descriptor (either STDIN_FILENO + or STDERR_FILENO). */ + +/* Return non-zero if full terminal fun is desired/possible. */ +int term_have_fun(int fd, int want_visuals); + +/* Return width of terminal associated with given descriptor, + -1 when there is none. */ +int term_width(int fd); + +/* + This is for more serious work with the terminal: It is sensible to + open some internal handle and continue to operate on that on subsequent + calls. +*/ + +/** Setup terminal for control work (things like disabling echo and + * buffering. You handle your handles internally. + * \return 0 on suceess, -1 on trouble + */ +int term_setup(void); + +/** Restore terminal properties to what they were before we messed + * around. Failure is not an option. + */ +void term_restore(void); + +/** Check for and return a key press event. + * \param do_delay Wait for up to 10 ms for a key event if true. + * \param val address to store character to + * \return 1 if there is a key, 0 if not + */ +int term_get_key(int do_delay, char *val); + +#endif diff --git a/src/win32_net.c b/src/win32_net.c index b76ffaa..11a5a42 100755 --- a/src/win32_net.c +++ b/src/win32_net.c @@ -396,7 +396,7 @@ static int win32_net_resolve_redirect(mpg123_string *response, mpg123_string *re return TRUE; } -int win32_net_http_open(char* url, struct httpdata *hd) +int win32_net_http_open(const char* url, struct httpdata *hd) { mpg123_string purl, host, port, path; mpg123_string request, response, request_url; @@ -600,7 +600,7 @@ exit: /* The end as well as the exception handling point... */ return 1; } #else -int win32_net_http_open(char* url, struct httpdata *hd) +int win32_net_http_open(const char* url, struct httpdata *hd) { return -1; } diff --git a/src/win32_support.h b/src/win32_support.h index afe9651..1c797de 100755 --- a/src/win32_support.h +++ b/src/win32_support.h @@ -40,7 +40,7 @@ so the socket handle is always associated with the last call to win32_net_http_o * @param[out] hd http data info * @return -1 for failure, 1 for success */ -int win32_net_http_open(char* url, struct httpdata *hd); +int win32_net_http_open(const char* url, struct httpdata *hd); /** * Reads from network socket