using System.IO; using System.Net.Http; using System.Threading.Tasks; using System.Windows; using KillerPDF.Services; namespace KillerPDF { /// /// Single-instance entry points: App forwards a second launch's file path here rather than /// starting another process. /// /// These stay on the window: RestoreAndActivate drives WindowState, Activate() and Topmost, /// which a UserControl does not have. The OpenInNewTab call routes through ActiveViewer so the /// forwarded file lands in whichever pane has focus. /// public partial class MainWindow { /// A forwarded path that arrived before the panes existed, replayed from Loaded. private string? _pendingExternalPath; public async void OpenFromExternal(string? path) { // A second launch is forwarded here off the pipe thread, and Application.MainWindow is // already set while this window's constructor is still running - so ActiveViewer can // still be null (it is assigned by InitSplitPanes). Hold the path and let Loaded // replay it rather than dereferencing null and losing the file the user double-clicked. if (ActiveViewer == null) { _pendingExternalPath = path; return; } if (!string.IsNullOrEmpty(path) && File.Exists(path)) { ActiveViewer.OpenInNewTabExt(path!); return; } if (ProtocolRegistrar.TryGetTargetUrl(path, out var target) && target != null) await OpenProtocolUrlAsync(target); } /// Replay whatever arrived during startup. No-op in the normal case. internal void FlushPendingExternalOpen() { if (_pendingExternalPath == null) return; var path = _pendingExternalPath; _pendingExternalPath = null; OpenFromExternal(path); } private async Task OpenProtocolUrlAsync(System.Uri target) { string temp = App.MakeTempFile("browser"); try { using var http = new HttpClient { Timeout = System.TimeSpan.FromSeconds(90) }; using var response = await http.GetAsync(target, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); const long MaxBytes = 256L * 1024 * 1024; if (response.Content.Headers.ContentLength is long length && length > MaxBytes) throw new InvalidDataException("The PDF is larger than the 256 MB browser handoff limit."); using (var input = await response.Content.ReadAsStreamAsync()) using (var output = File.Create(temp)) { var buffer = new byte[81920]; long total = 0; int read; while ((read = await input.ReadAsync(buffer, 0, buffer.Length)) > 0) { total += read; if (total > MaxBytes) throw new InvalidDataException("The PDF is larger than the 256 MB browser handoff limit."); await output.WriteAsync(buffer, 0, read); } } using (var check = File.OpenRead(temp)) { var magic = new byte[5]; if (check.Read(magic, 0, magic.Length) != magic.Length || System.Text.Encoding.ASCII.GetString(magic) != "%PDF-") throw new InvalidDataException("The downloaded file is not a PDF."); } ActiveViewer.OpenInNewTabExt(temp); } catch (System.Exception ex) { try { File.Delete(temp); } catch { } KillerDialog.Show(this, $"KillerPDF could not open the browser PDF.\n\n{ex.Message}", "KillerPDF", MessageBoxButton.OK, MessageBoxImage.Warning); } } public void RestoreAndActivate() { if (WindowState == WindowState.Minimized) WindowState = WindowState.Normal; Activate(); // Briefly toggle Topmost to pull the window in front without keeping it pinned. Topmost = true; Topmost = false; Focus(); } } }