Compare commits

11 Commits

Author SHA1 Message Date
e14598b92e Merge pull request 'hotkey-config' (#1) from hotkey-config into master
Reviewed-on: #1
2026-02-22 12:45:38 -05:00
86b4e10a83 Refactor MainWindow.xaml.cs to improve the visual feedback for active selections in navigation buttons, enhancing user experience. This update includes style adjustments to better indicate the currently selected page. 2026-02-22 12:42:09 -05:00
fa5bc81bd6 Implement visual style updates for navigation buttons in MainWindow.xaml.cs to indicate active selections. This change enhances user experience by providing clear feedback on the currently selected page. 2026-02-22 12:41:51 -05:00
fcd70b699d Refactor hotkey handling in MainWindow.xaml.cs to improve user experience by implementing a visual indicator for active hotkeys and enhancing the feedback mechanism during recording. Adjustments made to ensure better responsiveness and clarity in user interactions. 2026-02-22 12:36:49 -05:00
7f5d80ff68 Enhance hotkey recording functionality in MainWindow.xaml.cs by adding escape key support to cancel recording and improving error handling. Introduce a delayed registration mechanism to prevent immediate re-entry of hotkeys, ensuring stability during hotkey updates. 2026-02-22 12:33:55 -05:00
ae744f1077 Update MainWindow.xaml.cs to replace HotkeyRecorderText with HotkeyRecorderButton for displaying hotkey information, enhancing user interaction during hotkey recording. Adjustments made to ensure proper focus and event handling for the button. 2026-02-22 12:21:38 -05:00
9b73bdbb24 Refactor MainWindow.xaml to streamline the hotkey button definition by consolidating properties and removing unnecessary elements, improving code clarity. 2026-02-22 12:21:27 -05:00
4902802615 Update MainWindow.xaml to include a name and focus properties for the root grid, enhancing accessibility and usability. 2026-02-22 12:17:03 -05:00
6aa49c0b07 Add hotkey recording functionality in MainWindow. Users can now set custom hotkeys through a dedicated button, with real-time feedback on key presses and modifier keys. Updates to settings are reflected immediately. 2026-02-22 12:15:50 -05:00
52a2515e2d Enhance GlobalHotkeyService to allow for dynamic hotkey updates and improve user experience with customizable HotkeyModifiers and HotkeyVirtualKey settings. 2026-02-22 12:14:57 -05:00
742c1d70fe Refactor GlobalHotkeyService to support dynamic hotkey registration and update. Introduce HotkeyModifiers and HotkeyVirtualKey properties in SettingsService for user-defined hotkeys. 2026-02-22 12:14:41 -05:00
8 changed files with 487 additions and 104 deletions

View File

@@ -8,9 +8,44 @@
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
<!-- ClipForge custom palette (less Windows, more app identity) -->
<SolidColorBrush x:Key="ClipForgeSidebarBrush" Color="#0A0A0E"/>
<SolidColorBrush x:Key="ClipForgeMainBrush" Color="#0F0F14"/>
<SolidColorBrush x:Key="ClipForgeCardBrush" Color="#16161D"/>
<SolidColorBrush x:Key="ClipForgeCardBorderBrush" Color="#252532"/>
<SolidColorBrush x:Key="ClipForgeAccentBrush" Color="#E8FF47"/>
<SolidColorBrush x:Key="ClipForgeTextPrimaryBrush" Color="#F0F0F5"/>
<SolidColorBrush x:Key="ClipForgeTextSecondaryBrush" Color="#8E8E9A"/>
<SolidColorBrush x:Key="ClipForgeNavHoverBrush" Color="#1E1E26"/>
<!-- Sidebar nav: default (unselected) -->
<Style x:Key="ClipForgeNavButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource ClipForgeTextPrimaryBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="12,10"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
<!-- Sidebar nav: selected / accent -->
<Style x:Key="ClipForgeNavButtonSelectedStyle" TargetType="Button" BasedOn="{StaticResource ClipForgeNavButtonStyle}">
<Setter Property="Background" Value="{StaticResource ClipForgeAccentBrush}"/>
<Setter Property="Foreground" Value="#0A0A0E"/>
</Style>
<!-- Accent button (Save Settings, etc.) -->
<Style x:Key="ClipForgeAccentButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{StaticResource ClipForgeAccentBrush}"/>
<Setter Property="Foreground" Value="#0A0A0E"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="20,10"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Style>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
</Application> </Application>

View File

@@ -18,12 +18,12 @@
<PackageCertificateThumbprint>4A55954F2A73A9D620442C7DFBFC7C95A71D8D24</PackageCertificateThumbprint> <PackageCertificateThumbprint>4A55954F2A73A9D620442C7DFBFC7C95A71D8D24</PackageCertificateThumbprint>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm> <AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision> <AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
<AppxPackageDir>C:\Users\Blade\Desktop\Clipforge Packaged\V1\</AppxPackageDir> <AppxPackageDir>C:\Users\Blade\Desktop\Clipforge Packaged\V0.1\</AppxPackageDir>
<AppxSymbolPackageEnabled>False</AppxSymbolPackageEnabled> <AppxSymbolPackageEnabled>False</AppxSymbolPackageEnabled>
<GenerateTestArtifacts>True</GenerateTestArtifacts> <GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundle>Auto</AppxBundle> <AppxBundle>Auto</AppxBundle>
<AppxBundlePlatforms>x64</AppxBundlePlatforms> <AppxBundlePlatforms>x64</AppxBundlePlatforms>
<AppInstallerUri>J:\Projects\ClipForge\ClipForge\bin\x64\Release\net8.0-windows10.0.19041.0</AppInstallerUri> <AppInstallerUri>C:\Users\Blade\Desktop\Clipforge Packaged</AppInstallerUri>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks> <HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
@@ -6,39 +6,49 @@ namespace ClipForge
{ {
public class GlobalHotkeyService public class GlobalHotkeyService
{ {
// These two lines talk directly to Windows to register/unregister hotkeys
[DllImport("user32.dll")] [DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")] [DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id); private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
// This is the ID we'll use to identify our clip hotkey
private const int HOTKEY_CLIP = 1; private const int HOTKEY_CLIP = 1;
private const uint MOD_NOREPEAT = 0x4000; // don't fire on key repeat
// Alt key modifier
private const uint MOD_ALT = 0x0001;
// F9 key
private const uint VK_F9 = 0x78;
// This is the "event" that fires when the hotkey is pressed
public event Action? ClipRequested; public event Action? ClipRequested;
private IntPtr _hwnd; private IntPtr _hwnd;
private uint _modifiers;
private uint _vk;
public void Initialize(IntPtr hwnd) /// <summary>Register the clip hotkey. Use settings values (e.g. HotkeyModifiers, HotkeyVirtualKey).</summary>
public void Initialize(IntPtr hwnd, uint modifiers, uint vk)
{ {
_hwnd = hwnd; _hwnd = hwnd;
RegisterHotKey(_hwnd, HOTKEY_CLIP, MOD_ALT, VK_F9); _modifiers = modifiers;
_vk = vk;
TryRegister();
}
/// <summary>Update the hotkey at runtime (e.g. after user remaps). Unregisters old and registers new.</summary>
public bool UpdateHotkey(uint modifiers, uint vk)
{
UnregisterHotKey(_hwnd, HOTKEY_CLIP);
_modifiers = modifiers;
_vk = vk;
return TryRegister();
}
private bool TryRegister()
{
uint mod = _modifiers | MOD_NOREPEAT;
return RegisterHotKey(_hwnd, HOTKEY_CLIP, mod, _vk);
} }
public void ProcessHotkey(int id) public void ProcessHotkey(int id)
{ {
if (id == HOTKEY_CLIP) if (id == HOTKEY_CLIP)
{
ClipRequested?.Invoke(); ClipRequested?.Invoke();
}
} }
public void Cleanup() public void Cleanup()

183
ClipForge/HotkeyHelper.cs Normal file
View File

@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using Windows.System;
namespace ClipForge
{
/// <summary>Converts between hotkey modifier/vk and display strings.</summary>
public static class HotkeyHelper
{
public const uint MOD_ALT = 0x0001;
public const uint MOD_CONTROL = 0x0002;
public const uint MOD_SHIFT = 0x0004;
public const uint MOD_WIN = 0x0008;
public const uint MOD_NOREPEAT = 0x4000;
// Built in static ctor with TryAdd so duplicate VirtualKey enum values (e.g. Kana/Hangul = 0x15) don't throw.
private static readonly Dictionary<VirtualKey, string> KeyNames = new();
static HotkeyHelper()
{
TryAdd(VirtualKey.LeftButton, "LMB");
TryAdd(VirtualKey.RightButton, "RMB");
TryAdd(VirtualKey.Cancel, "Cancel");
TryAdd(VirtualKey.Back, "Backspace");
TryAdd(VirtualKey.Tab, "Tab");
TryAdd(VirtualKey.Clear, "Clear");
TryAdd(VirtualKey.Enter, "Enter");
TryAdd(VirtualKey.Shift, "Shift");
TryAdd(VirtualKey.Control, "Ctrl");
TryAdd(VirtualKey.Menu, "Alt");
TryAdd(VirtualKey.Pause, "Pause");
TryAdd(VirtualKey.CapitalLock, "Caps Lock");
TryAdd(VirtualKey.Kana, "Kana");
TryAdd(VirtualKey.Hangul, "Hangul"); // same value as Kana on some SDKs; TryAdd skips duplicate
TryAdd(VirtualKey.Junja, "Junja");
TryAdd(VirtualKey.Final, "Final");
TryAdd(VirtualKey.Hanja, "Hanja");
TryAdd(VirtualKey.Kanji, "Kanji");
TryAdd(VirtualKey.Escape, "Escape");
TryAdd(VirtualKey.Convert, "Convert");
TryAdd(VirtualKey.NonConvert, "NonConvert");
TryAdd(VirtualKey.Accept, "Accept");
TryAdd(VirtualKey.ModeChange, "ModeChange");
TryAdd(VirtualKey.Space, "Space");
TryAdd(VirtualKey.PageUp, "PgUp");
TryAdd(VirtualKey.PageDown, "PgDn");
TryAdd(VirtualKey.End, "End");
TryAdd(VirtualKey.Home, "Home");
TryAdd(VirtualKey.Left, "Left");
TryAdd(VirtualKey.Up, "Up");
TryAdd(VirtualKey.Right, "Right");
TryAdd(VirtualKey.Down, "Down");
TryAdd(VirtualKey.Select, "Select");
TryAdd(VirtualKey.Print, "Print");
TryAdd(VirtualKey.Execute, "Execute");
TryAdd(VirtualKey.Snapshot, "Print Screen");
TryAdd(VirtualKey.Insert, "Insert");
TryAdd(VirtualKey.Delete, "Delete");
TryAdd(VirtualKey.Help, "Help");
TryAdd(VirtualKey.Number0, "0");
TryAdd(VirtualKey.Number1, "1");
TryAdd(VirtualKey.Number2, "2");
TryAdd(VirtualKey.Number3, "3");
TryAdd(VirtualKey.Number4, "4");
TryAdd(VirtualKey.Number5, "5");
TryAdd(VirtualKey.Number6, "6");
TryAdd(VirtualKey.Number7, "7");
TryAdd(VirtualKey.Number8, "8");
TryAdd(VirtualKey.Number9, "9");
TryAdd(VirtualKey.A, "A");
TryAdd(VirtualKey.B, "B");
TryAdd(VirtualKey.C, "C");
TryAdd(VirtualKey.D, "D");
TryAdd(VirtualKey.E, "E");
TryAdd(VirtualKey.F, "F");
TryAdd(VirtualKey.G, "G");
TryAdd(VirtualKey.H, "H");
TryAdd(VirtualKey.I, "I");
TryAdd(VirtualKey.J, "J");
TryAdd(VirtualKey.K, "K");
TryAdd(VirtualKey.L, "L");
TryAdd(VirtualKey.M, "M");
TryAdd(VirtualKey.N, "N");
TryAdd(VirtualKey.O, "O");
TryAdd(VirtualKey.P, "P");
TryAdd(VirtualKey.Q, "Q");
TryAdd(VirtualKey.R, "R");
TryAdd(VirtualKey.S, "S");
TryAdd(VirtualKey.T, "T");
TryAdd(VirtualKey.U, "U");
TryAdd(VirtualKey.V, "V");
TryAdd(VirtualKey.W, "W");
TryAdd(VirtualKey.X, "X");
TryAdd(VirtualKey.Y, "Y");
TryAdd(VirtualKey.Z, "Z");
TryAdd(VirtualKey.LeftWindows, "Win");
TryAdd(VirtualKey.RightWindows, "Win");
TryAdd(VirtualKey.Application, "App");
TryAdd(VirtualKey.Sleep, "Sleep");
TryAdd(VirtualKey.NumberPad0, "Num 0");
TryAdd(VirtualKey.NumberPad1, "Num 1");
TryAdd(VirtualKey.NumberPad2, "Num 2");
TryAdd(VirtualKey.NumberPad3, "Num 3");
TryAdd(VirtualKey.NumberPad4, "Num 4");
TryAdd(VirtualKey.NumberPad5, "Num 5");
TryAdd(VirtualKey.NumberPad6, "Num 6");
TryAdd(VirtualKey.NumberPad7, "Num 7");
TryAdd(VirtualKey.NumberPad8, "Num 8");
TryAdd(VirtualKey.NumberPad9, "Num 9");
TryAdd(VirtualKey.Multiply, "Num *");
TryAdd(VirtualKey.Add, "Num +");
TryAdd(VirtualKey.Separator, "Num ,");
TryAdd(VirtualKey.Subtract, "Num -");
TryAdd(VirtualKey.Decimal, "Num .");
TryAdd(VirtualKey.Divide, "Num /");
TryAdd(VirtualKey.F1, "F1");
TryAdd(VirtualKey.F2, "F2");
TryAdd(VirtualKey.F3, "F3");
TryAdd(VirtualKey.F4, "F4");
TryAdd(VirtualKey.F5, "F5");
TryAdd(VirtualKey.F6, "F6");
TryAdd(VirtualKey.F7, "F7");
TryAdd(VirtualKey.F8, "F8");
TryAdd(VirtualKey.F9, "F9");
TryAdd(VirtualKey.F10, "F10");
TryAdd(VirtualKey.F11, "F11");
TryAdd(VirtualKey.F12, "F12");
TryAdd(VirtualKey.F13, "F13");
TryAdd(VirtualKey.F14, "F14");
TryAdd(VirtualKey.F15, "F15");
TryAdd(VirtualKey.F16, "F16");
TryAdd(VirtualKey.F17, "F17");
TryAdd(VirtualKey.F18, "F18");
TryAdd(VirtualKey.F19, "F19");
TryAdd(VirtualKey.F20, "F20");
TryAdd(VirtualKey.F21, "F21");
TryAdd(VirtualKey.F22, "F22");
TryAdd(VirtualKey.F23, "F23");
TryAdd(VirtualKey.F24, "F24");
TryAdd(VirtualKey.NumberKeyLock, "NumLock");
TryAdd(VirtualKey.Scroll, "Scroll Lock");
TryAdd(VirtualKey.LeftShift, "Shift");
TryAdd(VirtualKey.RightShift, "Shift");
TryAdd(VirtualKey.LeftControl, "Ctrl");
TryAdd(VirtualKey.RightControl, "Ctrl");
TryAdd(VirtualKey.LeftMenu, "Alt");
TryAdd(VirtualKey.RightMenu, "Alt");
}
private static void TryAdd(VirtualKey key, string name)
{
KeyNames.TryAdd(key, name);
}
/// <summary>Format modifier flags + virtual key as display string (e.g. "Alt + PgUp").</summary>
public static string ToDisplayString(uint modifiers, uint vk)
{
var parts = new List<string>();
if ((modifiers & MOD_WIN) != 0) parts.Add("Win");
if ((modifiers & MOD_CONTROL) != 0) parts.Add("Ctrl");
if ((modifiers & MOD_ALT) != 0) parts.Add("Alt");
if ((modifiers & MOD_SHIFT) != 0) parts.Add("Shift");
var keyName = GetKeyName((VirtualKey)vk);
if (!string.IsNullOrEmpty(keyName))
parts.Add(keyName);
return parts.Count > 0 ? string.Join(" + ", parts) : "None";
}
public static string GetKeyName(VirtualKey key)
{
return KeyNames.TryGetValue(key, out var name) ? name : key.ToString();
}
/// <summary>True if the key is typically used only as a modifier (don't use as sole key).</summary>
public static bool IsModifierKey(VirtualKey key)
{
return key == VirtualKey.LeftWindows || key == VirtualKey.RightWindows
|| key == VirtualKey.Control || key == VirtualKey.LeftControl || key == VirtualKey.RightControl
|| key == VirtualKey.Menu || key == VirtualKey.LeftMenu || key == VirtualKey.RightMenu
|| key == VirtualKey.Shift || key == VirtualKey.LeftShift || key == VirtualKey.RightShift;
}
}
}

View File

@@ -13,56 +13,71 @@
<MicaBackdrop /> <MicaBackdrop />
</Window.SystemBackdrop> </Window.SystemBackdrop>
<Grid> <Grid x:Name="RootGrid" Background="{StaticResource ClipForgeMainBrush}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/> <ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- SIDEBAR --> <!-- SIDEBAR -->
<Border Grid.Column="0" <Border Grid.Column="0"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" Background="{StaticResource ClipForgeSidebarBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="0,0,1,0"> BorderThickness="0,0,1,0">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="48"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- App title --> <!-- Logo + wordmark -->
<Border Grid.Row="0" Padding="16,0"> <Border Grid.Row="0" Padding="20,24,20,20" Margin="0,0,0,8">
<TextBlock Text="CLIPFORGE" <StackPanel Spacing="14" HorizontalAlignment="Left">
FontSize="13" <Border Width="44" Height="44"
FontWeight="Bold" Background="{StaticResource ClipForgeAccentBrush}"
CharacterSpacing="80" CornerRadius="10"
Foreground="#E8FF47" HorizontalAlignment="Left">
VerticalAlignment="Center"/> <TextBlock Text="&#xE8F1;"
FontFamily="Segoe MDL2 Assets"
FontSize="22"
Foreground="#0A0A0E"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<TextBlock Text="ClipForge"
FontSize="20"
FontWeight="Bold"
Foreground="{StaticResource ClipForgeAccentBrush}"
CharacterSpacing="60"/>
<TextBlock Text="Capture. Trim. Share."
FontSize="11"
Foreground="{StaticResource ClipForgeTextSecondaryBrush}"
Opacity="0.9"/>
</StackPanel>
</Border> </Border>
<!-- Nav items --> <!-- Nav items -->
<StackPanel Grid.Row="1" Spacing="2" Padding="8,8"> <StackPanel Grid.Row="1" Spacing="4" Padding="12,8" VerticalAlignment="Top">
<Button x:Name="NavClips" <Button x:Name="NavClips"
HorizontalAlignment="Stretch"
Click="NavClips_Click" Click="NavClips_Click"
Style="{StaticResource AccentButtonStyle}"> Style="{StaticResource ClipForgeNavButtonSelectedStyle}">
<StackPanel Orientation="Horizontal" Spacing="10"> <StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock Text="&#xE8F1;" <TextBlock Text="&#xE8F1;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="14"/> FontSize="16"/>
<TextBlock Text="Clips" <TextBlock Text="Clips"
FontSize="13" FontSize="13"
FontWeight="SemiBold"/> FontWeight="SemiBold"/>
</StackPanel> </StackPanel>
</Button> </Button>
<Button x:Name="NavSettings" <Button x:Name="NavSettings"
HorizontalAlignment="Stretch" Click="NavSettings_Click"
Click="NavSettings_Click"> Style="{StaticResource ClipForgeNavButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="10"> <StackPanel Orientation="Horizontal" Spacing="12">
<TextBlock Text="&#xE713;" <TextBlock Text="&#xE713;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="14"/> FontSize="16"/>
<TextBlock Text="Settings" <TextBlock Text="Settings"
FontSize="13" FontSize="13"
FontWeight="SemiBold"/> FontWeight="SemiBold"/>
@@ -73,10 +88,15 @@
<!-- Record button --> <!-- Record button -->
<Button x:Name="RecordButton" <Button x:Name="RecordButton"
Grid.Row="2" Grid.Row="2"
Margin="8" Margin="12,12,12,20"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Background="{StaticResource ClipForgeCardBrush}"
BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="12,10"
Click="RecordButton_Click"> Click="RecordButton_Click">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="10">
<Ellipse x:Name="RecordDot" <Ellipse x:Name="RecordDot"
Width="8" Height="8" Width="8" Height="8"
Fill="#FF4757"/> Fill="#FF4757"/>
@@ -84,6 +104,7 @@
Text="CAPTURING" Text="CAPTURING"
FontSize="11" FontSize="11"
FontWeight="Bold" FontWeight="Bold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
CharacterSpacing="40"/> CharacterSpacing="40"/>
</StackPanel> </StackPanel>
</Button> </Button>
@@ -91,7 +112,7 @@
</Border> </Border>
<!-- MAIN CONTENT --> <!-- MAIN CONTENT -->
<Grid Grid.Column="1"> <Grid Grid.Column="1" Background="{StaticResource ClipForgeMainBrush}">
<!-- CLIPS PAGE --> <!-- CLIPS PAGE -->
<Grid x:Name="ClipsPage" Visibility="Visible"> <Grid x:Name="ClipsPage" Visibility="Visible">
@@ -102,37 +123,44 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Header --> <!-- Header -->
<Border Grid.Row="0" Padding="24,20,24,0"> <Border Grid.Row="0" Padding="28,24,28,0">
<Grid> <Grid>
<StackPanel Orientation="Horizontal" Spacing="12" <StackPanel Orientation="Horizontal" Spacing="12"
VerticalAlignment="Bottom"> VerticalAlignment="Bottom">
<TextBlock Text="My Clips" <TextBlock Text="My Clips"
FontSize="24" FontSize="26"
FontWeight="Bold"/> FontWeight="Bold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"/>
<TextBlock x:Name="ClipCountText" <TextBlock x:Name="ClipCountText"
Text="0 clips" Text="0 clips"
FontSize="12" FontSize="13"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{StaticResource ClipForgeTextSecondaryBrush}"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Margin="0,0,0,3"/> Margin="0,0,0,4"/>
</StackPanel> </StackPanel>
<Button Content="Open Folder" <Button Content="Open Folder"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Background="{StaticResource ClipForgeCardBrush}"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,8"
Click="OpenFolder_Click"/> Click="OpenFolder_Click"/>
</Grid> </Grid>
</Border> </Border>
<!-- Toolbar --> <!-- Toolbar -->
<Border Grid.Row="1" Padding="24,12"> <Border Grid.Row="1" Padding="28,16">
<AutoSuggestBox x:Name="SearchBox" <AutoSuggestBox x:Name="SearchBox"
PlaceholderText="Search clips..." PlaceholderText="Search clips..."
Width="260" Width="280"
HorizontalAlignment="Left" HorizontalAlignment="Left"
TextChanged="SearchBox_TextChanged"/> TextChanged="SearchBox_TextChanged"/>
</Border> </Border>
<!-- Clip Grid --> <!-- Clip Grid -->
<ScrollViewer Grid.Row="2" Padding="24,0,24,24"> <ScrollViewer Grid.Row="2" Padding="28,0,28,28">
<ItemsControl x:Name="ClipGrid"> <ItemsControl x:Name="ClipGrid">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@@ -143,11 +171,11 @@
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:ClipFile"> <DataTemplate x:DataType="local:ClipFile">
<Border Width="220" <Border Width="220"
Margin="0,0,12,12" Margin="0,0,14,14"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" Background="{StaticResource ClipForgeCardBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8"> CornerRadius="10">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="124"/> <RowDefinition Height="124"/>
@@ -156,8 +184,8 @@
<!-- Thumbnail --> <!-- Thumbnail -->
<Border Grid.Row="0" <Border Grid.Row="0"
Background="#1a1a2e" Background="{StaticResource ClipForgeSidebarBrush}"
CornerRadius="8,8,0,0"> CornerRadius="10,10,0,0">
<Grid> <Grid>
<!-- Placeholder icon --> <!-- Placeholder icon -->
<TextBlock Text="&#xE786;" <TextBlock Text="&#xE786;"
@@ -198,19 +226,23 @@
<TextBlock Text="{x:Bind Title}" <TextBlock Text="{x:Bind Title}"
FontSize="12" FontSize="12"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
TextTrimming="CharacterEllipsis"/> TextTrimming="CharacterEllipsis"/>
<Grid> <Grid>
<TextBlock Text="{x:Bind CreatedAtDisplay}" <TextBlock Text="{x:Bind CreatedAtDisplay}"
FontSize="10" FontSize="10"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/> Foreground="{StaticResource ClipForgeTextSecondaryBrush}"/>
<TextBlock Text="{x:Bind FileSize}" <TextBlock Text="{x:Bind FileSize}"
FontSize="10" FontSize="10"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{StaticResource ClipForgeTextSecondaryBrush}"
HorizontalAlignment="Right"/> HorizontalAlignment="Right"/>
</Grid> </Grid>
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
<Button Tag="{x:Bind Path}" <Button Tag="{x:Bind Path}"
Click="TrimClip_Click" Click="TrimClip_Click"
Background="Transparent"
BorderThickness="0"
Foreground="{StaticResource ClipForgeTextSecondaryBrush}"
FontSize="11" Padding="8,4"> FontSize="11" Padding="8,4">
<TextBlock Text="&#xE8C6;" <TextBlock Text="&#xE8C6;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
@@ -218,6 +250,9 @@
</Button> </Button>
<Button Tag="{x:Bind Path}" <Button Tag="{x:Bind Path}"
Click="RenameClip_Click" Click="RenameClip_Click"
Background="Transparent"
BorderThickness="0"
Foreground="{StaticResource ClipForgeTextSecondaryBrush}"
FontSize="11" Padding="8,4"> FontSize="11" Padding="8,4">
<TextBlock Text="&#xE8AC;" <TextBlock Text="&#xE8AC;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
@@ -225,6 +260,9 @@
</Button> </Button>
<Button Tag="{x:Bind Path}" <Button Tag="{x:Bind Path}"
Click="DeleteClip_Click" Click="DeleteClip_Click"
Background="Transparent"
BorderThickness="0"
Foreground="{StaticResource ClipForgeTextSecondaryBrush}"
FontSize="11" Padding="8,4"> FontSize="11" Padding="8,4">
<TextBlock Text="&#xE74D;" <TextBlock Text="&#xE74D;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
@@ -243,33 +281,36 @@
<!-- SETTINGS PAGE --> <!-- SETTINGS PAGE -->
<Grid x:Name="SettingsPage" Visibility="Collapsed"> <Grid x:Name="SettingsPage" Visibility="Collapsed">
<ScrollViewer Padding="24"> <ScrollViewer Padding="28">
<StackPanel Spacing="24" MaxWidth="520" HorizontalAlignment="Left"> <StackPanel Spacing="24" MaxWidth="540" HorizontalAlignment="Left">
<TextBlock Text="Settings" <TextBlock Text="Settings"
FontSize="24" FontSize="26"
FontWeight="Bold"/> FontWeight="Bold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"/>
<!-- Clip Length --> <!-- Clip Length -->
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" <Border Background="{StaticResource ClipForgeCardBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8" CornerRadius="10"
Padding="20"> Padding="20">
<StackPanel Spacing="12"> <StackPanel Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="&#xE714;" <TextBlock Text="&#xE714;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="16" FontSize="16"
Foreground="{StaticResource ClipForgeAccentBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<TextBlock Text="Clip Length" <TextBlock Text="Clip Length"
FontSize="14" FontSize="14"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<TextBlock Text="How many seconds to save when you press the hotkey." <TextBlock Text="How many seconds to save when you press the hotkey."
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/> Foreground="{StaticResource ClipForgeTextSecondaryBrush}"/>
<Grid> <Grid>
<Slider x:Name="ClipLengthSlider" <Slider x:Name="ClipLengthSlider"
Minimum="10" Minimum="10"
@@ -281,7 +322,7 @@
Text="30 seconds" Text="30 seconds"
FontSize="12" FontSize="12"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="#E8FF47" Foreground="{StaticResource ClipForgeAccentBrush}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</Grid> </Grid>
@@ -289,25 +330,27 @@
</Border> </Border>
<!-- Video Quality --> <!-- Video Quality -->
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" <Border Background="{StaticResource ClipForgeCardBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8" CornerRadius="10"
Padding="20"> Padding="20">
<StackPanel Spacing="12"> <StackPanel Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="&#xE7F4;" <TextBlock Text="&#xE7F4;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="16" FontSize="16"
Foreground="{StaticResource ClipForgeAccentBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<TextBlock Text="Video Quality" <TextBlock Text="Video Quality"
FontSize="14" FontSize="14"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<TextBlock Text="Higher quality means larger file sizes." <TextBlock Text="Higher quality means larger file sizes."
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/> Foreground="{StaticResource ClipForgeTextSecondaryBrush}"/>
<Grid> <Grid>
<Slider x:Name="QualitySlider" <Slider x:Name="QualitySlider"
Minimum="10" Minimum="10"
@@ -319,7 +362,7 @@
Text="70%" Text="70%"
FontSize="12" FontSize="12"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="#E8FF47" Foreground="{StaticResource ClipForgeAccentBrush}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</Grid> </Grid>
@@ -327,25 +370,27 @@
</Border> </Border>
<!-- Framerate --> <!-- Framerate -->
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" <Border Background="{StaticResource ClipForgeCardBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8" CornerRadius="10"
Padding="20"> Padding="20">
<StackPanel Spacing="12"> <StackPanel Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="&#xE7F8;" <TextBlock Text="&#xE7F8;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="16" FontSize="16"
Foreground="{StaticResource ClipForgeAccentBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<TextBlock Text="Framerate" <TextBlock Text="Framerate"
FontSize="14" FontSize="14"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<TextBlock Text="Higher framerates are smoother but use more storage." <TextBlock Text="Higher framerates are smoother but use more storage."
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/> Foreground="{StaticResource ClipForgeTextSecondaryBrush}"/>
<ComboBox x:Name="FramerateCombo" <ComboBox x:Name="FramerateCombo"
SelectionChanged="FramerateCombo_SelectionChanged"> SelectionChanged="FramerateCombo_SelectionChanged">
<ComboBoxItem Content="30 FPS"/> <ComboBoxItem Content="30 FPS"/>
@@ -355,61 +400,66 @@
</Border> </Border>
<!-- Hotkey --> <!-- Hotkey -->
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" <Border Background="{StaticResource ClipForgeCardBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8" CornerRadius="10"
Padding="20"> Padding="20">
<StackPanel Spacing="12"> <StackPanel Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="&#xE92E;" <TextBlock Text="&#xE92E;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="16" FontSize="16"
Foreground="{StaticResource ClipForgeAccentBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<TextBlock Text="Clip Hotkey" <TextBlock Text="Clip Hotkey"
FontSize="14" FontSize="14"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<TextBlock Text="The key combination to save a clip." <TextBlock Text="Click the key below, then press your desired combination."
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/> Foreground="{StaticResource ClipForgeTextSecondaryBrush}"/>
<Border Background="#1a1a2e" <Button x:Name="HotkeyRecorderButton"
CornerRadius="6" HorizontalAlignment="Left"
Click="HotkeyRecorderButton_Click"
Background="{StaticResource ClipForgeSidebarBrush}"
BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,10" Padding="16,10"
HorizontalAlignment="Left"> MinWidth="140"
<TextBlock Text="Alt + F9" FontFamily="Consolas"
FontFamily="Consolas" FontSize="14"
FontSize="14" FontWeight="Bold"
FontWeight="Bold" Foreground="{StaticResource ClipForgeAccentBrush}"
Foreground="#E8FF47"/> Content="Alt + F9"/>
</Border>
<TextBlock Text="Hotkey remapping coming in a future update."
FontSize="11"
Foreground="{ThemeResource TextFillColorTertiaryBrush}"/>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Startup with Windows --> <!-- Startup with Windows -->
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" <Border Background="{StaticResource ClipForgeCardBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{StaticResource ClipForgeCardBorderBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8" CornerRadius="10"
Padding="20"> Padding="20">
<StackPanel Spacing="12"> <StackPanel Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="&#xE7E8;" <TextBlock Text="&#xE7E8;"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="16" FontSize="16"
Foreground="{StaticResource ClipForgeAccentBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<TextBlock Text="Startup" <TextBlock Text="Startup"
FontSize="14" FontSize="14"
FontWeight="SemiBold" FontWeight="SemiBold"
Foreground="{StaticResource ClipForgeTextPrimaryBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
<TextBlock Text="Launch ClipForge automatically when Windows starts." <TextBlock Text="Launch ClipForge automatically when Windows starts."
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/> Foreground="{StaticResource ClipForgeTextSecondaryBrush}"/>
<ToggleSwitch x:Name="StartupToggle" <ToggleSwitch x:Name="StartupToggle"
OnContent="Enabled" OnContent="Enabled"
OffContent="Disabled" OffContent="Disabled"
@@ -420,7 +470,7 @@
<!-- Save button --> <!-- Save button -->
<Button x:Name="SaveSettingsButton" <Button x:Name="SaveSettingsButton"
Content="Save Settings" Content="Save Settings"
Style="{StaticResource AccentButtonStyle}" Style="{StaticResource ClipForgeAccentButtonStyle}"
Click="SaveSettings_Click" Click="SaveSettings_Click"
HorizontalAlignment="Left"/> HorizontalAlignment="Left"/>

View File

@@ -1,10 +1,12 @@
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Windows.System;
using WinRT.Interop; using WinRT.Interop;
namespace ClipForge namespace ClipForge
@@ -16,6 +18,7 @@ namespace ClipForge
private ClipLibraryService _clipLibrary; private ClipLibraryService _clipLibrary;
private SettingsService _settingsService; private SettingsService _settingsService;
private ThumbnailService _thumbnailService; private ThumbnailService _thumbnailService;
private bool _isRecordingHotkey;
private delegate IntPtr WinProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); private delegate IntPtr WinProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private WinProc _newWndProc; private WinProc _newWndProc;
@@ -41,6 +44,13 @@ namespace ClipForge
private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, private static extern IntPtr SendMessage(IntPtr hWnd, uint msg,
IntPtr wParam, IntPtr lParam); 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 int GWLP_WNDPROC = -4;
private const uint WM_HOTKEY = 0x0312; private const uint WM_HOTKEY = 0x0312;
private const int GWL_EXSTYLE = -20; private const int GWL_EXSTYLE = -20;
@@ -78,7 +88,10 @@ namespace ClipForge
_hotkeyService = new GlobalHotkeyService(); _hotkeyService = new GlobalHotkeyService();
_hotkeyService.ClipRequested += OnClipRequested; _hotkeyService.ClipRequested += OnClipRequested;
_hotkeyService.Initialize(hwnd); 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; ClipGrid.ItemsSource = _clipLibrary.Clips;
UpdateClipCount(); UpdateClipCount();
@@ -169,6 +182,8 @@ namespace ClipForge
QualityLabel.Text = $"{s.VideoQuality}%"; QualityLabel.Text = $"{s.VideoQuality}%";
FramerateCombo.SelectedIndex = s.Framerate == 30 ? 0 : 1; FramerateCombo.SelectedIndex = s.Framerate == 30 ? 0 : 1;
StartupToggle.IsOn = IsStartupEnabled(); StartupToggle.IsOn = IsStartupEnabled();
if (HotkeyRecorderButton != null)
HotkeyRecorderButton.Content = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey);
} }
// --- STARTUP WITH WINDOWS --- // --- STARTUP WITH WINDOWS ---
@@ -235,17 +250,104 @@ namespace ClipForge
_ = ShowToastAsync("✅ Settings saved!"); _ = 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 --- // --- NAV ---
private void NavClips_Click(object sender, RoutedEventArgs e) private void NavClips_Click(object sender, RoutedEventArgs e)
{ {
ClipsPage.Visibility = Visibility.Visible; ClipsPage.Visibility = Visibility.Visible;
SettingsPage.Visibility = Visibility.Collapsed; SettingsPage.Visibility = Visibility.Collapsed;
NavClips.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonSelectedStyle"];
NavSettings.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonStyle"];
} }
private void NavSettings_Click(object sender, RoutedEventArgs e) private void NavSettings_Click(object sender, RoutedEventArgs e)
{ {
ClipsPage.Visibility = Visibility.Collapsed; ClipsPage.Visibility = Visibility.Collapsed;
SettingsPage.Visibility = Visibility.Visible; SettingsPage.Visibility = Visibility.Visible;
NavSettings.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonSelectedStyle"];
NavClips.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonStyle"];
} }
private void RecordButton_Click(object sender, RoutedEventArgs e) private void RecordButton_Click(object sender, RoutedEventArgs e)

View File

@@ -11,7 +11,7 @@
<Identity <Identity
Name="735fb287-32b4-4217-b84a-302365dc5e23" Name="735fb287-32b4-4217-b84a-302365dc5e23"
Publisher="CN=SCOPEDD" Publisher="CN=SCOPEDD"
Version="1.0.0.0" /> Version="0.1.0.0" />
<mp:PhoneIdentity PhoneProductId="735fb287-32b4-4217-b84a-302365dc5e23" PhonePublisherId="00000000-0000-0000-0000-000000000000"/> <mp:PhoneIdentity PhoneProductId="735fb287-32b4-4217-b84a-302365dc5e23" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
@@ -9,7 +9,10 @@ namespace ClipForge
public int ClipLengthSeconds { get; set; } = 30; public int ClipLengthSeconds { get; set; } = 30;
public int VideoQuality { get; set; } = 70; public int VideoQuality { get; set; } = 70;
public int Framerate { get; set; } = 60; public int Framerate { get; set; } = 60;
public string HotkeyDisplay { get; set; } = "Alt + F9"; /// <summary>Windows modifier flags: Alt=1, Ctrl=2, Shift=4, Win=8.</summary>
public int HotkeyModifiers { get; set; } = 1; // MOD_ALT
/// <summary>Windows virtual key code (e.g. VK_F9=0x78, VK_PRIOR=0x21 for PgUp).</summary>
public int HotkeyVirtualKey { get; set; } = 0x78; // VK_F9
} }
public class SettingsService public class SettingsService