using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interop; using Microsoft.Win32; using VideoDownloader.Core.Config; using VideoDownloader.Core.Social; namespace VideoDownloader.App; public partial class SettingsWindow : Window { public AppConfig Config { get; } private readonly string _origTheme; private readonly Func? _clearBrowsingData; public bool ThemeChanged { get; private set; } public SettingsWindow(AppConfig config, Func? clearBrowsingData = null) { InitializeComponent(); Config = config; _origTheme = config.Theme; _clearBrowsingData = clearBrowsingData; FolderBox.Text = config.OutputFolder; HomeBox.Text = config.HomeUrl; ConcurrencyBox.Text = config.Concurrency.ToString(); MinSizeBox.Text = config.MinSizeMB.ToString(); MaxConcurrentBox.Text = config.MaxConcurrentDownloads.ToString(); ThemeLightBox.IsChecked = config.Theme.Equals("light", StringComparison.OrdinalIgnoreCase); FfmpegBox.Text = config.FfmpegPath ?? ""; FmtHls.IsChecked = config.EnabledFormats.Contains("hls"); FmtDash.IsChecked = config.EnabledFormats.Contains("dash"); FmtMp4.IsChecked = config.EnabledFormats.Contains("mp4"); FmtWebM.IsChecked = config.EnabledFormats.Contains("webm"); FmtMkv.IsChecked = config.EnabledFormats.Contains("mkv"); FmtM4v.IsChecked = config.EnabledFormats.Contains("m4v"); FmtMov.IsChecked = config.EnabledFormats.Contains("mov"); FmtAvi.IsChecked = config.EnabledFormats.Contains("avi"); FmtAudio.IsChecked = config.EnabledFormats.Contains("audio"); YtDlpBox.Text = config.YtDlpPath ?? ""; UseCookiesBox.IsChecked = config.UseCookiesForSocial; AutoInstallBox.IsChecked = config.AutoInstallYtDlp; AutoUpdateBox.IsChecked = config.AutoUpdateYtDlp; AutoInstallDenoBox.IsChecked = config.AutoInstallDeno; DefaultQualityBox.SelectedValue = config.DefaultSocialQuality; if (DefaultQualityBox.SelectedIndex < 0) DefaultQualityBox.SelectedIndex = 0; YoutubeClientsBox.Text = config.YoutubePlayerClients ?? ""; ProfilePathBox.Text = MainWindow.ProfileFolder; ProfileModeText.Text = config.PrivateMode ? "개인정보 보호 모드(InPrivate) — 데이터를 저장하지 않습니다" : "일반 모드 — 쿠키·세션·localStorage 를 프로필에 보존합니다"; } private void BrowseFolder(object sender, RoutedEventArgs e) { var dlg = new OpenFolderDialog(); if (Directory.Exists(FolderBox.Text)) dlg.InitialDirectory = FolderBox.Text; if (dlg.ShowDialog() == true) FolderBox.Text = dlg.FolderName; } private void BrowseFfmpeg(object sender, RoutedEventArgs e) { var dlg = new OpenFileDialog { Filter = "ffmpeg|ffmpeg.exe|실행 파일|*.exe|모든 파일|*.*" }; if (dlg.ShowDialog() == true) FfmpegBox.Text = dlg.FileName; } private void BrowseYtDlp(object sender, RoutedEventArgs e) { var dlg = new OpenFileDialog { Filter = "yt-dlp|yt-dlp.exe|실행 파일|*.exe|모든 파일|*.*" }; if (dlg.ShowDialog() == true) YtDlpBox.Text = dlg.FileName; } private async void ReinstallYtDlp_Click(object sender, RoutedEventArgs e) { var btn = sender as Button; if (btn != null) btn.IsEnabled = false; try { var path = await YtDlpRunner.DownloadAsync(Config, null, CancellationToken.None); MessageBox.Show(path != null ? $"yt-dlp 설치 완료:\n{path}" : "yt-dlp 다운로드에 실패했습니다. 네트워크를 확인하세요.", "yt-dlp", MessageBoxButton.OK, path != null ? MessageBoxImage.Information : MessageBoxImage.Warning); if (path != null) YtDlpBox.Text = ""; } finally { if (btn != null) btn.IsEnabled = true; } } private async void ClearData_Click(object sender, RoutedEventArgs e) { if (_clearBrowsingData == null) return; if (MessageBox.Show("쿠키·localStorage·캐시 등 브라우저 데이터를 삭제합니다.\n로그인이 풀릴 수 있습니다. 계속할까요?", "데이터 삭제", MessageBoxButton.OKCancel, MessageBoxImage.Warning) != MessageBoxResult.OK) return; var btn = sender as Button; if (btn != null) btn.IsEnabled = false; try { await _clearBrowsingData(); } finally { if (btn != null) btn.IsEnabled = true; } } private void Ok_Click(object sender, RoutedEventArgs e) { Config.OutputFolder = FolderBox.Text?.Trim() ?? ""; Config.HomeUrl = string.IsNullOrWhiteSpace(HomeBox.Text) ? "https://www.google.com" : HomeBox.Text.Trim(); if (int.TryParse(ConcurrencyBox.Text, out var c)) Config.Concurrency = Math.Max(1, c); if (long.TryParse(MinSizeBox.Text, out var m) && m >= 0) Config.MinSizeMB = m; Config.FfmpegPath = string.IsNullOrWhiteSpace(FfmpegBox.Text) ? null : FfmpegBox.Text.Trim(); Config.Theme = ThemeLightBox.IsChecked == true ? "light" : "dark"; if (int.TryParse(MaxConcurrentBox.Text, out var mc)) Config.MaxConcurrentDownloads = Math.Max(1, mc); ThemeChanged = !Config.Theme.Equals(_origTheme, StringComparison.OrdinalIgnoreCase); var fmts = new List(); if (FmtHls.IsChecked == true) fmts.Add("hls"); if (FmtDash.IsChecked == true) fmts.Add("dash"); if (FmtMp4.IsChecked == true) fmts.Add("mp4"); if (FmtWebM.IsChecked == true) fmts.Add("webm"); if (FmtMkv.IsChecked == true) fmts.Add("mkv"); if (FmtM4v.IsChecked == true) fmts.Add("m4v"); if (FmtMov.IsChecked == true) fmts.Add("mov"); if (FmtAvi.IsChecked == true) fmts.Add("avi"); if (FmtAudio.IsChecked == true) fmts.Add("audio"); Config.EnabledFormats = fmts; Config.YtDlpPath = string.IsNullOrWhiteSpace(YtDlpBox.Text) ? null : YtDlpBox.Text.Trim(); Config.UseCookiesForSocial = UseCookiesBox.IsChecked == true; Config.AutoInstallYtDlp = AutoInstallBox.IsChecked == true; Config.AutoUpdateYtDlp = AutoUpdateBox.IsChecked == true; Config.AutoInstallDeno = AutoInstallDenoBox.IsChecked == true; Config.DefaultSocialQuality = (DefaultQualityBox.SelectedValue as string) ?? "best"; Config.YoutubePlayerClients = string.IsNullOrWhiteSpace(YoutubeClientsBox.Text) ? null : YoutubeClientsBox.Text.Trim(); DialogResult = true; } // 보더리스: Win11 어두운 테두리 + 둥근 모서리 protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); try { var hwnd = new WindowInteropHelper(this).Handle; int dark = App.IsLightTheme ? 0 : 1; // DWMWA_USE_IMMERSIVE_DARK_MODE (라이트 테마면 밝게) DwmSetWindowAttribute(hwnd, 20, ref dark, sizeof(int)); int round = 2; // DWMWCP_ROUND DwmSetWindowAttribute(hwnd, 33, ref round, sizeof(int)); } catch { } } private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed) { try { DragMove(); } catch { } } } private void Close_Click(object sender, RoutedEventArgs e) => Close(); [DllImport("dwmapi.dll")] private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attribute, ref int pvAttribute, int cbAttribute); }