feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization
This commit is contained in:
parent
a88aafa1e5
commit
0e568f1f0a
975 changed files with 130593 additions and 16783 deletions
10
src/Paca.Avalonia/App.axaml
Normal file
10
src/Paca.Avalonia/App.axaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Paca.Avalonia.App"
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
23
src/Paca.Avalonia/App.axaml.cs
Normal file
23
src/Paca.Avalonia/App.axaml.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Paca.Avalonia;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
BIN
src/Paca.Avalonia/Assets/paca.ico
Normal file
BIN
src/Paca.Avalonia/Assets/paca.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
187
src/Paca.Avalonia/MainViewModel.cs
Normal file
187
src/Paca.Avalonia/MainViewModel.cs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Threading;
|
||||
using Paca.Core.Config;
|
||||
using Paca.Core.Dash;
|
||||
using Paca.Core.Hls;
|
||||
using Paca.Core.Models;
|
||||
using Paca.Core.Social;
|
||||
using Paca.Core.Util;
|
||||
|
||||
namespace Paca.Avalonia;
|
||||
|
||||
/// <summary>Phase 2 Gate #0: 크로스플랫폼 셸의 최소 뷰모델.
|
||||
/// URL 을 붙여넣으면 유형(HLS/DASH/소셜/직링크) 을 판별해 Core 다운로더를 직접 호출해 진행률을 표시.
|
||||
/// 브라우저/감지/쿠키(Gate #1) 는 아직 없으므로 DownloadManager 가 아닌 Core 엔진을 직접 사용.</summary>
|
||||
public class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private readonly AppConfig _config = new();
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
private string _url = "";
|
||||
private string _status = "URL 을 붙여넣고 받기를 누르세요";
|
||||
private double _progress;
|
||||
private bool _isBusy;
|
||||
private bool _isPaused;
|
||||
private string _result = "";
|
||||
|
||||
public string Url { get => _url; set { _url = value; On(); _downloadCommand.RaiseCanExecuteChanged(); } }
|
||||
public string Status { get => _status; set { _status = value; On(); } }
|
||||
public double Progress { get => _progress; set { _progress = value; On(); } }
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set
|
||||
{
|
||||
_isBusy = value;
|
||||
if (!_isBusy) _isPaused = false;
|
||||
On();
|
||||
On(nameof(IsPaused));
|
||||
_downloadCommand.RaiseCanExecuteChanged();
|
||||
_cancelCommand.RaiseCanExecuteChanged();
|
||||
_pauseCommand.RaiseCanExecuteChanged();
|
||||
_resumeCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
public bool IsPaused
|
||||
{
|
||||
get => _isPaused;
|
||||
set
|
||||
{
|
||||
_isPaused = value;
|
||||
On();
|
||||
_pauseCommand.RaiseCanExecuteChanged();
|
||||
_resumeCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
public string Result { get => _result; set { _result = value; On(); On(nameof(HasResult)); } }
|
||||
public bool HasResult => !string.IsNullOrEmpty(_result);
|
||||
public ObservableCollection<string> Log { get; } = new();
|
||||
internal bool HasActiveCts => _cts != null;
|
||||
|
||||
internal void AddLogEntry(string detail)
|
||||
{
|
||||
if (string.IsNullOrEmpty(detail)) return;
|
||||
Log.Insert(0, detail);
|
||||
while (Log.Count > 200) Log.RemoveAt(Log.Count - 1);
|
||||
}
|
||||
|
||||
private readonly RelayCommand _downloadCommand;
|
||||
private readonly RelayCommand _cancelCommand;
|
||||
private readonly RelayCommand _pauseCommand;
|
||||
private readonly RelayCommand _resumeCommand;
|
||||
public ICommand DownloadCommand => _downloadCommand;
|
||||
public ICommand CancelCommand => _cancelCommand;
|
||||
public ICommand PauseCommand => _pauseCommand;
|
||||
public ICommand ResumeCommand => _resumeCommand;
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
_downloadCommand = new RelayCommand(_ => _ = DownloadAsync(), _ => !IsBusy && !string.IsNullOrWhiteSpace(Url));
|
||||
_cancelCommand = new RelayCommand(_ => Cancel(), _ => IsBusy);
|
||||
_pauseCommand = new RelayCommand(_ => Pause(), _ => IsBusy && !IsPaused);
|
||||
_resumeCommand = new RelayCommand(_ => Resume(), _ => IsBusy && IsPaused);
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (IsBusy && !IsPaused)
|
||||
{
|
||||
IsPaused = true;
|
||||
Status = "일시정지됨";
|
||||
}
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (IsBusy && IsPaused)
|
||||
{
|
||||
IsPaused = false;
|
||||
Status = "다운로드 재개 중...";
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel() { try { _cts?.Cancel(); } catch { } Status = "취소 중..."; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
internal async Task RunDownloadAsync() => await DownloadAsync();
|
||||
|
||||
private async Task DownloadAsync()
|
||||
{
|
||||
if (!Uri.TryCreate(Url.Trim(), UriKind.Absolute, out var uri))
|
||||
{ Status = "올바른 URL 이 아닙니다"; return; }
|
||||
|
||||
// UI 스레드에서 미리 유형 판별 + 상태 표시(백그라운드에서 INPC 속성을 건드리지 않도록).
|
||||
var kind = Classify(uri);
|
||||
IsBusy = true; Result = ""; Progress = 0; Log.Clear();
|
||||
Status = kind switch { Kind.Social => "yt-dlp 소셜 다운로드", Kind.Hls => "HLS 다운로드", Kind.Dash => "DASH 다운로드", _ => "직접 다운로드" };
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
var ct = _cts.Token;
|
||||
var progress = new Progress<DownloadProgress>(p => Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (p.OverridePercent >= 0) Progress = p.OverridePercent;
|
||||
Status = !string.IsNullOrEmpty(p.Detail) ? p.Detail
|
||||
: (p.Stage == "downloading" && p.Total > 0 ? $"{p.Completed}/{p.Total}" : p.Stage);
|
||||
if (p.OverridePercent < 0 && !string.IsNullOrEmpty(p.Detail)) AddLogEntry(p.Detail);
|
||||
}));
|
||||
|
||||
try
|
||||
{
|
||||
var name = FileNameUtil.Sanitize(FileNameUtil.DeriveName(uri));
|
||||
var outputBase = Path.Combine(_config.OutputFolder, name);
|
||||
Directory.CreateDirectory(_config.OutputFolder);
|
||||
string final = await Task.Run(() => DownloadByKindAsync(uri, kind, outputBase, progress, ct), ct);
|
||||
|
||||
Progress = 100;
|
||||
Result = $"완료: {final}";
|
||||
Status = "완료";
|
||||
}
|
||||
catch (OperationCanceledException) { Status = "취소됨"; }
|
||||
catch (Exception e) { Status = "오류"; Result = $"{e.GetType().Name}: {e.Message}"; }
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 백그라운드 스레드에서 실행. UI 속성 미참조(진행률은 progress 콜백→UI 디스패처로 전달).
|
||||
private async Task<string> DownloadByKindAsync(Uri uri, Kind kind, string outputBase, IProgress<DownloadProgress> progress, CancellationToken ct)
|
||||
{
|
||||
using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
|
||||
http.DefaultRequestHeaders.UserAgent.ParseAdd(_config.UserAgent);
|
||||
return kind switch
|
||||
{
|
||||
Kind.Social => await new YtDlpDownloader(_config)
|
||||
.DownloadAsync(uri, outputBase, new FormatOption("최고 화질 (MP4)", "bv*+ba/b"), null, progress, ct),
|
||||
Kind.Hls => await new HlsDownloader(http, _config).DownloadAsync(uri, outputBase, progress, ct),
|
||||
Kind.Dash => await new DashDownloader(http, _config).DownloadAsync(uri, outputBase, progress, ct),
|
||||
_ => await new DirectDownloader(http, _config).DownloadAsync(uri, outputBase, progress, ct),
|
||||
};
|
||||
}
|
||||
|
||||
internal enum Kind { Social, Hls, Dash, Direct }
|
||||
internal static Kind Classify(Uri uri)
|
||||
{
|
||||
string host = uri.Host.ToLowerInvariant(), path = uri.AbsolutePath.ToLowerInvariant();
|
||||
bool social = host.Contains("youtube") || host.Contains("youtu.be") || host.Contains("instagram")
|
||||
|| host.Contains("tiktok") || host.Contains("x.com") || host.Contains("twitter") || host.Contains("facebook");
|
||||
if (social) return Kind.Social;
|
||||
if (path.EndsWith(".m3u8")) return Kind.Hls;
|
||||
if (path.EndsWith(".mpd")) return Kind.Dash;
|
||||
return Kind.Direct;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
private void On([CallerMemberName] string? p = null) => PropertyChanged?.Invoke(this, new(p));
|
||||
}
|
||||
93
src/Paca.Avalonia/MainWindow.axaml
Normal file
93
src/Paca.Avalonia/MainWindow.axaml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<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"
|
||||
xmlns:local="clr-namespace:Paca.Avalonia"
|
||||
x:DataType="local:MainViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="740" d:DesignHeight="600"
|
||||
x:Class="Paca.Avalonia.MainWindow"
|
||||
Title="Paca (Avalonia · 크로스플랫폼)"
|
||||
Icon="/Assets/paca.ico"
|
||||
Width="760" Height="620"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Background="#090A10">
|
||||
|
||||
<!-- DataContext 는 MainWindow 생성자에서 코드비하인드로 설정(MainViewModel). -->
|
||||
|
||||
<Grid Margin="20">
|
||||
<!-- 배경 글래스모피즘 그라데이션 장식 -->
|
||||
<Border Background="#12131F" CornerRadius="16" BorderBrush="#222438" BorderThickness="1" Padding="24">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,*">
|
||||
|
||||
<!-- 헤더 툴바 -->
|
||||
<DockPanel Grid.Row="0" Margin="0,0,0,20" LastChildFill="True">
|
||||
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="34" Height="34" CornerRadius="9" Background="#5E56E8" VerticalAlignment="Center" Margin="0,0,12,0">
|
||||
<TextBlock Text="▶" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" FontSize="13"/>
|
||||
</Border>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="Paca" FontSize="18" FontWeight="SemiBold" Foreground="#F2F3F8"/>
|
||||
<TextBlock Text="크로스플랫폼 미디어 다운로더 · HLS / DASH / yt-dlp" FontSize="11" Foreground="#8E94A8"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Border DockPanel.Dock="Right" Background="#181A2A" CornerRadius="8" Padding="12,5" VerticalAlignment="Center" BorderBrush="#2D304D" BorderThickness="1">
|
||||
<TextBlock Text="Avalonia Engine" FontSize="11" Foreground="#C4B5FD" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
|
||||
<!-- URL 입력 카드 (글래스모피즘) -->
|
||||
<Border Grid.Row="1" Background="#181A28" BorderBrush="#2A2D44" BorderThickness="1" CornerRadius="12" Padding="16" Margin="0,0,0,16">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="영상 링크 (YouTube, Instagram, X, HLS m3u8, DASH mpd, 직링크)" FontSize="12" FontWeight="SemiBold" Foreground="#A9B0C7"/>
|
||||
<TextBox x:Name="UrlBox"
|
||||
Text="{Binding Url}"
|
||||
PlaceholderText="https://www.youtube.com/watch?v=... 또는 미디어 URL 입력"
|
||||
Background="#0E101B" Foreground="#F2F3F8"
|
||||
BorderBrush="#343753" BorderThickness="1" Padding="12,10" CornerRadius="8" FontSize="13"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
||||
<Button Content="취소" Command="{Binding CancelCommand}"
|
||||
Background="Transparent" Foreground="#A9B0C7" BorderBrush="#343753" BorderThickness="1"
|
||||
Padding="18,7" CornerRadius="8" FontSize="12" Cursor="Hand"/>
|
||||
<Button Content="다운로드 시작" Command="{Binding DownloadCommand}"
|
||||
Background="#5E56E8" Foreground="White" BorderThickness="0"
|
||||
Padding="22,7" CornerRadius="8" FontWeight="SemiBold" FontSize="12" Cursor="Hand"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 진행률 카드 -->
|
||||
<Border Grid.Row="2" Background="#181A28" BorderBrush="#2A2D44" BorderThickness="1" CornerRadius="12" Padding="16" Margin="0,0,0,16">
|
||||
<StackPanel Spacing="8">
|
||||
<DockPanel LastChildFill="True">
|
||||
<TextBlock Text="{Binding Status}" Foreground="#F2F3F8" FontSize="12.5" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding Progress, StringFormat={}{0:0}%}" DockPanel.Dock="Right" HorizontalAlignment="Right"
|
||||
Foreground="#C4B5FD" FontWeight="Bold" FontSize="12.5"/>
|
||||
</DockPanel>
|
||||
<ProgressBar Value="{Binding Progress}" Maximum="100"
|
||||
MinHeight="6" CornerRadius="3" Foreground="#5E56E8" Background="#24263B"/>
|
||||
<TextBlock Text="{Binding Result}" Foreground="#34D399" FontSize="11.5" TextWrapping="Wrap"
|
||||
IsVisible="{Binding HasResult}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 로그 콘솔 -->
|
||||
<DockPanel Grid.Row="3" Margin="0,0,0,6">
|
||||
<TextBlock Text="작업 로그" FontSize="12" FontWeight="SemiBold" Foreground="#8E94A8"/>
|
||||
</DockPanel>
|
||||
|
||||
<Border Grid.Row="4" Background="#0E101B" BorderBrush="#202235" BorderThickness="1" CornerRadius="10" Padding="12" Height="140">
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Log}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" Foreground="#8E94A8" FontSize="11" FontFamily="Cascadia Mono, Consolas, Courier New" Margin="0,2"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
19
src/Paca.Avalonia/MainWindow.axaml.cs
Normal file
19
src/Paca.Avalonia/MainWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace Paca.Avalonia;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MainViewModel();
|
||||
// URL 입력창에 포커스 + Enter 로 받기 실행.
|
||||
UrlBox.AttachedToVisualTree += (_, _) => UrlBox.Focus();
|
||||
UrlBox.KeyDown += (_, e) =>
|
||||
{
|
||||
if (e.Key == global::Avalonia.Input.Key.Enter && DataContext is MainViewModel vm && !vm.IsBusy && !string.IsNullOrWhiteSpace(vm.Url))
|
||||
vm.DownloadCommand.Execute(null);
|
||||
};
|
||||
}
|
||||
}
|
||||
37
src/Paca.Avalonia/Paca.Avalonia.csproj
Normal file
37
src/Paca.Avalonia/Paca.Avalonia.csproj
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<!-- Core(net8.0)와 정렬 + Mac/Linux LTS 가용성. 크로스플랫폼 Avalonia 셸. -->
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>Paca.Avalonia</RootNamespace>
|
||||
<AssemblyName>Paca.Avalonia</AssemblyName>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Assets\paca.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Paca.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="12.1.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="12.1.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.0" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.0" />
|
||||
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3">
|
||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- 공유 엔진(HLS/DASH/Social yt-dlp + 자동복구/자동설치). WPF 앱과 동일 Core 재사용. -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Paca.Core\Paca.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
24
src/Paca.Avalonia/Program.cs
Normal file
24
src/Paca.Avalonia/Program.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Avalonia;
|
||||
using System;
|
||||
|
||||
namespace Paca.Avalonia;
|
||||
|
||||
class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
#if DEBUG
|
||||
.WithDeveloperTools()
|
||||
#endif
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
16
src/Paca.Avalonia/RelayCommand.cs
Normal file
16
src/Paca.Avalonia/RelayCommand.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System.Windows.Input;
|
||||
|
||||
namespace Paca.Avalonia;
|
||||
|
||||
/// <summary>간단한 ICommand 구현(CommunityToolkit 없이 MVVM 커맨드 바인딩용).</summary>
|
||||
public sealed class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action<object?> _execute;
|
||||
private readonly Func<object?, bool>? _canExecute;
|
||||
public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
|
||||
{ _execute = execute; _canExecute = canExecute; }
|
||||
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
|
||||
public void Execute(object? parameter) => _execute(parameter);
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
18
src/Paca.Avalonia/app.manifest
Normal file
18
src/Paca.Avalonia/app.manifest
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="Paca.Avalonia.Desktop"/>
|
||||
|
||||
<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 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
Loading…
Add table
Add a link
Reference in a new issue