feat: made prototype in c#

This commit is contained in:
Guillaume Langlois 2025-10-10 18:26:49 -04:00
commit f727066c06
16 changed files with 926 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@ -0,0 +1,45 @@
# Build results
bin/
obj/
out/
[Bb]uild/
# User-specific files
*.user
*.suo
*.userosscache
*.sln.docstates
# Logs
*.log
# OS-specific files
.DS_Store
Thumbs.db
# Dotnet specific
*.dbmdl
*.jfm
*.pdb
*.mdb
# NuGet
*.nupkg
*.snupkg
.nuget/
packages/
project.lock.json
project.fragment.lock.json
# Rider / JetBrains
.idea/
# Visual Studio (if someone opens in full VS)
*.VC.db
*.VC.opendb
# Others
*.swp
*.bak
*.tmp

21
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Conjure Arcade Overlay",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/bin/Debug/net9.0-windows/Conjure.Arcade.Overlay.dll",
"args": ["${workspaceFolder}/Resources/placeholder_logo.png"],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole",
"preLaunchTask": "build"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
},
]
}

54
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,54 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Conjure.Arcade.Overlay.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Build Conjure.Arcade.Overlay (no dependencies)",
"type": "shell",
"command": "dotnet build Conjure.Arcade.Overlay.csproj --no-dependencies",
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Clean all obj", // A descriptive name for your task
"type": "shell",
"command": "${workspaceFolder}/cleaning_bin_obj.bat", // Replace with the actual path
"group": {
"kind": "build", // Optional: Assign to a task group (e.g., build, test)
"isDefault": true // Optional: Make it the default for the group
},
"presentation": {
"reveal": "always", // Show the terminal when the task runs
"panel": "new" // Open a new terminal panel for the task
},
"problemMatcher": [] // Configure problem matching if needed
},
{
"label": "Clean and Rebuild",
"dependsOrder": "sequence",
"dependsOn": [
"Clean all bin and obj",
"Build Conjure.Arcade.Overlay (no dependencies)"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<!-- Assembly info generation -->
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<Product>Conjure Arcade Overlay</Product>
<AssemblyName>Conjure.Arcade.Overlay</AssemblyName>
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<Content Include="Resources\placeholder_logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Conjure.Arcade.Overlay", "Conjure.Arcade.Overlay.csproj", "{89927838-91A3-5909-5048-DCD37BEFC139}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{89927838-91A3-5909-5048-DCD37BEFC139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89927838-91A3-5909-5048-DCD37BEFC139}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89927838-91A3-5909-5048-DCD37BEFC139}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89927838-91A3-5909-5048-DCD37BEFC139}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1AA29E9A-935E-4FE5-8E85-4D386D166F1C}
EndGlobalSection
EndGlobal

126
MainOverlay.cs Normal file
View File

@ -0,0 +1,126 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Interop;
namespace Conjure.Arcade.Overlay
{
public class MainOverlay : OverlayWindow
{
private readonly TickEngine _tickEngine;
private Process? _targetProcess;
private const string TARGET_PROCESS = "notepad";
private readonly TextBlock _overlayText;
private bool _isPaused;
private HotkeyManager? _hotkeyManager;
private bool _isVisible;
public MainOverlay()
{
_tickEngine = new TickEngine(Update, 60);
Loaded += OnLoaded;
Closed += OnClosed;
// Set semi-transparent background
Background = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0));
// Create and configure the text
_overlayText = new TextBlock
{
Text = "Overlay Active",
Foreground = Brushes.White,
FontSize = 24,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
// Add text to the window
Content = _overlayText;
// Add key event handler
KeyDown += OnKeyDown;
// Update text block
UpdateOverlayText();
// Start hidden
Visibility = Visibility.Hidden;
_isVisible = false;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var windowHandle = new WindowInteropHelper(this).Handle;
_hotkeyManager = new HotkeyManager(windowHandle, ToggleOverlay);
_hotkeyManager.Register();
_targetProcess = WindowUtils.FindProcess(TARGET_PROCESS);
if (_targetProcess == null)
{
MessageBox.Show($"Could not find process: {TARGET_PROCESS}");
Close();
return;
}
_tickEngine.Start();
}
private void OnClosed(object? sender, EventArgs e)
{
_hotkeyManager?.Unregister();
_tickEngine.Stop();
}
private void Update()
{
if (_targetProcess?.HasExited ?? true)
{
Dispatcher.Invoke(Close);
return;
}
var handle = _targetProcess.MainWindowHandle;
if (WindowUtils.GetWindowBounds(handle, out var rect))
{
Dispatcher.Invoke(() =>
{
Left = rect.Left;
Top = rect.Top;
Width = rect.Right - rect.Left;
Height = rect.Bottom - rect.Top;
});
}
}
private void ToggleOverlay()
{
_isVisible = !_isVisible;
_isPaused = _isVisible; // Pause when visible, unpause when hidden
Visibility = _isVisible ? Visibility.Visible : Visibility.Hidden;
if (_targetProcess != null)
{
if (_isPaused)
WindowUtils.SuspendProcess(_targetProcess);
else
WindowUtils.ResumeProcess(_targetProcess);
UpdateOverlayText();
}
}
// Remove the space key handler since we're using the hotkey now
private void OnKeyDown(object sender, KeyEventArgs e)
{
// Empty or remove this method
}
private void UpdateOverlayText()
{
_overlayText.Text = _isPaused ? "PAUSED (Ctrl+Alt+O to Resume)" : "Running";
}
}
}

50
Overlay/HotkeyManager.cs Normal file
View File

@ -0,0 +1,50 @@
using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;
namespace Conjure.Arcade.Overlay
{
public class HotkeyManager
{
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private const int HOTKEY_ID = 9000;
private const uint MOD_ALT = 0x0001;
private const uint MOD_CONTROL = 0x0002;
private readonly IntPtr _windowHandle;
private readonly Action _callback;
public HotkeyManager(IntPtr windowHandle, Action callback)
{
_windowHandle = windowHandle;
_callback = callback;
ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreprocessMessage;
}
public void Register()
{
// Register Ctrl+Alt+O as the hotkey
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL | MOD_ALT, 0x4F); // 0x4F is 'O'
}
public void Unregister()
{
UnregisterHotKey(_windowHandle, HOTKEY_ID);
ComponentDispatcher.ThreadPreprocessMessage -= OnThreadPreprocessMessage;
}
private void OnThreadPreprocessMessage(ref MSG msg, ref bool handled)
{
if (msg.message == 0x0312 && msg.wParam.ToInt32() == HOTKEY_ID)
{
_callback();
handled = true;
}
}
}
}

55
Overlay/OverlayWindow.cs Normal file
View File

@ -0,0 +1,55 @@
using System;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace Conjure.Arcade.Overlay
{
public class OverlayWindow : Window
{
[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);
private const int GWL_EXSTYLE = -20;
private const int WS_EX_TRANSPARENT = 0x20;
private const int WS_EX_LAYERED = 0x80000;
private bool _isClickThrough = true;
public OverlayWindow()
{
// Make window transparent and click-through
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = null;
Topmost = true;
ShowInTaskbar = false;
// Handle window creation
SourceInitialized += OnSourceInitialized;
}
private void OnSourceInitialized(object? sender, EventArgs e)
{
var hwnd = new WindowInteropHelper(this).Handle;
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT | WS_EX_LAYERED);
}
public void SetClickThrough(bool enabled)
{
var hwnd = new WindowInteropHelper(this).Handle;
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if (enabled)
extendedStyle |= WS_EX_TRANSPARENT;
else
extendedStyle &= ~WS_EX_TRANSPARENT;
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle);
_isClickThrough = enabled;
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
namespace Conjure.Arcade.Overlay.Settings
{
public class SerializableSettings<T> : IDisposable where T : new()
{
private T? _current; // Make nullable
private bool _currentExists;
private readonly string _settingsPath;
public SerializableSettings(string settingsName)
{
_settingsPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"Settings",
$"{settingsName}.json"
);
}
public T Current
{
get
{
if (!_currentExists)
{
_current = new T();
_currentExists = true;
}
return _current!; // We know it's not null here
}
private set
{
_current = value;
_currentExists = true;
}
}
public void Save()
{
var directory = Path.GetDirectoryName(_settingsPath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory!);
File.WriteAllText(_settingsPath,
System.Text.Json.JsonSerializer.Serialize(Current,
new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
}
public void Load()
{
if (!File.Exists(_settingsPath))
{
Save();
return;
}
var json = File.ReadAllText(_settingsPath);
Current = System.Text.Json.JsonSerializer.Deserialize<T>(json) ?? new T();
}
private bool _disposed;
public event EventHandler? OnDispose;
public void Dispose()
{
if (!_disposed)
{
OnDispose?.Invoke(this, EventArgs.Empty);
_disposed = true;
}
}
}
}

62
Overlay/TickEngine.cs Normal file
View File

@ -0,0 +1,62 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Conjure.Arcade.Overlay
{
public class TickEngine
{
private readonly Action _tickAction;
private readonly int _targetFps;
private readonly CancellationTokenSource _cancellationSource;
private bool _isRunning;
public TickEngine(Action tickAction, int targetFps = 60)
{
_tickAction = tickAction;
_targetFps = targetFps;
_cancellationSource = new CancellationTokenSource();
}
public void Start()
{
if (_isRunning) return;
_isRunning = true;
Task.Run(RunTickLoop, _cancellationSource.Token);
}
public void Stop()
{
_isRunning = false;
_cancellationSource.Cancel();
}
private async Task RunTickLoop()
{
var stopwatch = new Stopwatch();
var targetFrameTime = TimeSpan.FromSeconds(1.0 / _targetFps);
while (_isRunning && !_cancellationSource.Token.IsCancellationRequested)
{
stopwatch.Restart();
try
{
_tickAction();
}
catch (Exception ex)
{
Debug.WriteLine($"Error in tick loop: {ex}");
}
var elapsed = stopwatch.Elapsed;
if (elapsed < targetFrameTime)
{
await Task.Delay(targetFrameTime - elapsed);
}
}
}
}
}

89
Overlay/WindowUtils.cs Normal file
View File

@ -0,0 +1,89 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Conjure.Arcade.Overlay
{
public static class WindowUtils
{
[DllImport("kernel32.dll")]
private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll")]
private static extern uint SuspendThread(IntPtr hThread);
[DllImport("kernel32.dll")]
private static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
[Flags]
private enum ThreadAccess : int
{
SUSPEND_RESUME = 0x0002
}
[DllImport("user32.dll")]
private static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public static bool GetWindowBounds(IntPtr handle, out Rect rect)
{
rect = new Rect();
return GetWindowRect(handle, ref rect) != IntPtr.Zero;
}
public static Process? FindProcess(string processName)
{
var processes = Process.GetProcessesByName(processName);
return processes.Length > 0 ? processes[0] : null;
}
public static void SuspendProcess(Process process)
{
foreach (ProcessThread thread in process.Threads)
{
var threadHandle = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
if (threadHandle != IntPtr.Zero)
{
try
{
SuspendThread(threadHandle);
}
finally
{
CloseHandle(threadHandle);
}
}
}
}
public static void ResumeProcess(Process process)
{
foreach (ProcessThread thread in process.Threads)
{
var threadHandle = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
if (threadHandle != IntPtr.Zero)
{
try
{
ResumeThread(threadHandle);
}
finally
{
CloseHandle(threadHandle);
}
}
}
}
}
}

23
Program.cs Normal file
View File

@ -0,0 +1,23 @@
using System;
using System.Windows;
using Conjure.Arcade.Overlay.Views;
namespace Conjure.Arcade.Overlay
{
/// <summary>
/// </summary>
public class Program
{
/// <summary>
/// Defines the entry point of the application.
/// </summary>
[STAThread]
public static void Main(string[] args)
{
var logoPath = args.Length > 0 ? args[0] : "default_logo.png";
var application = new Application();
var mainWindow = new MainOverlayWindow(logoPath);
application.Run(mainWindow);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -0,0 +1,114 @@
<local:OverlayWindow x:Class="Conjure.Arcade.Overlay.Views.MainOverlayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Conjure.Arcade.Overlay"
Title="Overlay" Height="450" Width="800">
<Window.Resources>
<!-- Pixel art glow effect -->
<DropShadowEffect x:Key="GlowEffect"
Color="#4d97f8"
BlurRadius="15"
ShadowDepth="0"/>
<!-- Game UI button style -->
<Style x:Key="GameButton" TargetType="Button">
<Setter Property="Background" Value="#4d97f8"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Width" Value="180"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Margin" Value="15"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="8"
BorderThickness="0">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<!-- Background gradient matching Vue app -->
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<!-- Almost transparent at top -->
<GradientStop Color="#104d97f8" Offset="0"/>
<!-- Semi-transparent in middle -->
<GradientStop Color="#804d97f8" Offset="0.5"/>
<!-- More opaque at bottom -->
<GradientStop Color="#FF100a7d" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- Main content layout with defined rows -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Time -->
<RowDefinition Height="*"/> <!-- Main content -->
<RowDefinition Height="Auto"/> <!-- Buttons -->
</Grid.RowDefinitions>
<!-- Top bar with time -->
<TextBlock x:Name="TimeText"
Grid.Row="0"
Text="00:00:00"
Foreground="White"
FontFamily="Segoe UI"
FontSize="24"
FontWeight="SemiBold"
TextAlignment="Center"
Margin="0,20,0,0"/>
<!-- Center content -->
<StackPanel Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="20">
<!-- Game Logo -->
<Image x:Name="GameLogo"
Width="400"
Height="150"
Stretch="Uniform"
Margin="0,0,0,40"/>
<!-- Status text -->
<TextBlock x:Name="StatusText"
Text="PAUSED"
Foreground="White"
FontFamily="Segoe UI"
FontSize="36"
FontWeight="SemiBold"
TextAlignment="Center"
Effect="{StaticResource GlowEffect}"/>
</StackPanel>
<!-- Action Buttons at bottom -->
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0,0,0,40">
<Button Style="{StaticResource GameButton}"
x:Name="ResumeButton"
Content="Resume Game"
Click="OnResumeClick"/>
<Button Style="{StaticResource GameButton}"
x:Name="QuitButton"
Content="Exit"
Background="#FF4444"
Click="OnQuitClick"/>
</StackPanel>
</Grid>
</Grid>
</local:OverlayWindow>

View File

@ -0,0 +1,161 @@
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using System.Windows.Media.Imaging; // Add this for BitmapImage
namespace Conjure.Arcade.Overlay.Views
{
public partial class MainOverlayWindow : OverlayWindow
{
private readonly TickEngine _tickEngine;
private Process? _targetProcess;
private const string TARGET_PROCESS = "notepad";
private HotkeyManager? _hotkeyManager;
private bool _isPaused;
private bool _isVisible;
private readonly DispatcherTimer _timer;
private readonly string _logoPath;
public MainOverlayWindow(string logoPath)
{
InitializeComponent();
_logoPath = logoPath;
_tickEngine = new TickEngine(Update, 60);
Loaded += OnLoaded;
Closed += OnClosed;
// Start hidden
Visibility = Visibility.Hidden;
_isVisible = false;
// Setup timer for clock updates
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += Timer_Tick;
_timer.Start();
// Initial time update
UpdateTime();
// Load logo
LoadLogo();
}
private void LoadLogo()
{
try
{
var image = new BitmapImage(new Uri(_logoPath, UriKind.Absolute));
GameLogo.Source = image;
}
catch (Exception ex)
{
MessageBox.Show($"Failed to load game logo: {ex.Message}");
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var windowHandle = new WindowInteropHelper(this).Handle;
_hotkeyManager = new HotkeyManager(windowHandle, ToggleOverlay);
_hotkeyManager.Register();
_targetProcess = WindowUtils.FindProcess(TARGET_PROCESS);
if (_targetProcess == null)
{
MessageBox.Show($"Could not find process: {TARGET_PROCESS}");
Close();
return;
}
_tickEngine.Start();
}
private void OnClosed(object? sender, EventArgs e)
{
_hotkeyManager?.Unregister();
_tickEngine.Stop();
}
private void Update()
{
if (_targetProcess?.HasExited ?? true)
{
Dispatcher.Invoke(Close);
return;
}
var handle = _targetProcess.MainWindowHandle;
if (WindowUtils.GetWindowBounds(handle, out var rect))
{
Dispatcher.Invoke(() =>
{
Left = rect.Left;
Top = rect.Top;
Width = rect.Right - rect.Left;
Height = rect.Bottom - rect.Top;
});
}
}
private void ToggleOverlay()
{
_isVisible = !_isVisible;
_isPaused = _isVisible;
Visibility = _isVisible ? Visibility.Visible : Visibility.Hidden;
SetClickThrough(!_isVisible); // Make window interactive when visible
if (_targetProcess != null)
{
if (_isPaused)
WindowUtils.SuspendProcess(_targetProcess);
else
WindowUtils.ResumeProcess(_targetProcess);
UpdateOverlayText();
}
}
private void UpdateOverlayText()
{
StatusText.Text = _isPaused ? "PAUSED" : "Running";
}
private void Timer_Tick(object? sender, EventArgs e)
{
UpdateTime();
}
private void UpdateTime()
{
TimeText.Text = DateTime.Now.ToString("HH:mm:ss");
}
private void OnResumeClick(object sender, RoutedEventArgs e)
{
if (_targetProcess != null && _isPaused)
{
_isPaused = false;
_isVisible = false;
WindowUtils.ResumeProcess(_targetProcess);
Visibility = Visibility.Hidden;
SetClickThrough(true);
UpdateOverlayText();
}
}
private void OnQuitClick(object sender, RoutedEventArgs e)
{
Close();
}
protected override void OnClosed(EventArgs e)
{
_timer.Stop();
base.OnClosed(e);
}
}
}

2
cleaning_bin_obj.bat Normal file
View File

@ -0,0 +1,2 @@
@echo off
for /d /r . %%d in (bin,obj) do @if exist "%%d" rd /s/q "%%d"