Skip to content

Commit ddb9b3a

Browse files
committed
chore: screen capture tool and key legend
1 parent 87bff1a commit ddb9b3a

7 files changed

Lines changed: 394 additions & 226 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ All notable changes to GhostDraw will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
89
## v1.0.6
910

1011
### Added
12+
- **Screenshot Capture** - Capture your drawings as images
13+
- Press `Ctrl+S` to capture full screen with drawings (saved to Pictures\GhostDraw)
14+
- Key suppression prevents Windows from intercepting Ctrl+S during drawing mode
15+
- Optional: Copy to clipboard, open folder, play shutter sound (configurable in settings)
1116
- **Eraser Tool** - Remove drawing objects underneath the cursor
1217
- Press `E` to activate Eraser tool
1318
- Click and drag to erase drawings interactively
@@ -16,10 +21,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1621
- Works with both polylines (pen strokes) and lines (straight line tool)
1722
- Custom eraser cursor with visual feedback
1823
- **Improved Code Quality**
24+
- Added explicit type aliases to resolve WPF/WinForms namespace conflicts
25+
- Fixed `Point`, `Brush`, `Color`, `ColorConverter`, and `Brushes` type ambiguities
1926
- Enhanced tool interface consistency
2027

2128
### Fixed
29+
- Screenshot hotkey (`Ctrl+S`) now correctly detects Control key by tracking both left (VK_LCONTROL) and right (VK_RCONTROL) control keys instead of generic VK_CONTROL
30+
- Ambiguous reference errors caused by both WPF (`System.Windows`) and WinForms (`System.Drawing`) being enabled
2231
- LineTool's `OnDeactivated` method now properly resets state without calling non-existent method
32+
- Build errors related to namespace conflicts in drawing tool implementations
33+
34+
### Changed
35+
- Snipping tool (`S` key) now properly exits drawing mode to allow user interaction
36+
- User must manually reactivate drawing mode after using snipping tool (press hotkey)
2337

2438
## v1.0.5
2539

Src/GhostDraw/App.xaml.cs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ protected override void OnStartup(StartupEventArgs e)
7979
_keyboardHook.EraserToolPressed += OnEraserToolPressed;
8080
_keyboardHook.HelpPressed += OnHelpPressed;
8181
_keyboardHook.ScreenshotFullPressed += OnScreenshotFullPressed;
82-
_keyboardHook.ScreenshotSnipPressed += OnScreenshotSnipPressed;
8382
_keyboardHook.Start();
8483

8584
// Setup system tray icon
@@ -329,33 +328,27 @@ private void OnScreenshotFullPressed(object? sender, EventArgs e)
329328
{
330329
try
331330
{
331+
_logger?.LogInformation("====== OnScreenshotFullPressed CALLED ======");
332+
_logger?.LogInformation("DrawingManager null: {IsNull}", _drawingManager == null);
333+
_logger?.LogInformation("DrawingManager.IsDrawingMode: {IsDrawingMode}", _drawingManager?.IsDrawingMode);
334+
332335
// Capture full screenshot if drawing mode is active
333336
if (_drawingManager?.IsDrawingMode == true)
334337
{
335-
_logger?.LogInformation("Ctrl+S pressed - capturing full screenshot");
338+
_logger?.LogInformation("Ctrl+S pressed - capturing full screenshot (calling DrawingManager.CaptureFullScreenshot)");
336339
_drawingManager?.CaptureFullScreenshot();
340+
_logger?.LogInformation("DrawingManager.CaptureFullScreenshot call completed");
337341
}
338-
}
339-
catch (Exception ex)
340-
{
341-
_exceptionHandler?.HandleException(ex, "Screenshot full pressed handler");
342-
}
343-
}
344-
345-
private void OnScreenshotSnipPressed(object? sender, EventArgs e)
346-
{
347-
try
348-
{
349-
// Open snipping tool if drawing mode is active
350-
if (_drawingManager?.IsDrawingMode == true)
342+
else
351343
{
352-
_logger?.LogInformation("S pressed - opening snipping tool");
353-
_drawingManager?.OpenSnippingTool();
344+
_logger?.LogWarning("Screenshot ignored - drawing mode is NOT active");
354345
}
346+
_logger?.LogInformation("====== OnScreenshotFullPressed COMPLETED ======");
355347
}
356348
catch (Exception ex)
357349
{
358-
_exceptionHandler?.HandleException(ex, "Screenshot snip pressed handler");
350+
_logger?.LogError(ex, "Exception in OnScreenshotFullPressed");
351+
_exceptionHandler?.HandleException(ex, "Screenshot full pressed handler");
359352
}
360353
}
361354

Src/GhostDraw/Core/GlobalKeyboardHook.cs

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics;
1+
using System.Diagnostics;
22
using System.Runtime.InteropServices;
33
using Microsoft.Extensions.Logging;
44

@@ -17,8 +17,9 @@ public class GlobalKeyboardHook : IDisposable
1717
private const int VK_P = 0x50; // 80 - 'P' key for pen tool
1818
private const int VK_E = 0x45; // 69 - 'E' key for eraser tool
1919
private const int VK_F1 = 0x70; // 112 - 'F1' key for help
20-
private const int VK_S = 0x53; // 83 - 'S' key for screenshot (snipping tool)
21-
private const int VK_CONTROL = 0x11; // 17 - Control key
20+
private const int VK_S = 0x53; // 83 - 'S' key for screenshot (Ctrl+S only)
21+
private const int VK_LCONTROL = 0xA2; // 162 - Left Control key
22+
private const int VK_RCONTROL = 0xA3; // 163 - Right Control key
2223

2324
private readonly ILogger<GlobalKeyboardHook> _logger;
2425
private readonly LowLevelKeyboardProc _proc;
@@ -36,7 +37,6 @@ public class GlobalKeyboardHook : IDisposable
3637
public event EventHandler? EraserToolPressed;
3738
public event EventHandler? HelpPressed;
3839
public event EventHandler? ScreenshotFullPressed;
39-
public event EventHandler? ScreenshotSnipPressed;
4040

4141
// NEW: Raw key events for recorder
4242
public event EventHandler<KeyEventArgs>? KeyPressed;
@@ -47,6 +47,9 @@ public class GlobalKeyboardHook : IDisposable
4747
private Dictionary<int, bool> _keyStates = new();
4848
private bool _wasHotkeyActive = false;
4949
private volatile bool _isControlPressed = false;
50+
51+
// Drawing mode state - used to determine if we should suppress keys
52+
private volatile bool _isDrawingModeActive = false;
5053

5154
public GlobalKeyboardHook(ILogger<GlobalKeyboardHook> logger)
5255
{
@@ -58,7 +61,7 @@ public GlobalKeyboardHook(ILogger<GlobalKeyboardHook> logger)
5861
foreach (var vk in _hotkeyVKs)
5962
_keyStates[vk] = false;
6063
}
61-
64+
6265
/// <summary>
6366
/// Configures the hotkey combination
6467
/// </summary>
@@ -176,6 +179,8 @@ private nint SetHook(LowLevelKeyboardProc proc)
176179

177180
private nint HookCallback(int nCode, nint wParam, nint lParam)
178181
{
182+
bool shouldSuppressKey = false;
183+
179184
try
180185
{
181186
if (nCode >= 0)
@@ -189,16 +194,19 @@ private nint HookCallback(int nCode, nint wParam, nint lParam)
189194
else
190195
KeyReleased?.Invoke(this, new KeyEventArgs(vkCode));
191196

192-
// Track Control key state
193-
if (vkCode == VK_CONTROL)
197+
// Track Control key state (both left and right control keys)
198+
if (vkCode == VK_LCONTROL || vkCode == VK_RCONTROL)
194199
{
195200
_isControlPressed = isKeyDown;
201+
_logger.LogDebug("Control key ({Type}) {State}",
202+
vkCode == VK_LCONTROL ? "Left" : "Right",
203+
isKeyDown ? "PRESSED" : "RELEASED");
196204
}
197205

198206
// Check for ESC key press (emergency exit)
199207
if (vkCode == VK_ESCAPE && isKeyDown)
200208
{
201-
_logger.LogInformation("?? ESC pressed - emergency exit");
209+
_logger.LogInformation("🔴 ESC pressed - emergency exit");
202210
EscapePressed?.Invoke(this, EventArgs.Empty);
203211
}
204212

@@ -237,20 +245,30 @@ private nint HookCallback(int nCode, nint wParam, nint lParam)
237245
HelpPressed?.Invoke(this, EventArgs.Empty);
238246
}
239247

240-
// Check for S key press (screenshot)
241-
if (vkCode == VK_S && isKeyDown)
248+
// Check for Ctrl+S key press (full screenshot only - no snipping tool)
249+
if (vkCode == VK_S && isKeyDown && _isControlPressed)
242250
{
243-
// Use tracked Control key state to determine action
244-
if (_isControlPressed)
251+
_logger.LogInformation("====== CTRL+S DETECTED ======");
252+
_logger.LogInformation("Control key state: {IsControlPressed}", _isControlPressed);
253+
_logger.LogInformation("Drawing mode active: {IsDrawingModeActive}", _isDrawingModeActive);
254+
255+
_logger.LogInformation("Ctrl+S pressed - firing ScreenshotFullPressed event");
256+
ScreenshotFullPressed?.Invoke(this, EventArgs.Empty);
257+
_logger.LogInformation("ScreenshotFullPressed event fired, subscribers: {Count}",
258+
ScreenshotFullPressed?.GetInvocationList().Length ?? 0);
259+
260+
// Suppress Ctrl+S when drawing mode is active to prevent Windows Snipping Tool
261+
if (_isDrawingModeActive)
245262
{
246-
_logger.LogDebug("Ctrl+S pressed - full screenshot request");
247-
ScreenshotFullPressed?.Invoke(this, EventArgs.Empty);
263+
shouldSuppressKey = true;
264+
_logger.LogInformation("KEY WILL BE SUPPRESSED - Drawing mode is active");
248265
}
249266
else
250267
{
251-
_logger.LogDebug("S key pressed - snipping tool request");
252-
ScreenshotSnipPressed?.Invoke(this, EventArgs.Empty);
268+
_logger.LogInformation("KEY WILL NOT BE SUPPRESSED - Drawing mode is inactive");
253269
}
270+
271+
_logger.LogInformation("====== END CTRL+S HANDLING ======");
254272
}
255273

256274
// Track hotkey state
@@ -266,12 +284,12 @@ private nint HookCallback(int nCode, nint wParam, nint lParam)
266284
// Fire events on state changes
267285
if (allPressed && !_wasHotkeyActive)
268286
{
269-
_logger.LogInformation("?? HOTKEY PRESSED");
287+
_logger.LogInformation("🟢 HOTKEY PRESSED");
270288
HotkeyPressed?.Invoke(this, EventArgs.Empty);
271289
}
272290
else if (!allPressed && _wasHotkeyActive)
273291
{
274-
_logger.LogInformation("?? HOTKEY RELEASED");
292+
_logger.LogInformation("🟢 HOTKEY RELEASED");
275293
HotkeyReleased?.Invoke(this, EventArgs.Empty);
276294
}
277295

@@ -285,7 +303,15 @@ private nint HookCallback(int nCode, nint wParam, nint lParam)
285303
_logger.LogError(ex, "Exception in keyboard hook callback");
286304
}
287305

288-
// MUST ALWAYS call CallNextHookEx to allow other applications to process the hook
306+
// If we want to suppress the key, return 1 to block it from reaching other applications
307+
// Otherwise, call CallNextHookEx to allow other applications to process the hook
308+
if (shouldSuppressKey)
309+
{
310+
_logger.LogTrace("Key suppressed - not calling CallNextHookEx");
311+
return (nint)1;
312+
}
313+
314+
// MUST call CallNextHookEx for non-suppressed keys to allow other applications to process them
289315
return CallNextHookEx(_hookID, nCode, wParam, lParam);
290316
}
291317

@@ -307,6 +333,27 @@ public void Dispose()
307333
}
308334
}
309335

336+
/// <summary>
337+
/// Sets the drawing mode state. When active, certain keys (like Ctrl+S) will be suppressed
338+
/// to prevent Windows from intercepting them.
339+
/// </summary>
340+
public void SetDrawingModeActive(bool isActive)
341+
{
342+
var previousState = _isDrawingModeActive;
343+
_isDrawingModeActive = isActive;
344+
345+
if (previousState != isActive)
346+
{
347+
_logger.LogInformation("====== DRAWING MODE STATE CHANGED ======");
348+
_logger.LogInformation("Previous state: {PreviousState}, New state: {NewState}", previousState, isActive);
349+
_logger.LogInformation("Timestamp: {Timestamp}", DateTime.Now.ToString("HH:mm:ss.fff"));
350+
}
351+
else
352+
{
353+
_logger.LogDebug("Drawing mode state set to: {IsActive} (no change)", isActive);
354+
}
355+
}
356+
310357
// P/Invoke declarations
311358
private delegate nint LowLevelKeyboardProc(int nCode, nint wParam, nint lParam);
312359

0 commit comments

Comments
 (0)