From bfbca218bde2ffad26f0b1e3a55347d5c31a9b76 Mon Sep 17 00:00:00 2001 From: Toshaan Bharvani Date: Sun, 12 Nov 2023 22:41:31 +0100 Subject: [PATCH] add new patches Signed-off-by: Toshaan Bharvani --- SOURCES/moc-fortify.patch | 42 ++ SOURCES/moc-https.patch | 9 + SOURCES/moc-pulseaudio.patch | 811 +++++++++++++++++++++++++++++++++++ SPECS/moc.spec | 7 + 4 files changed, 869 insertions(+) create mode 100644 SOURCES/moc-fortify.patch create mode 100644 SOURCES/moc-https.patch create mode 100644 SOURCES/moc-pulseaudio.patch diff --git a/SOURCES/moc-fortify.patch b/SOURCES/moc-fortify.patch new file mode 100644 index 0000000..c749680 --- /dev/null +++ b/SOURCES/moc-fortify.patch @@ -0,0 +1,42 @@ +From 78556fc13026220f800384accf04e139f11e099a Mon Sep 17 00:00:00 2001 +From: Joan Bruguera +Date: Thu, 17 Feb 2022 22:27:34 +0100 +Subject: [PATCH] Workaround mbsrtowcs fortify crash in GLIBC 2.35 + +Reproduces with: + gcc -O2 -Wp,-D_FORTIFY_SOURCE=2 test.c -o test && ./test + +And test.c: + #include + #include + #include + + int main (void) + { + const char *hw = "HelloWorld"; + mbstate_t ps = {0}; + mbsrtowcs (NULL, &hw, (size_t)-1, &ps); + return 0; + } + +Output: + *** buffer overflow detected ***: terminated +--- + utf8.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/utf8.c b/utf8.c +index 2db18f2..806d528 100644 +--- a/utf8.c ++++ b/utf8.c +@@ -167,7 +167,7 @@ static size_t xmbstowcs (wchar_t *dest, const char *src, size_t len, + while (src && (len || !dest)) { + size_t res; + +- res = mbsrtowcs (dest, &src, len, &ps); ++ res = mbsrtowcs (dest, &src, dest ? len : 0, &ps); + if (res != (size_t)-1) { + count += res; + src = NULL; +-- +2.35.1 diff --git a/SOURCES/moc-https.patch b/SOURCES/moc-https.patch new file mode 100644 index 0000000..bbb81f3 --- /dev/null +++ b/SOURCES/moc-https.patch @@ -0,0 +1,9 @@ +--- files.c.orig ++++ files.c +@@ -93,6 +93,7 @@ + inline int is_url (const char *str) + { + return !strncasecmp (str, "http://", sizeof ("http://") - 1) ++ || !strncasecmp (str, "https://", sizeof ("https://") - 1) + || !strncasecmp (str, "ftp://", sizeof ("ftp://") - 1); + } diff --git a/SOURCES/moc-pulseaudio.patch b/SOURCES/moc-pulseaudio.patch new file mode 100644 index 0000000..795d066 --- /dev/null +++ b/SOURCES/moc-pulseaudio.patch @@ -0,0 +1,811 @@ +# HG changeset patch +# User Vladimir Protasov +# Date 1406065659 -14400 +# Branch eoranged +# Node ID aa464842c834f46d0bf8d92dc1841c5e90b8970b +# Parent 58f8152e9cd94f17c6dafbb2f7c44a0fe9638603 +PulseAudio backend. + +http://moc.daper.net/node/831 +Thanks for marienz. + +diff --git a/audio.c b/audio.c +--- a/audio.c ++++ b/audio.c +@@ -32,6 +32,9 @@ + #include "log.h" + #include "lists.h" + ++#ifdef HAVE_PULSE ++# include "pulse.h" ++#endif + #ifdef HAVE_OSS + # include "oss.h" + #endif +@@ -893,6 +896,15 @@ + } + #endif + ++#ifdef HAVE_PULSE ++ if (!strcasecmp(name, "pulseaudio")) { ++ pulse_funcs (funcs); ++ printf ("Trying PulseAudio...\n"); ++ if (funcs->init(&hw_caps)) ++ return; ++ } ++#endif ++ + #ifdef HAVE_OSS + if (!strcasecmp(name, "oss")) { + oss_funcs (funcs); +diff --git a/configure.in b/configure.in +--- a/configure.in ++++ b/configure.in +@@ -162,6 +162,21 @@ + AC_MSG_ERROR([BerkeleyDB (libdb) not found.])) + fi + ++AC_ARG_WITH(pulse, AS_HELP_STRING(--without-pulse, ++ Compile without PulseAudio support.)) ++ ++if test "x$with_pulse" != "xno" ++then ++ PKG_CHECK_MODULES(PULSE, [libpulse], ++ [SOUND_DRIVERS="$SOUND_DRIVERS PULSE" ++ EXTRA_OBJS="$EXTRA_OBJS pulse.o" ++ AC_DEFINE([HAVE_PULSE], 1, [Define if you have PulseAudio.]) ++ EXTRA_LIBS="$EXTRA_LIBS $PULSE_LIBS" ++ CFLAGS="$CFLAGS $PULSE_CFLAGS"], ++ [true]) ++fi ++ ++ + AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss], + [Compile without OSS support])) + +diff --git a/options.c b/options.c +--- a/options.c ++++ b/options.c +@@ -572,10 +572,11 @@ + + #ifdef OPENBSD + add_list ("SoundDriver", "SNDIO:JACK:OSS", +- CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null"); ++ CHECK_DISCRETE(5), "SNDIO", "PulseAudio", "Jack", "ALSA", "OSS", "null"); ++ + #else + add_list ("SoundDriver", "Jack:ALSA:OSS", +- CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null"); ++ CHECK_DISCRETE(5), "SNDIO", "PulseAudio", "Jack", "ALSA", "OSS", "null"); + #endif + + add_str ("JackClientName", "moc", CHECK_NONE); +diff --git a/pulse.c b/pulse.c +new file mode 100644 +--- /dev/null ++++ b/pulse.c +@@ -0,0 +1,705 @@ ++/* ++ * MOC - music on console ++ * Copyright (C) 2011 Marien Zwart ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ */ ++ ++/* PulseAudio backend. ++ * ++ * FEATURES: ++ * ++ * Does not autostart a PulseAudio server, but uses an already-started ++ * one, which should be better than alsa-through-pulse. ++ * ++ * Supports control of either our stream's or our entire sink's volume ++ * while we are actually playing. Volume control while paused is ++ * intentionally unsupported: the PulseAudio documentation strongly ++ * suggests not passing in an initial volume when creating a stream ++ * (allowing the server to track this instead), and we do not know ++ * which sink to control if we do not have a stream open. ++ * ++ * IMPLEMENTATION: ++ * ++ * Most client-side (resource allocation) errors are fatal. Failure to ++ * create a server context or stream is not fatal (and MOC should cope ++ * with these failures too), but server communication failures later ++ * on are currently not handled (MOC has no great way for us to tell ++ * it we no longer work, and I am not sure if attempting to reconnect ++ * is worth it or even a good idea). ++ * ++ * The pulse "simple" API is too simple: it combines connecting to the ++ * server and opening a stream into one operation, while I want to ++ * connect to the server when MOC starts (and fall back to a different ++ * backend if there is no server), and I cannot open a stream at that ++ * time since I do not know the audio format yet. ++ * ++ * PulseAudio strongly recommends we use a high-latency connection, ++ * which the MOC frontend code might not expect from its audio ++ * backend. We'll see. ++ * ++ * We map MOC's percentage volumes linearly to pulse's PA_VOLUME_MUTED ++ * (0) .. PA_VOLUME_NORM range. This is what the PulseAudio docs recommend ++ * ( http://pulseaudio.org/wiki/WritingVolumeControlUIs ). It does mean ++ * PulseAudio volumes above PA_VOLUME_NORM do not work well with MOC. ++ * ++ * Comments in audio.h claim "All functions are executed only by one ++ * thread" (referring to the function in the hw_funcs struct). This is ++ * a blatant lie. Most of them are invoked off the "output buffer" ++ * thread (out_buf.c) but at least the "playing" thread (audio.c) ++ * calls audio_close which calls our close function. We can mostly ++ * ignore this problem because we serialize on the pulseaudio threaded ++ * mainloop lock. But it does mean that functions that are normally ++ * only called between open and close (like reset) are sometimes ++ * called without us having a stream. Bulletproof, therefore: ++ * serialize setting/unsetting our global stream using the threaded ++ * mainloop lock, and check for that stream being non-null before ++ * using it. ++ * ++ * I am not convinced there are no further dragons lurking here: can ++ * the "playing" thread(s) close and reopen our output stream while ++ * the "output buffer" thread is sending output there? We can bail if ++ * our stream is simply closed, but we do not currently detect it ++ * being reopened and no longer using the same sample format, which ++ * might have interesting results... ++ * ++ * Also, read_mixer is called from the main server thread (handling ++ * commands). This crashed me once when it got at a stream that was in ++ * the "creating" state and therefore did not have a valid stream ++ * index yet. Fixed by only assigning to the stream global when the ++ * stream is valid. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#define DEBUG ++ ++#include ++#include "common.h" ++#include "log.h" ++#include "audio.h" ++ ++ ++/* The pulse mainloop and context are initialized in pulse_init and ++ * destroyed in pulse_shutdown. ++ */ ++static pa_threaded_mainloop *mainloop = NULL; ++static pa_context *context = NULL; ++ ++/* The stream is initialized in pulse_open and destroyed in pulse_close. */ ++static pa_stream *stream = NULL; ++ ++static int showing_sink_volume = 0; ++ ++/* Callbacks that do nothing but wake up the mainloop. */ ++ ++static void context_state_callback (pa_context *context ATTR_UNUSED, ++ void *userdata) ++{ ++ pa_threaded_mainloop *m = userdata; ++ ++ pa_threaded_mainloop_signal (m, 0); ++} ++ ++static void stream_state_callback (pa_stream *stream ATTR_UNUSED, ++ void *userdata) ++{ ++ pa_threaded_mainloop *m = userdata; ++ ++ pa_threaded_mainloop_signal (m, 0); ++} ++ ++static void stream_write_callback (pa_stream *stream ATTR_UNUSED, ++ size_t nbytes ATTR_UNUSED, void *userdata) ++{ ++ pa_threaded_mainloop *m = userdata; ++ ++ pa_threaded_mainloop_signal (m, 0); ++} ++ ++/* Initialize pulse mainloop and context. Failure to connect to the ++ * pulse daemon is nonfatal, everything else is fatal (as it ++ * presumably means we ran out of resources). ++ */ ++static int pulse_init (struct output_driver_caps *caps) ++{ ++ pa_context *c; ++ pa_proplist *proplist; ++ ++ assert (!mainloop); ++ assert (!context); ++ ++ mainloop = pa_threaded_mainloop_new (); ++ if (!mainloop) ++ fatal ("Cannot create PulseAudio mainloop"); ++ ++ if (pa_threaded_mainloop_start (mainloop) < 0) ++ fatal ("Cannot start PulseAudio mainloop"); ++ ++ /* TODO: possibly add more props. ++ * ++ * There are a few we could set in proplist.h but nothing I ++ * expect to be very useful. ++ * ++ * http://pulseaudio.org/wiki/ApplicationProperties recommends ++ * setting at least application.name, icon.name and media.role. ++ * ++ * No need to set application.name here, the name passed to ++ * pa_context_new_with_proplist overrides it. ++ */ ++ proplist = pa_proplist_new (); ++ if (!proplist) ++ fatal ("Cannot allocate PulseAudio proplist"); ++ ++ pa_proplist_sets (proplist, ++ PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION); ++ pa_proplist_sets (proplist, PA_PROP_MEDIA_ROLE, "music"); ++ pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "net.daper.moc"); ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ c = pa_context_new_with_proplist ( ++ pa_threaded_mainloop_get_api (mainloop), ++ PACKAGE_NAME, proplist); ++ pa_proplist_free (proplist); ++ ++ if (!c) ++ fatal ("Cannot allocate PulseAudio context"); ++ ++ pa_context_set_state_callback (c, context_state_callback, mainloop); ++ ++ /* Ignore return value, rely on state being set properly */ ++ pa_context_connect (c, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); ++ ++ while (1) { ++ pa_context_state_t state = pa_context_get_state (c); ++ ++ if (state == PA_CONTEXT_READY) ++ break; ++ ++ if (!PA_CONTEXT_IS_GOOD (state)) { ++ error ("PulseAudio connection failed: %s", ++ pa_strerror (pa_context_errno (c))); ++ ++ goto unlock_and_fail; ++ } ++ ++ debug ("waiting for context to become ready..."); ++ pa_threaded_mainloop_wait (mainloop); ++ } ++ ++ /* Only set the global now that the context is actually ready */ ++ context = c; ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ /* We just make up the hardware capabilities, since pulse is ++ * supposed to be abstracting these out. Assume pulse will ++ * deal with anything we want to throw at it, and that we will ++ * only want mono or stereo audio. ++ */ ++ caps->min_channels = 1; ++ caps->max_channels = 2; ++ caps->formats = (SFMT_S8 | SFMT_S16 | SFMT_S32 | ++ SFMT_FLOAT | SFMT_BE | SFMT_LE); ++ ++ return 1; ++ ++unlock_and_fail: ++ ++ pa_context_unref (c); ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ pa_threaded_mainloop_stop (mainloop); ++ pa_threaded_mainloop_free (mainloop); ++ mainloop = NULL; ++ ++ return 0; ++} ++ ++static void pulse_shutdown (void) ++{ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ pa_context_disconnect (context); ++ pa_context_unref (context); ++ context = NULL; ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ pa_threaded_mainloop_stop (mainloop); ++ pa_threaded_mainloop_free (mainloop); ++ mainloop = NULL; ++} ++ ++static int pulse_open (struct sound_params *sound_params) ++{ ++ pa_sample_spec ss; ++ pa_buffer_attr ba; ++ pa_stream *s; ++ ++ assert (!stream); ++ /* Initialize everything to -1, which in practice gets us ++ * about 2 seconds of latency (which is fine). This is not the ++ * same as passing NULL for this struct, which gets us an ++ * unnecessarily short alsa-like latency. ++ */ ++ ba.fragsize = (uint32_t) -1; ++ ba.tlength = (uint32_t) -1; ++ ba.prebuf = (uint32_t) -1; ++ ba.minreq = (uint32_t) -1; ++ ba.maxlength = (uint32_t) -1; ++ ++ ss.channels = sound_params->channels; ++ ss.rate = sound_params->rate; ++ switch (sound_params->fmt) { ++ case SFMT_U8: ++ ss.format = PA_SAMPLE_U8; ++ break; ++ case SFMT_S16 | SFMT_LE: ++ ss.format = PA_SAMPLE_S16LE; ++ break; ++ case SFMT_S16 | SFMT_BE: ++ ss.format = PA_SAMPLE_S16BE; ++ break; ++ case SFMT_FLOAT | SFMT_LE: ++ ss.format = PA_SAMPLE_FLOAT32LE; ++ break; ++ case SFMT_FLOAT | SFMT_BE: ++ ss.format = PA_SAMPLE_FLOAT32BE; ++ break; ++ case SFMT_S32 | SFMT_LE: ++ ss.format = PA_SAMPLE_S32LE; ++ break; ++ case SFMT_S32 | SFMT_BE: ++ ss.format = PA_SAMPLE_S32BE; ++ break; ++ ++ default: ++ fatal ("pulse: got unrequested format"); ++ } ++ ++ debug ("opening stream"); ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ /* TODO: figure out if there are useful stream properties to set. ++ * ++ * I do not really see any in proplist.h that we can set from ++ * here (there are media title/artist/etc props but we do not ++ * have that data available here). ++ */ ++ s = pa_stream_new (context, "music", &ss, NULL); ++ if (!s) ++ fatal ("pulse: stream allocation failed"); ++ ++ pa_stream_set_state_callback (s, stream_state_callback, mainloop); ++ pa_stream_set_write_callback (s, stream_write_callback, mainloop); ++ ++ /* Ignore return value, rely on failed stream state instead. */ ++ pa_stream_connect_playback ( ++ s, NULL, &ba, ++ PA_STREAM_INTERPOLATE_TIMING | ++ PA_STREAM_AUTO_TIMING_UPDATE | ++ PA_STREAM_ADJUST_LATENCY, ++ NULL, NULL); ++ ++ while (1) { ++ pa_stream_state_t state = pa_stream_get_state (s); ++ ++ if (state == PA_STREAM_READY) ++ break; ++ ++ if (!PA_STREAM_IS_GOOD (state)) { ++ error ("PulseAudio stream connection failed"); ++ ++ goto fail; ++ } ++ ++ debug ("waiting for stream to become ready..."); ++ pa_threaded_mainloop_wait (mainloop); ++ } ++ ++ /* Only set the global stream now that it is actually ready */ ++ stream = s; ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ return 1; ++ ++fail: ++ pa_stream_unref (s); ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ return 0; ++} ++ ++static void pulse_close (void) ++{ ++ debug ("closing stream"); ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ pa_stream_disconnect (stream); ++ pa_stream_unref (stream); ++ stream = NULL; ++ ++ pa_threaded_mainloop_unlock (mainloop); ++} ++ ++static int pulse_play (const char *buff, const size_t size) ++{ ++ size_t offset = 0; ++ ++ debug ("Got %d bytes to play", (int)size); ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ /* The buffer is usually writable when we get here, and there ++ * are usually few (if any) writes after the first one. So ++ * there is no point in doing further writes directly from the ++ * callback: we can just do all writes from this thread. ++ */ ++ ++ /* Break out of the loop if some other thread manages to close ++ * our stream underneath us. ++ */ ++ while (stream) { ++ size_t towrite = MIN(pa_stream_writable_size (stream), ++ size - offset); ++ debug ("writing %d bytes", (int)towrite); ++ ++ /* We have no working way of dealing with errors ++ * (see below). */ ++ if (pa_stream_write(stream, buff + offset, towrite, ++ NULL, 0, PA_SEEK_RELATIVE)) ++ error ("pa_stream_write failed"); ++ ++ offset += towrite; ++ ++ if (offset >= size) ++ break; ++ ++ pa_threaded_mainloop_wait (mainloop); ++ } ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ debug ("Done playing!"); ++ ++ /* We should always return size, calling code does not deal ++ * well with anything else. Only read the rest if you want to ++ * know why. ++ * ++ * The output buffer reader thread (out_buf.c:read_thread) ++ * repeatedly loads some 64k/0.1s of audio into a buffer on ++ * the stack, then calls audio_send_pcm repeatedly until this ++ * entire buffer has been processed (similar to the loop in ++ * this function). audio_send_pcm applies the softmixer and ++ * equalizer, then feeds the result to this function, passing ++ * through our return value. ++ * ++ * So if we return less than size the equalizer/softmixer is ++ * re-applied to the remaining data, which is silly. Also, ++ * audio_send_pcm checks for our return value being zero and ++ * calls fatal() if it is, so try to always process *some* ++ * data. Also, out_buf.c uses the return value of this ++ * function from the last run through its inner loop to update ++ * its time attribute, which means it will be interestingly ++ * off if that loop ran more than once. ++ * ++ * Oh, and alsa.c seems to think it can return -1 to indicate ++ * failure, which will cause out_buf.c to rewind its buffer ++ * (to before its start, usually). ++ */ ++ return size; ++} ++ ++static void volume_cb (const pa_cvolume *v, void *userdata) ++{ ++ int *result = userdata; ++ ++ if (v) ++ *result = 100 * pa_cvolume_avg (v) / PA_VOLUME_NORM; ++ ++ pa_threaded_mainloop_signal (mainloop, 0); ++} ++ ++static void sink_volume_cb (pa_context *c ATTR_UNUSED, ++ const pa_sink_info *i, int eol ATTR_UNUSED, ++ void *userdata) ++{ ++ volume_cb (i ? &i->volume : NULL, userdata); ++} ++ ++static void sink_input_volume_cb (pa_context *c ATTR_UNUSED, ++ const pa_sink_input_info *i, ++ int eol ATTR_UNUSED, ++ void *userdata ATTR_UNUSED) ++{ ++ volume_cb (i ? &i->volume : NULL, userdata); ++} ++ ++static int pulse_read_mixer (void) ++{ ++ pa_operation *op; ++ int result = 0; ++ ++ debug ("read mixer"); ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ if (stream) { ++ if (showing_sink_volume) ++ op = pa_context_get_sink_info_by_index ( ++ context, pa_stream_get_device_index (stream), ++ sink_volume_cb, &result); ++ else ++ op = pa_context_get_sink_input_info ( ++ context, pa_stream_get_index (stream), ++ sink_input_volume_cb, &result); ++ ++ while (pa_operation_get_state (op) == PA_OPERATION_RUNNING) ++ pa_threaded_mainloop_wait (mainloop); ++ ++ pa_operation_unref (op); ++ } ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ return result; ++} ++ ++static void pulse_set_mixer (int vol) ++{ ++ pa_cvolume v; ++ pa_operation *op; ++ ++ /* Setting volume for one channel does the right thing. */ ++ pa_cvolume_set(&v, 1, vol * PA_VOLUME_NORM / 100); ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ if (stream) { ++ if (showing_sink_volume) ++ op = pa_context_set_sink_volume_by_index ( ++ context, pa_stream_get_device_index (stream), ++ &v, NULL, NULL); ++ else ++ op = pa_context_set_sink_input_volume ( ++ context, pa_stream_get_index (stream), ++ &v, NULL, NULL); ++ ++ pa_operation_unref (op); ++ } ++ ++ pa_threaded_mainloop_unlock (mainloop); ++} ++ ++static int pulse_get_buff_fill (void) ++{ ++ /* This function is problematic. MOC uses it to for the "time ++ * remaining" in the UI, but calls it more than once per ++ * second (after each chunk of audio played, not for each ++ * playback time update). We have to be fairly accurate here ++ * for that time remaining to not jump weirdly. But PulseAudio ++ * cannot give us a 100% accurate value here, as it involves a ++ * server roundtrip. And if we call this a lot it suggests ++ * switching to a mode where the value is interpolated, making ++ * it presumably more inaccurate (see the flags we pass to ++ * pa_stream_connect_playback). ++ * ++ * MOC also contains what I believe to be a race: it calls ++ * audio_get_buff_fill "soon" (after playing the first chunk) ++ * after starting playback of the next song, at which point we ++ * still have part of the previous song buffered. This means ++ * our position into the new song is negative, which fails an ++ * assert (in out_buf.c:out_buf_time_get). There is no sane ++ * way for us to detect this condition. I believe no other ++ * backend triggers this because the assert sits after an ++ * implicit float -> int seconds conversion, which means we ++ * have to be off by at least an entire second to get a ++ * negative value, and none of the other backends have buffers ++ * that large (alsa buffers are supposedly a few 100 ms). ++ */ ++ pa_usec_t buffered_usecs = 0; ++ int buffered_bytes = 0; ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ /* Using pa_stream_get_timing_info and returning the distance ++ * between write_index and read_index would be more obvious, ++ * but because of how the result is actually used I believe ++ * using the latency value is slightly more correct, and it ++ * makes the following crash-avoidance hack more obvious. ++ */ ++ ++ /* This function will frequently fail the first time we call ++ * it (pulse does not have the requested data yet). We ignore ++ * that and just return 0. ++ * ++ * Deal with stream being NULL too, just in case this is ++ * called in a racy fashion similar to how reset() is. ++ */ ++ if (stream && ++ pa_stream_get_latency (stream, &buffered_usecs, NULL) >= 0) { ++ /* Crash-avoidance HACK: floor our latency to at most ++ * 1 second. It is usually more, but reporting that at ++ * the start of playback crashes MOC, and we cannot ++ * sanely detect when reporting it is safe. ++ */ ++ if (buffered_usecs > 1000000) ++ buffered_usecs = 1000000; ++ ++ buffered_bytes = pa_usec_to_bytes ( ++ buffered_usecs, ++ pa_stream_get_sample_spec (stream)); ++ } ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ debug ("buffer fill: %d usec / %d bytes", ++ (int) buffered_usecs, (int) buffered_bytes); ++ ++ return buffered_bytes; ++} ++ ++static void flush_callback (pa_stream *s ATTR_UNUSED, int success, ++ void *userdata) ++{ ++ int *result = userdata; ++ ++ *result = success; ++ ++ pa_threaded_mainloop_signal (mainloop, 0); ++} ++ ++static int pulse_reset (void) ++{ ++ pa_operation *op; ++ int result = 0; ++ ++ debug ("reset requested"); ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ /* We *should* have a stream here, but MOC is racy, so bulletproof */ ++ if (stream) { ++ op = pa_stream_flush (stream, flush_callback, &result); ++ ++ while (pa_operation_get_state (op) == PA_OPERATION_RUNNING) ++ pa_threaded_mainloop_wait (mainloop); ++ ++ pa_operation_unref (op); ++ } else ++ logit ("pulse_reset() called without a stream"); ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ return result; ++} ++ ++static int pulse_get_rate (void) ++{ ++ /* This is called once right after open. Do not bother making ++ * this fast. */ ++ ++ int result; ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ if (stream) ++ result = pa_stream_get_sample_spec (stream)->rate; ++ else { ++ error ("get_rate called without a stream"); ++ result = 0; ++ } ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ return result; ++} ++ ++static void pulse_toggle_mixer_channel (void) ++{ ++ showing_sink_volume = !showing_sink_volume; ++} ++ ++static void sink_name_cb (pa_context *c ATTR_UNUSED, ++ const pa_sink_info *i, int eol ATTR_UNUSED, ++ void *userdata) ++{ ++ char **result = userdata; ++ ++ if (i && !*result) ++ *result = xstrdup (i->name); ++ ++ pa_threaded_mainloop_signal (mainloop, 0); ++} ++ ++static void sink_input_name_cb (pa_context *c ATTR_UNUSED, ++ const pa_sink_input_info *i, ++ int eol ATTR_UNUSED, ++ void *userdata) ++{ ++ char **result = userdata; ++ ++ if (i && !*result) ++ *result = xstrdup (i->name); ++ ++ pa_threaded_mainloop_signal (mainloop, 0); ++} ++ ++static char *pulse_get_mixer_channel_name (void) ++{ ++ char *result = NULL; ++ pa_operation *op; ++ ++ pa_threaded_mainloop_lock (mainloop); ++ ++ if (stream) { ++ if (showing_sink_volume) ++ op = pa_context_get_sink_info_by_index ( ++ context, pa_stream_get_device_index (stream), ++ sink_name_cb, &result); ++ else ++ op = pa_context_get_sink_input_info ( ++ context, pa_stream_get_index (stream), ++ sink_input_name_cb, &result); ++ ++ while (pa_operation_get_state (op) == PA_OPERATION_RUNNING) ++ pa_threaded_mainloop_wait (mainloop); ++ ++ pa_operation_unref (op); ++ } ++ ++ pa_threaded_mainloop_unlock (mainloop); ++ ++ if (!result) ++ result = xstrdup ("disconnected"); ++ ++ return result; ++} ++ ++void pulse_funcs (struct hw_funcs *funcs) ++{ ++ funcs->init = pulse_init; ++ funcs->shutdown = pulse_shutdown; ++ funcs->open = pulse_open; ++ funcs->close = pulse_close; ++ funcs->play = pulse_play; ++ funcs->read_mixer = pulse_read_mixer; ++ funcs->set_mixer = pulse_set_mixer; ++ funcs->get_buff_fill = pulse_get_buff_fill; ++ funcs->reset = pulse_reset; ++ funcs->get_rate = pulse_get_rate; ++ funcs->toggle_mixer_channel = pulse_toggle_mixer_channel; ++ funcs->get_mixer_channel_name = pulse_get_mixer_channel_name; ++} +diff --git a/pulse.h b/pulse.h +new file mode 100644 +--- /dev/null ++++ b/pulse.h +@@ -0,0 +1,14 @@ ++#ifndef PULSE_H ++#define PULSE_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++void pulse_funcs (struct hw_funcs *funcs); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif diff --git a/SPECS/moc.spec b/SPECS/moc.spec index 43140d6..612effa 100644 --- a/SPECS/moc.spec +++ b/SPECS/moc.spec @@ -38,6 +38,10 @@ Patch1: %{name}-change_private_libdir.patch # Initial fix for FFMpeg-5 Patch2: %{name}-bugfix-ffmpeg5.patch +Patch3: moc-https.patch +Patch4: moc-pulseaudio.patch +Patch5: moc-fortify.patch + BuildRequires: pkgconfig(ncurses) BuildRequires: pkgconfig(alsa) BuildRequires: pkgconfig(jack) @@ -87,6 +91,9 @@ files in this directory beginning from the chosen file. %if %{without oldffmpeg} %patch2 -p1 %endif +%patch3 +%patch4 +%patch5 %build mv configure.in configure.ac