Commit f2bd0714 authored by Martin Finkel's avatar Martin Finkel

Add Dialog

parent e1978eef
using System.Threading.Tasks;
using NUnit.Framework;
using VideoLAN.LibVLC;
namespace LibVLCSharp.Tests
{
[TestFixture]
public class DialogTests
{
const string UrlRequireAuth = "http://httpbin.org/basic-auth/user/passwd";
const string Username = "username";
const string Password = "password";
[Test]
public async Task PostLogin()
{
var instance = new Instance();
var tcs = new TaskCompletionSource<bool>();
instance.SetDialogHandlers((title, text) => Task.CompletedTask,
(dialog, title, text, username, store, token) =>
{
// show UI dialog
// On "OK" call PostLogin
dialog.PostLogin(Username, Password, false);
tcs.SetResult(true);
return Task.CompletedTask;
},
(dialog, title, text, type, cancelText, actionText, secondActionText, token) => Task.CompletedTask,
(dialog, title, text, indeterminate, position, cancelText, token) => Task.CompletedTask,
(dialog, position, text) => Task.CompletedTask);
var mp = new MediaPlayer(instance)
{
Media = new Media(instance, UrlRequireAuth, Media.FromType.FromLocation)
};
mp.Play();
await tcs.Task;
Assert.True(tcs.Task.Result);
}
[Test]
public async Task ShouldThrowIfReusingSameDialogAfterLoginCall()
{
var instance = new Instance();
var tcs = new TaskCompletionSource<bool>();
instance.SetDialogHandlers((title, text) => Task.CompletedTask,
(dialog, title, text, username, store, token) =>
{
dialog.PostLogin(Username, Password, false);
Assert.Throws<VLCException>(() => dialog.PostLogin(Username, Password, false), "Calling method on dismissed Dialog instance");
tcs.SetResult(true);
return Task.CompletedTask;
},
(dialog, title, text, type, cancelText, actionText, secondActionText, token) => Task.CompletedTask,
(dialog, title, text, indeterminate, position, cancelText, token) => Task.CompletedTask,
(dialog, position, text) => Task.CompletedTask);
var mp = new MediaPlayer(instance)
{
Media = new Media(instance, UrlRequireAuth, Media.FromType.FromLocation)
};
mp.Play();
await tcs.Task;
Assert.True(tcs.Task.Result);
}
[Test]
public void ShouldUnsetDialogHandlersWhenInstanceDisposed()
{
var instance = new Instance();
instance.SetDialogHandlers((title, text) => Task.CompletedTask,
(dialog, title, text, username, store, token) => Task.CompletedTask,
(dialog, title, text, type, cancelText, actionText, secondActionText, token) => Task.CompletedTask,
(dialog, title, text, indeterminate, position, cancelText, token) => Task.CompletedTask,
(dialog, position, text) => Task.CompletedTask);
Assert.True(instance.DialogHandlersSet);
instance.Dispose();
Assert.False(instance.DialogHandlersSet);
}
}
}
\ No newline at end of file
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
namespace VideoLAN.LibVLC
{
public class Dialog : IDisposable
{
IntPtr _id;
bool _disposed;
struct Native
{
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "libvlc_dialog_post_login")]
internal static extern int LibVLCDialogPostLogin(IntPtr dialogId, string username, string password, bool store);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "libvlc_dialog_post_action")]
internal static extern int LibVLCDialogPostAction(IntPtr dialogId, int actionIndex);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "libvlc_dialog_dismiss")]
internal static extern int LibVLCDialogDismiss(IntPtr dialogId);
}
Dialog(IntPtr id)
{
if(id == IntPtr.Zero)
throw new ArgumentNullException(nameof(id));
_id = id;
}
public Dialog(DialogId id) : this(id.NativeReference)
{
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
Dismiss();
}
~Dialog()
{
Dispose();
}
/// <summary>
/// Post a login answer.
/// After this call, the instance won't be valid anymore
/// </summary>
/// <param name="username">valid non-empty string</param>
/// <param name="password">valid string</param>
/// <param name="store">if true stores the credentials</param>
/// <returns></returns>
public bool PostLogin(string username, string password, bool store)
{
if (_id == IntPtr.Zero)
throw new VLCException("Calling method on dismissed Dialog instance");
var result = Native.LibVLCDialogPostLogin(_id, username, password, store) == 0;
_id = IntPtr.Zero;
return result;
}
/// <summary>
/// Post a question answer.
/// After this call, this instance won't be valid anymore
/// QuestionCb
/// </summary>
/// <param name="actionIndex">1 for action1, 2 for action2</param>
/// <returns>return true on success, false otherwise</returns>
public bool PostAction(int actionIndex)
{
if (_id == IntPtr.Zero)
throw new VLCException("Calling method on dismissed Dialog instance");
var result = Native.LibVLCDialogPostAction(_id, actionIndex) == 0;
_id = IntPtr.Zero;
return result;
}
/// <summary>
/// Dismiss a dialog.
/// After this call, this instance won't be valid anymore
/// </summary>
/// <returns></returns>
public bool Dismiss()
{
if (_id == IntPtr.Zero)
throw new VLCException("Calling method on dismissed Dialog instance");
var result = Native.LibVLCDialogDismiss(_id) == 0;
Marshal.FreeHGlobal(_id);
_id = IntPtr.Zero;
return result;
}
}
public struct DialogId
{
public IntPtr NativeReference { get; set; }
}
public enum DialogQuestionType
{
Normal = 0,
Warning = 1,
Critical = 2
}
public delegate Task DisplayError(string title, string text);
public delegate Task DisplayLogin(Dialog dialog, string title, string text, string defaultUsername, bool askStore, CancellationToken token);
public delegate Task DisplayQuestion(Dialog dialog, string title, string text, DialogQuestionType type, string cancelText,
string firstActionText, string secondActionText, CancellationToken token);
public delegate Task DisplayProgress(Dialog dialog, string title, string text, bool indeterminate, float position, string cancelText, CancellationToken token);
public delegate Task UpdateProgress(Dialog dialog, float position, string text);
[SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisplayErrorCallback(IntPtr data, string title, string text);
[SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisplayLoginCallback(IntPtr data, IntPtr dialogId, string title, string text,
string defaultUsername, bool askStore);
[SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisplayQuestionCallback(IntPtr data, IntPtr dialogId, string title, string text,
DialogQuestionType type, string cancelText, string firstActionText, string secondActionText);
[SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisplayProgressCallback(IntPtr data, IntPtr dialogId, string title, string text,
bool indeterminate, float position, string cancelText);
[SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CancelCallback(IntPtr data, IntPtr dialogId);
[SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void UpdateProgressCallback(IntPtr data, IntPtr dialogId, float position, string text);
/// <summary>Dialog callbacks to be implemented</summary>
public struct DialogCallbacks
{
public DisplayErrorCallback DisplayError;
public DisplayLoginCallback DisplayLogin;
public DisplayQuestionCallback DisplayQuestion;
public DisplayProgressCallback DisplayProgress;
public CancelCallback Cancel;
public UpdateProgressCallback UpdateProgress;
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VideoLAN.LibVLC.Events;
using VideoLAN.LibVLC.Structures;
......@@ -34,9 +35,10 @@ namespace VideoLAN.LibVLC
/// <summary>
/// A boolean to make sure that we are calling SetLog only once
/// </summary>
bool _logAttached = false;
bool _logAttached;
IntPtr _logFileHandle;
IntPtr _dialogCbsPtr;
public override int GetHashCode()
{
......@@ -142,9 +144,14 @@ namespace VideoLAN.LibVLC
[DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "libvlc_media_discoverer_list_release")]
internal static extern void LibVLCMediaDiscovererListRelease(IntPtr ppServices, ulong count);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "libvlc_dialog_set_callbacks")]
internal static extern void LibVLCDialogSetCallbacks(IntPtr instance, IntPtr callbacks, IntPtr data);
#region Windows
/// <summary>
/// Compute the size required by vsprintf to print the parameters.
/// </summary>
......@@ -279,6 +286,7 @@ namespace VideoLAN.LibVLC
{
if(_logCallback != null)
UnsetLog();
UnsetDialogHandlers();
base.Dispose();
}
......@@ -370,6 +378,16 @@ namespace VideoLAN.LibVLC
throw new VLCException("Could not close log file");
}
public void UnsetDialogHandlers()
{
if (_dialogCbsPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(_dialogCbsPtr);
_dialogCbsPtr = IntPtr.Zero;
}
Native.LibVLCDialogSetCallbacks(NativeReference, IntPtr.Zero, IntPtr.Zero);
}
/// <summary>
/// Native close log file handle
/// </summary>
......@@ -579,10 +597,68 @@ namespace VideoLAN.LibVLC
return mediaDiscovererDescription;
}
public void SetDialogHandlers()
readonly Dictionary<IntPtr, CancellationTokenSource> _cts = new Dictionary<IntPtr, CancellationTokenSource>();
public void SetDialogHandlers(DisplayError error, DisplayLogin login, DisplayQuestion question,
DisplayProgress displayProgress, UpdateProgress updateProgress)
{
if (error == null) throw new ArgumentNullException(nameof(error));
if (login == null) throw new ArgumentNullException(nameof(login));
if (question == null) throw new ArgumentNullException(nameof(question));
if (displayProgress == null) throw new ArgumentNullException(nameof(displayProgress));
if (updateProgress == null) throw new ArgumentNullException(nameof(updateProgress));
var dialogCbs = new DialogCallbacks
{
DisplayError = (data, title, text) =>
{
// no dialogId ?!
error(title, text);
},
DisplayLogin = (data, id, title, text, username, store) =>
{
var cts = new CancellationTokenSource();
var dlg = new Dialog(new DialogId { NativeReference = id });
_cts.Add(id, cts);
login(dlg, title, text, username, store, cts.Token);
},
DisplayQuestion = (data, id, title, text, type, cancelText, firstActionText, secondActionText) =>
{
var cts = new CancellationTokenSource();
var dlg = new Dialog(new DialogId { NativeReference = id });
_cts.Add(id, cts);
question(dlg, title, text, type, cancelText, firstActionText, secondActionText, cts.Token);
},
DisplayProgress = (data, id, title, text, indeterminate, position, cancelText) =>
{
var cts = new CancellationTokenSource();
var dlg = new Dialog(new DialogId { NativeReference = id });
_cts.Add(id, cts);
displayProgress(dlg, title, text, indeterminate, position, cancelText, cts.Token);
},
Cancel = (data, id) =>
{
if (_cts.TryGetValue(id, out var token))
{
token.Cancel();
_cts.Remove(id);
}
},
UpdateProgress = (data, id, position, text) =>
{
var dlg = new Dialog(new DialogId { NativeReference = id });
updateProgress(dlg, position, text);
}
};
_dialogCbsPtr = Marshal.AllocHGlobal(Marshal.SizeOf<DialogCallbacks>());
Marshal.StructureToPtr(dialogCbs, _dialogCbsPtr, true);
Native.LibVLCDialogSetCallbacks(NativeReference, _dialogCbsPtr, IntPtr.Zero);
}
public bool DialogHandlersSet => _dialogCbsPtr != IntPtr.Zero;
static TU[] Retrieve<T, TU>(Func<IntPtr> getRef, Func<IntPtr, T> retrieve,
Func<T, TU> create, Func<TU, TU> next, Action<IntPtr> releaseRef)
{
......
// ----------------------------------------------------------------------------
// <auto-generated>
// This is autogenerated code by CppSharp.
// Do not edit this file or all your changes will be lost after re-generation.
// </auto-generated>
// ----------------------------------------------------------------------------
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace VideoLAN.LibVLC
{
/// <summary>
/// <para>@{</para>
/// <para></para>
/// <para>LibVLC dialog external API</para>
/// </summary>
public enum DialogQuestionType
{
Normal = 0,
Warning = 1,
Critical = 2
}
public unsafe partial class DialogId
{
[StructLayout(LayoutKind.Explicit, Size = 0)]
public partial struct __Internal
{
}
public global::System.IntPtr __Instance { get; protected set; }
protected int __PointerAdjustment;
internal static readonly global::System.Collections.Concurrent.ConcurrentDictionary<IntPtr, global::VideoLAN.LibVLC.DialogId> NativeToManagedMap = new global::System.Collections.Concurrent.ConcurrentDictionary<IntPtr, global::VideoLAN.LibVLC.DialogId>();
protected void*[] __OriginalVTables;
protected bool __ownsNativeInstance;
internal static global::VideoLAN.LibVLC.DialogId __CreateInstance(global::System.IntPtr native, bool skipVTables = false)
{
return new global::VideoLAN.LibVLC.DialogId(native.ToPointer(), skipVTables);
}
internal static global::VideoLAN.LibVLC.DialogId __CreateInstance(global::VideoLAN.LibVLC.DialogId.__Internal native, bool skipVTables = false)
{
return new global::VideoLAN.LibVLC.DialogId(native, skipVTables);
}
private static void* __CopyValue(global::VideoLAN.LibVLC.DialogId.__Internal native)
{
var ret = Marshal.AllocHGlobal(sizeof(global::VideoLAN.LibVLC.DialogId.__Internal));
*(global::VideoLAN.LibVLC.DialogId.__Internal*) ret = native;
return ret.ToPointer();
}
private DialogId(global::VideoLAN.LibVLC.DialogId.__Internal native, bool skipVTables = false)
: this(__CopyValue(native), skipVTables)
{
__ownsNativeInstance = true;
NativeToManagedMap[__Instance] = this;
}
protected DialogId(void* native, bool skipVTables = false)
{
if (native == null)
return;
__Instance = new global::System.IntPtr(native);
}
}
/// <summary>Dialog callbacks to be implemented</summary>
public unsafe partial class DialogCallback : IDisposable
{
[StructLayout(LayoutKind.Explicit, Size = 48)]
public partial struct __Internal
{
[FieldOffset(0)]
internal global::System.IntPtr pf_display_error;
[FieldOffset(8)]
internal global::System.IntPtr pf_display_login;
[FieldOffset(16)]
internal global::System.IntPtr pf_display_question;
[FieldOffset(24)]
internal global::System.IntPtr pf_display_progress;
[FieldOffset(32)]
internal global::System.IntPtr pf_cancel;
[FieldOffset(40)]
internal global::System.IntPtr pf_update_progress;
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint="??0libvlc_dialog_cbs@@QEAA@AEBU0@@Z")]
internal static extern global::System.IntPtr cctor(global::System.IntPtr instance, global::System.IntPtr _0);
}
public global::System.IntPtr __Instance { get; protected set; }
protected int __PointerAdjustment;
internal static readonly global::System.Collections.Concurrent.ConcurrentDictionary<IntPtr, global::VideoLAN.LibVLC.DialogCallback> NativeToManagedMap = new global::System.Collections.Concurrent.ConcurrentDictionary<IntPtr, global::VideoLAN.LibVLC.DialogCallback>();
protected void*[] __OriginalVTables;
protected bool __ownsNativeInstance;
internal static global::VideoLAN.LibVLC.DialogCallback __CreateInstance(global::System.IntPtr native, bool skipVTables = false)
{
return new global::VideoLAN.LibVLC.DialogCallback(native.ToPointer(), skipVTables);
}
internal static global::VideoLAN.LibVLC.DialogCallback __CreateInstance(global::VideoLAN.LibVLC.DialogCallback.__Internal native, bool skipVTables = false)
{
return new global::VideoLAN.LibVLC.DialogCallback(native, skipVTables);
}
private static void* __CopyValue(global::VideoLAN.LibVLC.DialogCallback.__Internal native)
{
var ret = Marshal.AllocHGlobal(sizeof(global::VideoLAN.LibVLC.DialogCallback.__Internal));
*(global::VideoLAN.LibVLC.DialogCallback.__Internal*) ret = native;
return ret.ToPointer();
}
private DialogCallback(global::VideoLAN.LibVLC.DialogCallback.__Internal native, bool skipVTables = false)
: this(__CopyValue(native), skipVTables)
{
__ownsNativeInstance = true;
NativeToManagedMap[__Instance] = this;
}
protected DialogCallback(void* native, bool skipVTables = false)
{
if (native == null)
return;
__Instance = new global::System.IntPtr(native);
}
public DialogCallback()
{
__Instance = Marshal.AllocHGlobal(sizeof(global::VideoLAN.LibVLC.DialogCallback.__Internal));
__ownsNativeInstance = true;
NativeToManagedMap[__Instance] = this;
}
public DialogCallback(global::VideoLAN.LibVLC.DialogCallback _0)
{
__Instance = Marshal.AllocHGlobal(sizeof(global::VideoLAN.LibVLC.DialogCallback.__Internal));
__ownsNativeInstance = true;
NativeToManagedMap[__Instance] = this;
*((global::VideoLAN.LibVLC.DialogCallback.__Internal*) __Instance) = *((global::VideoLAN.LibVLC.DialogCallback.__Internal*) _0.__Instance);
}
public void Dispose()
{
Dispose(disposing: true);
}
public virtual void Dispose(bool disposing)
{
if (__Instance == IntPtr.Zero)
return;
global::VideoLAN.LibVLC.DialogCallback __dummy;
NativeToManagedMap.TryRemove(__Instance, out __dummy);
if (__ownsNativeInstance)
Marshal.FreeHGlobal(__Instance);
__Instance = IntPtr.Zero;
}
}
public unsafe partial class libvlc_dialog
{
public partial struct __Internal
{
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint="libvlc_dialog_set_callbacks")]
internal static extern void LibvlcDialogSetCallbacks(global::System.IntPtr p_instance, global::System.IntPtr p_cbs, global::System.IntPtr p_data);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint="libvlc_dialog_set_context")]
internal static extern void LibvlcDialogSetContext(global::System.IntPtr p_id, global::System.IntPtr p_context);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint="libvlc_dialog_get_context")]
internal static extern global::System.IntPtr LibvlcDialogGetContext(global::System.IntPtr p_id);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint="libvlc_dialog_post_login")]
internal static extern int LibvlcDialogPostLogin(global::System.IntPtr p_id, [MarshalAs(UnmanagedType.LPStr)] string psz_username, [MarshalAs(UnmanagedType.LPStr)] string psz_password, bool b_store);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint="libvlc_dialog_post_action")]
internal static extern int LibvlcDialogPostAction(global::System.IntPtr p_id, int i_action);
[SuppressUnmanagedCodeSecurity]
[DllImport("libvlc", CallingConvention = global::System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint="libvlc_dialog_dismiss")]
internal static extern int LibvlcDialogDismiss(global::System.IntPtr p_id);
}
/// <summary>Register callbacks in order to handle VLC dialogs</summary>
/// <param name="p_cbs">a pointer to callbacks, or NULL to unregister callbacks.</param>
/// <param name="p_data">opaque pointer for the callback</param>
/// <remarks>LibVLC 3.0.0 and later.</remarks>
public static void LibvlcDialogSetCallbacks(global::VideoLAN.LibVLC.Instance p_instance, global::VideoLAN.LibVLC.DialogCallback p_cbs, global::System.IntPtr p_data)