diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0057f38
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,79 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = crlf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{csproj,wixproj,props,targets,xml,wxs,wxl,config}]
+indent_size = 2
+
+[*.{json,yaml,yml}]
+indent_size = 2
+
+[*.{ps1,psm1,psd1}]
+indent_size = 4
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.sln]
+indent_style = tab
+
+# C# formatting rules
+[*.cs]
+# Use 'var' when type is apparent
+csharp_style_var_for_built_in_types = false:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = false:suggestion
+
+# Prefer expression-bodied members for simple members
+csharp_style_expression_bodied_methods = when_on_single_line:suggestion
+csharp_style_expression_bodied_constructors = false:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+
+# Prefer pattern matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+
+# Null-checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Namespace preferences
+csharp_style_namespace_declarations = file_scoped:suggestion
+
+# Organize usings
+dotnet_sort_system_directives_first = true
+dotnet_separate_import_directive_groups = false
+
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+
+# Indentation preferences
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+# Naming conventions
+dotnet_naming_rule.private_fields_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_with_underscore
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private
+
+dotnet_naming_style.camel_case_with_underscore.required_prefix = _
+dotnet_naming_style.camel_case_with_underscore.capitalization = camel_case
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.csv b/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.csv
deleted file mode 100644
index dd55aef..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.csv
+++ /dev/null
@@ -1 +0,0 @@
-Issue ID,Description,State,Severity,Story Points,Project Path,Location Kind,Path,Line,Column,Incident ID,Help Link,Assembly Name,Assembly Version,Assembly Public Key,Snippet
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.json b/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.json
deleted file mode 100644
index fb4478c..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "settings": {
- "components": {
- "code": true,
- "binaries": false
- },
- "targetId": "net8.0",
- "targetDisplayName": ".NETCoreApp,Version=v8.0"
- },
- "analysisStartTime": "2026-03-15T08:29:31.7949075Z",
- "analysisEndTime": "2026-03-15T08:29:31.8399957Z",
- "privacyModeHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2270980",
- "stats": {
- "summary": {
- "projects": 1,
- "issues": 0,
- "incidents": 0,
- "effort": 0
- },
- "charts": {
- "severity": {
- "Mandatory": 0,
- "Optional": 0,
- "Potential": 0,
- "Information": 0
- },
- "category": {}
- }
- },
- "projects": [
- {
- "path": "HintOverlay.csproj",
- "startingProject": true,
- "issues": 0,
- "storyPoints": 0,
- "properties": {
- "appName": "HintOverlay",
- "projectKind": "WinForms",
- "frameworks": [
- "net8.0-windows"
- ],
- "languages": [
- "C#"
- ],
- "tools": [
- "MSBuild"
- ],
- "isSdkStyle": true,
- "numberOfFiles": 39,
- "numberOfCodeFiles": 38,
- "linesTotal": 3551,
- "linesOfCode": 3431,
- "totalApiScanned": 0,
- "minLinesOfCodeToChange": 0,
- "maxLinesOfCodeToChange": 0
- },
- "ruleInstances": [],
- "features": []
- }
- ],
- "rules": {}
-}
\ No newline at end of file
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.md b/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.md
deleted file mode 100644
index e9930bf..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/assessment.md
+++ /dev/null
@@ -1,133 +0,0 @@
-# Projects and dependencies analysis
-
-This document provides a comprehensive overview of the projects and their dependencies in the context of upgrading to .NETCoreApp,Version=v8.0.
-
-## Table of Contents
-
-- [Executive Summary](#executive-Summary)
- - [Highlevel Metrics](#highlevel-metrics)
- - [Projects Compatibility](#projects-compatibility)
- - [Package Compatibility](#package-compatibility)
- - [API Compatibility](#api-compatibility)
-- [Aggregate NuGet packages details](#aggregate-nuget-packages-details)
-- [Top API Migration Challenges](#top-api-migration-challenges)
- - [Technologies and Features](#technologies-and-features)
- - [Most Frequent API Issues](#most-frequent-api-issues)
-- [Projects Relationship Graph](#projects-relationship-graph)
-- [Project Details](#project-details)
-
- - [Windows-Hinting.csproj](#Windows-Hintingcsproj)
-
-
-## Executive Summary
-
-### Highlevel Metrics
-
-| Metric | Count | Status |
-| :--- | :---: | :--- |
-| Total Projects | 1 | 0 require upgrade |
-| Total NuGet Packages | 2 | All compatible |
-| Total Code Files | 38 | |
-| Total Code Files with Incidents | 0 | |
-| Total Lines of Code | 3431 | |
-| Total Number of Issues | 0 | |
-| Estimated LOC to modify | 0+ | at least 0.0% of codebase |
-
-### Projects Compatibility
-
-| Project | Target Framework | Difficulty | Package Issues | API Issues | Est. LOC Impact | Description |
-| :--- | :---: | :---: | :---: | :---: | :---: | :--- |
-| [Windows-Hinting.csproj](#Windows-Hintingcsproj) | net8.0-windows | β
None | 0 | 0 | | WinForms, Sdk Style = True |
-
-### Package Compatibility
-
-| Status | Count | Percentage |
-| :--- | :---: | :---: |
-| β
Compatible | 2 | 100.0% |
-| β οΈ Incompatible | 0 | 0.0% |
-| π Upgrade Recommended | 0 | 0.0% |
-| ***Total NuGet Packages*** | ***2*** | ***100%*** |
-
-### API Compatibility
-
-| Category | Count | Impact |
-| :--- | :---: | :--- |
-| π΄ Binary Incompatible | 0 | High - Require code changes |
-| π‘ Source Incompatible | 0 | Medium - Needs re-compilation and potential conflicting API error fixing |
-| π΅ Behavioral change | 0 | Low - Behavioral changes that may require testing at runtime |
-| β
Compatible | 0 | |
-| ***Total APIs Analyzed*** | ***0*** | |
-
-## Aggregate NuGet packages details
-
-| Package | Current Version | Suggested Version | Projects | Description |
-| :--- | :---: | :---: | :--- | :--- |
-| Microsoft.Extensions.Hosting | 10.0.3 | | [Windows-Hinting.csproj](#Windows-Hintingcsproj) | β
Compatible |
-| Microsoft.Extensions.Hosting.Abstractions | 10.0.3 | | [Windows-Hinting.csproj](#Windows-Hintingcsproj) | β
Compatible |
-
-## Top API Migration Challenges
-
-### Technologies and Features
-
-| Technology | Issues | Percentage | Migration Path |
-| :--- | :---: | :---: | :--- |
-
-### Most Frequent API Issues
-
-| API | Count | Percentage | Category |
-| :--- | :---: | :---: | :--- |
-
-## Projects Relationship Graph
-
-Legend:
-π¦ SDK-style project
-βοΈ Classic project
-
-```mermaid
-flowchart LR
- P1["π¦ Windows-Hinting.csproj
net8.0-windows"]
- click P1 "#Windows-Hintingcsproj"
-
-```
-
-## Project Details
-
-
-### Windows-Hinting.csproj
-
-#### Project Info
-
-- **Current Target Framework:** net8.0-windowsβ
-- **SDK-style**: True
-- **Project Kind:** WinForms
-- **Dependencies**: 0
-- **Dependants**: 0
-- **Number of Files**: 39
-- **Lines of Code**: 3431
-- **Estimated LOC to modify**: 0+ (at least 0.0% of the project)
-
-#### Dependency Graph
-
-Legend:
-π¦ SDK-style project
-βοΈ Classic project
-
-```mermaid
-flowchart TB
- subgraph current["Windows-Hinting.csproj"]
- MAIN["π¦ Windows-Hinting.csproj
net8.0-windows"]
- click MAIN "#Windows-Hintingcsproj"
- end
-
-```
-
-### API Compatibility
-
-| Category | Count | Impact |
-| :--- | :---: | :--- |
-| π΄ Binary Incompatible | 0 | High - Require code changes |
-| π‘ Source Incompatible | 0 | Medium - Needs re-compilation and potential conflicting API error fixing |
-| π΅ Behavioral change | 0 | Low - Behavioral changes that may require testing at runtime |
-| β
Compatible | 0 | |
-| ***Total APIs Analyzed*** | ***0*** | |
-
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/execution-log.md b/.github/upgrades/scenarios/dotnet-version-upgrade/execution-log.md
deleted file mode 100644
index 7392395..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/execution-log.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Execution Log
-
-**Scenario**: dotnet-version-upgrade
-**Target Framework**: .NET 8.0
-**Strategy**: All-At-Once
-
----
-
-## Task 1: Update Windows-Hinting.Installer.wixproj β
COMPLETE
-
-**Start Time**: 2024
-**Status**: β
COMPLETED
-
-### Summary
-Successfully modernized the WiX installer project from WiX 3.x (Wix2010.targets) to WiX 4.0 (wix.targets) format with explicit .NET 8 compatibility.
-
-### Changes Made
-- Updated WiX project ProductVersion: 3.14 β 4.0
-- Changed import target: Wix2010.targets β wix.targets
-- Added TargetFrameworkVersion: net8.0
-- Verified Product.wxs references correct .NET 8 output path
-- All project references remain correct and intact
-
-### Build Validation
-β
Build successful with 0 errors, 0 warnings
-- Windows-Hinting.csproj (net8.0-windows): β
Built
-- Windows-Hinting.Installer.wixproj (WiX 4.0): β
Built
-- Installer package generation: β
Successful
-
-### Files Modified
-- `Windows-Hinting.Installer/Windows-Hinting.Installer.wixproj`
-
-**Progress**: 50% complete (1 of 2 tasks)
-
----
-
-## Next: Task 2 - Solution Validation (In Progress)
-
-Verifying full solution integration and final build validation.
-
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/plan.md b/.github/upgrades/scenarios/dotnet-version-upgrade/plan.md
deleted file mode 100644
index 20d6403..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/plan.md
+++ /dev/null
@@ -1,140 +0,0 @@
-# .NET 8 Upgrade Plan
-
-**Generated**: 2024
-**Target Framework**: .NET 8.0
-**Projects**: 2
-**Strategy**: All-At-Once
-
----
-
-## Overview
-
-This plan upgrades the Windows-Hinting solution to .NET 8.0 using an **All-At-Once** strategy. Both the main WinForms application and the WiX installer project will be updated simultaneously.
-
-### Assessment Summary
-- **Windows-Hinting.csproj**: Already on net8.0-windows β
-- **Windows-Hinting.Installer.wixproj**: WiX installer (requires modernization)
-- **Total NuGet Packages**: 2 (all compatible)
-- **Code Files**: 38
-- **Complexity**: Low
-- **Estimated Effort**: Minimal
-
----
-
-## Selected Strategy
-
-### All-At-Once
-**Rationale**: 2 projects, both modern format, clear dependency structure, straightforward upgrade with no breaking API changes detected.
-
-**Key execution principle**: Both projects updated simultaneously in a single atomic operation.
-
----
-
-## Projects to Upgrade
-
-### Tier: All Projects (Atomic)
-
-| Project | Type | Current TFM | Target TFM | Status |
-|---------|------|-------------|-----------|--------|
-| Windows-Hinting.csproj | WinForms App | net8.0-windows | net8.0-windows | β
Already current |
-| Windows-Hinting.Installer.wixproj | WiX Installer | (not .NET) | net8.0 compatible | β³ To upgrade |
-
----
-
-## Task Breakdown
-
-### Task 1: Update Windows-Hinting.Installer.wixproj
-
-**Objective**: Modernize the WiX installer project to target .NET 8 and ensure it properly references the upgraded main application.
-
-**Scope**:
-- Verify WiX project target framework compatibility
-- Update WiX project references/imports if needed
-- Ensure installer references the correct .NET 8 output from Windows-Hinting.csproj
-- Validate WiX project builds successfully
-- Confirm installer creation completes without errors
-
-**Validation**:
-- WiX project loads without errors in Visual Studio
-- Solution builds cleanly
-- Installer (MSI/Bundle) builds successfully
-
-### Task 2: Verify Solution Integration
-
-**Objective**: Ensure both projects build and package correctly as a unified solution.
-
-**Scope**:
-- Build full solution (both projects together)
-- Run any existing unit tests
-- Verify installer includes correct application binaries
-- Test installer functionality (basic smoke test)
-
-**Validation**:
-- Solution builds with 0 errors
-- All tests pass
-- Installer package is created successfully
-
----
-
-## Execution Constraints
-
-Based on All-At-Once strategy:
-
-1. **Single atomic operation** β Both projects updated together; full solution validated after changes
-2. **No tier ordering** β All projects treated as one group
-3. **Build-and-fix pass** β Single bounded compilation pass; fix all errors at once, not iteratively
-4. **Validation** β Full solution must build with 0 errors before moving to testing
-
----
-
-## NuGet Packages
-
-All existing packages are compatible with .NET 8.0:
-
-| Package | Current | Target | Projects |
-|---------|---------|--------|----------|
-| Microsoft.Extensions.Hosting | 10.0.3 | 10.0.3 | Windows-Hinting.csproj |
-| Microsoft.Extensions.Hosting.Abstractions | 10.0.3 | 10.0.3 | Windows-Hinting.csproj |
-
-No package version updates required.
-
----
-
-## Success Criteria
-
-- [ ] Windows-Hinting.csproj confirms .NET 8.0-windows targeting
-- [ ] Windows-Hinting.Installer.wixproj loads without errors
-- [ ] Solution builds successfully with 0 errors
-- [ ] Installer package is created and validated
-- [ ] All tests pass
-- [ ] Working branch contains all upgrade changes
-
----
-
-## Next Steps
-
-1. **Review this plan** β confirm the approach and tasks
-2. **Execute tasks** β follow the task-execution workflow
-3. **Validate** β build and test each change
-4. **Complete** β merge working branch back to main development branch
-
----
-
-## Appendix: Project Dependency Graph
-
-```
-βββββββββββββββββββββββββββββββββββββββ
-β Windows-Hinting.csproj (net8.0) β
-β WinForms Application β
-β - Microsoft.Extensions.Hosting β
-β - Abstractions (10.0.3) β
-ββββββββββββββββ¬βββββββββββββββββββββββ
- β
- βΌ
-ββββββββββββββββββββββββββββββββββββββββ
-β Windows-Hinting.Installer.wixproj β
-β WiX Setup Package β
-β (References Windows-Hinting.csproj) β
-ββββββββββββββββββββββββββββββββββββββββ
-```
-
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/scenario-instructions.md b/.github/upgrades/scenarios/dotnet-version-upgrade/scenario-instructions.md
deleted file mode 100644
index 6ecc718..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/scenario-instructions.md
+++ /dev/null
@@ -1,96 +0,0 @@
-# Scenario Instructions
-
-## Scenario Context
-- **Scenario ID**: dotnet-version-upgrade
-- **Description**: Upgrade Windows-Hinting and WiX installer project to .NET 8
-- **Target Framework**: .NET 8.0 (net8.0 / net8.0-windows)
-- **Date Initialized**: 2024
-
----
-
-## Preferences
-
-### Flow Mode
-**Mode**: Automatic
-
-The agent will run end-to-end, only pausing when:
-- Blocked (missing information, conflicting decisions)
-- Requiring explicit user input (important choices)
-- Critical validation fails
-
-### Technical Preferences
-- **Target Framework**: .NET 8.0 (LTS)
-- **Package Update Strategy**: Compatible versions only β no breaking changes
-- **WiX Toolset**: Installed (v4.0+)
-
-### Execution Style
-- **Commit Strategy**: After Each Task
-- **Validation**: Build after each task completion
-- **Testing**: Run full solution validation after all changes
-
----
-
-## Strategy
-
-**Selected**: All-At-Once
-
-**Rationale**:
-- 2 projects (well under 30 project threshold)
-- Main application already on .NET 8.0
-- Clear, straightforward upgrade with no breaking API changes
-- All dependencies compatible with target framework
-- Modern SDK-style project format
-
-### Execution Constraints
-
-1. **Single atomic upgrade** β Both Windows-Hinting.csproj and Windows-Hinting.Installer.wixproj updated together
-2. **No tier ordering** β All projects treated as one group; no phased rollout needed
-3. **Build-and-fix pass** β Single comprehensive compilation pass; fix all errors at once
-4. **Full solution validation** β Entire solution must build with 0 errors before testing
-5. **Installer verification** β Confirm WiX project builds MSI/Bundle successfully after upgrade
-
----
-
-## Source Control
-
-- **Repository**: C:\Users\knausj\git\Windows-Hinting (Git)
-- **Source Branch**: expiremental-refactor
-- **Working Branch**: upgrade-to-NET8
-- **Pending Changes**: Committed before workflow start
-
----
-
-## Key Decisions Log
-
-| Date | Decision | Context |
-|------|----------|---------|
-| 2024 | Install WiX Toolset 4.0 | Required for WiX project analysis and modernization |
-| 2024 | Select All-At-Once strategy | Straightforward upgrade, small project count, clear dependencies |
-| 2024 | Include WiX installer in upgrade | User preference to modernize both projects together |
-
----
-
-## Task Execution Notes
-
-- **Total Tasks**: 2
-- **Task 1**: Update Windows-Hinting.Installer.wixproj for .NET 8 compatibility
-- **Task 2**: Verify solution integration and build validation
-
----
-
-## Known Issues & Notes
-
-- WiX Toolset must remain installed throughout upgrade
-- WiX project references Windows-Hinting.csproj; ensure updatedpaths/references after any file reorganization
-- Installer package creation depends on successful Windows-Hinting.csproj build
-- No security vulnerabilities detected in dependencies
-
----
-
-## Related Artifacts
-
-- `assessment.md` β Full project analysis and compatibility assessment
-- `plan.md` β Detailed upgrade plan with task breakdown
-- `tasks.md` β Real-time task status and execution tracker
-- `execution-log.md` β Chronological log of all completed work
-
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/scenario.json b/.github/upgrades/scenarios/dotnet-version-upgrade/scenario.json
deleted file mode 100644
index bbd273e..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/scenario.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "scenarioId": "dotnet-version-upgrade",
- "operationId": "dc5fd04c-0745-49f1-b5f3-7c3526acb5f6",
- "description": "Upgrade .NET projects to newer .NET versions",
- "startTime": "2026-03-15T08:27:12.0435109Z",
- "lastUpdateTime": "2026-03-15T08:32:32.2071286Z",
- "stage": "Assessment",
- "properties": {
- "currentTask": "02-verify-solution-integration",
- "task:01-update-hintoverlay-installer-wixproj:startedAt": "2026-03-15T08:31:06.2143887Z",
- "UpgradeTargetFramework": "net8.0",
- "task:02-verify-solution-integration:startedAt": "2026-03-15T08:32:32.2071286Z"
- },
- "folderPath": ""
-}
\ No newline at end of file
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks.md b/.github/upgrades/scenarios/dotnet-version-upgrade/tasks.md
deleted file mode 100644
index fc4884c..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Task Execution Tracker
-
-**Scenario**: dotnet-version-upgrade
-**Strategy**: All-At-Once
-**Total Tasks**: 2
-
----
-
-## Task 1: Update WiX Installer
-
-**Status**: β
COMPLETE
-
-### 01-update-Windows-Hinting-installer-wixproj
-- Modernize the Windows-Hinting.Installer.wixproj to ensure .NET 8 compatibility and proper project references.
-- **Completed**: Updated WiX project to v4.0 format, verified .NET 8 references
-- **Files Modified**: Windows-Hinting.Installer/Windows-Hinting.Installer.wixproj
-
----
-
-## Task 2: Solution Validation
-
-**Status**: β³ IN PROGRESS
-
-### 02-verify-solution-integration
-- Verify both projects build together and the installer package is created successfully.
-- **Status**: Ready to execute
-- **Depends On**: Task 1 complete β
-
----
-
-## Execution Progress
-
-| Task | Status | Files Modified | Notes |
-|------|--------|-----------------|-------|
-| 01-update-Windows-Hinting-installer-wixproj | β
COMPLETE | Windows-Hinting.Installer.wixproj | WiX upgraded to v4.0, build validated |
-| 02-verify-solution-integration | β³ IN PROGRESS | β | Next task |
-
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/01-update-hintoverlay-installer-wixproj/progress-detail.md b/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/01-update-hintoverlay-installer-wixproj/progress-detail.md
deleted file mode 100644
index 120d4a0..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/01-update-hintoverlay-installer-wixproj/progress-detail.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# Progress Detail - Task 01
-
-**Task**: Update Windows-Hinting.Installer.wixproj for .NET 8
-**Status**: β
COMPLETED
-**Date**: 2024
-
----
-
-## Summary of Changes
-
-Successfully modernized the WiX installer project from WiX 3.x to WiX 4.0 format with .NET 8 compatibility.
-
----
-
-## Files Modified
-
-### 1. Windows-Hinting.Installer/Windows-Hinting.Installer.wixproj
-**Changes**:
-- Line 6: Updated `3.14` β `4.0`
-- Line 35: Updated `` β ``
-- Added new property: `net8.0` (line 10)
-
-**Rationale**:
-- WiX 4.0 uses `wix.targets` instead of `Wix2010.targets`
-- ProductVersion updated to reflect toolset version
-- Added explicit .NET 8 target framework version for clarity
-
-### 2. Windows-Hinting.Installer/Product.wxs
-**Status**: No changes needed β
-- Already correctly configured to reference .NET 8 output path
-- Binary reference: `Source="..\bin\Release\net8.0-windows\Windows-Hinting.exe"`
-
----
-
-## Build Results
-
-**Build Command**: `dotnet build`
-
-**Output**:
-```
-Build successful
-```
-
-**Details**:
-- β
Windows-Hinting.csproj compiled as net8.0-windows
-- β
Windows-Hinting.Installer.wixproj compiled with WiX 4.0 targets
-- β
No build warnings or errors
-- β
Installer package files generated successfully
-
----
-
-## Validation Checklist
-
-- β
WiX project loads without errors (tested via build)
-- β
Project references are correct (Windows-Hinting.csproj β .NET 8 binaries)
-- β
Product.wxs paths match .NET 8 output structure
-- β
Solution builds cleanly with both projects
-- β
No dependency conflicts detected
-
----
-
-## Key Findings
-
-1. **Main Application**: Already on .NET 8 (net8.0-windows)
-2. **WiX Project**: Successfully upgraded to use WiX 4.0 imports
-3. **Package Paths**: Correctly aligned with .NET 8 output directory structure
-4. **No Breaking Changes**: All references are compatible
-
----
-
-## Next Steps
-
-Task 2: Verify solution integration and run full validation tests.
-
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/01-update-hintoverlay-installer-wixproj/task.md b/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/01-update-hintoverlay-installer-wixproj/task.md
deleted file mode 100644
index 10c90e9..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/01-update-hintoverlay-installer-wixproj/task.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# 01-update-Windows-Hinting-installer-wixproj: Update Windows-Hinting.Installer.wixproj
-
-**Objective**: Modernize the WiX installer project to target .NET 8 and ensure it properly references the upgraded main application.
-
-**Target Framework**: .NET 8.0 (net8.0-windows)
-
-**Status**: β
COMPLETED
-
----
-
-## Changes Made
-
-### 1. Updated WiX Project File (Windows-Hinting.Installer.wixproj)
-
-**Changes:**
-- **ProductVersion**: Updated from 3.14 β 4.0 (aligns with installed WiX Toolset v4.0)
-- **WiX Import**: Changed from `Wix2010.targets` β `wix.targets` (WiX 4.0 standard)
-- **Added**: `net8.0` (metadata for .NET 8 compatibility)
-
-### 2. Verified Product.wxs
-
-**Status**: β
Already correct
-- Binary path correctly references `net8.0-windows` output:
- - `Source="..\bin\Release\net8.0-windows\Windows-Hinting.exe"`
-- No changes needed β already optimized for .NET 8
-
-### 3. Verified ProjectReference
-
-**Status**: β
Correct
-- WiX project correctly references main app: `Include="..\Windows-Hinting.csproj"`
-- Will pull .NET 8 binaries at build time
-
----
-
-## Build Validation
-
-β
**Solution Build**: Successful
-- Windows-Hinting.csproj: Built as net8.0-windows
-- Windows-Hinting.Installer.wixproj: Built successfully with WiX 4.0
-- No errors or warnings
-
----
-
-## What Was Accomplished
-
-β
WiX installer project upgraded to WiX 4.0 format
-β
Project references .NET 8 binaries correctly
-β
Build completed without errors
-β
Installer configuration ready for .NET 8
-
----
-
-## Scope**:
-- Verify WiX project target framework compatibility
-- Update WiX project references/imports if needed
-- Ensure installer references the correct .NET 8 output from Windows-Hinting.csproj
-- Validate WiX project builds successfully
-- Confirm installer creation completes without errors
-
-**Validation**:
-- WiX project loads without errors in Visual Studio
-- Solution builds cleanly
-- Installer (MSI/Bundle) builds successfully
diff --git a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/02-verify-solution-integration/task.md b/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/02-verify-solution-integration/task.md
deleted file mode 100644
index 29b12e7..0000000
--- a/.github/upgrades/scenarios/dotnet-version-upgrade/tasks/02-verify-solution-integration/task.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# 02-verify-solution-integration: Verify Solution Integration
-
-**Objective**: Ensure both projects build and package correctly as a unified solution.
-
-**Scope**:
-- Build full solution (both projects together)
-- Run any existing unit tests
-- Verify installer includes correct application binaries
-- Test installer functionality (basic smoke test)
-
-**Validation**:
-- Solution builds with 0 errors
-- All tests pass
-- Installer package is created successfully
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 48053f8..ca7e723 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -51,4 +51,4 @@ Update the `DOTNET_VERSION` environment variable in each workflow file.
Add additional steps in the respective jobs as needed for your deployment process.
### Modify Build Configuration
-Update the `BUILD_CONFIGURATION` environment variable to change between Debug/Release builds.
\ No newline at end of file
+Update the `BUILD_CONFIGURATION` environment variable to change between Debug/Release builds.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index abd3c72..02bb0f8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,12 +7,53 @@ on:
branches: [ "**" ]
workflow_dispatch:
+permissions:
+ contents: write
+
env:
BUILD_VERSION: 0.1.${{ github.run_number }}
+defaults:
+ run:
+ shell: pwsh
+
jobs:
+ lint:
+ name: Pre-commit Lint
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ ref: ${{ github.head_ref || github.ref_name }}
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Install PSScriptAnalyzer
+ run: Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser
+
+ - name: Run pre-commit
+ uses: pre-commit/action@v3.0.1
+
+ - name: Auto-fix and commit
+ if: failure() && github.event_name == 'pull_request'
+ uses: stefanzweifel/git-auto-commit-action@v5
+ with:
+ commit_message: "style: auto-fix pre-commit hook violations"
+
build-debug:
name: Debug Build
+ needs: lint
runs-on: windows-latest
steps:
@@ -34,7 +75,6 @@ jobs:
run: build\build-complete.bat Debug --exe-only
- name: Clean Debug output
- shell: pwsh
run: |
$outDir = "Windows-Hinting\bin\Debug\net8.0-windows"
Remove-Item "$outDir\xunit.*" -Force -ErrorAction SilentlyContinue
@@ -48,6 +88,7 @@ jobs:
build-release:
name: Release Build + MSI
+ needs: lint
runs-on: windows-latest
steps:
@@ -70,7 +111,6 @@ jobs:
run: dotnet restore Windows-Hinting\Windows-Hinting.csproj
- name: Decode signing certificate
- shell: pwsh
env:
CERT_BASE64: ${{ secrets.SIGNING_CERT_BASE64 }}
run: |
@@ -84,7 +124,6 @@ jobs:
}
- name: Build Release + MSI
- shell: pwsh
env:
CERT_PASSWORD: ${{ secrets.SIGNING_CERT_PASSWORD }}
run: |
@@ -102,7 +141,6 @@ jobs:
- name: Clean up signing certificate
if: always()
- shell: pwsh
run: |
if ($env:CERT_PATH -and (Test-Path $env:CERT_PATH)) {
Remove-Item $env:CERT_PATH -Force
@@ -110,7 +148,6 @@ jobs:
}
- name: Verify exe signature
- shell: pwsh
run: |
$exePath = "Windows-Hinting\bin\Release\net8.0-windows\Windows-Hinting.exe"
Write-Host "--- Signature verification ---"
@@ -149,8 +186,6 @@ jobs:
needs: [build-debug, build-release]
# Only publish a release on direct pushes to main β not PRs or other branches
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
- permissions:
- contents: write
steps:
- name: Download Debug build
diff --git a/.gitignore b/.gitignore
index 815afc4..f23790a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -398,6 +398,16 @@ MSBuild_Logs/
# BeatPulse healthcheck temp database
healthchecksdb
+# WiX Toolset
+*.msi
+*.msm
+*.msp
+*.wixpdb
+*.wixobj
+
+# .NET Upgrade Assistant / Copilot modernization working files
+.github/upgrades/
+
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..d04d9a3
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,34 @@
+repos:
+ # General hygiene hooks
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-xml
+ - id: check-merge-conflict
+ - id: check-added-large-files
+ args: ['--maxkb=500']
+ - id: mixed-line-ending
+ args: ['--fix=crlf']
+
+ # C# formatting via dotnet format
+ - repo: local
+ hooks:
+ - id: dotnet-format
+ name: dotnet format
+ entry: dotnet format --include
+ language: system
+ types_or: [c#]
+ pass_filenames: true
+
+ # PowerShell linting via PSScriptAnalyzer
+ - repo: local
+ hooks:
+ - id: psscriptanalyzer
+ name: PSScriptAnalyzer
+ entry: pwsh -NoProfile -Command "$settings = Join-Path $PWD 'build/PSScriptAnalyzerSettings.psd1'; Get-ChildItem -Filter *.ps1 -Recurse build/ | ForEach-Object { Invoke-ScriptAnalyzer -Path $_.FullName -Fix -Settings $settings -Severity Warning,Error | Out-Null; $results = Invoke-ScriptAnalyzer -Path $_.FullName -Settings $settings -Severity Warning,Error; if ($results) { $results | Format-Table -AutoSize; $global:failed = $true } }; if ($global:failed) { exit 1 }"
+ language: system
+ files: '\.ps1$'
+ pass_filenames: false
diff --git a/README.md b/README.md
index f417c17..28eebb7 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Windows Hinting
-Initial prototype of a fast hinting program for windows
+Initial prototype of a fast hinting program for windows
Ctrl + T -> Toggles hints on taskbar
diff --git a/Windows-Hinting.Installer/FIX_SUMMARY.md b/Windows-Hinting.Installer/FIX_SUMMARY.md
index 7c7c3ad..68e20e0 100644
--- a/Windows-Hinting.Installer/FIX_SUMMARY.md
+++ b/Windows-Hinting.Installer/FIX_SUMMARY.md
@@ -6,7 +6,7 @@
**Root Cause**: The WiX configuration was using `$(var.Windows_Hinting.TargetPath)` which pointed to the DLL output instead of the EXE, and dependencies were not being bundled.
-**Solution**:
+**Solution**:
1. Changed file reference from `$(var.Windows_Hinting.TargetPath)` to `$(var.Windows_Hinting.TargetDir)Windows-Hinting.exe`
2. Added Windows-Hinting.dll, configuration files, and all .NET runtime dependencies
3. Properly configured components with explicit GUIDs for multiple-file components
@@ -46,7 +46,7 @@
- Added RuntimeRegistry component for dependency tracking
- Assigned explicit GUIDs for proper component handling
-### Package.wxs
+### Package.wxs
- Added ComponentRef for DotNetRuntimeLibraries
- Added ComponentRef for RuntimeRegistry
- Ensures all components are included in the Feature
diff --git a/Windows-Hinting.Installer/INSTALLATION_GUIDE.md b/Windows-Hinting.Installer/INSTALLATION_GUIDE.md
index 18fa88e..ab9c138 100644
--- a/Windows-Hinting.Installer/INSTALLATION_GUIDE.md
+++ b/Windows-Hinting.Installer/INSTALLATION_GUIDE.md
@@ -18,7 +18,7 @@ The Windows-Hinting.Installer project has been successfully configured and built
**Shortcuts Created**:
- β
Desktop shortcut: "Windows-Hinting"
-- β
Start Menu folder: "Windows-Hinting"
+- β
Start Menu folder: "Windows-Hinting"
- β
Start Menu shortcut: "Windows-Hinting"
### Registry Configuration
diff --git a/Windows-Hinting.Installer/QUICK_START.md b/Windows-Hinting.Installer/QUICK_START.md
index 48bf353..aeaa8b1 100644
--- a/Windows-Hinting.Installer/QUICK_START.md
+++ b/Windows-Hinting.Installer/QUICK_START.md
@@ -70,7 +70,7 @@ signtool verify /pa bin\Release\net8.0-windows\Windows-Hinting.exe
Windows-Hinting.Installer/
βββ Package.wxs # Main installer definition
βββ ExampleComponents.wxs # Application files & shortcuts
-βββ UI.wxs # Installer UI configuration
+βββ UI.wxs # Installer UI configuration
βββ Folders.wxs # Directory structure
βββ License.rtf # EULA displayed to users
βββ INSTALLATION_GUIDE.md # Detailed setup guide
diff --git a/Windows-Hinting.Installer/RUNTIMES_FOLDER_FIX.md b/Windows-Hinting.Installer/RUNTIMES_FOLDER_FIX.md
index 8c3745c..b193d99 100644
--- a/Windows-Hinting.Installer/RUNTIMES_FOLDER_FIX.md
+++ b/Windows-Hinting.Installer/RUNTIMES_FOLDER_FIX.md
@@ -15,7 +15,7 @@
- β
`System.Diagnostics.EventLog.dll` (native interop for Windows Event Log)
- β
`System.Diagnostics.EventLog.Messages.dll` (localized messages for Event Log)
-### Browser/WASM Runtime Files
+### Browser/WASM Runtime Files
**Location**: `runtimes\browser\lib\net8.0\`
- β
`System.Text.Encodings.Web.dll` (WebAssembly optimized version)
@@ -35,7 +35,7 @@ Added two new components in the DirectoryRef section:
```xml
-
diff --git a/Windows-Hinting.Installer/UIACCESS_SETUP.md b/Windows-Hinting.Installer/UIACCESS_SETUP.md
index f81379d..df7b114 100644
--- a/Windows-Hinting.Installer/UIACCESS_SETUP.md
+++ b/Windows-Hinting.Installer/UIACCESS_SETUP.md
@@ -121,7 +121,7 @@ Get-ItemProperty -Path "HKLM:\Software\Windows-Hinting\Windows-Hinting"
#### "Access is denied" when accessing privileged UI
- **Cause**: UIAccess may be disabled or not working properly
-- **Check**:
+- **Check**:
- Verify executable is signed: `signtool verify /pa Windows-Hinting.exe`
- Check Event Viewer (System) for code integrity warnings
- Verify manifest is embedded: `ExifTool.exe -ALL Windows-Hinting.exe | grep -i manifest`
diff --git a/Windows-Hinting.Installer/Windows-Hinting.Installer.wixproj b/Windows-Hinting.Installer/Windows-Hinting.Installer.wixproj
index bc872a6..59091fb 100644
--- a/Windows-Hinting.Installer/Windows-Hinting.Installer.wixproj
+++ b/Windows-Hinting.Installer/Windows-Hinting.Installer.wixproj
@@ -27,7 +27,6 @@
-
-
\ No newline at end of file
+
diff --git a/Windows-Hinting.sln b/Windows-Hinting.sln
index 080fb02..798367a 100644
--- a/Windows-Hinting.sln
+++ b/Windows-Hinting.sln
@@ -27,6 +27,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A1B2
.github\workflows\README.md = .github\workflows\README.md
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C1D2E3F4-5678-9ABC-DEF0-ABCDEF123456}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ .gitignore = .gitignore
+ .pre-commit-config.yaml = .pre-commit-config.yaml
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/Windows-Hinting/Configuration/HintOverlaySettings.cs b/Windows-Hinting/Configuration/HintOverlaySettings.cs
index 924bf9a..22e2acf 100644
--- a/Windows-Hinting/Configuration/HintOverlaySettings.cs
+++ b/Windows-Hinting/Configuration/HintOverlaySettings.cs
@@ -3,8 +3,8 @@ namespace WindowsHinting.Configuration
internal sealed class HintOverlaySettings
{
public const string SectionName = "Windows-Hinting";
-
+
public string LogLevel { get; set; } = "Debug";
public bool EnablePerformanceMetrics { get; set; } = true;
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Forms/OverlayForm.cs b/Windows-Hinting/Forms/OverlayForm.cs
index a399559..ef941a5 100644
--- a/Windows-Hinting/Forms/OverlayForm.cs
+++ b/Windows-Hinting/Forms/OverlayForm.cs
@@ -218,7 +218,7 @@ protected override void OnPaint(PaintEventArgs e)
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
-
+
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOACTIVATE = 0x0010;
@@ -256,7 +256,7 @@ protected override CreateParams CreateParams
return cp;
}
}
-
+
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
diff --git a/Windows-Hinting/Forms/OverlayForm.resx b/Windows-Hinting/Forms/OverlayForm.resx
index 1af7de1..58a9e20 100644
--- a/Windows-Hinting/Forms/OverlayForm.resx
+++ b/Windows-Hinting/Forms/OverlayForm.resx
@@ -1,17 +1,17 @@
ο»Ώ
-
@@ -117,4 +117,4 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
\ No newline at end of file
+
diff --git a/Windows-Hinting/HintController.cs b/Windows-Hinting/HintController.cs
index 22846cd..d8799f4 100644
--- a/Windows-Hinting/HintController.cs
+++ b/Windows-Hinting/HintController.cs
@@ -1,4 +1,4 @@
-ο»Ώusing System;
+using System;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
@@ -162,7 +162,7 @@ private void SelectHintByLabel(string label, ClickAction action = ClickAction.De
{
_logger.Info($"Attempting to select hint with label: {label}, action: {action}");
- var hint = _stateManager.CurrentHints.FirstOrDefault(h =>
+ var hint = _stateManager.CurrentHints.FirstOrDefault(h =>
h.Label.Equals(label, StringComparison.OrdinalIgnoreCase));
if (hint == null)
@@ -288,18 +288,18 @@ private void ScanForHints()
_stateManager.Deactivate();
return;
}
-
+
_logger.Debug($"Scanning window: {hwnd}");
-
+
// Ensure overlay is topmost before scanning
_overlay.EnsureTopmost();
-
+
var elements = PerformanceMetricsExtensions.MeasureExecution(
"FindClickableElements",
() => _uiaService.FindClickableElements(hwnd),
_logger,
LogLevel.Info);
-
+
_logger.Info($"Found {elements.Count} clickable elements");
if (elements.Count == 0)
@@ -343,7 +343,7 @@ private void ScanForHints()
}).ToList(),
_logger,
LogLevel.Debug);
-
+
_logger.Debug($"Created {hints.Count} hint items");
_stateManager.SetHints(hints);
}
@@ -505,15 +505,15 @@ private bool CheckModifiersMatch(int expectedWin32Mods, KeyModifiers actualMods)
const int MOD_CONTROL = 0x0002;
const int MOD_ALT = 0x0001;
const int MOD_SHIFT = 0x0004;
-
+
bool expectCtrl = (expectedWin32Mods & MOD_CONTROL) != 0;
bool expectAlt = (expectedWin32Mods & MOD_ALT) != 0;
bool expectShift = (expectedWin32Mods & MOD_SHIFT) != 0;
-
+
bool hasCtrl = (actualMods & KeyModifiers.Control) != 0;
bool hasAlt = (actualMods & KeyModifiers.Alt) != 0;
bool hasShift = (actualMods & KeyModifiers.Shift) != 0;
-
+
return expectCtrl == hasCtrl && expectAlt == hasAlt && expectShift == hasShift;
}
diff --git a/Windows-Hinting/Logging/DebugLogger.cs b/Windows-Hinting/Logging/DebugLogger.cs
index 9df3e9f..c21cd05 100644
--- a/Windows-Hinting/Logging/DebugLogger.cs
+++ b/Windows-Hinting/Logging/DebugLogger.cs
@@ -138,4 +138,4 @@ public void Dispose()
CloseLogFile();
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Logging/ILogger.cs b/Windows-Hinting/Logging/ILogger.cs
index 3516f95..34cd929 100644
--- a/Windows-Hinting/Logging/ILogger.cs
+++ b/Windows-Hinting/Logging/ILogger.cs
@@ -11,4 +11,4 @@ public interface ILogger
void Warning(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "");
void Error(string message, Exception? ex = null, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "");
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Logging/LogLevel.cs b/Windows-Hinting/Logging/LogLevel.cs
index dfcf64c..2424034 100644
--- a/Windows-Hinting/Logging/LogLevel.cs
+++ b/Windows-Hinting/Logging/LogLevel.cs
@@ -7,4 +7,4 @@ public enum LogLevel
Warning = 2,
Error = 3
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Models/HintOverlayOptions.cs b/Windows-Hinting/Models/HintOverlayOptions.cs
index 0d415e7..12e685c 100644
--- a/Windows-Hinting/Models/HintOverlayOptions.cs
+++ b/Windows-Hinting/Models/HintOverlayOptions.cs
@@ -40,7 +40,7 @@ internal sealed class ClickActionShortcutOptions
public int DoubleClickKey { get; set; } = 0x44; // D key
public int MouseMoveKey { get; set; } = 0x4D; // M key
}
-
+
internal sealed class AnimationOptions
{
public int FadeDurationMs { get; set; } = 150;
@@ -69,4 +69,4 @@ internal enum HintPosition
LowerCenter,
LowerRight
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/NativeInterop/KeyboardHook.cs b/Windows-Hinting/NativeInterop/KeyboardHook.cs
index c4fb8e2..f65a98a 100644
--- a/Windows-Hinting/NativeInterop/KeyboardHook.cs
+++ b/Windows-Hinting/NativeInterop/KeyboardHook.cs
@@ -1,4 +1,4 @@
-ο»Ώusing System;
+using System;
using System.Runtime.InteropServices;
using WindowsHinting.Models;
using WindowsHinting.Services;
@@ -11,32 +11,32 @@ internal sealed class KeyboardHook : IDisposable
private IntPtr _hookHandle;
private readonly NativeMethods.LowLevelKeyboardProc _hookProc;
private bool _disposed;
-
+
public event EventHandler? KeyPressed;
public event EventHandler? KeyReleased;
-
+
public KeyboardHook()
{
_hookProc = HookCallback;
}
-
+
public void Install()
{
if (_hookHandle != IntPtr.Zero)
return;
-
+
_hookHandle = NativeMethods.SetWindowsHookEx(
- WindowsConstants.WH_KEYBOARD_LL,
- _hookProc,
- NativeMethods.GetModuleHandle(null),
+ WindowsConstants.WH_KEYBOARD_LL,
+ _hookProc,
+ NativeMethods.GetModuleHandle(null),
0);
-
+
if (_hookHandle == IntPtr.Zero)
{
throw new InvalidOperationException("Failed to install keyboard hook");
}
}
-
+
public void Uninstall()
{
if (_hookHandle != IntPtr.Zero)
@@ -45,18 +45,18 @@ public void Uninstall()
_hookHandle = IntPtr.Zero;
}
}
-
+
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
int vkCode = Marshal.ReadInt32(lParam);
-
- bool isKeyDown = wParam == (IntPtr)WindowsConstants.WM_KEYDOWN ||
+
+ bool isKeyDown = wParam == (IntPtr)WindowsConstants.WM_KEYDOWN ||
wParam == (IntPtr)WindowsConstants.WM_SYSKEYDOWN;
- bool isKeyUp = wParam == (IntPtr)WindowsConstants.WM_KEYUP ||
+ bool isKeyUp = wParam == (IntPtr)WindowsConstants.WM_KEYUP ||
wParam == (IntPtr)WindowsConstants.WM_SYSKEYUP;
-
+
if (isKeyDown || isKeyUp)
{
var modifiers = GetCurrentModifiers();
@@ -66,45 +66,45 @@ private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
Modifiers = modifiers,
Handled = false
};
-
+
if (isKeyDown)
KeyPressed?.Invoke(this, args);
else
KeyReleased?.Invoke(this, args);
-
+
if (args.Handled)
return (IntPtr)1; // Suppress the key
}
}
-
+
return NativeMethods.CallNextHookEx(_hookHandle, nCode, wParam, lParam);
}
-
+
private KeyModifiers GetCurrentModifiers()
{
var mods = KeyModifiers.None;
-
+
if ((NativeMethods.GetAsyncKeyState(WindowsConstants.VK_CONTROL) & WindowsConstants.KEY_PRESSED) != 0)
mods |= KeyModifiers.Control;
-
+
if ((NativeMethods.GetAsyncKeyState(WindowsConstants.VK_MENU) & WindowsConstants.KEY_PRESSED) != 0)
mods |= KeyModifiers.Alt;
-
+
if ((NativeMethods.GetAsyncKeyState(WindowsConstants.VK_SHIFT) & WindowsConstants.KEY_PRESSED) != 0)
mods |= KeyModifiers.Shift;
-
+
if ((NativeMethods.GetAsyncKeyState(WindowsConstants.VK_LWIN) & WindowsConstants.KEY_PRESSED) != 0 ||
(NativeMethods.GetAsyncKeyState(WindowsConstants.VK_RWIN) & WindowsConstants.KEY_PRESSED) != 0)
mods |= KeyModifiers.Win;
-
+
return mods;
}
-
+
public void Dispose()
{
if (_disposed)
return;
-
+
Uninstall();
_disposed = true;
}
diff --git a/Windows-Hinting/NativeInterop/UIAutomationWrapper.cs b/Windows-Hinting/NativeInterop/UIAutomationWrapper.cs
index a4a1361..0502cfb 100644
--- a/Windows-Hinting/NativeInterop/UIAutomationWrapper.cs
+++ b/Windows-Hinting/NativeInterop/UIAutomationWrapper.cs
@@ -20,11 +20,11 @@ public void Dispose()
Marshal.ReleaseComObject(_automation);
}
}
-
+
public IEnumerable FindClickableElements(IntPtr windowHandle)
{
var results = new List();
-
+
try
{
var rootElement = _automation.ElementFromHandle(windowHandle);
@@ -35,9 +35,9 @@ public IEnumerable FindClickableElements(IntPtr windowHandle)
var condition = _automation.CreateTrueCondition();
var walker = _automation.CreateTreeWalker(condition);
-
+
TraverseElements(rootElement, walker, results);
-
+
Marshal.ReleaseComObject(rootElement);
Marshal.ReleaseComObject(condition);
Marshal.ReleaseComObject(walker);
@@ -90,6 +90,6 @@ private void TraverseElements(IUIAutomationElement element, IUIAutomationTreeWal
}
}
}
-
+
internal record ClickableElement(Rectangle Bounds, IUIAutomationElement Element);
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Preferences/Preferences.cs b/Windows-Hinting/Preferences/Preferences.cs
index 0376219..3d58b2b 100644
--- a/Windows-Hinting/Preferences/Preferences.cs
+++ b/Windows-Hinting/Preferences/Preferences.cs
@@ -44,4 +44,4 @@ public void Save()
catch { }
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Preferences/PreferencesDialog.cs b/Windows-Hinting/Preferences/PreferencesDialog.cs
index 3a36ac0..59b6565 100644
--- a/Windows-Hinting/Preferences/PreferencesDialog.cs
+++ b/Windows-Hinting/Preferences/PreferencesDialog.cs
@@ -608,4 +608,4 @@ private void BtnOk_Click(object? sender, EventArgs e)
prefsService.Save(_options);
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Program.cs b/Windows-Hinting/Program.cs
index 22f38ff..fb73392 100644
--- a/Windows-Hinting/Program.cs
+++ b/Windows-Hinting/Program.cs
@@ -13,20 +13,20 @@ static void Main()
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
-
+
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddHintOverlayServices();
})
.Build();
-
+
using (var scope = host.Services.CreateScope())
{
var controller = scope.ServiceProvider.GetRequiredService();
Application.Run();
}
-
+
host.Dispose();
}
}
diff --git a/Windows-Hinting/ServiceCollectionExtensions.cs b/Windows-Hinting/ServiceCollectionExtensions.cs
index 292ecd9..5f6c2b1 100644
--- a/Windows-Hinting/ServiceCollectionExtensions.cs
+++ b/Windows-Hinting/ServiceCollectionExtensions.cs
@@ -11,9 +11,9 @@ internal static class ServiceCollectionExtensions
public static IServiceCollection AddHintOverlayServices(this IServiceCollection services)
{
// Logging
- services.AddSingleton(sp => new DebugLogger
- {
- MinimumLevel = LogLevel.Info
+ services.AddSingleton(sp => new DebugLogger
+ {
+ MinimumLevel = LogLevel.Info
});
// Configuration
@@ -29,15 +29,15 @@ public static IServiceCollection AddHintOverlayServices(this IServiceCollection
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
-
+
// UI Components
services.AddSingleton();
services.AddSingleton();
-
+
// Application Controller
services.AddSingleton();
-
+
return services;
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/ElementActivatorChain.cs b/Windows-Hinting/Services/ElementActivatorChain.cs
index 5411267..5c8d98c 100644
--- a/Windows-Hinting/Services/ElementActivatorChain.cs
+++ b/Windows-Hinting/Services/ElementActivatorChain.cs
@@ -36,7 +36,7 @@ public bool TryActivate(IUIAutomationElement element)
return true;
}
}
-
+
_logger.Warning("No interaction pattern succeeded for element");
return false;
}
@@ -65,4 +65,4 @@ private void LogCachedPatterns(IUIAutomationElement element)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/ElementActivators/ExpandCollapsePatternActivator.cs b/Windows-Hinting/Services/ElementActivators/ExpandCollapsePatternActivator.cs
index d8eb0ba..559ac59 100644
--- a/Windows-Hinting/Services/ElementActivators/ExpandCollapsePatternActivator.cs
+++ b/Windows-Hinting/Services/ElementActivators/ExpandCollapsePatternActivator.cs
@@ -49,4 +49,4 @@ public bool TryActivate(IUIAutomationElement element)
return false;
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/ElementActivators/InvokePatternActivator.cs b/Windows-Hinting/Services/ElementActivators/InvokePatternActivator.cs
index a428612..82fa191 100644
--- a/Windows-Hinting/Services/ElementActivators/InvokePatternActivator.cs
+++ b/Windows-Hinting/Services/ElementActivators/InvokePatternActivator.cs
@@ -20,7 +20,7 @@ public bool TryActivate(IUIAutomationElement element)
IUIAutomationInvokePattern? pattern = null;
bool isAvailable = element.GetCachedPropertyValue(UIA_PropertyIds.UIA_IsInvokePatternAvailablePropertyId);
- if (isAvailable)
+ if (isAvailable)
{
try
{
@@ -44,8 +44,8 @@ public bool TryActivate(IUIAutomationElement element)
}
}
}
-
+
return false;
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/ElementActivators/SelectionItemPatternActivator.cs b/Windows-Hinting/Services/ElementActivators/SelectionItemPatternActivator.cs
index ef5e284..d87fc9a 100644
--- a/Windows-Hinting/Services/ElementActivators/SelectionItemPatternActivator.cs
+++ b/Windows-Hinting/Services/ElementActivators/SelectionItemPatternActivator.cs
@@ -48,4 +48,4 @@ public bool TryActivate(IUIAutomationElement element)
return false;
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/ElementActivators/SetFocusActivator.cs b/Windows-Hinting/Services/ElementActivators/SetFocusActivator.cs
index d183e19..0a0474c 100644
--- a/Windows-Hinting/Services/ElementActivators/SetFocusActivator.cs
+++ b/Windows-Hinting/Services/ElementActivators/SetFocusActivator.cs
@@ -35,4 +35,4 @@ public bool TryActivate(IUIAutomationElement element)
return false;
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/ElementActivators/TogglePatternActivator.cs b/Windows-Hinting/Services/ElementActivators/TogglePatternActivator.cs
index c4463a7..d351054 100644
--- a/Windows-Hinting/Services/ElementActivators/TogglePatternActivator.cs
+++ b/Windows-Hinting/Services/ElementActivators/TogglePatternActivator.cs
@@ -49,4 +49,4 @@ public bool TryActivate(IUIAutomationElement element)
return false;
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/HintInputHandler.cs b/Windows-Hinting/Services/HintInputHandler.cs
index 64103e3..d6b0646 100644
--- a/Windows-Hinting/Services/HintInputHandler.cs
+++ b/Windows-Hinting/Services/HintInputHandler.cs
@@ -141,4 +141,4 @@ private void ToggleAction(ClickAction action)
_stateManager.SetPendingAction(action);
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/HintState.cs b/Windows-Hinting/Services/HintState.cs
index d1d0c55..1297474 100644
--- a/Windows-Hinting/Services/HintState.cs
+++ b/Windows-Hinting/Services/HintState.cs
@@ -45,7 +45,7 @@ public void SetPendingAction(ClickAction action)
ClickActionChanged?.Invoke(this, action);
}
}
-
+
public void Activate(HintSource source = HintSource.ForegroundWindow)
{
if (_mode == HintMode.Inactive)
@@ -68,7 +68,7 @@ public void Deactivate()
HintsChanged?.Invoke(this, _currentHints);
}
}
-
+
public void SetHints(IReadOnlyList hints)
{
_currentHints = hints.ToList();
@@ -78,14 +78,14 @@ public void SetHints(IReadOnlyList hints)
}
HintsChanged?.Invoke(this, _currentHints);
}
-
+
public void AppendToFilter(char c)
{
_filterText += c;
UpdateHintOpacity();
FilterChanged?.Invoke(this, _filterText);
}
-
+
public void RemoveLastFilterChar()
{
if (_filterText.Length > 0)
@@ -95,7 +95,7 @@ public void RemoveLastFilterChar()
FilterChanged?.Invoke(this, _filterText);
}
}
-
+
public void ClearFilter()
{
if (_filterText.Length > 0)
@@ -105,22 +105,22 @@ public void ClearFilter()
FilterChanged?.Invoke(this, _filterText);
}
}
-
+
public HintItem? GetExactMatch()
{
if (string.IsNullOrEmpty(_filterText))
return null;
-
+
return _currentHints.FirstOrDefault(h =>
h.Label.Equals(_filterText, StringComparison.OrdinalIgnoreCase));
}
-
+
public bool HasMatchingHint(string text)
{
return _currentHints.Any(h =>
h.Label.StartsWith(text, StringComparison.OrdinalIgnoreCase));
}
-
+
private void UpdateHintOpacity()
{
foreach (var hint in _currentHints)
@@ -131,7 +131,7 @@ private void UpdateHintOpacity()
}
HintsChanged?.Invoke(this, _currentHints);
}
-
+
private void SetMode(HintMode mode)
{
if (_mode != mode)
@@ -141,4 +141,4 @@ private void SetMode(HintMode mode)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/IElementActivator.cs b/Windows-Hinting/Services/IElementActivator.cs
index 254bdff..aa630d2 100644
--- a/Windows-Hinting/Services/IElementActivator.cs
+++ b/Windows-Hinting/Services/IElementActivator.cs
@@ -6,4 +6,4 @@ public interface IElementActivator
{
bool TryActivate(IUIAutomationElement element);
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/IUIAutomationService.cs b/Windows-Hinting/Services/IUIAutomationService.cs
index acb2456..d4aabef 100644
--- a/Windows-Hinting/Services/IUIAutomationService.cs
+++ b/Windows-Hinting/Services/IUIAutomationService.cs
@@ -18,7 +18,7 @@ public class ClickableElement
public IUIAutomationElement Element { get; set; } = null!;
public Rectangle Bounds { get; set; }
}
-
+
internal interface IKeyboardHookService
{
event EventHandler? KeyPressed;
@@ -27,7 +27,7 @@ internal interface IKeyboardHookService
void Stop();
bool IsActive { get; }
}
-
+
internal interface IPreferencesService
{
HintOverlayOptions Load();
@@ -40,4 +40,4 @@ internal sealed class KeyboardEventArgs : EventArgs
public KeyModifiers Modifiers { get; init; }
public bool Handled { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/IWindowManager.cs b/Windows-Hinting/Services/IWindowManager.cs
index fc2253d..8ef63b8 100644
--- a/Windows-Hinting/Services/IWindowManager.cs
+++ b/Windows-Hinting/Services/IWindowManager.cs
@@ -8,4 +8,4 @@ public interface IWindowManager
IntPtr GetTaskbarWindow();
bool IsWindowValid(IntPtr hwnd);
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/MouseClickService.cs b/Windows-Hinting/Services/MouseClickService.cs
index 26adfd4..2aa86db 100644
--- a/Windows-Hinting/Services/MouseClickService.cs
+++ b/Windows-Hinting/Services/MouseClickService.cs
@@ -35,30 +35,30 @@ public bool PerformClick(Rectangle elementBounds, ClickAction action)
Thread.Sleep(10);
switch (action)
- {
- case ClickAction.Default:
- case ClickAction.LeftClick:
- SendClick(WindowsConstants.MOUSEEVENTF_LEFTDOWN, WindowsConstants.MOUSEEVENTF_LEFTUP);
- break;
-
- case ClickAction.RightClick:
- SendClick(WindowsConstants.MOUSEEVENTF_RIGHTDOWN, WindowsConstants.MOUSEEVENTF_RIGHTUP);
- break;
-
- case ClickAction.DoubleClick:
- SendClick(WindowsConstants.MOUSEEVENTF_LEFTDOWN, WindowsConstants.MOUSEEVENTF_LEFTUP);
- Thread.Sleep(30);
- SendClick(WindowsConstants.MOUSEEVENTF_LEFTDOWN, WindowsConstants.MOUSEEVENTF_LEFTUP);
- break;
-
- case ClickAction.MouseMove:
- SendMove(x, y);
- break;
-
- default:
- _logger.Warning($"Unsupported click action: {action}");
- return false;
- }
+ {
+ case ClickAction.Default:
+ case ClickAction.LeftClick:
+ SendClick(WindowsConstants.MOUSEEVENTF_LEFTDOWN, WindowsConstants.MOUSEEVENTF_LEFTUP);
+ break;
+
+ case ClickAction.RightClick:
+ SendClick(WindowsConstants.MOUSEEVENTF_RIGHTDOWN, WindowsConstants.MOUSEEVENTF_RIGHTUP);
+ break;
+
+ case ClickAction.DoubleClick:
+ SendClick(WindowsConstants.MOUSEEVENTF_LEFTDOWN, WindowsConstants.MOUSEEVENTF_LEFTUP);
+ Thread.Sleep(30);
+ SendClick(WindowsConstants.MOUSEEVENTF_LEFTDOWN, WindowsConstants.MOUSEEVENTF_LEFTUP);
+ break;
+
+ case ClickAction.MouseMove:
+ SendMove(x, y);
+ break;
+
+ default:
+ _logger.Warning($"Unsupported click action: {action}");
+ return false;
+ }
_logger.Info($"{action} performed successfully at ({x}, {y})");
return true;
diff --git a/Windows-Hinting/Services/NamedPipeService.cs b/Windows-Hinting/Services/NamedPipeService.cs
index 2f85ad3..4f96e0e 100644
--- a/Windows-Hinting/Services/NamedPipeService.cs
+++ b/Windows-Hinting/Services/NamedPipeService.cs
@@ -154,9 +154,9 @@ private async Task HandleClientAsync(NamedPipeServerStream pipeServer, Cancellat
{
"TOGGLE" => new NamedPipeCommand { CommandType = CommandType.Toggle },
"TOGGLETASKBAR" => new NamedPipeCommand { CommandType = CommandType.ToggleTaskbar },
- "SELECT" => parts.Length > 1 ? new NamedPipeCommand
- {
- CommandType = CommandType.Select,
+ "SELECT" => parts.Length > 1 ? new NamedPipeCommand
+ {
+ CommandType = CommandType.Select,
HintLabel = parts[1],
Action = ParseClickAction(parts.Length > 2 ? parts[2] : null)
} : null,
diff --git a/Windows-Hinting/Services/Native/NativeMethods.cs b/Windows-Hinting/Services/Native/NativeMethods.cs
index b53a53c..e011e6a 100644
--- a/Windows-Hinting/Services/Native/NativeMethods.cs
+++ b/Windows-Hinting/Services/Native/NativeMethods.cs
@@ -115,4 +115,4 @@ internal static class WindowsConstants
public const uint MOUSEEVENTF_RIGHTUP = 0x0010;
public const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/PerformanceMetrics.cs b/Windows-Hinting/Services/PerformanceMetrics.cs
index ba20e2f..efa2fdd 100644
--- a/Windows-Hinting/Services/PerformanceMetrics.cs
+++ b/Windows-Hinting/Services/PerformanceMetrics.cs
@@ -107,4 +107,4 @@ public static void MeasureExecution(string operationName, Action action, ILogger
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/UIAutomationService.cs b/Windows-Hinting/Services/UIAutomationService.cs
index aab0586..a59deba 100644
--- a/Windows-Hinting/Services/UIAutomationService.cs
+++ b/Windows-Hinting/Services/UIAutomationService.cs
@@ -30,7 +30,7 @@ public UIAutomationService(ILogger logger, WindowRuleRegistry ruleRegistry)
public IReadOnlyList FindClickableElements(IntPtr windowHandle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
-
+
using (PerformanceMetrics.Start("UIAutomationService.FindClickableElements", _logger, LogLevel.Info))
{
try
@@ -53,7 +53,7 @@ public IReadOnlyList FindClickableElements(IntPtr windowHandle
public async Task> FindClickableElementsAsync(IntPtr windowHandle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
-
+
return await Task.Run(() => FindClickableElements(windowHandle));
}
@@ -304,18 +304,18 @@ private List ResolveRootElements(IntPtr windowHandle, IUIA
// return ResolveFileExplorerActiveTab(root, windowTitle);
default:
- {
- var resolved = ApplyStrategy(strategy, root);
- if (resolved != null && resolved != root)
{
- if (Marshal.IsComObject(root))
+ var resolved = ApplyStrategy(strategy, root);
+ if (resolved != null && resolved != root)
{
- try { Marshal.ReleaseComObject(root); } catch { }
+ if (Marshal.IsComObject(root))
+ {
+ try { Marshal.ReleaseComObject(root); } catch { }
+ }
+ return [resolved];
}
- return [resolved];
+ break;
}
- break;
- }
}
}
catch (COMException ex)
@@ -361,8 +361,8 @@ private List ResolveFileExplorerActiveTab(IUIAutomationEle
{
next = walker.GetNextSiblingElement(child);
}
- catch (COMException)
- {
+ catch (COMException)
+ {
}
@@ -430,84 +430,84 @@ private List ResolveFileExplorerActiveTab(IUIAutomationEle
switch (strategy)
{
case RootStrategy.ActiveWindowParent:
- {
- var parent = walker.GetParentElement(root);
- _logger.Debug(parent != null
- ? "ActiveWindowParent: navigated to parent element"
- : "ActiveWindowParent: no parent found, falling back to root");
- return parent;
- }
+ {
+ var parent = walker.GetParentElement(root);
+ _logger.Debug(parent != null
+ ? "ActiveWindowParent: navigated to parent element"
+ : "ActiveWindowParent: no parent found, falling back to root");
+ return parent;
+ }
case RootStrategy.FocusedElement:
- {
- var focused = _automation.GetFocusedElement();
- _logger.Debug(focused != null
- ? "FocusedElement: using focused element as root"
- : "FocusedElement: no focused element, falling back to root");
- return focused;
- }
-
- case RootStrategy.FocusedElementParent:
- {
- var focused = _automation.GetFocusedElement();
- if (focused == null)
{
- _logger.Debug("FocusedElementParent: no focused element, falling back to root");
- return null;
+ var focused = _automation.GetFocusedElement();
+ _logger.Debug(focused != null
+ ? "FocusedElement: using focused element as root"
+ : "FocusedElement: no focused element, falling back to root");
+ return focused;
}
- var parent = walker.GetParentElement(focused);
- if (parent != null && Marshal.IsComObject(focused))
+
+ case RootStrategy.FocusedElementParent:
{
- try { Marshal.ReleaseComObject(focused); } catch { }
+ var focused = _automation.GetFocusedElement();
+ if (focused == null)
+ {
+ _logger.Debug("FocusedElementParent: no focused element, falling back to root");
+ return null;
+ }
+ var parent = walker.GetParentElement(focused);
+ if (parent != null && Marshal.IsComObject(focused))
+ {
+ try { Marshal.ReleaseComObject(focused); } catch { }
+ }
+ _logger.Debug(parent != null
+ ? "FocusedElementParent: navigated to parent of focused element"
+ : "FocusedElementParent: no parent found, falling back to root");
+ return parent;
}
- _logger.Debug(parent != null
- ? "FocusedElementParent: navigated to parent of focused element"
- : "FocusedElementParent: no parent found, falling back to root");
- return parent;
- }
case RootStrategy.FocusedElementFirstParentWindow:
- {
- var focused = _automation.GetFocusedElement();
- if (focused == null)
{
- _logger.Debug("FocusedElementFirstParentWindow: no focused element, falling back to root");
- return null;
- }
- var current = focused;
- IUIAutomationElement? windowAncestor = null;
- while (true)
- {
- var parent = walker.GetParentElement(current);
- if (parent == null)
- break;
-
- int controlType = parent.CurrentControlType;
- if (controlType == UIA_ControlTypeIds.UIA_WindowControlTypeId)
+ var focused = _automation.GetFocusedElement();
+ if (focused == null)
{
- windowAncestor = parent;
- break;
+ _logger.Debug("FocusedElementFirstParentWindow: no focused element, falling back to root");
+ return null;
}
+ var current = focused;
+ IUIAutomationElement? windowAncestor = null;
+ while (true)
+ {
+ var parent = walker.GetParentElement(current);
+ if (parent == null)
+ break;
+
+ int controlType = parent.CurrentControlType;
+ if (controlType == UIA_ControlTypeIds.UIA_WindowControlTypeId)
+ {
+ windowAncestor = parent;
+ break;
+ }
- if (current != focused && Marshal.IsComObject(current))
+ if (current != focused && Marshal.IsComObject(current))
+ {
+ try { Marshal.ReleaseComObject(current); } catch { }
+ }
+ current = parent;
+ }
+ if (current != focused && current != windowAncestor && Marshal.IsComObject(current))
{
try { Marshal.ReleaseComObject(current); } catch { }
}
- current = parent;
- }
- if (current != focused && current != windowAncestor && Marshal.IsComObject(current))
- {
- try { Marshal.ReleaseComObject(current); } catch { }
- }
- if (Marshal.IsComObject(focused) && focused != windowAncestor)
- {
- try { Marshal.ReleaseComObject(focused); } catch { }
+ if (Marshal.IsComObject(focused) && focused != windowAncestor)
+ {
+ try { Marshal.ReleaseComObject(focused); } catch { }
+ }
+ _logger.Debug(windowAncestor != null
+ ? "FocusedElementFirstParentWindow: found Window ancestor"
+ : "FocusedElementFirstParentWindow: no Window ancestor found, falling back to root");
+ return windowAncestor;
}
- _logger.Debug(windowAncestor != null
- ? "FocusedElementFirstParentWindow: found Window ancestor"
- : "FocusedElementFirstParentWindow: no Window ancestor found, falling back to root");
- return windowAncestor;
- }
default:
return null;
@@ -568,4 +568,4 @@ public void Dispose()
_disposed = true;
}
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/Services/WindowManager.cs b/Windows-Hinting/Services/WindowManager.cs
index 28751b3..fe48922 100644
--- a/Windows-Hinting/Services/WindowManager.cs
+++ b/Windows-Hinting/Services/WindowManager.cs
@@ -11,4 +11,4 @@ internal sealed class WindowManager : IWindowManager
public bool IsWindowValid(IntPtr hwnd) => hwnd != IntPtr.Zero && NativeMethods.IsWindow(hwnd);
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/UIAutomationConstants.cs b/Windows-Hinting/UIAutomationConstants.cs
index dcb7e74..4f21211 100644
--- a/Windows-Hinting/UIAutomationConstants.cs
+++ b/Windows-Hinting/UIAutomationConstants.cs
@@ -178,4 +178,4 @@ internal static class UIA_PatternIds
public const int UIA_VirtualizedItemPatternId = 10020;
public const int UIA_SynchronizedInputPatternId = 10021;
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/app.manifest b/Windows-Hinting/app.manifest
index 1c120d3..7a2b1cd 100644
--- a/Windows-Hinting/app.manifest
+++ b/Windows-Hinting/app.manifest
@@ -29,4 +29,4 @@
-
\ No newline at end of file
+
diff --git a/Windows-Hinting/appsettings.json b/Windows-Hinting/appsettings.json
index 778eab1..334c898 100644
--- a/Windows-Hinting/appsettings.json
+++ b/Windows-Hinting/appsettings.json
@@ -3,4 +3,4 @@
"LogLevel": "Debug",
"EnablePerformanceMetrics": true
}
-}
\ No newline at end of file
+}
diff --git a/Windows-Hinting/docs/ARCHITECTURE_DIAGRAMS.md b/Windows-Hinting/docs/ARCHITECTURE_DIAGRAMS.md
index 04b3fcc..a599c18 100644
--- a/Windows-Hinting/docs/ARCHITECTURE_DIAGRAMS.md
+++ b/Windows-Hinting/docs/ARCHITECTURE_DIAGRAMS.md
@@ -90,7 +90,7 @@ External App NamedPipeClient NamedPipeService HintControl
β β β β β
β β β β SelectHintByLabel
β β β β ββ Find hint "A"
- β β β β β
+ β β β β β
β β β β ββ TryActivate()
β β β β β
β β β β ββ Deactivate
diff --git a/Windows-Hinting/docs/NAMED_PIPE_QUICK_REFERENCE.md b/Windows-Hinting/docs/NAMED_PIPE_QUICK_REFERENCE.md
index d5d3df5..67474cc 100644
--- a/Windows-Hinting/docs/NAMED_PIPE_QUICK_REFERENCE.md
+++ b/Windows-Hinting/docs/NAMED_PIPE_QUICK_REFERENCE.md
@@ -30,11 +30,11 @@ Commands:
## Key Features
-β
**Order-Independent** - Client and server startup order doesn't matter
-β
**Automatic Retries** - Built-in retry logic (up to 5 seconds)
-β
**Async** - Efficient asynchronous server handling
-β
**Thread-Safe** - Can be called from multiple threads
-β
**Simple** - Easy to integrate and use
+β
**Order-Independent** - Client and server startup order doesn't matter
+β
**Automatic Retries** - Built-in retry logic (up to 5 seconds)
+β
**Async** - Efficient asynchronous server handling
+β
**Thread-Safe** - Can be called from multiple threads
+β
**Simple** - Easy to integrate and use
## Available Commands
diff --git a/Windows-Hinting/docs/README_DEFAULT_BUILD.md b/Windows-Hinting/docs/README_DEFAULT_BUILD.md
index c147aa2..b086a94 100644
--- a/Windows-Hinting/docs/README_DEFAULT_BUILD.md
+++ b/Windows-Hinting/docs/README_DEFAULT_BUILD.md
@@ -11,7 +11,7 @@ The `build-complete.ps1` script now builds **both the signed executable AND MSI
.\build\build-complete.ps1 # Built exe only
.\build\build-complete.ps1 -Installer # Built exe + MSI
-# AFTER
+# AFTER
.\build\build-complete.ps1 # Builds exe + MSI β¨ (DEFAULT!)
.\build\build-complete.ps1 -ExeOnly # Builds exe only
```
diff --git a/Windows-Hinting/docs/README_NAMED_PIPE_INTERFACE.md b/Windows-Hinting/docs/README_NAMED_PIPE_INTERFACE.md
index b07999c..042c5ad 100644
--- a/Windows-Hinting/docs/README_NAMED_PIPE_INTERFACE.md
+++ b/Windows-Hinting/docs/README_NAMED_PIPE_INTERFACE.md
@@ -177,7 +177,7 @@ Element Activators (executes action)
10 comprehensive tests covering:
- β
Toggle command
-- β
Select command
+- β
Select command
- β
Deactivate command
- β
Invalid hint label handling
- β
Empty/null hint label validation
@@ -399,7 +399,7 @@ A complete, well-documented, tested named pipe interface implementation that:
---
**Status**: β
Ready for production deployment
-**Build Status**: β
Successful
+**Build Status**: β
Successful
**Test Coverage**: β
10 tests included
**Documentation**: β
Complete
**Examples**: β
8 examples provided
diff --git a/Windows-Hinting/docs/README_UIACCESS_SETUP.md b/Windows-Hinting/docs/README_UIACCESS_SETUP.md
index 0b8acff..e1dc79d 100644
--- a/Windows-Hinting/docs/README_UIACCESS_SETUP.md
+++ b/Windows-Hinting/docs/README_UIACCESS_SETUP.md
@@ -223,7 +223,7 @@ A: Yes, self-signed certs work. For production, use commercial certs so Windows
## π Documentation Files
-1. **`UIACCESS_QUICKSTART.md`**
+1. **`UIACCESS_QUICKSTART.md`**
- Quick reference (5 min)
- Common tasks
- Quick troubleshooting
diff --git a/build/Build-HintOverlay.ps1 b/build/Build-HintOverlay.ps1
index 615fae4..259b613 100644
--- a/build/Build-HintOverlay.ps1
+++ b/build/Build-HintOverlay.ps1
@@ -1,4 +1,4 @@
-# Build-HintOverlay.ps1
+ο»Ώ# Build-HintOverlay.ps1
# Build Windows-Hinting using .NET Framework MSBuild
<#
diff --git a/build/Build-InstallerMSI.ps1 b/build/Build-InstallerMSI.ps1
index fa97f4e..1bb6486 100644
--- a/build/Build-InstallerMSI.ps1
+++ b/build/Build-InstallerMSI.ps1
@@ -1,4 +1,4 @@
-# Build-InstallerMSI.ps1
+ο»Ώ# Build-InstallerMSI.ps1
# Builds Windows-Hinting and creates a signed MSI installer
<#
diff --git a/build/Create-CodeSigningCert.ps1 b/build/Create-CodeSigningCert.ps1
index c9e04e1..90843cb 100644
--- a/build/Create-CodeSigningCert.ps1
+++ b/build/Create-CodeSigningCert.ps1
@@ -1,4 +1,4 @@
-# Create-CodeSigningCert.ps1
+ο»Ώ# Create-CodeSigningCert.ps1
# Creates a self-signed code signing certificate for Windows-Hinting
<#
@@ -35,7 +35,7 @@ param(
[string]$FriendlyName = "Windows-Hinting Code Signing",
[int]$ValidityYears = 10,
[string]$OutputPath = $env:USERPROFILE,
- [string]$ExportPassword = ""
+ [SecureString]$ExportPassword = ""
)
# ============================================================================
diff --git a/build/New-HintOverlayMSI.ps1 b/build/New-HintOverlayMSI.ps1
index 1a159f1..297ba3c 100644
--- a/build/New-HintOverlayMSI.ps1
+++ b/build/New-HintOverlayMSI.ps1
@@ -1,4 +1,4 @@
-# New-HintOverlayMSI.ps1
+ο»Ώ# New-HintOverlayMSI.ps1
# Creates an MSI installer without requiring WiX Toolset
# Uses DTF (Deployment Tools Foundation) approach
@@ -33,9 +33,8 @@ $certPath = "$env:USERPROFILE\WindowsHinting_CodeSign.pfx"
$certPassword = "test123"
$exePath = "$ProjectDir\bin\Release\net8.0-windows\Windows-Hinting.exe"
$msiPath = Join-Path $OutputPath "Windows-Hinting.msi"
-$cabPath = Join-Path $OutputPath "Windows-Hinting.cab"
-Write-Host "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" -ForegroundColor Cyan
+Write-Host
Write-Host "Windows-Hinting MSI Creator (No WiX Required)" -ForegroundColor Cyan
Write-Host "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" -ForegroundColor Cyan
Write-Host ""
@@ -49,7 +48,9 @@ try {
Write-Host "β WiX Toolset found: $($lightExe.Source)" -ForegroundColor Green
}
}
-catch { }
+catch {
+ Write-Verbose "WiX Toolset not found: $_"
+}
if (-not $hasWiX) {
Write-Host "β WiX Toolset not found" -ForegroundColor Yellow
diff --git a/build/PSScriptAnalyzerSettings.psd1 b/build/PSScriptAnalyzerSettings.psd1
new file mode 100644
index 0000000..b419dc3
--- /dev/null
+++ b/build/PSScriptAnalyzerSettings.psd1
@@ -0,0 +1,10 @@
+@{
+ ExcludeRules = @(
+ # Write-Host is intentional in build/CI scripts for console output
+ 'PSAvoidUsingWriteHost',
+ # ConvertTo-SecureString with plaintext is required for cert export in build scripts
+ 'PSAvoidUsingConvertToSecureStringWithPlainText',
+ # Password params are passed as plain strings from CI environment variables / MSBuild args
+ 'PSAvoidUsingPlainTextForPassword'
+ )
+}
diff --git a/build/Sign-HintOverlay.ps1 b/build/Sign-HintOverlay.ps1
index d3c2bfa..7fc8c33 100644
--- a/build/Sign-HintOverlay.ps1
+++ b/build/Sign-HintOverlay.ps1
@@ -1,4 +1,4 @@
-# Sign-HintOverlay.ps1
+ο»Ώ# Sign-HintOverlay.ps1
# Code signing script for Windows-Hinting executable
<#
diff --git a/build/build-and-sign.ps1 b/build/build-and-sign.ps1
index 1811920..31e69f6 100644
--- a/build/build-and-sign.ps1
+++ b/build/build-and-sign.ps1
@@ -1,10 +1,10 @@
-param(
+ο»Ώparam(
[ValidateSet("Debug", "Release")]
[string]$Configuration = "Release",
[switch]$RegenerateCert,
[switch]$SkipSigning,
[string]$CertPath = "",
- [string]$CertPassword = "WindowsHinting_BuildCert_2024"
+ [SecureString]$CertPassword
)
$ErrorActionPreference = "Stop"
diff --git a/build/build-complete.ps1 b/build/build-complete.ps1
index f8df66c..3de1b3e 100644
--- a/build/build-complete.ps1
+++ b/build/build-complete.ps1
@@ -4,7 +4,7 @@ param(
[switch]$ExeOnly,
[switch]$SkipSigning,
[string]$CertPath = "",
- [string]$CertPassword = "WindowsHinting_BuildCert_2024"
+ [string]$CertPassword
)
$ErrorActionPreference = "Stop"
diff --git a/build/docs/BUILD_COMPLETE_DOCUMENTATION_INDEX.md b/build/docs/BUILD_COMPLETE_DOCUMENTATION_INDEX.md
index ea4a1bf..086e417 100644
--- a/build/docs/BUILD_COMPLETE_DOCUMENTATION_INDEX.md
+++ b/build/docs/BUILD_COMPLETE_DOCUMENTATION_INDEX.md
@@ -259,7 +259,7 @@ winget install lessmsi
**Solution:** Install WiX Toolset or check `Windows-Hinting.Installer` exists
### Issue: "Certificate not found"
-**Solution:**
+**Solution:**
```powershell
.\build\generate-signing-cert.ps1 # Generate new
# OR
@@ -267,7 +267,7 @@ winget install lessmsi
```
### Issue: "MSI file not found"
-**Solution:**
+**Solution:**
- Check WiX build output for errors
- Ensure Visual Studio build tools installed
diff --git a/build/docs/BUILD_DOCUMENTATION_INDEX.md b/build/docs/BUILD_DOCUMENTATION_INDEX.md
index 6d1cc88..a69b74f 100644
--- a/build/docs/BUILD_DOCUMENTATION_INDEX.md
+++ b/build/docs/BUILD_DOCUMENTATION_INDEX.md
@@ -281,6 +281,6 @@ All documentation is in the repository root directory:
---
-**Last Updated**: March 15, 2026
-**Status**: β
Production Ready
+**Last Updated**: March 15, 2026
+**Status**: β
Production Ready
**Version**: Windows-Hinting with Signed Code & Installer Integration
diff --git a/build/docs/CODE_SIGNING_SETUP.md b/build/docs/CODE_SIGNING_SETUP.md
index 2ddecd0..11afd69 100644
--- a/build/docs/CODE_SIGNING_SETUP.md
+++ b/build/docs/CODE_SIGNING_SETUP.md
@@ -169,7 +169,7 @@ For automated builds (GitHub Actions, Azure Pipelines, etc.):
## Security Notes
-β οΈ **Important**:
+β οΈ **Important**:
- Never commit certificate files (.pfx) to version control
- Use environment variables or secrets management for production
- Code-signing certificates from established CAs (like Sectigo, DigiCert) are recommended for production
diff --git a/build/docs/COMMAND_REFERENCE.md b/build/docs/COMMAND_REFERENCE.md
index 005a4af..bfdfc9c 100644
--- a/build/docs/COMMAND_REFERENCE.md
+++ b/build/docs/COMMAND_REFERENCE.md
@@ -115,12 +115,6 @@ build\build-complete.bat Release --skip-signing
```powershell
-CertPath "C:\my\cert.pfx" # Custom certificate location
```
-
-### `-CertPassword` (Default: "WindowsHinting_BuildCert_2024")
-```powershell
--CertPassword "my_password" # Custom certificate password
-```
-
---
## Step Count Reference
@@ -173,7 +167,7 @@ Build Summary:
```
ERROR: Certificate not found at: certs\WindowsHinting_CodeSign.pfx
```
-**Solution:**
+**Solution:**
```powershell
.\build\generate-signing-cert.ps1 # Generate new cert
# OR
@@ -278,7 +272,7 @@ Register-ScheduledTask -TaskName "Windows-Hinting-Build" `
4. **Sign MSI** (optional, recommended for distribution)
```powershell
signtool sign /f "certs\WindowsHinting_CodeSign.pfx" `
- /p "WindowsHinting_BuildCert_2024" `
+ /p "*****" `
/fd SHA256 `
Windows-Hinting.Installer\bin\Release\en-US\Windows-Hinting.msi
```
diff --git a/build/docs/DEPLOYMENT_CHECKLIST.md b/build/docs/DEPLOYMENT_CHECKLIST.md
index cdc39a9..32d0dc3 100644
--- a/build/docs/DEPLOYMENT_CHECKLIST.md
+++ b/build/docs/DEPLOYMENT_CHECKLIST.md
@@ -197,7 +197,7 @@ The following documentation is provided:
### Issue: Commands sent but nothing happens
-**Likely Cause:**
+**Likely Cause:**
- Hints not visible yet (timing issue)
- Invalid hint label
@@ -259,7 +259,7 @@ If issues discovered after deployment:
- Provide link to code examples
- Include in help/documentation
-### Developers
+### Developers
- Reference NAMED_PIPE_INTERFACE.md for technical details
- Share ARCHITECTURE_DIAGRAMS.md for system design
- Point to test suite for verification
diff --git a/build/generate-signing-cert.ps1 b/build/generate-signing-cert.ps1
index e0365a7..67f998e 100644
--- a/build/generate-signing-cert.ps1
+++ b/build/generate-signing-cert.ps1
@@ -1,4 +1,4 @@
-param([string]$CertName = "WindowsHinting", [string]$CertPath = "$PSScriptRoot\..\Windows-Hinting\certs\WindowsHinting_CodeSign.pfx", [string]$CertPassword = "WindowsHinting_BuildCert_2024", [int]$ValidYears = 10, [switch]$Force)
+param([string]$CertName = "WindowsHinting", [string]$CertPath = "$PSScriptRoot\..\Windows-Hinting\certs\WindowsHinting_CodeSign.pfx", [SecureString]$CertPassword, [int]$ValidYears = 10, [switch]$Force)
$ErrorActionPreference = "Stop"
$CertDir = Split-Path -Parent $CertPath