Search code examples
.netvb.netwinformsevent-handlingwakeup

Windows Forms handlers are ignored upon computer wakeup


(Scope: C# and VB.NET although code here is in VB.NET.)

My WinForms application is working well until first suspend and resume of the PC. After resume, certain event handlers are ignored.

Logging mechanism logs first suspend and first resume. Similarly, breakpoint in VisualStudio stops in Sub PowerModeChanged() after first suspend and resume. But they never do it again.

Sub PowerModeChanged() never gets called again.
Sub FeedRawInput() never gets called again and keystrokes go to standard WinForms handlers which are otherwise inactive in my app.

I'm not messing with any system tweaks, it is pretty standard WinForms MDI application. Any idea what can be killing some internal bindings so events get never called after resume?

If I restart the application, everything is immediately running fine again... until suspend-resume.

Sub Initialize() 'called from MainForm_Load()
    If AreAllSettingsMissing() Then PushAllDefaultSettings() 'needs database connections available
    AddHandler Microsoft.Win32.SystemEvents.PowerModeChanged, AddressOf PowerModeChanged

    _rawInput = New RawInputProcessor.RawFormsInput(My.Application.OpenForms(0),
                                                    RawInputProcessor.RawInputCaptureMode.Foreground)
    AddHandler _rawInput.KeyPressed, AddressOf FeedRawInput
End Sub

'my attempt to log suspend/resume and to rebuild USB scanner support
Sub PowerModeChanged(sender As Object, e As Microsoft.Win32.PowerModeChangedEventArgs)
    clsLogging.Log(String.Format("{0}:{1}:{2}", sender.ToString(), [Enum].GetName(GetType(Microsoft.Win32.PowerModes), e.Mode), _rawInput.ToString()))
    If e.Mode <> Microsoft.Win32.PowerModes.Resume Then Return
    'bring all down
    If _rawInput Is Nothing Then Return
    _rawInput.Dispose()
    _rawInput = Nothing
    RemoveHandler _rawInput.KeyPressed, AddressOf FeedRawInput 'problem 1 - called too late

    'restore all back
    _rawInput = New RawInputProcessor.RawFormsInput(My.Application.OpenForms(0),
                                                    RawInputProcessor.RawInputCaptureMode.Foreground)
    AddHandler _rawInput.KeyPressed, AddressOf FeedRawInput
End Sub


Sub FeedRawInput(sender As Object, e As RawInputProcessor.RawInputEventArgs)
    'push raw input key attributes into processing queue where 
    'each detected USB keyboard has its own async processing queue,
    'marshalling complete keystroke or scan back to main thread - standard stuff
End Sub

Regarding RawInput library, I have ported this one (C#->VB) which is a fork of this one. At the moment I don't know if creation of hidden window (what library does for catching messages) can play any role in the problem. (But it handles keyboard, so why also power event is ignored?) When I've tested library in small WPF project, it could survive resume well. WinForms testing of standalone library I didn't do yet.

If you have any quick hints where to check instead of lengthy dismantling of everything, separating pieces of app for easier diagnostics etc. please let me know.

Update 9 hours later:

  • slight improvement was achieved when line marked problem 1 was moved before Dispose(). Debugger is no longer strangely losing tracking.
  • handler PowerModeChanged() is always called (no longer ignored) after I removed my multi-threaded USB keyboard decoder. Now it can survive suspend-resume.

Solution

  • I didn't find solution (workaround?) other than to terminate all worker threads and remove global handlers on receiving Suspend event. After computer resume, on receving Resume event in my application I can re-create all listeners and re-add global handlers. (Listeners were not enough, handlers needed "refresh", too.) After applying this approach it started to work correctly.

    Sub PowerModeChanged(sender As Object, e As Microsoft.Win32.PowerModeChangedEventArgs)
        clsLogging.Log(String.Format("{0}:{1}:{2}",
                                     sender.ToString(),
                                     [Enum].GetName(GetType(Microsoft.Win32.PowerModes), e.Mode),
                                     Threading.Thread.CurrentThread.GetHashCode()))
        Select Case e.Mode
            Case Microsoft.Win32.PowerModes.Suspend
                My.Application.Scanners.Suspend()
                    'inside: 1) send termination request to all threads
                    '        2) clear USB keyboard map and any other references
                    '        3) remove global raw keyboard event handler
                    '        4) destroy instance of rawInput engine
            Case Microsoft.Win32.PowerModes.Resume
                My.Application.Scanners.Resume()
                    'inside: undo (rebuild) steps 4, 3, 2, 1 seen in Suspend()
        End Select
    
        'debug printout: see list of all thread-powered device readers
        Dim logEntry As String = ""
        For Each key As IntPtr In My.Application.Scanners.Devices.Keys
            logEntry &= String.Format("{0} {1} ", key, My.Application.Scanners.Devices(key))
        Next
        clsLogging.Log(logEntry)
    End Sub