Como posso determinar programaticamente se minha estação de trabalho está bloqueada?

Estou escrevendo algumas ferramentas de produtividade / métricas para ajudar a monitorar meu foco ao longo do dia. Recentemente, tenho notado que tendem a sair da pista mais do que o habitual e sinto a necessidade de me levantar e ir para passeios / bebidas / etc e estou preocupado que eu estou “desperdiçando” muito tempo.

Como eu sempre tranco meu computador quando vou a qualquer lugar, e o desbloqueio assim que volto (mesmo que esteja lendo na minha mesa, etc.), fiquei me perguntando como posso determinar, em código, por quanto tempo a máquina está bloqueado.

Estou escrevendo isso em C # se isso ajudar, mas estou aberto a outras ideias.


Eu gosto da idéia de serviço do Windows (e aceitei) por simplicidade e limpeza, mas infelizmente não acho que funcionará para mim neste caso em particular. Eu queria executar isso no meu local de trabalho no trabalho, em vez de em casa (ou além de casa, suponho), mas é muito bem cedido pelo DoD. Isso é parte da razão pela qual estou desenvolvendo a minha, na verdade.

Vou escrevê-lo de qualquer maneira e ver se funciona. Obrigado a todos!

Eu não tinha encontrado isso antes, mas a partir de qualquer aplicativo você pode ligar um SessionSwitchEventHandler. Obviamente, seu aplicativo precisará estar em execução, mas desde que seja:

Microsoft.Win32.SystemEvents.SessionSwitch += new Microsoft.Win32.SessionSwitchEventHandler(SystemEvents_SessionSwitch); void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e) { if (e.Reason == SessionSwitchReason.SessionLock) { //I left my desk } else if (e.Reason == SessionSwitchReason.SessionUnlock) { //I returned to my desk } } 

Gostaria de criar um serviço do Windows (um tipo de projeto do Visual Studio 2005) que manipula o evento OnSessionChange conforme mostrado abaixo:

 protected override void OnSessionChange(SessionChangeDescription changeDescription) { if (changeDescription.Reason == SessionChangeReason.SessionLock) { //I left my desk } else if (changeDescription.Reason == SessionChangeReason.SessionUnlock) { //I returned to my desk } } 

O que e como você registra a atividade nesse ponto é com você, mas um serviço do Windows fornece access rápido e fácil a events do Windows, como boot, desligamento, login / out, juntamente com os events de bloqueio e desbloqueio.

A solução abaixo usa a API do Win32. OnSessionLock é chamado quando a estação de trabalho é bloqueada e OnSessionUnlock é chamado quando é desbloqueado.

 [DllImport("wtsapi32.dll")] private static extern bool WTSRegisterSessionNotification(IntPtr hWnd, int dwFlags); [DllImport("wtsapi32.dll")] private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd); private const int NotifyForThisSession = 0; // This session only private const int SessionChangeMessage = 0x02B1; private const int SessionLockParam = 0x7; private const int SessionUnlockParam = 0x8; protected override void WndProc(ref Message m) { // check for session change notifications if (m.Msg == SessionChangeMessage) { if (m.WParam.ToInt32() == SessionLockParam) OnSessionLock(); // Do something when locked else if (m.WParam.ToInt32() == SessionUnlockParam) OnSessionUnlock(); // Do something when unlocked } base.WndProc(ref m); return; } void OnSessionLock() { Debug.WriteLine("Locked..."); } void OnSessionUnlock() { Debug.WriteLine("Unlocked..."); } private void Form1Load(object sender, EventArgs e) { WTSRegisterSessionNotification(this.Handle, NotifyForThisSession); } // and then when we are done, we should unregister for the notification // WTSUnRegisterSessionNotification(this.Handle); 

Eu sei que esta é uma pergunta antiga, mas eu encontrei um método para obter o estado de bloqueio para uma determinada session.

Eu encontrei a minha resposta aqui, mas foi em C + +, então eu traduzi o máximo possível para C # para obter o estado de bloqueio.

Então aqui vai:

 static class SessionInfo { private const Int32 FALSE = 0; private static readonly IntPtr WTS_CURRENT_SERVER = IntPtr.Zero; private const Int32 WTS_SESSIONSTATE_LOCK = 0; private const Int32 WTS_SESSIONSTATE_UNLOCK = 1; private static bool _is_win7 = false; static SessionInfo() { var os_version = Environment.OSVersion; _is_win7 = (os_version.Platform == PlatformID.Win32NT && os_version.Version.Major == 6 && os_version.Version.Minor == 1); } [DllImport("wtsapi32.dll")] private static extern Int32 WTSQuerySessionInformation( IntPtr hServer, [MarshalAs(UnmanagedType.U4)] UInt32 SessionId, [MarshalAs(UnmanagedType.U4)] WTS_INFO_CLASS WTSInfoClass, out IntPtr ppBuffer, [MarshalAs(UnmanagedType.U4)] out UInt32 pBytesReturned ); [DllImport("wtsapi32.dll")] private static extern void WTSFreeMemoryEx( WTS_TYPE_CLASS WTSTypeClass, IntPtr pMemory, UInt32 NumberOfEntries ); private enum WTS_INFO_CLASS { WTSInitialProgram = 0, WTSApplicationName = 1, WTSWorkingDirectory = 2, WTSOEMId = 3, WTSSessionId = 4, WTSUserName = 5, WTSWinStationName = 6, WTSDomainName = 7, WTSConnectState = 8, WTSClientBuildNumber = 9, WTSClientName = 10, WTSClientDirectory = 11, WTSClientProductId = 12, WTSClientHardwareId = 13, WTSClientAddress = 14, WTSClientDisplay = 15, WTSClientProtocolType = 16, WTSIdleTime = 17, WTSLogonTime = 18, WTSIncomingBytes = 19, WTSOutgoingBytes = 20, WTSIncomingFrames = 21, WTSOutgoingFrames = 22, WTSClientInfo = 23, WTSSessionInfo = 24, WTSSessionInfoEx = 25, WTSConfigInfo = 26, WTSValidationInfo = 27, WTSSessionAddressV4 = 28, WTSIsRemoteSession = 29 } private enum WTS_TYPE_CLASS { WTSTypeProcessInfoLevel0, WTSTypeProcessInfoLevel1, WTSTypeSessionInfoLevel1 } public enum WTS_CONNECTSTATE_CLASS { WTSActive, WTSConnected, WTSConnectQuery, WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown, WTSInit } public enum LockState { Unknown, Locked, Unlocked } [StructLayout(LayoutKind.Sequential)] private struct WTSINFOEX { public UInt32 Level; public UInt32 Reserved; /* I have observed the Data field is pushed down by 4 bytes so i have added this field as padding. */ public WTSINFOEX_LEVEL Data; } [StructLayout(LayoutKind.Sequential)] private struct WTSINFOEX_LEVEL { public WTSINFOEX_LEVEL1 WTSInfoExLevel1; } [StructLayout(LayoutKind.Sequential)] private struct WTSINFOEX_LEVEL1 { public UInt32 SessionId; public WTS_CONNECTSTATE_CLASS SessionState; public Int32 SessionFlags; /* I can't figure out what the rest of the struct should look like but as i don't need anything past the SessionFlags i'm not going to. */ } public static LockState GetSessionLockState(UInt32 session_id) { IntPtr ppBuffer; UInt32 pBytesReturned; Int32 result = WTSQuerySessionInformation( WTS_CURRENT_SERVER, session_id, WTS_INFO_CLASS.WTSSessionInfoEx, out ppBuffer, out pBytesReturned ); if (result == FALSE) return LockState.Unknown; var session_info_ex = Marshal.PtrToStructure(ppBuffer); if (session_info_ex.Level != 1) return LockState.Unknown; var lock_state = session_info_ex.Data.WTSInfoExLevel1.SessionFlags; WTSFreeMemoryEx(WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, ppBuffer, pBytesReturned); if (_is_win7) { /* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ee621019(v=vs.85).aspx * Windows Server 2008 R2 and Windows 7: Due to a code defect, the usage of the WTS_SESSIONSTATE_LOCK * and WTS_SESSIONSTATE_UNLOCK flags is reversed. That is, WTS_SESSIONSTATE_LOCK indicates that the * session is unlocked, and WTS_SESSIONSTATE_UNLOCK indicates the session is locked. * */ switch (lock_state) { case WTS_SESSIONSTATE_LOCK: return LockState.Unlocked; case WTS_SESSIONSTATE_UNLOCK: return LockState.Locked; default: return LockState.Unknown; } } else { switch (lock_state) { case WTS_SESSIONSTATE_LOCK: return LockState.Locked; case WTS_SESSIONSTATE_UNLOCK: return LockState.Unlocked; default: return LockState.Unknown; } } } } 

Nota: O código acima foi extraído de um projeto muito maior por isso, se eu perdi um pouco desculpe. Eu não tenho tempo para testar o código acima, mas pretendo voltar em uma semana ou duas para verificar tudo. Eu só postei agora porque eu não queria esquecer de fazer isso.

NOTA : Esta não é uma resposta, mas uma (contribuição) para Timothy Carter responder, porque minha reputação não me permite comentar até agora.

Apenas no caso de alguém ter tentado o código da resposta de Timothy Carter e não ter conseguido que ele funcionasse imediatamente em um serviço do Windows, há uma propriedade que precisa ser definida como true no construtor do serviço. Apenas adicione a linha no construtor:

 CanHandleSessionChangeEvent = true; 

E não se esqueça de definir essa propriedade depois que o serviço for iniciado, caso contrário, uma InvalidOperationException será lançada.

Se você estiver interessado em escrever um serviço do Windows para “localizar” esses events, o topshelf (a biblioteca / estrutura que torna a gravação de serviços do Windows muito mais fácil) tem um gancho.

 public interface IMyServiceContract { void Start(); void Stop(); void SessionChanged(Topshelf.SessionChangedArguments args); } public class MyService : IMyServiceContract { public void Start() { } public void Stop() { } public void SessionChanged(SessionChangedArguments e) { Console.WriteLine(e.ReasonCode); } } 

e agora o código para conectar o serviço topshelf à interface / concreto acima

Tudo abaixo é a configuração típica “topshelf” …. exceto por duas linhas marcadas como

/ * ISTO É LINHA MÁGICA * /

Aquele é o que obtém o método SessionChanged para triggersr.

Eu testei isso com o windows 10 x64. Eu tranquei e destranquei minha máquina e obtive o resultado desejado.

  IMyServiceContract myServiceObject = new MyService(); /* container.Resolve(); */ HostFactory.Run(x => { x.Service(s => { s.ConstructUsing(name => myServiceObject); s.WhenStarted(sw => sw.Start()); s.WhenStopped(sw => sw.Stop()); s.WhenSessionChanged((csm, hc, chg) => csm.SessionChanged(chg)); /* THIS IS MAGIC LINE */ }); x.EnableSessionChanged(); /* THIS IS MAGIC LINE */ /* use command line variables for the below commented out properties */ /* x.RunAsLocalService(); x.SetDescription("My Description"); x.SetDisplayName("My Display Name"); x.SetServiceName("My Service Name"); x.SetInstanceName("My Instance"); */ x.StartManually(); // Start the service manually. This allows the identity to be tweaked before the service actually starts /* the below map to the "Recover" tab on the properties of the Windows Service in Control Panel */ x.EnableServiceRecovery(r => { r.OnCrashOnly(); r.RestartService(1); ////first r.RestartService(1); ////second r.RestartService(1); ////subsequents r.SetResetPeriod(0); }); x.DependsOnEventLog(); // Windows Event Log x.UseLog4Net(); x.EnableShutdown(); x.OnException(ex => { /* Log the exception */ /* not seen, I have a log4net logger here */ }); }); 

Meu packages.config para fornecer dicas sobre versões:

     

Abaixo está o código de trabalho 100% para descobrir se o PC está bloqueado ou não.

Antes de usar isso, use o namespace System.Runtime.InteropServices .

 [DllImport("user32", EntryPoint = "OpenDesktopA", CharSet = CharSet.Ansi,SetLastError = true, ExactSpelling = true)] private static extern Int32 OpenDesktop(string lpszDesktop, Int32 dwFlags, bool fInherit, Int32 dwDesiredAccess); [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] private static extern Int32 CloseDesktop(Int32 hDesktop); [DllImport("user32", CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling = true)] private static extern Int32 SwitchDesktop(Int32 hDesktop); public static bool IsWorkstationLocked() { const int DESKTOP_SWITCHDESKTOP = 256; int hwnd = -1; int rtn = -1; hwnd = OpenDesktop("Default", 0, false, DESKTOP_SWITCHDESKTOP); if (hwnd != 0) { rtn = SwitchDesktop(hwnd); if (rtn == 0) { // Locked CloseDesktop(hwnd); return true; } else { // Not locked CloseDesktop(hwnd); } } else { // Error: "Could not access the desktop..." } return false; }