2008-09-04 12 views
98

Estoy escribiendo algunas herramientas de productividad/métricas para ayudarme a controlar mi enfoque a lo largo del día. Recientemente, me he dado cuenta de que tiendo a perder el rumbo más de lo habitual y siento la necesidad de levantarme e ir a caminar/beber/etc. y me preocupa que esté "perdiendo" demasiado tiempo.¿Cómo puedo determinar mediante programación si mi estación de trabajo está bloqueada?

Como siempre bloqueo mi computadora cuando voy a algún lado, y la desbloqueo tan pronto como regrese (incluso si solo estoy leyendo en mi escritorio, etc.), me preguntaba cómo puedo determinar, en código, cuánto tiempo la máquina está bloqueada.

Escribo esto en C# si eso ayuda, pero estoy abierto a otras ideas.


me gusta la idea de servicio de Windows (y la han aceptado) por la sencillez y la limpieza, pero por desgracia no creo que va a trabajar para mí en este caso particular. Quería ejecutar esto en mi estación de trabajo en el trabajo en lugar de en casa (o además de la casa, supongo), pero está bloqueado por cortesía del Departamento de Defensa. Esa es en parte la razón por la que estoy haciendo lo mío, en realidad.

Lo escribiré de todos modos y veré si funciona. ¡Gracias a todos!

Respuesta

116

No había encontrado esto antes, pero desde cualquier aplicación puede conectar un SessionSwitchEventHandler. Obviamente su aplicación tendrá que ser correr, pero mientras que es:

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 
    } 
} 
+1

Probado al 100% en Windows 7 x64 y Windows 10 x64. – Contango

+0

¡Guau, funciona increíble! sin errores, sin excepciones, suave y limpio! –

-4

A continuación se muestra el código de 100% de trabajo para encontrar si la PC está bloqueada o no.

Antes de usar esto, utilice el espacio de nombres 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; 
} 
+3

Comprobar MSDN para OpenInputDesktop y GetUserObjectInformation, para obtener el nombre escritorio activo en su lugar. El código anterior no es seguro/agradable para los usuarios que trabajan en varios escritorios, utilizando la utilidad desktops.exe de Microsoft o de otro modo. O mejor aún, solo intente crear una ventana en el escritorio activo (SetThreadDesktop), y si funciona, muestre su UI en él. Si no, entonces es un escritorio protegido/especial, entonces no lo haga. – eselk

33

me gustaría crear un servicio de Windows (un Visual Studio 2005 Tipo de proyecto) que controla el evento OnSessionChange como se muestra a continuación:

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 
    } 
} 

¿Qué y cómo se conecte la actividad en ese punto es hasta usted, pero un servicio de Windows proporciona acceso rápido y fácil a eventos de Windows como inicio, apagado, inicio de sesión/salida, junto con los eventos de bloqueo y desbloqueo.

18

La siguiente solución utiliza la API de Win32. Se llama a OnSessionLock cuando la estación de trabajo está bloqueada, y se llama a OnSessionUnlock cuando está 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); 
+0

Esta es una buena opción si descubre que el evento SessionSwitch (de otras respuestas) no se dispara (por ejemplo, su aplicación lo suprime). – kad81

+0

Para futuros lectores ... I ~ think ~ la anulación aquí proviene de System.Windows.Forms.Form, ya que podría escribir una clase como esta: public class Form1: System.Windows.Forms.Form – granadaCoder

3

Sé que esto es una vieja pregunta, pero he encontrado un método para obtener el estado de bloqueo para una sesión dada.

Encontré mi respuesta here pero estaba en C++, así que traduje todo lo que pude a C# para obtener el estado de bloqueo.

así que aquí va:

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<WTSINFOEX>(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: El código anterior se extrajo de un proyecto mucho más grande por lo que si me perdí un poco de pena. No tengo tiempo para probar el código anterior pero planeo volver en una semana o dos para verificar todo. Solo lo publiqué ahora porque no quería olvidarlo.

+0

Esto funciona (Windows 7 probado hasta el momento). Gracias, hemos estado buscando esto durante las últimas semanas y tu respuesta ha llegado justo a tiempo. – SteveP

+0

Hay pocos errores en el código: 1. 'if (session_info_ex.Level! = 1)' - si la condición es verdadera, la memoria no se liberará. 2. si session_info_ex.Level! = 1 no debe hacer esto: 'Marshal.PtrToStructure (ppBuffer);' porque el tamaño del buffer devuelto puede diferir del tamaño de WTSINFOEX – SergeyT

+0

(continuación) 3. No fue necesario agregue el campo 'UInt32 Reserved;' en su lugar, debe definir la estructura 'WTSINFOEX_LEVEL1' por completo. En este caso, el compilador corregirá el relleno (alineación) de los campos dentro de la estructura. 4. La función 'WTSFreeMemoryEx' está mal utilizada aquí. 'WTSFreeMemory' se debe utilizar en su lugar. 'WTSFreeMemoryEx' está destinado a liberar memoria después de' WTSEnumerateSessionsEx'. – SergeyT

3

NOTA: Esta no es una respuesta, sino una (contribución) a Timothy Carter respuesta, porque mi reputación no me permite comentar hasta el momento.

Por si alguien probó el código de la respuesta de Timothy Carter y no lo consiguió para que funcione inmediatamente en un servicio de Windows, hay una propiedad que debe configurarse en true en el constructor del servicio. Sólo tiene que añadir la línea en el constructor:

CanHandleSessionChangeEvent = true; 

Y asegúrese de no establece esta propiedad después de que el servicio se inicia otra manera un InvalidOperationException será lanzada.

1

Si está interesado en escribir un servicio de Windows para "encontrar" estos eventos, topshelf (la biblioteca/marco que hace que escribir servicios de Windows sea mucho más fácil) tiene un 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); 
    } 
} 

y ahora el código para cable hasta el servicio topshelf a la interfaz/hormigón por encima de

Todo continuación se configura topshelf "típico" .... a excepción de 2 líneas que he marcado como

/* ESTA ES LA LÍNEA MÁGICA */

Esas son las razones por las que se activa el método SessionChanged.

He probado esto con Windows 10 x64. Cerré y abrí mi máquina y obtuve el resultado deseado.

  IMyServiceContract myServiceObject = new MyService(); /* container.Resolve<IMyServiceContract>(); */ 


      HostFactory.Run(x => 
      { 
       x.Service<IMyServiceContract>(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 */ 
       }); 
      });     

Mi packages.config para proporcionar consejos acerca de las versiones:

<package id="log4net" version="2.0.5" targetFramework="net45" /> 
    <package id="Topshelf" version="4.0.3" targetFramework="net461" /> 
    <package id="Topshelf.Log4Net" version="4.0.3" targetFramework="net461" /> 
Cuestiones relacionadas