Add WebLoginDialog for Windows Chardonnay

This commit is contained in:
Mbucari 2023-04-13 13:33:29 -06:00 committed by MBucari
parent df2936e0b6
commit 4456432116
34 changed files with 704 additions and 47 deletions

View File

@ -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 }} `

View 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();
}
}
}

View File

@ -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">

View File

@ -4,7 +4,7 @@ namespace LibationAvalonia.Dialogs.Login
{
public partial class ApprovalNeededDialog : DialogWindow
{
public ApprovalNeededDialog()
public ApprovalNeededDialog() : base(saveAndRestorePosition: false)
{
InitializeComponent();
}

View File

@ -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 ||

View File

@ -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">

View File

@ -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));

View File

@ -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">

View File

@ -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();

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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">

View File

@ -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();

View 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>

View File

@ -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();
}
}
})()
""";
}
}

View File

@ -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">

View File

@ -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));

View File

@ -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'">

View File

@ -14,6 +14,7 @@ namespace LibationAvalonia
{
static class Program
{
[STAThread]
static void Main(string[] args)
{

View File

@ -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>

View 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>

View File

@ -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; }
}
}

View File

@ -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();

View File

@ -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();
}
}
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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();

View File

@ -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>

View File

@ -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();

View 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";
}
*/

View File

@ -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);
}
}
}
}

View File

@ -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>

View 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";
}