{"id":21070822,"url":"https://github.com/fafalone/clspresencemon","last_synced_at":"2026-03-16T08:36:40.108Z","repository":{"id":225676457,"uuid":"694600652","full_name":"fafalone/clsPresenceMon","owner":"fafalone","description":"Self-contained, threaded class to monitor user presence in several ways","archived":false,"fork":false,"pushed_at":"2024-03-03T16:15:34.000Z","size":5263,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-20T21:56:48.009Z","etag":null,"topics":["multithreading","power","power-monitor","power-monitoring","twinbasic"],"latest_commit_sha":null,"homepage":"","language":"Visual Basic 6.0","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fafalone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-09-21T10:19:41.000Z","updated_at":"2023-12-31T18:41:16.000Z","dependencies_parsed_at":"2024-03-03T17:38:27.397Z","dependency_job_id":null,"html_url":"https://github.com/fafalone/clsPresenceMon","commit_stats":null,"previous_names":["fafalone/clspresencemon"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FclsPresenceMon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FclsPresenceMon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FclsPresenceMon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FclsPresenceMon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fafalone","download_url":"https://codeload.github.com/fafalone/clsPresenceMon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243513858,"owners_count":20303022,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["multithreading","power","power-monitor","power-monitoring","twinbasic"],"created_at":"2024-11-19T18:48:29.363Z","updated_at":"2025-12-28T09:22:30.362Z","avatar_url":"https://github.com/fafalone.png","language":"Visual Basic 6.0","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clsPresenceMon\n*A self-contained, threaded class to monitor user presence in several ways*\n\n\nThis is a simple class that monitors for 3 events: The monitor state (off, on, or dimmed), the system's 'user present' message, and, if you're on a laptop, the lid status (open, close). It does this in an entirely self contained class-- the complication with this arises because these events are sent in the form of a `WM_POWERBROADCAST` message, requring a window to receive it. But I didn't want to limit the class to graphical apps and just subclass a form hWnd, although for the sake of completeness the demo app shows how to do it that way too. So the class creates it's own entirely custom hidden window, in a separate thread to avoid the message loop blocking the Form's message loop if present. \n\n![image](https://github.com/fafalone/clsPresenceMon/assets/7834493/62c830f3-bef1-40e1-a112-fc0d2db5882c)\n\n## Setting things up\n\nThe class takes advantage of twinBASIC's parameterized constructors to specify the available arguments right in the new keyword; here that's optional arguments for which events to raise and the hInstance to register under (`App.hInstance` 99% of the time; if you omit it, it will use `GetModuleHandleW()`, which returns the same value as `App.hInstance` without creating a dependency on WinNativeForms). Then we get to one of the great pleasures of tB: Calling `CreateThread` without any elaborate hacks neccessary, as cool as they are in VB6. The ThreadProc itself simply calls the rest of the code.\n\n```\n    Sub New(Optional ByVal dwNotifyMask As CPMonEventNotify = CPMEN_ALL, Optional ByVal hInst As LongPtr)\n        m_hInst = If(hInst = 0, GetModuleHandleW(), hInst)\n        If dwNotifyMask = CPMEN_ERROR Then Exit Sub\n        m_Mask = dwNotifyMask\n        \n        tConfig.hInst = m_hInst\n        tConfig.Mask = m_Mask\n        \n        m_hThread = CreateThread(ByVal 0, 0, AddressOf CPMonProc, tConfig, 0, m_idThread)\n            \n    End Sub\n```\n\n`RegisterClassEx` and `CreateWindowEx`are used in just a normal routine to create a hidden window, with the WndProc being a function within the class thanks to tB's supporting `AddressOf` here. The last important part of setup is that we have to register for the events we want; they're all delivered as `PBT_POWERSETTINGCHANGE` messages. We need to keep a handle for each as a class variable, but registration is straightforward; tbShellLib provides the API and GUIDs:\n\n```\n    Private Function RegisterEvents() As Boolean\n        m_hEventM = RegisterPowerSettingNotification(m_hWnd, GUID_SESSION_DISPLAY_STATUS, DEVICE_NOTIFY_WINDOW_HANDLE)\n        m_hEventP = RegisterPowerSettingNotification(m_hWnd, GUID_SESSION_USER_PRESENCE, DEVICE_NOTIFY_WINDOW_HANDLE)\n        m_hEventL = RegisterPowerSettingNotification(m_hWnd, GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE)\n        If m_hEventM Then Return True\n    End Function\n```\n## Receiving the messages\n\nAs already mentioned, they're delivered by uMsg `WM_POWERBROADCAST` with wParam `PBT_POWERSETTINGCHANGE`; we know from MSDN the lParam then points to a `POWERBROADCAST_SETTING` UDT. But we run into a problem:\n\n```\ntypedef struct {\n  GUID  PowerSetting;\n  DWORD DataLength;\n  UCHAR Data[1];\n} POWERBROADCAST_SETTING, *PPOWERBROADCAST_SETTING;\n```\n\nThat's a C-style array, not a `SAFEARRAY`; the data immediately follows in memory, where variable length arrays in tB (currently) can only be a `SAFEARRAY` with an entirely different structure. The solution tbShellLib uses is a bit clunky;\n\n```\n[ Description (\"WARNING: You can't use this directly due to the SAFEARRAY. To receive, fill the first 20 bytes, then the data in the array. To send, create a byte buffer excluding the safearray member.\") ]\nPublic Type POWERBROADCAST_SETTING\n    PowerSetting As UUID\n    DataLength As Long\n    Data() As Byte\nEnd Type\n```\n\nThe idea is to copy the fixed part, then use the length to ReDim the variable part, and do a separate copy into VarPtr(Data(0)). This class takes a shortcut though; since we're only working with a single 4-byte DWORD for the properties we're interested in, it looks like this:\n\n```\n            Case WM_POWERBROADCAST\n                If wParam = PBT_POWERSETTINGCHANGE Then\n                    Dim pSetting As POWERBROADCAST_SETTING\n                    CopyMemory pSetting, ByVal lParam, 20\n                    If IsEqualGUID(pSetting.PowerSetting, GUID_SESSION_DISPLAY_STATUS) Then\n                        Dim pState As MONITOR_DISPLAY_STATE\n                        CopyMemory pState, ByVal PointerAdd(lParam, 20), 4\n                        Select Case pState\n                            Case PowerMonitorOff\n                                If (m_Mask And CPMEN_MONITOROFF) Then RaiseEvent MonitorOff()\n```\n\nWe copy the fixed part so we can check the GUID for which event it is this time, but then just copy 4 bytes starting from the offset of `Data` (a GUID is 16 bytes, plus the 4 byte Long, =20) directly to a variable representing the enum of possible values. Everything besides the last line is provided by tbShellLib, including the generic `PointerAdd`, which safely performs an unsigned addition. Then we just check if the caller wants the event, and raise it. Surprisingly, calling RaiseEvent from out of thread like this worked with no special handling and hasn't crashed yet, and I've left the class running for 6+ hour stretches with many events being raised.\n\n\u003e [!NOTE]\n\u003e You'll receive the current status when you first register for events. So it will send 'Monitor on' even though the monitor has not just been turned on. \n\n## Cleaning up\n\nThe class provides a `Destroy()` method to turn off monitoring and get rid of the hidden window. It's required that you call this before trying to set it to `Nothing` if your app plans to keep running (if exiting you can let the system destroy everything). The thread is in it's message loop, so it won't exit util the message loop exits, so to do that, the window needs to be destroyed. You can't call `DestroyWindow` on window in a different thread, so what you do instead is send `WM_CLOSE` with `PostMessage`, and the window destroys itself, and unregisters the events:\n\n```\n            Case WM_CLOSE\n                DestroyWindow m_hWnd\n            \n            Case WM_DESTROY\n                UnregisterEvents\n                PostQuitMessage 0\n```\n\nBesides that we give the thread a few seconds to shut down, then unregister our custom window class. After that, everything is cleaned up and the class itself is ready to be destroyed.\n\n\n```\n    Public Sub Destroy()\n        If m_hWnd Then PostMessageW(m_hWnd, WM_CLOSE, 0, ByVal 0)\n        Dim lRet As WaitForObjOutcomes = WaitForSingleObject(m_hThread, 5000)\n        Debug.Print \"Wait outcome=\" \u0026 lRet\n        Dim hr As Long = UnregisterClassW(StrPtr(wndClass), m_hInst)\n        Debug.Print \"Unregister hr=\" \u0026 hr \u0026 \", lastErr=\" \u0026 Err.LastDllError\n    End Sub\n```\n\n---\nAnd that's all there is to it! It's all pretty straightforward, but worth writing up since a lot of are new to the whole 'easy multithreading' thing.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffafalone%2Fclspresencemon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffafalone%2Fclspresencemon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffafalone%2Fclspresencemon/lists"}