LoginEn ocasiones resulta muy útil detectar desde tu aplicación Windows el hecho de que el usuario actual se esté conectando o desconectando a la sesión actual, o simplemente que se haya bloqueado el escritorio. Por ejemplo, si el usuario tiene el escritorio bloqueado es una tontería mostrarle mensajes o globos de notificación ya que no va a haber nadie para verlos ¿no?

¿Cómo podemos detectar estas circunstancias?

La API de .NET dispone de un espacio de nombres especial llamado Microsoft.Win32 que contiene clases que encapsulan funcionalidad atada al sistema operativo Windows. Dentro de este espacio de nombres existe una clase llamada SystemEvents que ofrece multitud de eventos estáticos que nos permiten responder a diversas cuestiones que ocurren en el sistema operativo: cambio de los ajustes de pantalla, agregar o quitar fuentes, que el equipo se está apagando o suspendiendo, cambios de preferencia de usuario, cambio de la hora del sistema, etc... Es realmente útil, y utiliza por debajo llamadas a la API de Windows, evitándonos tener que conocerlas al detalle y facilitándonos su uso.

Para la pregunta que nos ocupa en esta ocasión la clase define un evento llamado SessionSwitch que es llamado automáticamente cuando cambia el estado de "logueado" del usuario actual. Este evento es un delegado de tipo SessionSwitchEventHandler, por lo que deberemos definir una función apropiada para este tipo de delegado, la cual toma como argumentos un objeto (como origen del mismo, como siempre) y otro de tipo SessionSwitchEventArgs.

Así, que una vez añadido el espacio de nombres Microsoft.Win32 a nuestra clase, podemos definir un método como este:

   1: public static void OnSessionSwitch (Object sender, SessionSwitchEventArgs e)
   2: {
   3:     //Código para responder al evento
   4: }

y asignarlo al evento SessionSwitch de la siguiente manera (por ejemplo en el método Main de nuestra aplicación):

   1: SystemEvents.SessionSwitch += new SessionSwitchEventHandler(OnSessionSwitch);

Con esto, cuando cambie el estado de login del usuario se llamará a nuestro método OnSessionSwitch.

El único miembro interesante del segundo parámetro del evento (llamado "e" en el ejemplo) es Reason, que devuelve un tipo enumerado SessionSwitchReason y nos indica el motivo para haber lanzado el evento. Así e.Reason puede tomar los siguientes valores:

Valor Descripción
ConsoleConnect Se ha conectado una sesión desde la consola.
ConsoleDisconnect Se ha desconectado una sesión desde la consola.
RemoteConnect Se ha conectado una sesión desde una conexión remota.
RemoteDisconnect Se ha desconectado una sesión desde una conexión remota.
SessionLogon Un usuario ha iniciado una sesión.
SessionLogoff Un usuario ha cerrado una sesión.
SessionLock Se ha bloqueado una sesión.
SessionUnlock Se ha desbloqueado una sesión.
SessionRemoteControl Una sesión ha cambiado su estado a o desde el modo controlado remoto.

He marcado en negrita las más comunes.

Por lo tanto es muy sencillo actuar en función de este valor y bloquear los mensajes, por ejemplo, cuando se bloquee la sesión o se desconecte remotamente el usuario (cuando se conecta a través de escritorio remoto),  restaurarlos nuevamente cuando se vuelva a conectar o se desbloquee.

Cómo detectar el estado actual con la API de Windows

Todo lo anterior está muy bien y es muy sencillo, pero ¿qué pasa si queremos determinar el estado actual de "logueo" y no sólo detectar cuando cambie?. Por ejemplo, para determinar si nuestro programa se ha lanzado desde una sesión de Terminal Server o con la sesión local bloqueada (por ejemplo con una tarea programada).

Para determinar si el usuario actual está trabajando en remoto, bajo una sesión de Terminal Server (o escritorio remoto, es exactamente lo mismo), la API de Windows Forms nos ofrece una propiedad de la clase SystemInformation, por lo que es muy sencillo hacer una función IsTerminalServer así:

   1: public static bool IsTerminalServer()
   2: {
   3:     return System.Windows.Forms.SystemInformation.TerminalServerSession;
   4: }

Para averiguar si la sesión actual está bloqueada en este momento o no, tendremos que recurrir a la API de Windows directamente.

Os dejo aquí el código necesario extraído de una de las primeras versiones internas de mi software Open Source Twitter Followers Monitor (es un proyecto en inglés, por lo que los comentarios, etc... están en este idioma):

   1: using System;
   2: using System.Runtime.InteropServices;
   3:  
   4: namespace TwitterFollowersMonitor
   5: {
   6:     /// <summary>
   7:     /// Common utilities class
   8:     /// </summary>
   9:     class Utils
  10:     {
  11:  
  12: #region "API Calls"
  13:         private const Int32 DESKTOP_SWITCHDESKTOP = 0x100;
  14:         [DllImport("user32", EntryPoint = "OpenDesktopA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
  15:         private static extern Int32 OpenDesktop(string lpszDesktop, Int32 dwFlags, bool fInherit, Int32 dwDesiredAccess);
  16:         [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
  17:         private static extern Int32 CloseDesktop(Int32 hDesktop);
  18:         [DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
  19:         private static extern Int32 SwitchDesktop(Int32 hDesktop);
  20: #endregion
  21:  
  22:         /// <summary>
  23:         /// Determines if the current session is locked so that I can choose not to show any visual clues (like balloons)
  24:         /// </summary>
  25:         /// <returns></returns>
  26:         public static bool IsWorkstationLocked()
  27:         {
  28:             Int32 p_lngHwnd;
  29:             Int32 p_lngRtn;
  30:             Int32 p_lngErr;
  31:  
  32:             p_lngHwnd = OpenDesktop("Default", 0, false, DESKTOP_SWITCHDESKTOP);
  33:  
  34:             if (p_lngHwnd == 0)
  35:             {
  36:                 return false;
  37:             }
  38:             else
  39:             {
  40:                 p_lngRtn = SwitchDesktop(p_lngHwnd);
  41:                 p_lngErr = Marshal.GetLastWin32Error();
  42:                 if (p_lngRtn == 0)
  43:                 {
  44:                     if (p_lngErr == 0)
  45:                     {
  46:                         p_lngHwnd = CloseDesktop(p_lngHwnd);
  47:                         return true;
  48:                     }
  49:                     else
  50:                     {
  51:                         p_lngHwnd = CloseDesktop(p_lngHwnd);
  52:                         return false;
  53:                     }
  54:                 }
  55:                 else
  56:                 {
  57:                     //Not locked
  58:                     p_lngHwnd = CloseDesktop(p_lngHwnd);
  59:                     return false;
  60:                 }
  61:             }
  62:         }
  63:  
  64:     }
  65: }

Como vemos se necesitan tres funciones de la API de Windows (OpenDesktop, CloseDesktop y SwitchDesktop, declaradas con Interop al principio de la clase. se usan para intentar abrir el escritorio actual y activarlo (sólo hay uno, así que si está disponible y no bloqueado deberían funcionar las llamadas, fracasando en otro caso).

¡Espero que te sea útil!

¿Te ha gustado este post? - Aprende .NET con los cursos on-line tutelados de campusMVP:
   ·
Preparación del examen 70-515: Desarrollo Web con .NET 4.0 (Tutelado por mi)
   · Desarrollo Web con ASP.NET 4.0 Web Forms (Tutelado por mi)
   · ASP.NET 4.0 Web Forms desde cero (Tutelado por mi)
   · Desarrollo Web con ASP.NET MVC 3 
   · Silverlight 4.0 - Aplicaciones Ricas para Internet (RIA)
   · jQuery paso a paso para programadores ASP.NET
   · Visual Studio 2010 desde cero

💪🏻 ¿Este post te ha ayudado?, ¿has aprendido algo nuevo?
Pues NO te pido que me invites a un café... Te pido algo más fácil y mucho mejor

Escrito por un humano, no por una IA