Skip to content

Latest commit

 

History

History
553 lines (432 loc) · 15 KB

File metadata and controls

553 lines (432 loc) · 15 KB

Technical Deep Dive - Always On Top Manager

Windows API Strategy

SetWindowPos vs SetWindowLong Approach

Why SetWindowPos?

We use SetWindowPos with HWND_TOPMOST rather than just setting the WS_EX_TOPMOST extended style because:

  1. Immediate Effect: SetWindowPos immediately repositions the window in the Z-order
  2. Atomic Operation: Combines style change + repositioning in one API call
  3. No Activation: Using SWP_NOACTIVATE flag prevents stealing focus
// Our approach (preferred)
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, 
    SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

// Alternative (less reliable)
int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
SetWindowLong(hWnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);

Z-Order Stack Explained

Windows maintains windows in a Z-order list (doubly-linked list internally):

[Front] → HWND_TOPMOST windows → HWND_NOTOPMOST windows → [Back]

When we call SetWindowPos(hWnd, HWND_TOPMOST, ...):

  • Window is moved to the topmost region of the Z-order
  • All topmost windows stay above all non-topmost windows
  • Within topmost region, order is determined by SetWindowPos calls

Priority Enforcement Algorithm

Phase 1: Flag Application

foreach (var window in managedWindows)
{
    SetWindowPos(window.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}

Result: All managed windows now have topmost flag, but relative order may be wrong.

Phase 2: Priority Stacking

// Sort by priority: [1, 2, 3, 4, ...]
var sorted = windows.OrderBy(w => w.Priority);

// Apply in reverse order (lowest priority first)
for (int i = count - 1; i >= 0; i--)
{
    SetWindowPos(sorted[i].Handle, HWND_TOPMOST, ...);
}

Why reverse? Each SetWindowPos call places the window at the front of the topmost region. By processing from lowest to highest priority, the highest priority window is placed last (on top).

Phase 3: Final Pass

foreach (var window in sorted)  // Now in priority order
{
    SetWindowPos(window.Handle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}

Purpose: Ensures strict ordering by bringing each window to front in priority sequence.

Why 500ms Monitoring Interval?

  • Too Fast (< 100ms): Excessive CPU usage, may interfere with user interactions
  • Too Slow (> 1000ms): User may notice windows briefly appearing out of order
  • 500ms: Sweet spot balancing responsiveness and efficiency

CPU Impact:

Interval | CPU Usage | Response Time | UX Impact
---------|-----------|---------------|----------
100ms    | ~5%       | Excellent     | Choppy animations
500ms    | ~1-2%     | Good          | Imperceptible
1000ms   | <1%       | Fair          | Noticeable delays

Edge Cases - Deep Dive

1. Fullscreen Exclusive Mode Games

Problem: DirectX/Vulkan games in exclusive fullscreen bypass DWM (Desktop Window Manager).

Technical:

  • Exclusive mode renders directly to display, bypassing window manager
  • WS_EX_TOPMOST flag is ignored
  • No other windows can appear on top

Detection:

// Check if window is fullscreen
RECT rect;
GetWindowRect(hWnd, out rect);
bool isFullscreen = (rect.Width == screenWidth && rect.Height == screenHeight);

// Alternative: Check window style
int style = GetWindowLong(hWnd, GWL_STYLE);
bool hasCaption = (style & WS_CAPTION) != 0;  // Fullscreen usually lacks caption

Workarounds:

  1. Game in borderless windowed mode → topmost works
  2. Use window hooks (advanced, requires injecting DLL)
  3. Accept limitation and document it

2. UAC Elevation & Secure Desktop

Problem: Windows running with higher integrity level cannot be controlled by lower-level apps.

UIPI (User Interface Privilege Isolation):

High Integrity (Admin)    ← Cannot be controlled by →    Medium Integrity (User)
├─ UAC Dialogs                                           ├─ Normal Applications
├─ Admin Apps                                            ├─ AlwaysOnTop Manager
└─ System Processes                                      └─ Browsers, etc.

Solution: Run AlwaysOnTop Manager as Administrator

<!-- Add to app.manifest -->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

Trade-off:

  • ✓ Can manage admin windows
  • ✗ UAC prompt on every launch
  • ✗ Breaks drag-and-drop from normal apps

Recommended: Default to normal mode, provide "Run as Admin" shortcut for power users.

3. Virtual Desktop Handling

Windows 10+ Virtual Desktops:

  • Each virtual desktop has its own window set
  • Windows on Desktop 2 are "cloaked" when viewing Desktop 1

Detection via DWM API:

const int DWMWA_CLOAKED = 14;

int result = DwmGetWindowAttribute(hWnd, DWMWA_CLOAKED, out bool cloaked, sizeof(bool));

// Cloaked reasons:
// 0 = Not cloaked
// 1 = Cloaked by app (UWP suspend)
// 2 = Cloaked by shell (virtual desktop)
// 4 = Cloaked by another window (?)

Our Implementation:

public static bool IsWindowCloaked(IntPtr hWnd)
{
    try
    {
        DwmGetWindowAttribute(hWnd, DWMWA_CLOAKED, out bool cloaked, sizeof(bool));
        return cloaked;
    }
    catch
    {
        return false;  // Assume not cloaked if API fails
    }
}

Filter Strategy: Exclude cloaked windows from enumeration to show only current desktop windows.

4. Multi-Monitor Scenarios

Challenge: User has 3 monitors with different windows on each.

Windows Behavior:

  • Topmost windows are topmost across all monitors
  • Z-order is global, not per-monitor

User Expectation:

  • "Priority 1 window on Monitor 1 should stay above other windows on Monitor 1"
  • But NOT cover windows on Monitor 2

Reality: Cannot achieve true per-monitor Z-order with standard Win32 API.

Partial Solution:

  1. Group windows by monitor
  2. Assign priority ranges: Monitor 1 (1-10), Monitor 2 (11-20), etc.
  3. Document that topmost is global

Advanced Solution (not implemented):

  • Use window hooks to detect focus changes
  • Dynamically adjust topmost flag based on active monitor
  • Complexity: High, Performance: Impact moderate

5. Minimized Window Behavior

Scenario: User applies topmost to a window, then minimizes it.

What Happens:

bool isMinimized = IsIconic(hWnd);  // Returns true

// Window is minimized BUT:
GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_TOPMOST  // Still has topmost flag

Result: When restored, window immediately appears on top (correct behavior).

Edge Case: Clicking taskbar button to restore

1. User clicks taskbar
2. Window begins restoring (animation)
3. Window appears briefly below other windows
4. Our monitor (500ms later) detects it
5. Re-applies topmost → window jumps to front

Improvement: React to WM_WINDOWPOSCHANGED message instead of polling.

6. Race Conditions

Scenario: User rapidly clicks Apply → Remove → Apply

Thread Safety:

private readonly object _lockObject = new object();

public void EnforceZOrder()
{
    lock (_lockObject)  // Critical section
    {
        // Z-order manipulation here
    }
}

Why Needed:

  • Background timer thread runs EnforceZOrder() every 500ms
  • UI thread calls EnforceZOrder() when user clicks Apply
  • Without lock → concurrent SetWindowPos calls → undefined behavior

Deadlock Prevention:

  • All locks acquired in same order
  • No nested locks
  • Lock scope is minimal (only Z-order operations)

7. Window Title Changes

Scenario: Browser tab changes → window title updates.

Our Approach:

public bool UpdateWindowInfo(WindowInfo windowInfo)
{
    windowInfo.Title = GetWindowTitle(windowInfo.Handle);
    windowInfo.LastUpdated = DateTime.Now;
}

UI Refresh:

  • Title is bound to WindowInfo.Title property
  • Implements INotifyPropertyChanged
  • UI automatically updates when property changes

Performance: Title retrieval is fast (~0.1ms per window).

Performance Optimization

Memory Management

Managed Windows List:

private readonly List<WindowInfo> _managedWindows;  // Typically < 10 items

Memory per WindowInfo:

IntPtr Handle         = 8 bytes
string Title          ≈ 50 bytes (average)
string ProcessName    ≈ 20 bytes
int Priority          = 4 bytes
bool flags × 3        = 3 bytes
DateTime              = 8 bytes
-----------------------------------
Total ≈ 93 bytes per window

For 100 managed windows: ~9.3 KB (negligible).

CPU Optimization

Enumeration Efficiency:

EnumWindows((hWnd, lParam) => {
    // Early exits reduce API calls
    if (!IsWindowVisible(hWnd)) return true;  // ~60% of windows filtered here
    if (string.IsNullOrWhiteSpace(GetWindowTitle(hWnd))) return true;  // ~20% more
    
    // Expensive operations only for valid windows
    GetWindowThreadProcessId(...);
    Process.GetProcessById(...);
}, IntPtr.Zero);

Benchmark (typical Windows 11 system):

  • Total windows: ~200
  • Visible windows: ~80
  • Windows with titles: ~30
  • Enumeration time: ~50ms

Timer vs Message Hooks

Our Choice: Timer (500ms)

private readonly Timer _monitorTimer = new Timer(500);

Alternative: WH_CALLWNDPROC hook

SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, IntPtr.Zero, GetCurrentThreadId());

IntPtr CallWndProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    // Intercept all window messages
    if (nCode >= 0)
    {
        CWPSTRUCT msg = Marshal.PtrToStructure<CWPSTRUCT>(lParam);
        if (msg.message == WM_WINDOWPOSCHANGED)
        {
            // React immediately to Z-order changes
            EnforceZOrder();
        }
    }
    return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}

Comparison:

Approach Pros Cons
Timer Simple, safe, no injection 500ms latency
Hooks Instant response Crashes if buggy, global hooks require DLL

Decision: Timer provides sufficient UX with zero crash risk.

Architectural Decisions

Why WPF over WinForms?

  1. Data Binding: INotifyPropertyChanged → automatic UI updates
  2. Modern UI: XAML styling, animations, vector graphics
  3. MVVM Pattern: Clean separation of concerns (future maintainability)
  4. Composition: Easier to create complex layouts

Trade-off: Larger binary size (WPF runtime ~30 MB added to self-contained exe).

Why Not Use WinRT/UWP?

WinRT Pros: Modern API, fluent design WinRT Cons:

  • Sandboxed → cannot enumerate arbitrary windows
  • Limited Win32 interop
  • Requires Windows 10 1809+

Win32 Pros: Full system access, works on Windows 7+ Win32 Cons: C++/COM complexity

Our Choice: Win32 via P/Invoke (best of both worlds).

Why Self-Contained Deployment?

Alternatives:

  1. Framework-Dependent: User must install .NET → friction
  2. Self-Contained: Includes runtime → larger size, no dependencies

Decision: Prioritize user experience over file size.

  • Target users may not be developers
  • One-click run without installation
  • 75 MB is acceptable for modern systems

Future Enhancements

1. Hotkey Support

[DllImport("user32.dll")]
static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

// Register Ctrl+Alt+T
RegisterHotKey(this.Handle, HOTKEY_ID, MOD_CONTROL | MOD_ALT, VK_T);

// Handle in WndProc
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_HOTKEY)
    {
        // Toggle topmost for foreground window
        IntPtr hWnd = GetForegroundWindow();
        ToggleTopmostForWindow(hWnd);
    }
    base.WndProc(ref m);
}

2. Persistent Settings

public class AppSettings
{
    public List<ManagedWindowSettings> Windows { get; set; }
    public int MonitorInterval { get; set; } = 500;
    public bool StartMinimized { get; set; }
}

// Serialize to JSON
var json = JsonSerializer.Serialize(settings);
File.WriteAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
    "AlwaysOnTop", "settings.json"), json);

3. System Tray Integration

private NotifyIcon _trayIcon;

void InitializeTrayIcon()
{
    _trayIcon = new NotifyIcon
    {
        Icon = SystemIcons.Application,
        Text = "Always On Top Manager",
        Visible = true
    };
    
    _trayIcon.DoubleClick += (s, e) => { this.Show(); this.WindowState = WindowState.Normal; };
}

4. Window Profiles

  • Save/load named profiles (e.g., "Coding Setup", "Gaming Setup")
  • Auto-apply profiles based on running applications

5. Advanced Filtering

  • Exclude by process name
  • Include only specific window classes
  • Regex pattern matching for titles

Security Considerations

Code Injection Prevention

  • No DLL injection used
  • No code execution in other processes
  • All operations use documented Win32 APIs

Data Privacy

  • No network access
  • No telemetry
  • No external dependencies
  • All data in memory only (no disk writes by default)

Malware False Positives

Some antivirus software may flag the app due to:

  1. EnumWindows: Enumerating all windows (common in keyloggers)
  2. SetWindowPos: Manipulating other processes' windows
  3. Single-file executable: Packed format can resemble malware

Mitigation:

  • Sign executable with code signing certificate
  • Submit to Microsoft SmartScreen (via Windows Dev Center)
  • Publish source code on GitHub for transparency

Testing Strategy

Unit Tests (Example)

[Test]
public void SetWindowTopmost_ValidWindow_ReturnsTrue()
{
    // Arrange
    IntPtr hWnd = CreateTestWindow();
    
    // Act
    bool result = WindowsApi.SetWindowTopmost(hWnd, true);
    
    // Assert
    Assert.IsTrue(result);
    Assert.IsTrue(WindowsApi.IsWindowTopmost(hWnd));
    
    // Cleanup
    DestroyWindow(hWnd);
}

Integration Tests

  1. Launch Notepad
  2. Apply topmost via AlwaysOnTop Manager
  3. Launch Calculator
  4. Verify Notepad stays above Calculator
  5. Close Notepad
  6. Verify app removes it from managed list

Performance Tests

[Benchmark]
public void BenchmarkEnumeration()
{
    var stopwatch = Stopwatch.StartNew();
    var windows = _enumerator.GetAllWindows();
    stopwatch.Stop();
    
    Assert.Less(stopwatch.ElapsedMilliseconds, 100, "Enumeration took too long");
}

Debugging Tips

Enable Diagnostic Logging

#if DEBUG
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] Setting window {hWnd} to topmost");
#endif

Spy++ Alternative

Use built-in Windows tools:

# PowerShell: List all windows with handles
Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;

public class WindowInfo {
    [DllImport("user32.dll")]
    public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
    
    public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
}
"@

[WindowInfo]::EnumWindows({
    param($hWnd, $lParam)
    Write-Host "HWND: $hWnd"
    return $true
}, [IntPtr]::Zero)

Document Version: 1.0
Last Updated: January 2026
Audience: Developers, Security Auditors, Power Users