Add WebLoginDialog for Windows Chardonnay
This commit is contained in:
parent
df2936e0b6
commit
4456432116
2
.github/workflows/build-windows.yml
vendored
2
.github/workflows/build-windows.yml
vendored
@ -69,7 +69,7 @@ jobs:
|
||||
LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj `
|
||||
--configuration ${{ env.DOTNET_CONFIGURATION }} `
|
||||
--output bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} `
|
||||
-p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml
|
||||
-p:PublishProfile=LoadByOS/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml
|
||||
dotnet publish `
|
||||
LibationCli/LibationCli.csproj `
|
||||
--configuration ${{ env.DOTNET_CONFIGURATION }} `
|
||||
|
||||
176
Source/LibationAvalonia/Controls/NativeWebView.cs
Normal file
176
Source/LibationAvalonia/Controls/NativeWebView.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Controls;
|
||||
|
||||
#nullable enable
|
||||
public class NativeWebView : NativeControlHost, IWebView
|
||||
{
|
||||
private IWebViewAdapter? _webViewAdapter;
|
||||
private Uri? _delayedSource;
|
||||
private TaskCompletionSource _webViewReadyCompletion = new();
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
public event EventHandler? DOMContentLoaded;
|
||||
|
||||
public bool CanGoBack => _webViewAdapter?.CanGoBack ?? false;
|
||||
|
||||
public bool CanGoForward => _webViewAdapter?.CanGoForward ?? false;
|
||||
|
||||
public Uri? Source
|
||||
{
|
||||
get => _webViewAdapter?.Source ?? throw new InvalidOperationException("Control was not initialized");
|
||||
set
|
||||
{
|
||||
if (_webViewAdapter is null)
|
||||
{
|
||||
_delayedSource = value;
|
||||
return;
|
||||
}
|
||||
_webViewAdapter.Source = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
return _webViewAdapter?.GoBack() ?? throw new InvalidOperationException("Control was not initialized");
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
return _webViewAdapter?.GoForward() ?? throw new InvalidOperationException("Control was not initialized");
|
||||
}
|
||||
|
||||
public Task<string?> InvokeScriptAsync(string scriptName)
|
||||
{
|
||||
return _webViewAdapter is null
|
||||
? throw new InvalidOperationException("Control was not initialized")
|
||||
: _webViewAdapter.InvokeScriptAsync(scriptName);
|
||||
}
|
||||
|
||||
public void Navigate(Uri url)
|
||||
{
|
||||
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.Navigate(url);
|
||||
}
|
||||
|
||||
public Task NavigateToString(string text)
|
||||
{
|
||||
return (_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.NavigateToString(text);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.Refresh();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
(_webViewAdapter ?? throw new InvalidOperationException("Control was not initialized"))
|
||||
.Stop();
|
||||
}
|
||||
|
||||
public Task WaitForNativeHost()
|
||||
{
|
||||
return _webViewReadyCompletion.Task;
|
||||
}
|
||||
|
||||
private class PlatformHandle : IPlatformHandle
|
||||
{
|
||||
public nint Handle { get; init; }
|
||||
|
||||
public string? HandleDescriptor { get; init; }
|
||||
}
|
||||
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||
{
|
||||
_webViewAdapter = InteropFactory.Create().CreateWebViewAdapter();
|
||||
|
||||
if (_webViewAdapter is null)
|
||||
return base.CreateNativeControlCore(parent);
|
||||
else
|
||||
{
|
||||
SubscribeOnEvents();
|
||||
var handle = new PlatformHandle
|
||||
{
|
||||
Handle = _webViewAdapter.PlatformHandle.Handle,
|
||||
HandleDescriptor = _webViewAdapter.PlatformHandle.HandleDescriptor
|
||||
};
|
||||
|
||||
if (_delayedSource is not null)
|
||||
{
|
||||
_webViewAdapter.Source = _delayedSource;
|
||||
}
|
||||
|
||||
_webViewReadyCompletion.TrySetResult();
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeOnEvents()
|
||||
{
|
||||
if (_webViewAdapter is not null)
|
||||
{
|
||||
_webViewAdapter.NavigationStarted += WebViewAdapterOnNavigationStarted;
|
||||
_webViewAdapter.NavigationCompleted += WebViewAdapterOnNavigationCompleted;
|
||||
_webViewAdapter.DOMContentLoaded += _webViewAdapter_DOMContentLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
private void _webViewAdapter_DOMContentLoaded(object? sender, EventArgs e)
|
||||
{
|
||||
DOMContentLoaded?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void WebViewAdapterOnNavigationStarted(object? sender, WebViewNavigationEventArgs e)
|
||||
{
|
||||
NavigationStarted?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void WebViewAdapterOnNavigationCompleted(object? sender, WebViewNavigationEventArgs e)
|
||||
{
|
||||
NavigationCompleted?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == BoundsProperty && change.NewValue is Rect rect)
|
||||
{
|
||||
var scaling = (float)(VisualRoot?.RenderScaling ?? 1.0f);
|
||||
_webViewAdapter?.HandleResize((int)(rect.Width * scaling), (int)(rect.Height * scaling), scaling);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (_webViewAdapter != null)
|
||||
{
|
||||
e.Handled = _webViewAdapter.HandleKeyDown((uint)e.Key, (uint)e.KeyModifiers);
|
||||
}
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
if (_webViewAdapter is not null)
|
||||
{
|
||||
_webViewReadyCompletion = new TaskCompletionSource();
|
||||
_webViewAdapter.NavigationStarted -= WebViewAdapterOnNavigationStarted;
|
||||
_webViewAdapter.NavigationCompleted -= WebViewAdapterOnNavigationCompleted;
|
||||
(_webViewAdapter as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
MinWidth="240" MinHeight="140"
|
||||
MaxWidth="240" MaxHeight="140"
|
||||
Width="240" Height="140"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.ApprovalNeededDialog"
|
||||
Title="Approval Alert Detected"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@ -4,7 +4,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class ApprovalNeededDialog : DialogWindow
|
||||
{
|
||||
public ApprovalNeededDialog()
|
||||
public ApprovalNeededDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
using AudibleApi;
|
||||
using AudibleUtilities;
|
||||
using Avalonia.Threading;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -23,6 +25,20 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
|
||||
public async Task<ChoiceOut> StartAsync(ChoiceIn choiceIn)
|
||||
{
|
||||
if (Configuration.IsWindows && Environment.OSVersion.Version.Major >= 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
var weblogin = new WebLoginDialog(_account.AccountId, choiceIn.LoginUrl);
|
||||
if (await weblogin.ShowDialog<DialogResult>(App.MainWindow) is DialogResult.OK)
|
||||
return ChoiceOut.External(weblogin.ResponseUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"Failed to run {nameof(WebLoginDialog)}");
|
||||
}
|
||||
}
|
||||
|
||||
var dialog = new LoginChoiceEagerDialog(_account);
|
||||
|
||||
if (await dialog.ShowDialogAsync() is not DialogResult.OK ||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
MinWidth="220" MinHeight="250"
|
||||
MaxWidth="220" MaxHeight="250"
|
||||
Width="220" Height="250"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog"
|
||||
Title="CAPTCHA"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@ -13,7 +13,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
public string Answer => _viewModel.Answer;
|
||||
|
||||
private readonly CaptchaDialogViewModel _viewModel;
|
||||
public CaptchaDialog()
|
||||
public CaptchaDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
passwordBox = this.FindControl<TextBox>(nameof(passwordBox));
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="120"
|
||||
MinWidth="300" MinHeight="120"
|
||||
Width="300" Height="120"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.LoginCallbackDialog"
|
||||
Title="Audible Login"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@ -11,7 +11,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
public Account Account { get; }
|
||||
public string Password { get; set; }
|
||||
|
||||
public LoginCallbackDialog()
|
||||
public LoginCallbackDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,5,0,5"
|
||||
ColumnDefinitions="Auto,*">
|
||||
ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
@ -46,6 +46,12 @@
|
||||
Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}" />
|
||||
<Button
|
||||
Margin="5,0"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Stretch"
|
||||
Content="Submit"
|
||||
Command="{Binding SaveAndCloseAsync}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel
|
||||
|
||||
@ -12,7 +12,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
public string Password { get; set; }
|
||||
public LoginMethod LoginMethod { get; private set; }
|
||||
|
||||
public LoginChoiceEagerDialog()
|
||||
public LoginChoiceEagerDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
public string ExternalLoginUrl { get; }
|
||||
public string ResponseUrl { get; set; }
|
||||
|
||||
public LoginExternalDialog()
|
||||
public LoginExternalDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
MinWidth="400" MinHeight="200"
|
||||
MaxWidth="400" MaxHeight="400"
|
||||
Width="400" Height="200"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.MfaDialog"
|
||||
Title="Two-Step Verification"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@ -14,7 +14,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
public string SelectedValue { get; private set; }
|
||||
private RbValues Values { get; } = new();
|
||||
|
||||
public MfaDialog()
|
||||
public MfaDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
13
Source/LibationAvalonia/Dialogs/Login/WebLoginDialog.axaml
Normal file
13
Source/LibationAvalonia/Dialogs/Login/WebLoginDialog.axaml
Normal file
@ -0,0 +1,13 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||
x:Class="LibationAvalonia.Dialogs.Login.WebLoginDialog"
|
||||
Width="500" Height="800"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/Assets/libation.ico"
|
||||
Title="Audible Login">
|
||||
<controls:NativeWebView Name="webView" />
|
||||
</Window>
|
||||
@ -0,0 +1,54 @@
|
||||
using Avalonia.Controls;
|
||||
using Dinah.Core;
|
||||
using System;
|
||||
|
||||
namespace LibationAvalonia.Dialogs.Login
|
||||
{
|
||||
public partial class WebLoginDialog : Window
|
||||
{
|
||||
public string ResponseUrl { get; private set; }
|
||||
private readonly string accountID;
|
||||
|
||||
public WebLoginDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
webView.NavigationStarted += WebView_NavigationStarted;
|
||||
webView.DOMContentLoaded += WebView_NavigationCompleted;
|
||||
}
|
||||
|
||||
public WebLoginDialog(string accountID, string loginUrl) : this()
|
||||
{
|
||||
this.accountID = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountID, nameof(accountID));
|
||||
webView.Source = new Uri(ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl)));
|
||||
}
|
||||
|
||||
private void WebView_NavigationStarted(object sender, LibationFileManager.WebViewNavigationEventArgs e)
|
||||
{
|
||||
if (e.Request?.AbsolutePath.Contains("/ap/maplanding") is true)
|
||||
{
|
||||
ResponseUrl = e.Request.ToString();
|
||||
Close(DialogResult.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private async void WebView_NavigationCompleted(object sender, EventArgs e)
|
||||
{
|
||||
await webView.InvokeScriptAsync(getScript(accountID));
|
||||
}
|
||||
|
||||
private static string getScript(string accountID) => $$"""
|
||||
(function() {
|
||||
var inputs = document.getElementsByTagName('input');
|
||||
for (index = 0; index < inputs.length; ++index) {
|
||||
if (inputs[index].name.includes('email')) {
|
||||
inputs[index].value = '{{accountID}}';
|
||||
}
|
||||
if (inputs[index].name.includes('password')) {
|
||||
inputs[index].focus();
|
||||
}
|
||||
}
|
||||
})()
|
||||
""";
|
||||
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
MinWidth="200" MinHeight="200"
|
||||
MaxWidth="200" MaxHeight="200"
|
||||
Width="200" Height="200"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog"
|
||||
Title="2FA Code"
|
||||
Icon="/Assets/libation.ico">
|
||||
|
||||
@ -9,7 +9,7 @@ namespace LibationAvalonia.Dialogs.Login
|
||||
public string Prompt { get; } = "For added security, please enter the One Time Password (OTP) generated by your Authenticator App";
|
||||
|
||||
|
||||
public _2faCodeDialog()
|
||||
public _2faCodeDialog() : base(saveAndRestorePosition: false)
|
||||
{
|
||||
InitializeComponent();
|
||||
_2FABox = this.FindControl<TextBox>(nameof(_2FABox));
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationIcon>Assets/libation.ico</ApplicationIcon>
|
||||
<AssemblyName>Libation</AssemblyName>
|
||||
@ -16,6 +17,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@ -14,6 +14,7 @@ namespace LibationAvalonia
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\Publish\Windows-chardonnay</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
79
Source/LibationAvalonia/app.manifest
Normal file
79
Source/LibationAvalonia/app.manifest
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC Manifest Options
|
||||
If you want to change the Windows User Account Control level replace the
|
||||
requestedExecutionLevel node with one of the following.
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||
Remove this element if your application requires this virtualization for backwards
|
||||
compatibility.
|
||||
-->
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
|
||||
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
|
||||
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
|
||||
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
|
||||
|
||||
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
||||
@ -1,14 +1,56 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public interface IInteropFunctions
|
||||
#nullable enable
|
||||
public interface IInteropFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of native web view control https://github.com/maxkatz6/AvaloniaWebView
|
||||
/// </summary>
|
||||
IWebViewAdapter? CreateWebViewAdapter();
|
||||
void SetFolderIcon(string image, string directory);
|
||||
void DeleteFolderIcon(string directory);
|
||||
Process RunAsRoot(string exe, string args);
|
||||
void InstallUpgrade(string upgradeBundle);
|
||||
bool CanUpgrade { get; }
|
||||
}
|
||||
|
||||
public class WebViewNavigationEventArgs : EventArgs
|
||||
{
|
||||
public Uri? Request { get; init; }
|
||||
}
|
||||
|
||||
public interface IWebView
|
||||
{
|
||||
event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
event EventHandler? DOMContentLoaded;
|
||||
bool CanGoBack { get; }
|
||||
bool CanGoForward { get; }
|
||||
Uri? Source { get; set; }
|
||||
bool GoBack();
|
||||
bool GoForward();
|
||||
Task<string?> InvokeScriptAsync(string scriptName);
|
||||
void Navigate(Uri url);
|
||||
Task NavigateToString(string text);
|
||||
void Refresh();
|
||||
void Stop();
|
||||
}
|
||||
|
||||
public interface IWebViewAdapter : IWebView
|
||||
{
|
||||
object NativeWebView { get; }
|
||||
IPlatformHandle2 PlatformHandle { get; }
|
||||
void HandleResize(int width, int height, float zoom);
|
||||
bool HandleKeyDown(uint key, uint keyModifiers);
|
||||
}
|
||||
|
||||
public interface IPlatformHandle2
|
||||
{
|
||||
IntPtr Handle { get; }
|
||||
string? HandleDescriptor { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public class NullInteropFunctions : IInteropFunctions
|
||||
@ -9,7 +11,8 @@ namespace LibationFileManager
|
||||
public NullInteropFunctions() { }
|
||||
public NullInteropFunctions(params object[] values) { }
|
||||
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public IWebViewAdapter? CreateWebViewAdapter() => throw new PlatformNotSupportedException();
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
public bool CanUpgrade => throw new PlatformNotSupportedException();
|
||||
public Process RunAsRoot(string exe, string args) => throw new PlatformNotSupportedException();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using Dinah.Core;
|
||||
using Microsoft.Web.WebView2.WinForms;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@ -8,35 +8,41 @@ namespace LibationWinForms.Login
|
||||
public partial class WebLoginDialog : Form
|
||||
{
|
||||
public string ResponseUrl { get; private set; }
|
||||
private readonly string loginUrl;
|
||||
private readonly string accountID;
|
||||
private readonly WebView2 webView = new();
|
||||
private readonly IWebViewAdapter webView;
|
||||
public WebLoginDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
webView.Dock = DockStyle.Fill;
|
||||
Controls.Add(webView);
|
||||
Shown += WebLoginDialog_Shown;
|
||||
webView = InteropFactory.Create().CreateWebViewAdapter();
|
||||
|
||||
var webViewControl = webView.NativeWebView as Control;
|
||||
webViewControl.Dock = DockStyle.Fill;
|
||||
Controls.Add(webViewControl);
|
||||
|
||||
webView.NavigationStarted += WebView_NavigationStarted;
|
||||
webView.DOMContentLoaded += WebView_DOMContentLoaded;
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
public WebLoginDialog(string accountID, string loginUrl) : this()
|
||||
{
|
||||
this.accountID = ArgumentValidator.EnsureNotNullOrWhiteSpace(accountID, nameof(accountID));
|
||||
this.loginUrl = ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl));
|
||||
webView.Source = new Uri(ArgumentValidator.EnsureNotNullOrWhiteSpace(loginUrl, nameof(loginUrl)));
|
||||
}
|
||||
|
||||
private async void WebLoginDialog_Shown(object sender, EventArgs e)
|
||||
private void WebView_NavigationStarted(object sender, WebViewNavigationEventArgs e)
|
||||
{
|
||||
await webView.EnsureCoreWebView2Async();
|
||||
webView.CoreWebView2.NavigationStarting += CoreWebView2_NavigationStarting;
|
||||
webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
|
||||
webView.CoreWebView2.Navigate(loginUrl);
|
||||
if (e.Request?.AbsolutePath.Contains("/ap/maplanding") is true)
|
||||
{
|
||||
ResponseUrl = e.Request.ToString();
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private async void CoreWebView2_DOMContentLoaded(object sender, Microsoft.Web.WebView2.Core.CoreWebView2DOMContentLoadedEventArgs e)
|
||||
private async void WebView_DOMContentLoaded(object sender, EventArgs e)
|
||||
{
|
||||
await webView.CoreWebView2.ExecuteScriptAsync(getScript(accountID));
|
||||
await webView.InvokeScriptAsync(getScript(accountID));
|
||||
}
|
||||
|
||||
private static string getScript(string accountID) => $$"""
|
||||
@ -52,16 +58,5 @@ namespace LibationWinForms.Login
|
||||
}
|
||||
})()
|
||||
""";
|
||||
|
||||
private void CoreWebView2_NavigationStarting(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
|
||||
{
|
||||
if (new Uri(e.Uri).AbsolutePath.Contains("/ap/maplanding"))
|
||||
{
|
||||
ResponseUrl = e.Uri;
|
||||
e.Cancel = true;
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,10 +29,16 @@ namespace LibationWinForms.Login
|
||||
{
|
||||
if (Environment.OSVersion.Version.Major >= 10)
|
||||
{
|
||||
using var browserLogin = new WebLoginDialog(_account.AccountId, choiceIn.LoginUrl);
|
||||
|
||||
if (ShowDialog(browserLogin))
|
||||
return Task.FromResult(ChoiceOut.External(browserLogin.ResponseUrl));
|
||||
try
|
||||
{
|
||||
using var weblogin = new WebLoginDialog(_account.AccountId, choiceIn.LoginUrl);
|
||||
if (ShowDialog(weblogin))
|
||||
return Task.FromResult(ChoiceOut.External(weblogin.ResponseUrl));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"Failed to run {nameof(WebLoginDialog)}");
|
||||
}
|
||||
}
|
||||
|
||||
using var dialog = new LoginChoiceEagerDialog(_account);
|
||||
|
||||
@ -38,7 +38,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.2.1" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1777-prerelease" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -20,6 +20,7 @@ namespace LinuxConfigApp
|
||||
public LinuxInterop() { }
|
||||
public LinuxInterop(params object[] values) { }
|
||||
|
||||
public IWebViewAdapter CreateWebViewAdapter() => null;
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<!--<TargetFramework>net7.0-macos</TargetFramework>-->
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
|
||||
|
||||
@ -10,6 +10,7 @@ namespace MacOSConfigApp
|
||||
public MacOSInterop() { }
|
||||
public MacOSInterop(params object[] values) { }
|
||||
|
||||
public IWebViewAdapter CreateWebViewAdapter() => null;
|
||||
public void SetFolderIcon(string image, string directory)
|
||||
{
|
||||
Process.Start("fileicon", $"set {directory.SurroundWithQuotes()} {image.SurroundWithQuotes()}").WaitForExit();
|
||||
|
||||
134
Source/LoadByOS/MacOSConfigApp/MacWebViewAdapter.cs
Normal file
134
Source/LoadByOS/MacOSConfigApp/MacWebViewAdapter.cs
Normal file
@ -0,0 +1,134 @@
|
||||
/* Work-in-progress
|
||||
*
|
||||
*
|
||||
using LibationFileManager;
|
||||
using ObjCRuntime;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WebKit;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace MacOSConfigApp;
|
||||
|
||||
internal class WKNavigationDelegate1 : WKNavigationDelegate
|
||||
{
|
||||
public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
|
||||
{
|
||||
base.DidStartProvisionalNavigation(webView, navigation);
|
||||
}
|
||||
}
|
||||
internal class MacWebViewAdapter : IWebViewAdapter, IDisposable
|
||||
{
|
||||
private readonly WKWebView _webView;
|
||||
public IPlatformHandle2 PlatformHandle { get; }
|
||||
|
||||
public bool CanGoBack => _webView.CanGoBack;
|
||||
|
||||
public bool CanGoForward => _webView.CanGoForward;
|
||||
|
||||
public Uri? Source { get => _webView?.Url; set => throw new NotImplementedException(); }
|
||||
|
||||
public object NativeWebView { get; }
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
public event EventHandler? DOMContentLoaded;
|
||||
|
||||
WKNavigationDelegate1 navDelegate;
|
||||
public MacWebViewAdapter()
|
||||
{
|
||||
var frame = new CGRect(0, 0, 500, 800);
|
||||
NativeWebView = _webView = new WKWebView(frame, new WKWebViewConfiguration());
|
||||
_webView.NavigationDelegate = navDelegate = new WKNavigationDelegate1();
|
||||
PlatformHandle = new MacViewHandle(_webView.Handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_webView?.Dispose();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (_webView.CanGoBack)
|
||||
{
|
||||
_webView.GoBack();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
if (_webView.CanGoForward)
|
||||
{
|
||||
_webView.GoForward();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public bool HandleKeyDown(uint key, uint keyModifiers)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void HandleResize(int width, int height, float zoom)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async Task<string?> InvokeScriptAsync(string scriptName)
|
||||
{
|
||||
var result = await _webView.EvaluateJavaScriptAsync(scriptName);
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
public void Navigate(Uri url)
|
||||
{
|
||||
NSUrl? nsurl = url;
|
||||
if (nsurl is null) return;
|
||||
|
||||
var request = new NSUrlRequest(nsurl);
|
||||
|
||||
_webView.LoadRequest(request);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Task NavigateToString(string text)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class MacViewHandle : IPlatformHandle2
|
||||
{
|
||||
private NativeHandle? _view;
|
||||
|
||||
public MacViewHandle(NativeHandle view)
|
||||
{
|
||||
_view = view;
|
||||
}
|
||||
|
||||
public nint Handle => _view?.Handle ?? 0;
|
||||
public string HandleDescriptor => "NativeHandle";
|
||||
}
|
||||
|
||||
*/
|
||||
@ -12,7 +12,10 @@ namespace WindowsConfigApp
|
||||
public WinInterop() { }
|
||||
public WinInterop(params object[] values) { }
|
||||
|
||||
public void SetFolderIcon(string image, string directory)
|
||||
#nullable enable
|
||||
public IWebViewAdapter? CreateWebViewAdapter() => new WindowsWebView2Adapter();
|
||||
#nullable disable
|
||||
public void SetFolderIcon(string image, string directory)
|
||||
{
|
||||
var icon = Image.Load(image).ToIcon();
|
||||
new DirectoryInfo(directory)?.SetIcon(icon, "Music");
|
||||
@ -50,5 +53,5 @@ namespace WindowsConfigApp
|
||||
|
||||
return Process.Start(psi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
@ -23,6 +25,10 @@
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1722.45" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\LibationUiBase\LibationUiBase.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
114
Source/LoadByOS/WindowsConfigApp/WindowsWebView2Adapter.cs
Normal file
114
Source/LoadByOS/WindowsConfigApp/WindowsWebView2Adapter.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using LibationFileManager;
|
||||
using Microsoft.Web.WebView2.WinForms;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
namespace WindowsConfigApp;
|
||||
|
||||
internal class WindowsWebView2Adapter : IWebViewAdapter, IDisposable
|
||||
{
|
||||
public object NativeWebView { get; }
|
||||
private readonly WebView2 _webView;
|
||||
|
||||
public WindowsWebView2Adapter()
|
||||
{
|
||||
NativeWebView = _webView = new WebView2();
|
||||
PlatformHandle = new WebView2Handle { Handle = _webView.Handle };
|
||||
|
||||
_webView.CoreWebView2InitializationCompleted += _webView_CoreWebView2InitializationCompleted;
|
||||
|
||||
_webView.NavigationStarting += (s, a) =>
|
||||
{
|
||||
NavigationStarted?.Invoke(this, new WebViewNavigationEventArgs { Request = new Uri(a.Uri) });
|
||||
};
|
||||
_webView.NavigationCompleted += (s, a) =>
|
||||
{
|
||||
NavigationCompleted?.Invoke(this, new WebViewNavigationEventArgs { Request = _webView.Source });
|
||||
};
|
||||
}
|
||||
|
||||
private void _webView_CoreWebView2InitializationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
|
||||
{
|
||||
_webView.CoreWebView2.DOMContentLoaded -= CoreWebView2_DOMContentLoaded;
|
||||
_webView.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
|
||||
}
|
||||
|
||||
private void CoreWebView2_DOMContentLoaded(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2DOMContentLoadedEventArgs e)
|
||||
=> DOMContentLoaded?.Invoke(this, e);
|
||||
|
||||
public IPlatformHandle2 PlatformHandle { get; }
|
||||
|
||||
public bool CanGoBack => _webView.CanGoBack;
|
||||
|
||||
public bool CanGoForward => _webView.CanGoForward;
|
||||
|
||||
public Uri? Source
|
||||
{
|
||||
get => _webView.Source;
|
||||
set => _webView.Source = value;
|
||||
}
|
||||
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationStarted;
|
||||
public event EventHandler<WebViewNavigationEventArgs>? NavigationCompleted;
|
||||
public event EventHandler? DOMContentLoaded;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_webView.Dispose();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
_webView.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
_webView.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<string?> InvokeScriptAsync(string scriptName)
|
||||
{
|
||||
return await _webView.ExecuteScriptAsync(scriptName);
|
||||
}
|
||||
|
||||
public void Navigate(Uri url)
|
||||
{
|
||||
_webView.Source = url;
|
||||
}
|
||||
|
||||
public async Task NavigateToString(string text)
|
||||
{
|
||||
await _webView.EnsureCoreWebView2Async();
|
||||
|
||||
_webView.NavigateToString(text);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
_webView.Refresh();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_webView.Stop();
|
||||
}
|
||||
|
||||
public void HandleResize(int width, int height, float zoom)
|
||||
{
|
||||
}
|
||||
|
||||
public bool HandleKeyDown(uint key, uint keyModifiers)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class WebView2Handle : IPlatformHandle2
|
||||
{
|
||||
public IntPtr Handle { get; init; }
|
||||
public string HandleDescriptor => "HWND";
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user