Compare commits

...

9 Commits

Author SHA1 Message Date
stax76 11067ed0ae
Merge cac5d06c9c into 773c5e2ae0 2024-04-27 01:05:36 +02:00
ferreum 773c5e2ae0 af_scaletempo2: migrate to internals to talloc
Fixes corrupted audio after resize_input_buffer; realloc_2d did not move
data to new location. Rather than reimplementing more allocator logic,
migrate internals to use talloc and grow buffer with realloc.
2024-04-27 01:05:07 +02:00
ferreum 190b15c827 af_scaletempo2: remove redundant buffer zeroing
First iteration does not overlap with initial buffer contents any more,
so this zeroing was redundant.
2024-04-27 01:05:07 +02:00
nanahi 51e01e9772 ao_wasapi: fix player core lockup when avoiding premature buffer fills
6863eefc3d handled this situation by using
an atomic variable to express the state for which the wakeup is caused
by AO control, and the dispatch queue is only processed at this state.
However, this can cause permanent lockup of the player core when the
following happens:

- AO control sets the thread state to WASAPI_THREAD_DISPATCH, and
  sets the wakeup handle.
- WASAPI thread reads the WASAPI_THREAD_DISPATCH state and processes
  the dispatch queue.
- Another AO control happens. A dispatch item is enqueued, and the
  state stays at WASAPI_THREAD_DISPATCH.
- WASAPI thread resets the thread state to WASAPI_THREAD_FEED since
  the state has not changed.
- WaitForSingleObject() returns in the WASAPI thread, sees this state,
  and does not process the dispatch queue.
- The player core locks permanently because it is waiting for the dispatch
  to be processed.

This has been experimentally verified on a system under high contention:
The easiest way to trigger this lockup is to continuously hold down "i",
which rapidly issues AO get volume/mute controls.

To properly handle this, use separate handles for system and user wakeup
requests. Only feed audio when woke up by system and only process the
dispatch queue when woke up by user.

Fixes: 6863eefc3d
2024-04-27 00:59:09 +02:00
nanahi 7f0961479a Revert "ao_wasapi: address premature buffer fills in exclusive mode"
This reverts commit 6863eefc3d.
2024-04-27 00:59:09 +02:00
Dudemanguy 76367dae35 m_config_core: fix forced option notification with m_config_cache
bc28f7693d originally added this, but the
implementation isn't correct and causes excessive notifications when
writing to other options which may have bad behavior in some some
circumstances. Fix this by reworking the implementation for force
options so that the timestamps of the option update compared instead.
Whenever an option gets changed, the internal timestamp in the cache is
always incremented. For a special force option, we can save this
timestamp internally as well.

Because cache_check_update is always checked before potentially sending
an option notification, we know that if the internal timestamp is equal
to the timestamp saved by a force update option, it must have been
previously written. Thus, the notification can be sent. This lets
options like geometry work repeatedly but without constantly sending
notifications. Fixes #13954.
2024-04-26 17:42:52 +00:00
stax76 cac5d06c9c powershell-completion: rewrite dynamic options 2024-04-24 02:29:33 +02:00
stax76 f90929ac67 powershell-completion: use variables instead of table 2023-11-26 08:32:54 +01:00
stax76 65d9c7f8ba powershell-completion: add PowerShell command line completion 2023-11-25 19:12:25 +01:00
7 changed files with 391 additions and 115 deletions

View File

@ -8,7 +8,7 @@
#include "options/m_option.h"
struct priv {
struct mp_scaletempo2 data;
struct mp_scaletempo2 *data;
struct mp_pin *in_pin;
struct mp_aframe *cur_format;
struct mp_aframe_pool *out_pool;
@ -29,7 +29,7 @@ static void af_scaletempo2_process(struct mp_filter *f)
return;
while (!p->initialized || !p->pending ||
!mp_scaletempo2_frames_available(&p->data, p->speed))
!mp_scaletempo2_frames_available(p->data, p->speed))
{
bool eof = false;
if (!p->pending || !mp_aframe_get_size(p->pending)) {
@ -64,16 +64,16 @@ static void af_scaletempo2_process(struct mp_filter *f)
if (p->pending && !format_change && !p->sent_final) {
int frame_size = mp_aframe_get_size(p->pending);
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
int read = mp_scaletempo2_fill_input_buffer(&p->data,
int read = mp_scaletempo2_fill_input_buffer(p->data,
planes, frame_size, p->speed);
mp_aframe_skip_samples(p->pending, read);
}
if (final && p->pending && !p->sent_final) {
mp_scaletempo2_set_final(&p->data);
mp_scaletempo2_set_final(p->data);
p->sent_final = true;
}
if (mp_scaletempo2_frames_available(&p->data, p->speed)) {
if (mp_scaletempo2_frames_available(p->data, p->speed)) {
if (eof) {
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
}
@ -89,9 +89,9 @@ static void af_scaletempo2_process(struct mp_filter *f)
}
assert(p->pending);
if (mp_scaletempo2_frames_available(&p->data, p->speed)) {
if (mp_scaletempo2_frames_available(p->data, p->speed)) {
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
int out_samples = p->data.ola_hop_size;
int out_samples = p->data->ola_hop_size;
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
talloc_free(out);
goto error;
@ -101,14 +101,14 @@ static void af_scaletempo2_process(struct mp_filter *f)
uint8_t **planes = mp_aframe_get_data_rw(out);
assert(planes);
assert(mp_aframe_get_planes(out) == p->data.channels);
assert(mp_aframe_get_planes(out) == p->data->channels);
out_samples = mp_scaletempo2_fill_buffer(&p->data,
out_samples = mp_scaletempo2_fill_buffer(p->data,
(float**)planes, out_samples, p->speed);
double pts = mp_aframe_get_pts(p->pending);
if (pts != MP_NOPTS_VALUE) {
double frame_delay = mp_scaletempo2_get_latency(&p->data, p->speed)
double frame_delay = mp_scaletempo2_get_latency(p->data, p->speed)
+ out_samples * p->speed;
mp_aframe_set_pts(out, pts - frame_delay / mp_aframe_get_effective_rate(out));
@ -122,7 +122,7 @@ static void af_scaletempo2_process(struct mp_filter *f)
// reset the filter to ensure it stops generating audio
// and mp_scaletempo2_frames_available returns false
mp_scaletempo2_reset(&p->data);
mp_scaletempo2_reset(p->data);
}
}
}
@ -150,7 +150,7 @@ static bool init_scaletempo2(struct mp_filter *f)
p->sent_final = false;
mp_aframe_config_copy(p->cur_format, p->pending);
mp_scaletempo2_init(&p->data, mp_aframe_get_channels(p->pending),
mp_scaletempo2_init(p->data, mp_aframe_get_channels(p->pending),
mp_aframe_get_rate(p->pending));
return true;
@ -172,7 +172,7 @@ static bool af_scaletempo2_command(struct mp_filter *f, struct mp_filter_command
static void af_scaletempo2_reset(struct mp_filter *f)
{
struct priv *p = f->priv;
mp_scaletempo2_reset(&p->data);
mp_scaletempo2_reset(p->data);
p->initialized = false;
TA_FREEP(&p->pending);
}
@ -180,8 +180,8 @@ static void af_scaletempo2_reset(struct mp_filter *f)
static void af_scaletempo2_destroy(struct mp_filter *f)
{
struct priv *p = f->priv;
mp_scaletempo2_destroy(&p->data);
talloc_free(p->pending);
TA_FREEP(&p->data);
TA_FREEP(&p->pending);
}
static const struct mp_filter_info af_scaletempo2_filter = {
@ -206,7 +206,8 @@ static struct mp_filter *af_scaletempo2_create(
mp_filter_add_pin(f, MP_PIN_OUT, "out");
struct priv *p = f->priv;
p->data.opts = talloc_steal(p, options);
p->data = talloc_zero(p, struct mp_scaletempo2);
p->data->opts = talloc_steal(p, options);
p->speed = 1.0;
p->cur_format = talloc_steal(p, mp_aframe_create());
p->out_pool = mp_aframe_pool_create(p);

View File

@ -41,19 +41,15 @@ static bool in_interval(int n, struct interval q)
return n >= q.lo && n <= q.hi;
}
static float **realloc_2d(float **p, int x, int y)
static void alloc_sample_buffer(struct mp_scaletempo2 *p, float ***ptr, size_t size)
{
float **array = realloc(p, sizeof(float*) * x + sizeof(float) * x * y);
float* data = (float*) (array + x);
for (int i = 0; i < x; ++i) {
array[i] = data + i * y;
}
return array;
}
talloc_free(*ptr);
static void zero_2d(float **a, int x, int y)
{
memset(a + x, 0, sizeof(float) * x * y);
float **buff = talloc_array(p, float*, p->channels);
for (int i = 0; i < p->channels; ++i) {
buff[i] = talloc_array(buff, float, size);
}
*ptr = buff;
}
static void zero_2d_partial(float **a, int x, int y)
@ -480,12 +476,6 @@ static bool can_perform_wsola(struct mp_scaletempo2 *p, double playback_rate)
return frames_needed(p, playback_rate) <= 0;
}
static void resize_input_buffer(struct mp_scaletempo2 *p, int size)
{
p->input_buffer_size = size;
p->input_buffer = realloc_2d(p->input_buffer, p->channels, size);
}
// pad end with silence until a wsola iteration can be performed
static void add_input_buffer_final_silence(struct mp_scaletempo2 *p, double playback_rate)
{
@ -493,11 +483,9 @@ static void add_input_buffer_final_silence(struct mp_scaletempo2 *p, double play
if (needed <= 0)
return; // no silence needed for iteration
int required_size = needed + p->input_buffer_frames;
if (required_size > p->input_buffer_size)
resize_input_buffer(p, required_size);
int last_index = needed + p->input_buffer_frames - 1;
for (int i = 0; i < p->channels; ++i) {
MP_TARRAY_GROW(p, p->input_buffer[i], last_index);
float *ch_input = p->input_buffer[i];
for (int j = 0; j < needed; ++j) {
ch_input[p->input_buffer_frames + j] = 0.0f;
@ -523,11 +511,9 @@ int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
if (read == 0)
return 0;
int required_size = read + p->input_buffer_frames;
if (required_size > p->input_buffer_size)
resize_input_buffer(p, required_size);
int last_index = read + p->input_buffer_frames - 1;
for (int i = 0; i < p->channels; ++i) {
MP_TARRAY_GROW(p, p->input_buffer[i], last_index);
memcpy(p->input_buffer[i] + p->input_buffer_frames,
planes[i], read * sizeof(float));
}
@ -771,18 +757,6 @@ bool mp_scaletempo2_frames_available(struct mp_scaletempo2 *p, double playback_r
|| p->num_complete_frames > 0;
}
void mp_scaletempo2_destroy(struct mp_scaletempo2 *p)
{
free(p->ola_window);
free(p->transition_window);
free(p->wsola_output);
free(p->optimal_block);
free(p->search_block);
free(p->target_block);
free(p->input_buffer);
free(p->energy_candidate_blocks);
}
void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
{
p->input_buffer_frames = 0;
@ -791,8 +765,6 @@ void mp_scaletempo2_reset(struct mp_scaletempo2 *p)
p->output_time = 0.0;
p->search_block_index = 0;
p->target_block_index = 0;
// Clear the queue of decoded packets.
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
p->num_complete_frames = 0;
p->wsola_output_started = false;
}
@ -847,28 +819,26 @@ void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
// 1, ... |num_candidate_blocks|
p->search_block_center_offset = p->num_candidate_blocks / 2
+ (p->ola_window_size / 2 - 1);
p->ola_window = realloc(p->ola_window, sizeof(float) * p->ola_window_size);
MP_RESIZE_ARRAY(p, p->ola_window, p->ola_window_size);
get_symmetric_hanning_window(p->ola_window_size, p->ola_window);
p->transition_window = realloc(p->transition_window,
sizeof(float) * p->ola_window_size * 2);
MP_RESIZE_ARRAY(p, p->transition_window, p->ola_window_size * 2);
get_symmetric_hanning_window(2 * p->ola_window_size, p->transition_window);
p->wsola_output_size = p->ola_window_size + p->ola_hop_size;
p->wsola_output = realloc_2d(p->wsola_output, p->channels, p->wsola_output_size);
// Initialize for overlap-and-add of the first block.
zero_2d(p->wsola_output, p->channels, p->wsola_output_size);
alloc_sample_buffer(p, &p->wsola_output, p->wsola_output_size);
// Auxiliary containers.
p->optimal_block = realloc_2d(p->optimal_block, p->channels, p->ola_window_size);
alloc_sample_buffer(p, &p->optimal_block, p->ola_window_size);
p->search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
p->search_block = realloc_2d(p->search_block, p->channels, p->search_block_size);
p->target_block = realloc_2d(p->target_block, p->channels, p->ola_window_size);
alloc_sample_buffer(p, &p->search_block, p->search_block_size);
alloc_sample_buffer(p, &p->target_block, p->ola_window_size);
resize_input_buffer(p, 4 * MPMAX(p->ola_window_size, p->search_block_size));
p->input_buffer_frames = 0;
p->input_buffer_final_frames = 0;
p->input_buffer_added_silence = 0;
size_t initial_size = 4 * MPMAX(p->ola_window_size, p->search_block_size);
alloc_sample_buffer(p, &p->input_buffer, initial_size);
p->energy_candidate_blocks = realloc(p->energy_candidate_blocks,
sizeof(float) * p->channels * p->num_candidate_blocks);
MP_RESIZE_ARRAY(p, p->energy_candidate_blocks,
p->channels * p->num_candidate_blocks);
}

View File

@ -110,7 +110,6 @@ struct mp_scaletempo2 {
float **target_block;
// Buffered audio data.
float **input_buffer;
int input_buffer_size;
int input_buffer_frames;
// How many frames in |input_buffer| need to be flushed by padding with
// silence to process the final packet. While this is nonzero, the filter

View File

@ -193,17 +193,19 @@ static void thread_resume(struct ao *ao)
thread_unpause(ao);
}
static void set_state_and_wakeup_thread(struct ao *ao,
enum wasapi_thread_state thread_state)
static void thread_wakeup(void *ptr)
{
struct ao *ao = ptr;
struct wasapi_state *state = ao->priv;
SetEvent(state->hUserWake);
}
static void set_thread_state(struct ao *ao,
enum wasapi_thread_state thread_state)
{
struct wasapi_state *state = ao->priv;
atomic_store(&state->thread_state, thread_state);
SetEvent(state->hWake);
}
static void thread_process_dispatch(void *ptr)
{
set_state_and_wakeup_thread(ptr, WASAPI_THREAD_DISPATCH);
thread_wakeup(ao);
}
static DWORD __stdcall AudioThread(void *lpParameter)
@ -220,18 +222,25 @@ static DWORD __stdcall AudioThread(void *lpParameter)
MP_DBG(ao, "Entering dispatch loop\n");
while (true) {
if (WaitForSingleObject(state->hWake, INFINITE) != WAIT_OBJECT_0)
MP_ERR(ao, "Unexpected return value from WaitForSingleObject\n");
HANDLE handles[] = {state->hWake, state->hUserWake};
switch (WaitForMultipleObjects(MP_ARRAY_SIZE(handles), handles, FALSE, INFINITE)) {
case WAIT_OBJECT_0:
// fill twice on under-full buffer (see comment in thread_feed)
if (thread_feed(ao) && thread_feed(ao))
MP_ERR(ao, "Unable to fill buffer fast enough\n");
continue;
case WAIT_OBJECT_0 + 1:
break;
default:
MP_ERR(ao, "Unexpected return value from WaitForMultipleObjects\n");
break;
}
mp_dispatch_queue_process(state->dispatch, 0);
int thread_state = atomic_load(&state->thread_state);
switch (thread_state) {
case WASAPI_THREAD_FEED:
// fill twice on under-full buffer (see comment in thread_feed)
if (thread_feed(ao) && thread_feed(ao))
MP_ERR(ao, "Unable to fill buffer fast enough\n");
break;
case WASAPI_THREAD_DISPATCH:
mp_dispatch_queue_process(state->dispatch, 0);
break;
case WASAPI_THREAD_RESET:
thread_reset(ao);
@ -267,8 +276,8 @@ static void uninit(struct ao *ao)
{
MP_DBG(ao, "Uninit wasapi\n");
struct wasapi_state *state = ao->priv;
if (state->hWake)
set_state_and_wakeup_thread(ao, WASAPI_THREAD_SHUTDOWN);
if (state->hWake && state->hUserWake)
set_thread_state(ao, WASAPI_THREAD_SHUTDOWN);
if (state->hAudioThread &&
WaitForSingleObject(state->hAudioThread, INFINITE) != WAIT_OBJECT_0)
@ -279,6 +288,7 @@ static void uninit(struct ao *ao)
SAFE_DESTROY(state->hInitDone, CloseHandle(state->hInitDone));
SAFE_DESTROY(state->hWake, CloseHandle(state->hWake));
SAFE_DESTROY(state->hUserWake, CloseHandle(state->hUserWake));
SAFE_DESTROY(state->hAudioThread,CloseHandle(state->hAudioThread));
wasapi_change_uninit(ao);
@ -312,14 +322,15 @@ static int init(struct ao *ao)
state->hInitDone = CreateEventW(NULL, FALSE, FALSE, NULL);
state->hWake = CreateEventW(NULL, FALSE, FALSE, NULL);
if (!state->hInitDone || !state->hWake) {
state->hUserWake = CreateEventW(NULL, FALSE, FALSE, NULL);
if (!state->hInitDone || !state->hWake || !state->hUserWake) {
MP_FATAL(ao, "Error creating events\n");
uninit(ao);
return -1;
}
state->dispatch = mp_dispatch_create(state);
mp_dispatch_set_wakeup_fn(state->dispatch, thread_process_dispatch, ao);
mp_dispatch_set_wakeup_fn(state->dispatch, thread_wakeup, ao);
state->init_ok = false;
state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL);
@ -474,17 +485,17 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
static void audio_reset(struct ao *ao)
{
set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESET);
set_thread_state(ao, WASAPI_THREAD_RESET);
}
static void audio_resume(struct ao *ao)
{
set_state_and_wakeup_thread(ao, WASAPI_THREAD_RESUME);
set_thread_state(ao, WASAPI_THREAD_RESUME);
}
static bool audio_set_pause(struct ao *ao, bool paused)
{
set_state_and_wakeup_thread(ao, paused ? WASAPI_THREAD_PAUSE : WASAPI_THREAD_UNPAUSE);
set_thread_state(ao, paused ? WASAPI_THREAD_PAUSE : WASAPI_THREAD_UNPAUSE);
return true;
}

View File

@ -48,7 +48,6 @@ void wasapi_change_uninit(struct ao* ao);
enum wasapi_thread_state {
WASAPI_THREAD_FEED = 0,
WASAPI_THREAD_DISPATCH,
WASAPI_THREAD_RESUME,
WASAPI_THREAD_RESET,
WASAPI_THREAD_SHUTDOWN,
@ -66,6 +65,7 @@ typedef struct wasapi_state {
HANDLE hWake; // thread wakeup event
atomic_int thread_state; // enum wasapi_thread_state (what to do on wakeup)
struct mp_dispatch_queue *dispatch; // for volume/mute/session display
HANDLE hUserWake; // mpv-requested wakeup event
// for setting the audio thread priority
HANDLE hTask;

View File

@ -0,0 +1,287 @@
#
# This file is part of mpv.
#
# mpv is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# mpv is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
#
# PowerShell command line completion for the mpv media player.
# It can be installed by dot sourcing it in the PowerShell profile.
$Options = New-Object Collections.Generic.List[Object]
$DynamicOptions = @(
@{ name = 'vaapi-device'; pattern = '^\s*([-\w]+)' },
@{ name = 'd3d11-adapter'; pattern = 'description: (.+)' },
@{ name = 'vulkan-device'; pattern = "^\s*('.+?')" },
@{ name = 'audio-device'; pattern = "^\s*('\S+')" },
@{ name = 'hwdec'; pattern = '^\s*([-\w]+)' },
@{ name = 'error-diffusion'; pattern = '^\s*([-\w]+)' },
@{ name = 'scale'; pattern = '^\s*([-\w]+)' },
@{ name = 'cscale'; pattern = '^\s*([-\w]+)' },
@{ name = 'dscale'; pattern = '^\s*([-\w]+)' },
@{ name = 'tscale'; pattern = '^\s*([-\w]+)' },
@{ name = 'profile'; pattern = '^\s*([-\w]+)' },
@{ name = 'ao'; pattern = '^\s*([-\w]+)' },
@{ name = 'vo'; pattern = '^\s*([-\w]+)' }
)
Function SetOptions
{
try
{
$optionContent = mpv --no-config --list-options
}
catch
{
throw
}
foreach ($line in $optionContent)
{
$line = $line.Trim()
if (-not $line.StartsWith('--'))
{
continue
}
$name = ''; $value = ''; $type = ''; $choices = $null;
if ($line.Contains(' '))
{
$name = $line.Substring(2, $line.IndexOf(' ') - 2)
$value = $line.Substring($line.IndexOf(' ') + 1).Trim()
if ($value.Contains('('))
{
$value = $value.Substring(0, $value.IndexOf('(')).TrimEnd()
}
$value = $value
}
else
{
$name = $line.Substring(2)
}
if ($value.StartsWith('Choices:'))
{
$type = 'choice'
$choices = $value.Substring(8).TrimStart() -split ' '
}
if ($value.StartsWith('Flag'))
{
$type = 'flag'
}
if ($value.Contains('[file]') -or $name.Contains('-file'))
{
$type = 'file'
}
$table = @{ name = $name; value = $value; type = $type; choices = $choices }
if ($type -eq 'flag')
{
$noTable = @{ name = 'no-' + $name; value = $value; type = ''; choices = $null }
$Options.Add($table)
$Options.Add($noTable)
}
else
{
$Options.Add($table)
}
}
}
Function Update-Option($name)
{
foreach ($it in $Options)
{
if ($name -eq $it.name)
{
$option = $it
break
}
}
if ($null -eq $option)
{
Write-Error "Option $name is unknown."
return
}
if ($null -ne $option.choices)
{
return
}
foreach ($opt in $DynamicOptions)
{
if ($name -eq $opt.name)
{
$output = mpv ('--' + $opt.name + '=help') | Select-Object -Skip 1 |
Select-String ($opt.pattern) -AllMatches |
ForEach-Object { $_.matches.Groups[1].Value } |
Select-Object -Unique | Sort-Object
$output = $output | foreach { if ($_ -match "'\w+'") { $_ -replace "'", '' } else { $_ } }
$output = $output | foreach { if ($_ -match "^'.+'$") { $_ -replace "'", '' } else { $_ } }
$output = $output | foreach { if ($_.Contains(' ') -or $_.Contains('{')) { '"' + $_ + '"' } else { $_ } }
if ($output -is [string])
{
$output = @($output)
}
$output += @('help')
$option.choices = $output
$option.type = 'choice'
break
}
}
}
Function Get-Completion($cursorPosition, $wordToComplete, $commandName)
{
if ($Options.Count -eq 0)
{
SetOptions
}
if ($commandName.StartsWith('--'))
{
if ($commandName -like '--*-file*=')
{
return (Get-ChildItem -file).FullName | Resolve-Path -Relative |
ForEach-Object { if ($_.Contains(' ')) { $commandName + "'$_'" } else { $commandName + $_ } }
}
if ($commandName -match '(--.+-file.*=)(.+)')
{
return (Get-ChildItem -file).FullName | Resolve-Path -Relative |
Where-Object { $_.ToLower().Contains($Matches[2].ToLower()) } |
ForEach-Object { if ($_.Contains(' ')) { $Matches[1] + "'$_'" } else { $Matches[1] + $_ } }
}
$shortCommandName = $commandName.Substring(2)
$argName = ''
if ($commandName.EndsWith('='))
{
$shortCommandName = $shortCommandName.Substring(0, $shortCommandName.Length -1)
}
elseif ($commandName.Contains('='))
{
$shortCommandName = $shortCommandName.Substring(0, $shortCommandName.IndexOf('='))
$argName = $commandName.Substring($commandName.IndexOf('=') + 1)
}
foreach ($it in $DynamicOptions)
{
if ($shortCommandName -eq $it.name)
{
Update-Option $it.name
break
}
}
$results = New-Object Collections.Generic.List[Object]
$exactMatches = $Options | Where-Object { $_.name -eq $shortCommandName }
foreach ($it in $exactMatches)
{
if (-not $commandName.Contains('='))
{
continue
}
$arguments = $null
if ($it.type -eq 'flag')
{
$arguments = 'yes', 'no'
}
if ($it.type -eq 'choice' -and $null -ne $it.choices)
{
$arguments = $it.choices
}
if ($null -ne $arguments)
{
foreach ($arg in $arguments)
{
if ($argName -ne '')
{
if ($arg.StartsWith($argName))
{
$results.Add('--' + $it.name + '=' + $arg)
}
}
else
{
$results.Add('--' + $it.name + '=' + $arg)
}
}
}
}
if (-not $commandName.Contains('='))
{
$partlyMatches = $Options | Where-Object { $_.name.StartsWith($shortCommandName) }
foreach ($it in $partlyMatches)
{
if ($it.name -eq $shortCommandName)
{
continue
}
$results.Add('--' + $it.name)
}
}
return $results
}
elseif ($commandName -eq '')
{
return (Get-ChildItem).FullName | Resolve-Path -Relative
}
else
{
return (Get-ChildItem).FullName | Resolve-Path -Relative |
Where-Object { $_.ToLower().Contains($commandName.ToLower()) }
}
}
Register-ArgumentCompleter -Native -CommandName mpv -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
Get-Completion $cursorPosition "$wordToComplete" "$commandName" | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
Register-ArgumentCompleter -Native -CommandName mpvnet -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
Get-Completion $cursorPosition "$wordToComplete" "$commandName" | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}

View File

@ -99,12 +99,17 @@ struct config_cache {
void *wakeup_cb_ctx;
};
struct force_update {
char *name;
uint64_t ts;
};
// Per m_config_data state for each m_config_group.
struct m_group_data {
char *udata; // pointer to group user option struct
uint64_t ts; // timestamp of the data copy
char **force_updates; // track if any opt in group was written with force_update
int force_updates_len;
char *udata; // pointer to group user option struct
uint64_t ts; // timestamp of the data copy
struct force_update **force_update; // tracks opts that are written with force update
int force_update_len;
};
static void add_sub_group(struct m_config_shadow *shadow, const char *name_prefix,
@ -590,23 +595,30 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent,
return m_config_cache_from_shadow(ta_parent, global->config, group);
}
static void clear_force_update_list(struct m_group_data *gsrc)
static void append_force_update(struct m_config_cache *cache, struct m_group_data *gdata,
const char *opt_name)
{
int index = 0;
while (index < gsrc->force_updates_len) {
TA_FREEP(&gsrc->force_updates[index]);
++index;
for (int i = 0; i < gdata->force_update_len; ++i) {
if (strcmp(opt_name, gdata->force_update[i]->name) == 0) {
gdata->force_update[i]->ts = gdata->ts;
return;
}
}
gsrc->force_updates_len = 0;
struct force_update *new_update = talloc_zero(cache, struct force_update);
new_update->name = talloc_strdup(cache, opt_name);
new_update->ts = gdata->ts;
MP_TARRAY_APPEND(cache, gdata->force_update, gdata->force_update_len, new_update);
}
static bool check_force_update_list(struct m_group_data *gsrc, const char *opt_name)
static bool check_force_update(struct m_group_data *gdata, const char *opt_name,
uint64_t timestamp)
{
int index = 0;
while (index < gsrc->force_updates_len) {
if (strcmp(opt_name, gsrc->force_updates[index]) == 0)
for (int i = 0; i < gdata->force_update_len; ++i) {
if ((strcmp(opt_name, gdata->force_update[i]->name) == 0) &&
gdata->force_update[i]->ts == timestamp)
{
return true;
++index;
}
}
return false;
}
@ -637,8 +649,8 @@ static void update_next_option(struct m_config_cache *cache, void **p_opt)
if (opt->offset >= 0 && opt->type->size) {
bool opt_equal = m_option_equal(opt, ddst, dsrc);
bool force_update = opt->force_update && gsrc->force_updates_len &&
check_force_update_list(gsrc, opt->name);
bool force_update = opt->force_update &&
check_force_update(gsrc, opt->name, in->ts);
if (!opt_equal || force_update) {
uint64_t ch = get_opt_change_mask(dst->shadow,
in->upd_group, dst->group_index, opt);
@ -666,8 +678,6 @@ static void update_next_option(struct m_config_cache *cache, void **p_opt)
}
gdst->ts = gsrc->ts;
} else {
clear_force_update_list(gsrc);
}
in->upd_group++;
@ -791,10 +801,8 @@ bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr)
}
}
if (opt->force_update) {
MP_TARRAY_APPEND(shadow, gsrc->force_updates, gsrc->force_updates_len,
talloc_strdup(shadow, opt->name));
}
if (opt->force_update)
append_force_update(cache, gsrc, opt->name);
mp_mutex_unlock(&shadow->lock);