using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.Win32; using System; using System.Linq; using System.Runtime.InteropServices; using Windows.System; using WinRT.Interop; namespace ClipForge { public sealed partial class MainWindow : Window { private GlobalHotkeyService _hotkeyService; private ScreenCaptureService _captureService; private ClipLibraryService _clipLibrary; private SettingsService _settingsService; private ThumbnailService _thumbnailService; private bool _isRecordingHotkey; private delegate IntPtr WinProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); private WinProc _newWndProc; private IntPtr _oldWndProc; [DllImport("user32.dll")] private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, WinProc newProc); [DllImport("user32.dll")] private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr LoadImage(IntPtr hInst, string lpszName, uint uType, int cxDesired, int cyDesired, uint fuLoad); [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vk); private const int VK_SHIFT = 0x10; private const int VK_CONTROL = 0x11; private const int VK_MENU = 0x12; // Alt private const int VK_LWIN = 0x5B; private const int GWLP_WNDPROC = -4; private const uint WM_HOTKEY = 0x0312; private const int GWL_EXSTYLE = -20; private const int WS_EX_NOACTIVATE = 0x08000000; private const int WS_EX_TOOLWINDOW = 0x00000080; private const int WS_EX_TOPMOST = 0x00000008; public MainWindow() { this.InitializeComponent(); var hwnd = WindowNative.GetWindowHandle(this); // Set window icon var iconPath = System.IO.Path.Combine( AppContext.BaseDirectory, "clipforge.ico"); if (System.IO.File.Exists(iconPath)) { var hIcon = LoadImage(IntPtr.Zero, iconPath, 1, 32, 32, 0x0010); SendMessage(hwnd, 0x0080, new IntPtr(1), hIcon); SendMessage(hwnd, 0x0080, new IntPtr(0), hIcon); } _newWndProc = WndProc; _oldWndProc = SetWindowLongPtr(hwnd, GWLP_WNDPROC, _newWndProc); _captureService = new ScreenCaptureService(); _clipLibrary = new ClipLibraryService(); _clipLibrary.Initialize(); _settingsService = new SettingsService(); _settingsService.Load(); _thumbnailService = new ThumbnailService(); _hotkeyService = new GlobalHotkeyService(); _hotkeyService.ClipRequested += OnClipRequested; var mod = _settingsService.Settings.HotkeyModifiers; var vk = _settingsService.Settings.HotkeyVirtualKey; if (mod == 0 && vk == 0) { mod = 1; vk = 0x78; } _hotkeyService.Initialize(hwnd, (uint)mod, (uint)vk); ClipGrid.ItemsSource = _clipLibrary.Clips; UpdateClipCount(); _clipLibrary.Clips.CollectionChanged += (s, e) => UpdateClipCount(); this.AppWindow.Closing += OnWindowClosing; this.Activated += OnWindowActivated; } private void OnWindowClosing( Microsoft.UI.Windowing.AppWindow sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs args) { args.Cancel = true; this.AppWindow.Hide(); var app = Microsoft.UI.Xaml.Application.Current as App; app?.TrayIcon?.SetStatus("Running in background"); } private void UpdateClipCount() { ClipCountText.Text = $"{_clipLibrary.Clips.Count} clips"; } private async void OnWindowActivated(object sender, WindowActivatedEventArgs args) { this.Activated -= OnWindowActivated; await System.Threading.Tasks.Task.Delay(500); StartCaptureAsync(); ApplySettings(); GenerateThumbnailsAsync(); } private async void StartCaptureAsync() { var hwnd = WindowNative.GetWindowHandle(this); var success = await _captureService.StartCaptureAsync(hwnd); if (success) await ShowToastAsync("⬡ Recording started"); else await ShowToastAsync("❌ Failed to start recording"); } private async void GenerateThumbnailsAsync() { await _thumbnailService.GenerateAllThumbnailsAsync(_clipLibrary.Clips); } private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { if (msg == WM_HOTKEY) _hotkeyService.ProcessHotkey((int)wParam); return CallWindowProc(_oldWndProc, hWnd, msg, wParam, lParam); } private async void OnClipRequested() { try { await ShowToastAsync("⬡ Saving clip..."); var savedPath = await _captureService.SaveClipAsync( _settingsService.Settings.ClipLengthSeconds); if (savedPath != null) { await ShowToastAsync("✅ Clip saved!"); var newClip = _clipLibrary.Clips.FirstOrDefault( c => c.Path == savedPath); if (newClip != null) await _thumbnailService.GenerateThumbnailAsync(newClip); } else { await ShowToastAsync("❌ Failed to save clip"); } } catch (Exception ex) { await ShowToastAsync($"❌ {ex.Message}"); } } private void ApplySettings() { var s = _settingsService.Settings; ClipLengthSlider.Value = s.ClipLengthSeconds; QualitySlider.Value = s.VideoQuality; ClipLengthLabel.Text = $"{s.ClipLengthSeconds} seconds"; QualityLabel.Text = $"{s.VideoQuality}%"; FramerateCombo.SelectedIndex = s.Framerate == 30 ? 0 : 1; StartupToggle.IsOn = IsStartupEnabled(); if (HotkeyRecorderButton != null) HotkeyRecorderButton.Content = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey); } // --- STARTUP WITH WINDOWS --- private const string StartupKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; private const string StartupValueName = "ClipForge"; private bool IsStartupEnabled() { using var key = Registry.CurrentUser.OpenSubKey(StartupKey); return key?.GetValue(StartupValueName) != null; } private void SetStartup(bool enable) { using var key = Registry.CurrentUser.OpenSubKey(StartupKey, true); if (enable) { var exePath = System.Diagnostics.Process .GetCurrentProcess().MainModule?.FileName ?? ""; key?.SetValue(StartupValueName, $"\"{exePath}\""); } else { key?.DeleteValue(StartupValueName, false); } } private void StartupToggle_Toggled(object sender, RoutedEventArgs e) { SetStartup(StartupToggle.IsOn); } // --- SETTINGS --- private void ClipLengthSlider_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e) { if (ClipLengthLabel == null) return; int val = (int)e.NewValue; ClipLengthLabel.Text = $"{val} seconds"; _settingsService.Settings.ClipLengthSeconds = val; } private void QualitySlider_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e) { if (QualityLabel == null) return; int val = (int)e.NewValue; QualityLabel.Text = $"{val}%"; _settingsService.Settings.VideoQuality = val; } private void FramerateCombo_SelectionChanged(object sender, Microsoft.UI.Xaml.Controls.SelectionChangedEventArgs e) { if (_settingsService == null) return; _settingsService.Settings.Framerate = FramerateCombo.SelectedIndex == 0 ? 30 : 60; } private void SaveSettings_Click(object sender, RoutedEventArgs e) { _settingsService.Save(); _ = ShowToastAsync("✅ Settings saved!"); } // --- CUSTOM HOTKEY --- private void HotkeyRecorderButton_Click(object sender, RoutedEventArgs e) { if (_isRecordingHotkey) return; _isRecordingHotkey = true; HotkeyRecorderButton.Content = "Press any key... (Esc to cancel)"; HotkeyRecorderButton.Focus(FocusState.Programmatic); HotkeyRecorderButton.KeyDown += OnHotkeyCaptureKeyDown; } private void OnHotkeyCaptureKeyDown(object sender, KeyRoutedEventArgs e) { if (!_isRecordingHotkey) return; e.Handled = true; try { var key = e.Key; // Escape cancels without changing the hotkey if (key == VirtualKey.Escape) { StopRecordingHotkey(restoreDisplay: true); return; } if (HotkeyHelper.IsModifierKey(key)) return; // wait for a non-modifier key if (key == VirtualKey.None) return; uint mod = 0; if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) mod |= HotkeyHelper.MOD_CONTROL; if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) mod |= HotkeyHelper.MOD_ALT; if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) mod |= HotkeyHelper.MOD_SHIFT; if ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) mod |= HotkeyHelper.MOD_WIN; uint vk = (uint)key; _settingsService.Settings.HotkeyModifiers = (int)mod; _settingsService.Settings.HotkeyVirtualKey = (int)vk; var display = HotkeyHelper.ToDisplayString(mod, vk); StopRecordingHotkey(restoreDisplay: false); HotkeyRecorderButton.Content = display; // Defer registration so the key is released first; otherwise the new hotkey can fire // immediately and re-enter (e.g. OnClipRequested) and crash. _ = DelayedHotkeyRegister(mod, vk, display); } catch (Exception ex) { StopRecordingHotkey(restoreDisplay: true); _ = ShowToastAsync("⚠ " + ex.Message); } } private void StopRecordingHotkey(bool restoreDisplay) { HotkeyRecorderButton.KeyDown -= OnHotkeyCaptureKeyDown; _isRecordingHotkey = false; if (restoreDisplay) { var s = _settingsService.Settings; HotkeyRecorderButton.Content = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey); } } private async System.Threading.Tasks.Task DelayedHotkeyRegister(uint mod, uint vk, string display) { await System.Threading.Tasks.Task.Delay(300); App.MainQueue?.TryEnqueue(() => { try { var ok = _hotkeyService.UpdateHotkey(mod, vk); HotkeyRecorderButton.Content = ok ? display : display + " (in use?)"; } catch (Exception ex) { HotkeyRecorderButton.Content = display; _ = ShowToastAsync("⚠ " + ex.Message); } }); } // --- NAV --- private void NavClips_Click(object sender, RoutedEventArgs e) { ClipsPage.Visibility = Visibility.Visible; SettingsPage.Visibility = Visibility.Collapsed; } private void NavSettings_Click(object sender, RoutedEventArgs e) { ClipsPage.Visibility = Visibility.Collapsed; SettingsPage.Visibility = Visibility.Visible; } private void RecordButton_Click(object sender, RoutedEventArgs e) { if (_captureService.IsCapturing) { _captureService.StopCapture(); RecordLabel.Text = "START"; RecordDot.Fill = new Microsoft.UI.Xaml.Media.SolidColorBrush( Windows.UI.Color.FromArgb(255, 100, 100, 100)); } else { StartCaptureAsync(); RecordLabel.Text = "CAPTURING"; RecordDot.Fill = new Microsoft.UI.Xaml.Media.SolidColorBrush( Windows.UI.Color.FromArgb(255, 255, 71, 87)); } } private void OpenFolder_Click(object sender, RoutedEventArgs e) { System.Diagnostics.Process.Start("explorer.exe", _clipLibrary.ClipsDirectory); } private void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { var query = sender.Text.ToLower(); if (string.IsNullOrWhiteSpace(query)) ClipGrid.ItemsSource = _clipLibrary.Clips; else ClipGrid.ItemsSource = _clipLibrary.Clips .Where(c => c.Title.ToLower().Contains(query)) .ToList(); } // --- CLIP ACTIONS --- private void TrimClip_Click(object sender, RoutedEventArgs e) { var button = sender as Microsoft.UI.Xaml.Controls.Button; var clipPath = button?.Tag as string; if (clipPath == null) return; var trimmer = new TrimmerWindow(clipPath); trimmer.Activate(); } private async void DeleteClip_Click(object sender, RoutedEventArgs e) { var button = sender as Microsoft.UI.Xaml.Controls.Button; var clipPath = button?.Tag as string; if (clipPath == null) return; var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog { Title = "Delete Clip", Content = "Are you sure? This cannot be undone.", PrimaryButtonText = "Delete", CloseButtonText = "Cancel", DefaultButton = Microsoft.UI.Xaml.Controls.ContentDialogButton.Close, XamlRoot = this.Content.XamlRoot }; var result = await dialog.ShowAsync(); if (result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary) { var clip = _clipLibrary.Clips.FirstOrDefault(c => c.Path == clipPath); if (clip != null) { _thumbnailService.DeleteThumbnail(clip); _clipLibrary.DeleteClip(clip); } } } private async void RenameClip_Click(object sender, RoutedEventArgs e) { var button = sender as Microsoft.UI.Xaml.Controls.Button; var clipPath = button?.Tag as string; if (clipPath == null) return; var clip = _clipLibrary.Clips.FirstOrDefault(c => c.Path == clipPath); if (clip == null) return; var input = new Microsoft.UI.Xaml.Controls.TextBox { Text = clip.Title, PlaceholderText = "Enter new name" }; var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog { Title = "Rename Clip", Content = input, PrimaryButtonText = "Rename", CloseButtonText = "Cancel", DefaultButton = Microsoft.UI.Xaml.Controls.ContentDialogButton.Primary, XamlRoot = this.Content.XamlRoot }; var result = await dialog.ShowAsync(); if (result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary) { var newName = input.Text.Trim(); if (string.IsNullOrEmpty(newName)) return; var dir = System.IO.Path.GetDirectoryName(clipPath)!; var newPath = System.IO.Path.Combine(dir, newName + ".mp4"); var newThumbPath = System.IO.Path.Combine(dir, newName + ".thumb.png"); try { System.IO.File.Move(clipPath, newPath); if (System.IO.File.Exists(clip.ThumbnailPath)) System.IO.File.Move(clip.ThumbnailPath, newThumbPath); clip.Path = newPath; clip.Title = newName; clip.ThumbnailPath = newThumbPath; ClipGrid.ItemsSource = null; ClipGrid.ItemsSource = _clipLibrary.Clips; } catch (Exception ex) { await ShowToastAsync($"❌ {ex.Message}"); } } } // --- TOAST --- private async System.Threading.Tasks.Task ShowToastAsync(string message) { var overlayWindow = new Microsoft.UI.Xaml.Window(); var textBlock = new Microsoft.UI.Xaml.Controls.TextBlock { Text = message, Foreground = new Microsoft.UI.Xaml.Media.SolidColorBrush( Windows.UI.Color.FromArgb(255, 232, 255, 71)), FontSize = 14, FontWeight = Microsoft.UI.Text.FontWeights.Bold, VerticalAlignment = Microsoft.UI.Xaml.VerticalAlignment.Center }; var border = new Microsoft.UI.Xaml.Controls.Border { Background = new Microsoft.UI.Xaml.Media.SolidColorBrush( Windows.UI.Color.FromArgb(230, 18, 18, 24)), CornerRadius = new Microsoft.UI.Xaml.CornerRadius(10), Padding = new Microsoft.UI.Xaml.Thickness(20, 12, 20, 12), Child = textBlock, RenderTransform = new Microsoft.UI.Xaml.Media.TranslateTransform { X = 40 }, Opacity = 0 }; overlayWindow.Content = border; var hwnd = WindowNative.GetWindowHandle(overlayWindow); var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd); var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId); int toastWidth = 220; int toastHeight = 48; int margin = 20; var displayArea = Microsoft.UI.Windowing.DisplayArea.Primary; int x = displayArea.WorkArea.Width - toastWidth - margin; int y = displayArea.WorkArea.Y + margin + 40; appWindow.MoveAndResize(new Windows.Graphics.RectInt32(x, y, toastWidth, toastHeight)); SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST); var overlayPresenter = Microsoft.UI.Windowing.OverlappedPresenter.Create(); overlayPresenter.IsAlwaysOnTop = true; overlayPresenter.IsResizable = false; overlayPresenter.IsMaximizable = false; overlayPresenter.IsMinimizable = false; overlayPresenter.SetBorderAndTitleBar(false, false); appWindow.SetPresenter(overlayPresenter); var titleBar = appWindow.TitleBar; titleBar.ExtendsContentIntoTitleBar = true; titleBar.ButtonBackgroundColor = Windows.UI.Color.FromArgb(0, 0, 0, 0); overlayWindow.Activate(); var slideIn = new DoubleAnimation { From = 40, To = 0, Duration = new Duration(TimeSpan.FromMilliseconds(250)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } }; var fadeIn = new DoubleAnimation { From = 0, To = 1, Duration = new Duration(TimeSpan.FromMilliseconds(200)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } }; var storyboardIn = new Storyboard(); Storyboard.SetTarget(slideIn, border); Storyboard.SetTargetProperty(slideIn, "(UIElement.RenderTransform).(TranslateTransform.X)"); Storyboard.SetTarget(fadeIn, border); Storyboard.SetTargetProperty(fadeIn, "Opacity"); storyboardIn.Children.Add(slideIn); storyboardIn.Children.Add(fadeIn); storyboardIn.Begin(); await System.Threading.Tasks.Task.Delay(1500); var slideOut = new DoubleAnimation { From = 0, To = 40, Duration = new Duration(TimeSpan.FromMilliseconds(200)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; var fadeOut = new DoubleAnimation { From = 1, To = 0, Duration = new Duration(TimeSpan.FromMilliseconds(200)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; var storyboardOut = new Storyboard(); Storyboard.SetTarget(slideOut, border); Storyboard.SetTargetProperty(slideOut, "(UIElement.RenderTransform).(TranslateTransform.X)"); Storyboard.SetTarget(fadeOut, border); Storyboard.SetTargetProperty(fadeOut, "Opacity"); storyboardOut.Children.Add(slideOut); storyboardOut.Children.Add(fadeOut); var tcs = new System.Threading.Tasks.TaskCompletionSource(); storyboardOut.Completed += (s, e) => tcs.SetResult(true); storyboardOut.Begin(); await tcs.Task; overlayWindow.Close(); } } }