releases/VideoDownloader.App/Browser/PairingWindow.cs
Yun Chan 02995f4153 MEDIABROWSER 개명·업데이트 채널 전환·아이콘 tofu 근본 수정 (BACKLOG 13차) — ①개명: installer.iss AppName/출력 MediaBrowser-Setup-*(AppId·exe·데이터 경로 유지로 업그레이드 호환), 워크플로 산출물명(mediabrowser-*.tar.gz, MediaBrowser-<ver>-arm64.apk), 창 타이틀·모바일 표시명·obtainium.json·README. ②UpdateChecker: apiBase 주입(기본=자체 Forgejo /api/v1, GitHub 는 apiBase 로 호환), osx 자산 tar.gz 허용(기존 dmg 만 → mac 영원히 자산 없음 결함 수정), AppConfig 기본 UpdateRepo=Video-Downloader/releases+UpdateApiBase — 테스트 4건. ③아이콘 tofu(사용자 보고 "절대 안고쳐지네") 근본 원인: Shared.xaml 암시적 TextBlock 스타일의 FontFamily=UiFont 강제가 버튼 ContentPresenter 자동 TextBlock 의 IconFont 상속을 덮어 모든 PUA 글리프가 notdef — 폰트 설치/글리프 존재/HW·SW 렌더/RTB/DPI/런타임 8·10/자체포함·공유 전부 배제 후 실창 픽셀 비교로 격리, 콘텐츠 TextBlock 의 실폰트가 Pretendard 체인임을 테스트로 실증. 수정: 암시 스타일 FontFamily 제거(창 루트 상속으로 통일, PairingWindow/PlayerWindow 는 SetResourceReference), PUA+텍스트 혼합 콘텐츠 7곳+코드 1곳 정리, 순수 글리프 2곳 IconFont 명시. 회귀 방어: IconGlyphXamlTests(정적 스캔)·IconContentFontTests(실창 폰트 단정)·IconGlyphRenderTests(픽셀 비교) — 462/462 GREEN, 수정 전후 실창 캡처로 글리프 렌더 확인
2026-08-18 07:13:18 +09:00

67 lines
3.4 KiB
C#

using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using QRCoder;
using VideoDownloader.Server;
namespace VideoDownloader.App.Browser;
/// <summary>M0 원격 접근 페어링: QR(주소+토큰 동시 전달) + 상태 표시.</summary>
public sealed class PairingWindow : Window
{
public PairingWindow(ServerHost server, string deviceName)
{
Title = "기기 연결 - MediaBrowser";
Width = 420; Height = 560;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
SetResourceReference(FontFamilyProperty, "UiFont"); // 암시 스타일 폰트 강제 제거 후 창 루트 상속으로 통일
Background = System.Windows.Media.Brushes.White;
var lanIp = GetLanIp();
var url = $"http://{lanIp}:{server.Port}/#t={server.Token}";
var qrPayload = new QRCodeGenerator().CreateQrCode(url, QRCodeGenerator.ECCLevel.M);
var qr = new QRCode(qrPayload);
var bmp = qr.GetGraphic(12);
var stack = new StackPanel { Margin = new Thickness(24) };
var title = new TextBlock { Text = "📱 폰 카메라로 QR 스캔", FontSize = 16, FontWeight = FontWeights.Bold, Margin = new Thickness(0, 0, 0, 4) };
var sub = new TextBlock { Text = "브라우저에서 바로 열립니다(앱 설치 불필요). 같은 Wi-Fi 에 있어야 합니다.", FontSize = 12, Foreground = System.Windows.Media.Brushes.Gray, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 16) };
var img = new Image { Width = 300, Height = 300 };
using var ms = new MemoryStream();
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
img.Source = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
var urlBox = new TextBox { Text = url, IsReadOnly = true, FontSize = 11, Margin = new Thickness(0, 12, 0, 4), TextWrapping = TextWrapping.Wrap };
var copyBtn = new Button { Content = "주소 복사", Padding = new Thickness(14, 6, 14, 6), HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(0, 8, 0, 0) };
copyBtn.Click += (_, _) => { try { Clipboard.SetText(url); } catch { } };
var note = new TextBlock { Text = $"포트 {server.Port} · Windows 방화벽 확인이 뜨면 '허용'을 선택하세요\n토큰은 이 기기와 페어링된 폰에서만 유효합니다",
FontSize = 11, Foreground = System.Windows.Media.Brushes.Gray, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 12, 0, 0) };
stack.Children.Add(title);
stack.Children.Add(sub);
stack.Children.Add(img);
stack.Children.Add(urlBox);
stack.Children.Add(copyBtn);
stack.Children.Add(note);
Content = stack;
}
/// <summary>LAN IPv4(게이트웨이 있는 첫 후보) — 없으면 loopback.</summary>
private static string GetLanIp()
{
try
{
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0);
socket.Connect("8.8.8.8", 65530); // 외부 행선지 지정 → 라우팅되는 인터페이스 IP 획득(패킷 전송 없음)
return (socket.LocalEndPoint as IPEndPoint)?.Address.ToString() ?? "127.0.0.1";
}
catch { return "127.0.0.1"; }
}
}